2020-06-18 15:04:41 +00:00
|
|
|
package godo
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"net/http"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
|
|
|
)
|
|
|
|
|
|
|
|
const firewallsBasePath = "/v2/firewalls"
|
|
|
|
|
|
|
|
// FirewallsService is an interface for managing Firewalls with the DigitalOcean API.
|
|
|
|
// See: https://developers.digitalocean.com/documentation/v2/#firewalls
|
|
|
|
type FirewallsService interface {
|
|
|
|
Get(context.Context, string) (*Firewall, *Response, error)
|
|
|
|
Create(context.Context, *FirewallRequest) (*Firewall, *Response, error)
|
|
|
|
Update(context.Context, string, *FirewallRequest) (*Firewall, *Response, error)
|
|
|
|
Delete(context.Context, string) (*Response, error)
|
|
|
|
List(context.Context, *ListOptions) ([]Firewall, *Response, error)
|
|
|
|
ListByDroplet(context.Context, int, *ListOptions) ([]Firewall, *Response, error)
|
|
|
|
AddDroplets(context.Context, string, ...int) (*Response, error)
|
|
|
|
RemoveDroplets(context.Context, string, ...int) (*Response, error)
|
|
|
|
AddTags(context.Context, string, ...string) (*Response, error)
|
|
|
|
RemoveTags(context.Context, string, ...string) (*Response, error)
|
|
|
|
AddRules(context.Context, string, *FirewallRulesRequest) (*Response, error)
|
|
|
|
RemoveRules(context.Context, string, *FirewallRulesRequest) (*Response, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FirewallsServiceOp handles communication with Firewalls methods of the DigitalOcean API.
|
|
|
|
type FirewallsServiceOp struct {
|
|
|
|
client *Client
|
|
|
|
}
|
|
|
|
|
|
|
|
// Firewall represents a DigitalOcean Firewall configuration.
|
|
|
|
type Firewall struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
InboundRules []InboundRule `json:"inbound_rules"`
|
|
|
|
OutboundRules []OutboundRule `json:"outbound_rules"`
|
|
|
|
DropletIDs []int `json:"droplet_ids"`
|
|
|
|
Tags []string `json:"tags"`
|
|
|
|
Created string `json:"created_at"`
|
|
|
|
PendingChanges []PendingChange `json:"pending_changes"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// String creates a human-readable description of a Firewall.
|
|
|
|
func (fw Firewall) String() string {
|
|
|
|
return Stringify(fw)
|
|
|
|
}
|
|
|
|
|
2020-11-19 14:02:19 +00:00
|
|
|
// URN returns the firewall name in a valid DO API URN form.
|
2020-06-18 15:04:41 +00:00
|
|
|
func (fw Firewall) URN() string {
|
|
|
|
return ToURN("Firewall", fw.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FirewallRequest represents the configuration to be applied to an existing or a new Firewall.
|
|
|
|
type FirewallRequest struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
InboundRules []InboundRule `json:"inbound_rules"`
|
|
|
|
OutboundRules []OutboundRule `json:"outbound_rules"`
|
|
|
|
DropletIDs []int `json:"droplet_ids"`
|
|
|
|
Tags []string `json:"tags"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// FirewallRulesRequest represents rules configuration to be applied to an existing Firewall.
|
|
|
|
type FirewallRulesRequest struct {
|
|
|
|
InboundRules []InboundRule `json:"inbound_rules"`
|
|
|
|
OutboundRules []OutboundRule `json:"outbound_rules"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// InboundRule represents a DigitalOcean Firewall inbound rule.
|
|
|
|
type InboundRule struct {
|
|
|
|
Protocol string `json:"protocol,omitempty"`
|
|
|
|
PortRange string `json:"ports,omitempty"`
|
|
|
|
Sources *Sources `json:"sources"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// OutboundRule represents a DigitalOcean Firewall outbound rule.
|
|
|
|
type OutboundRule struct {
|
|
|
|
Protocol string `json:"protocol,omitempty"`
|
|
|
|
PortRange string `json:"ports,omitempty"`
|
|
|
|
Destinations *Destinations `json:"destinations"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sources represents a DigitalOcean Firewall InboundRule sources.
|
|
|
|
type Sources struct {
|
|
|
|
Addresses []string `json:"addresses,omitempty"`
|
|
|
|
Tags []string `json:"tags,omitempty"`
|
|
|
|
DropletIDs []int `json:"droplet_ids,omitempty"`
|
|
|
|
LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// PendingChange represents a DigitalOcean Firewall status details.
|
|
|
|
type PendingChange struct {
|
|
|
|
DropletID int `json:"droplet_id,omitempty"`
|
|
|
|
Removing bool `json:"removing,omitempty"`
|
|
|
|
Status string `json:"status,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destinations represents a DigitalOcean Firewall OutboundRule destinations.
|
|
|
|
type Destinations struct {
|
|
|
|
Addresses []string `json:"addresses,omitempty"`
|
|
|
|
Tags []string `json:"tags,omitempty"`
|
|
|
|
DropletIDs []int `json:"droplet_ids,omitempty"`
|
|
|
|
LoadBalancerUIDs []string `json:"load_balancer_uids,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ FirewallsService = &FirewallsServiceOp{}
|
|
|
|
|
|
|
|
// Get an existing Firewall by its identifier.
|
|
|
|
func (fw *FirewallsServiceOp) Get(ctx context.Context, fID string) (*Firewall, *Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID)
|
|
|
|
|
|
|
|
req, err := fw.client.NewRequest(ctx, http.MethodGet, path, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
root := new(firewallRoot)
|
|
|
|
resp, err := fw.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return root.Firewall, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new Firewall with a given configuration.
|
|
|
|
func (fw *FirewallsServiceOp) Create(ctx context.Context, fr *FirewallRequest) (*Firewall, *Response, error) {
|
|
|
|
req, err := fw.client.NewRequest(ctx, http.MethodPost, firewallsBasePath, fr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
root := new(firewallRoot)
|
|
|
|
resp, err := fw.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return root.Firewall, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update an existing Firewall with new configuration.
|
|
|
|
func (fw *FirewallsServiceOp) Update(ctx context.Context, fID string, fr *FirewallRequest) (*Firewall, *Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID)
|
|
|
|
|
|
|
|
req, err := fw.client.NewRequest(ctx, "PUT", path, fr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
root := new(firewallRoot)
|
|
|
|
resp, err := fw.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return root.Firewall, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete a Firewall by its identifier.
|
|
|
|
func (fw *FirewallsServiceOp) Delete(ctx context.Context, fID string) (*Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID)
|
|
|
|
return fw.createAndDoReq(ctx, http.MethodDelete, path, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
// List Firewalls.
|
|
|
|
func (fw *FirewallsServiceOp) List(ctx context.Context, opt *ListOptions) ([]Firewall, *Response, error) {
|
|
|
|
path, err := addOptions(firewallsBasePath, opt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return fw.listHelper(ctx, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListByDroplet Firewalls.
|
|
|
|
func (fw *FirewallsServiceOp) ListByDroplet(ctx context.Context, dID int, opt *ListOptions) ([]Firewall, *Response, error) {
|
|
|
|
basePath := path.Join(dropletBasePath, strconv.Itoa(dID), "firewalls")
|
|
|
|
path, err := addOptions(basePath, opt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return fw.listHelper(ctx, path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddDroplets to a Firewall.
|
|
|
|
func (fw *FirewallsServiceOp) AddDroplets(ctx context.Context, fID string, dropletIDs ...int) (*Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID, "droplets")
|
|
|
|
return fw.createAndDoReq(ctx, http.MethodPost, path, &dropletsRequest{IDs: dropletIDs})
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveDroplets from a Firewall.
|
|
|
|
func (fw *FirewallsServiceOp) RemoveDroplets(ctx context.Context, fID string, dropletIDs ...int) (*Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID, "droplets")
|
|
|
|
return fw.createAndDoReq(ctx, http.MethodDelete, path, &dropletsRequest{IDs: dropletIDs})
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddTags to a Firewall.
|
|
|
|
func (fw *FirewallsServiceOp) AddTags(ctx context.Context, fID string, tags ...string) (*Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID, "tags")
|
|
|
|
return fw.createAndDoReq(ctx, http.MethodPost, path, &tagsRequest{Tags: tags})
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveTags from a Firewall.
|
|
|
|
func (fw *FirewallsServiceOp) RemoveTags(ctx context.Context, fID string, tags ...string) (*Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID, "tags")
|
|
|
|
return fw.createAndDoReq(ctx, http.MethodDelete, path, &tagsRequest{Tags: tags})
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddRules to a Firewall.
|
|
|
|
func (fw *FirewallsServiceOp) AddRules(ctx context.Context, fID string, rr *FirewallRulesRequest) (*Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID, "rules")
|
|
|
|
return fw.createAndDoReq(ctx, http.MethodPost, path, rr)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveRules from a Firewall.
|
|
|
|
func (fw *FirewallsServiceOp) RemoveRules(ctx context.Context, fID string, rr *FirewallRulesRequest) (*Response, error) {
|
|
|
|
path := path.Join(firewallsBasePath, fID, "rules")
|
|
|
|
return fw.createAndDoReq(ctx, http.MethodDelete, path, rr)
|
|
|
|
}
|
|
|
|
|
|
|
|
type dropletsRequest struct {
|
|
|
|
IDs []int `json:"droplet_ids"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type tagsRequest struct {
|
|
|
|
Tags []string `json:"tags"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type firewallRoot struct {
|
|
|
|
Firewall *Firewall `json:"firewall"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type firewallsRoot struct {
|
|
|
|
Firewalls []Firewall `json:"firewalls"`
|
|
|
|
Links *Links `json:"links"`
|
|
|
|
Meta *Meta `json:"meta"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fw *FirewallsServiceOp) createAndDoReq(ctx context.Context, method, path string, v interface{}) (*Response, error) {
|
|
|
|
req, err := fw.client.NewRequest(ctx, method, path, v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return fw.client.Do(ctx, req, nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fw *FirewallsServiceOp) listHelper(ctx context.Context, path string) ([]Firewall, *Response, error) {
|
|
|
|
req, err := fw.client.NewRequest(ctx, http.MethodGet, path, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
root := new(firewallsRoot)
|
|
|
|
resp, err := fw.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
if l := root.Links; l != nil {
|
|
|
|
resp.Links = l
|
|
|
|
}
|
|
|
|
if m := root.Meta; m != nil {
|
|
|
|
resp.Meta = m
|
|
|
|
}
|
|
|
|
|
|
|
|
return root.Firewalls, resp, err
|
|
|
|
}
|