From c4db7c075914a08ca388324940ec068bbb7cbb35 Mon Sep 17 00:00:00 2001 From: Charles Kenney Date: Tue, 19 May 2020 12:56:41 -0400 Subject: [PATCH] add firewall rules methods --- client.go | 3 + firewall_rules.go | 41 +++ resources.go | 2 + test/integration/firewall_rules_test.go | 54 ++++ .../fixtures/TestGetFirewallRules.yaml | 206 +++++++++++++ .../fixtures/TestUpdateFirewallRules.yaml | 271 ++++++++++++++++++ 6 files changed, 577 insertions(+) create mode 100644 test/integration/fixtures/TestGetFirewallRules.yaml create mode 100644 test/integration/fixtures/TestUpdateFirewallRules.yaml diff --git a/client.go b/client.go index c36bf429d..f7817f694 100644 --- a/client.go +++ b/client.go @@ -59,6 +59,7 @@ type Client struct { Events *Resource Firewalls *Resource FirewallDevices *Resource + FirewallRules *Resource IPAddresses *Resource IPv6Pools *Resource IPv6Ranges *Resource @@ -263,6 +264,7 @@ func addResources(client *Client) { eventsName: NewResource(client, eventsName, eventsEndpoint, false, Event{}, EventsPagedResponse{}), firewallsName: NewResource(client, firewallsName, firewallsEndpoint, false, Firewall{}, FirewallsPagedResponse{}), firewallDevicesName: NewResource(client, firewallDevicesName, firewallDevicesEndpoint, true, FirewallDevice{}, FirewallDevicesPagedResponse{}), + firewallRulesName: NewResource(client, firewallRulesName, firewallRulesEndpoint, true, FirewallRule{}, nil), imagesName: NewResource(client, imagesName, imagesEndpoint, false, Image{}, ImagesPagedResponse{}), instanceConfigsName: NewResource(client, instanceConfigsName, instanceConfigsEndpoint, true, InstanceConfig{}, InstanceConfigsPagedResponse{}), instanceDisksName: NewResource(client, instanceDisksName, instanceDisksEndpoint, true, InstanceDisk{}, InstanceDisksPagedResponse{}), @@ -315,6 +317,7 @@ func addResources(client *Client) { client.Events = resources[eventsName] client.Firewalls = resources[firewallsName] client.FirewallDevices = resources[firewallDevicesName] + client.FirewallRules = resources[firewallRulesName] client.IPAddresses = resources[ipaddressesName] client.IPv6Pools = resources[ipv6poolsName] client.IPv6Ranges = resources[ipv6rangesName] diff --git a/firewall_rules.go b/firewall_rules.go index a0b6e2cc3..5dae71dc0 100644 --- a/firewall_rules.go +++ b/firewall_rules.go @@ -1,5 +1,10 @@ package linodego +import ( + "context" + "encoding/json" +) + // NetworkProtocol enum type type NetworkProtocol string @@ -28,3 +33,39 @@ type FirewallRuleSet struct { Inbound []FirewallRule `json:"inbound,omitempty"` Outbound []FirewallRule `json:"outbound,omitempty"` } + +// GetFirewallRules gets the FirewallRuleSet for the given Firewall. +func (c *Client) GetFirewallRules(ctx context.Context, firewallID int) (*FirewallRuleSet, error) { + e, err := c.FirewallRules.endpointWithID(firewallID) + if err != nil { + return nil, err + } + + r, err := coupleAPIErrors(c.R(ctx).SetResult(&FirewallRuleSet{}).Get(e)) + if err != nil { + return nil, err + } + return r.Result().(*FirewallRuleSet), nil +} + +// UpdateFirewallRules updates the FirewallRuleSet for the given Firewall +func (c *Client) UpdateFirewallRules(ctx context.Context, firewallID int, rules FirewallRuleSet) (*FirewallRuleSet, error) { + e, err := c.FirewallRules.endpointWithID(firewallID) + if err != nil { + return nil, err + } + + var body string + req := c.R(ctx).SetResult(&FirewallRuleSet{}) + if bodyData, err := json.Marshal(rules); err == nil { + body = string(bodyData) + } else { + return nil, NewError(err) + } + + r, err := coupleAPIErrors(req.SetBody(body).Put(e)) + if err != nil { + return nil, err + } + return r.Result().(*FirewallRuleSet), nil +} diff --git a/resources.go b/resources.go index 8dd925f1c..b5390b7b1 100644 --- a/resources.go +++ b/resources.go @@ -17,6 +17,7 @@ const ( eventsName = "events" firewallsName = "firewalls" firewallDevicesName = "firewalldevices" + firewallRulesName = "firewallrules" imagesName = "images" instanceConfigsName = "configs" instanceDisksName = "disks" @@ -67,6 +68,7 @@ const ( eventsEndpoint = "account/events" firewallsEndpoint = "networking/firewalls" firewallDevicesEndpoint = "networking/firewalls/{{ .ID }}/devices" + firewallRulesEndpoint = "networking/firewalls/{{ .ID }}/rules" imagesEndpoint = "images" instanceConfigsEndpoint = "linode/instances/{{ .ID }}/configs" instanceDisksEndpoint = "linode/instances/{{ .ID }}/disks" diff --git a/test/integration/firewall_rules_test.go b/test/integration/firewall_rules_test.go index f0fddb37a..c0299b0e5 100644 --- a/test/integration/firewall_rules_test.go +++ b/test/integration/firewall_rules_test.go @@ -1,6 +1,11 @@ package integration import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/linode/linodego" ) @@ -19,3 +24,52 @@ var ( Outbound: []linodego.FirewallRule{testFirewallRule}, } ) + +// ignoreNetworkAddresses negates comparing IP addresses. Because of fixture sanitization, +// these addresses will be changed to bogus values when running tests. +var ignoreNetworkAddresses = cmpopts.IgnoreFields(linodego.FirewallRule{}, "Addresses") + +func TestGetFirewallRules(t *testing.T) { + client, firewall, teardown, err := setupFirewall(t, []firewallModifier{func(createOpts *linodego.FirewallCreateOptions) { + createOpts.Rules = testFirewallRuleSet + }}, "fixtures/TestGetFirewallRules") + if err != nil { + t.Error(err) + } + defer teardown() + + rules, err := client.GetFirewallRules(context.Background(), firewall.ID) + if !cmp.Equal(rules, &testFirewallRuleSet, ignoreNetworkAddresses) { + t.Errorf("expected rules to match test rules, but got diff: %s", cmp.Diff(rules, testFirewallRuleSet, ignoreNetworkAddresses)) + } +} + +func TestUpdateFirewallRules(t *testing.T) { + client, firewall, teardown, err := setupFirewall(t, []firewallModifier{}, "fixtures/TestUpdateFirewallRules") + if err != nil { + t.Error(err) + } + defer teardown() + + newRules := linodego.FirewallRuleSet{ + Inbound: []linodego.FirewallRule{ + { + Ports: "22", + Protocol: "TCP", + Addresses: linodego.NetworkAddresses{ + IPv4: []string{"0.0.0.0/0"}, + IPv6: []string{"::0/0"}, + }, + }, + }, + } + + if _, err := client.UpdateFirewallRules(context.Background(), firewall.ID, newRules); err != nil { + t.Error(err) + } + + rules, err := client.GetFirewallRules(context.Background(), firewall.ID) + if !cmp.Equal(rules, &newRules, ignoreNetworkAddresses) { + t.Errorf("expected rules to have been updated but got diff: %s", cmp.Diff(rules, &newRules, ignoreNetworkAddresses)) + } +} diff --git a/test/integration/fixtures/TestGetFirewallRules.yaml b/test/integration/fixtures/TestGetFirewallRules.yaml new file mode 100644 index 000000000..ba79dfcc5 --- /dev/null +++ b/test/integration/fixtures/TestGetFirewallRules.yaml @@ -0,0 +1,206 @@ +--- +version: 1 +interactions: +- request: + body: '{"label":"label","rules":{"inbound":[{"ports":"22","protocol":"TCP","addresses":{"ipv4":["10.20.30.40/0"],"ipv6":["1234::5678/0"]}}],"outbound":[{"ports":"22","protocol":"TCP","addresses":{"ipv4":["10.20.30.40/0"],"ipv6":["1234::5678/0"]}}]},"tags":["testing"],"devices":{}}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego 0.12.0 https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/firewalls + method: POST + response: + body: '{"id": 217, "label": "label", "created": "2018-01-02T03:04:05", "updated": + "2018-01-02T03:04:05", "status": "enabled", "rules": {"inbound": [{"ports": + "22", "protocol": "TCP", "addresses": {"ipv4": ["10.20.30.40/0"], "ipv6": ["1234::5678/0"]}}], + "outbound": [{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["10.20.30.40/0"], + "ipv6": ["1234::5678/0"]}}]}, "tags": ["testing"]}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - "363" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Date: + - Tue, 19 May 2020 16:26:33 GMT + Retry-After: + - "119" + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - firewall:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1600" + X-Ratelimit-Remaining: + - "1599" + X-Ratelimit-Reset: + - "1589905713" + X-Spec-Version: + - 4.65.1 + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego 0.12.0 https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/firewalls/217/rules + method: GET + response: + body: '{"inbound": [{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["10.20.30.40/0"], + "ipv6": ["1234::5678/0"]}}], "outbound": [{"ports": "22", "protocol": "TCP", "addresses": + {"ipv4": ["10.20.30.40/0"], "ipv6": ["1234::5678/0"]}}]}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=0, s-maxage=0, no-cache, no-store + - private, max-age=60, s-maxage=60 + Content-Length: + - "213" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Date: + - Tue, 19 May 2020 16:26:33 GMT + Retry-After: + - "119" + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - firewall:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1600" + X-Ratelimit-Remaining: + - "1598" + X-Ratelimit-Reset: + - "1589905713" + X-Spec-Version: + - 4.65.1 + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego 0.12.0 https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/firewalls/217 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Date: + - Tue, 19 May 2020 16:26:33 GMT + Retry-After: + - "118" + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - firewall:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1600" + X-Ratelimit-Remaining: + - "1597" + X-Ratelimit-Reset: + - "1589905712" + X-Spec-Version: + - 4.65.1 + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" diff --git a/test/integration/fixtures/TestUpdateFirewallRules.yaml b/test/integration/fixtures/TestUpdateFirewallRules.yaml new file mode 100644 index 000000000..30da3ec38 --- /dev/null +++ b/test/integration/fixtures/TestUpdateFirewallRules.yaml @@ -0,0 +1,271 @@ +--- +version: 1 +interactions: +- request: + body: '{"label":"label","rules":{"inbound":[{"ports":"22","protocol":"TCP","addresses":{"ipv4":["10.20.30.40/0"],"ipv6":["1234::5678/0"]}}],"outbound":[{"ports":"22","protocol":"TCP","addresses":{"ipv4":["10.20.30.40/0"],"ipv6":["1234::5678/0"]}}]},"tags":["testing"],"devices":{}}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego 0.12.0 https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/firewalls + method: POST + response: + body: '{"id": 223, "label": "label", "created": "2018-01-02T03:04:05", "updated": + "2018-01-02T03:04:05", "status": "enabled", "rules": {"inbound": [{"ports": + "22", "protocol": "TCP", "addresses": {"ipv4": ["10.20.30.40/0"], "ipv6": ["1234::5678/0"]}}], + "outbound": [{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["10.20.30.40/0"], + "ipv6": ["1234::5678/0"]}}]}, "tags": ["testing"]}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - "363" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Date: + - Tue, 19 May 2020 16:41:15 GMT + Retry-After: + - "92" + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - firewall:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1600" + X-Ratelimit-Remaining: + - "1595" + X-Ratelimit-Reset: + - "1589906568" + X-Spec-Version: + - 4.65.1 + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: '{"inbound":[{"ports":"22","protocol":"TCP","addresses":{"ipv4":["10.20.30.40/0"],"ipv6":["1234::5678/0"]}}]}' + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego 0.12.0 https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/firewalls/223/rules + method: PUT + response: + body: '{"inbound": [{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["10.20.30.40/0"], + "ipv6": ["1234::5678/0"]}}]}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - "106" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Date: + - Tue, 19 May 2020 16:41:15 GMT + Retry-After: + - "92" + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - firewall:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1600" + X-Ratelimit-Remaining: + - "1594" + X-Ratelimit-Reset: + - "1589906568" + X-Spec-Version: + - 4.65.1 + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego 0.12.0 https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/firewalls/223/rules + method: GET + response: + body: '{"inbound": [{"ports": "22", "protocol": "TCP", "addresses": {"ipv4": ["10.20.30.40/0"], + "ipv6": ["1234::5678/0"]}}]}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=0, s-maxage=0, no-cache, no-store + - private, max-age=60, s-maxage=60 + Content-Length: + - "106" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Date: + - Tue, 19 May 2020 16:41:16 GMT + Retry-After: + - "92" + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - firewall:read_only + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1600" + X-Ratelimit-Remaining: + - "1593" + X-Ratelimit-Reset: + - "1589906569" + X-Spec-Version: + - 4.65.1 + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: "" +- request: + body: "" + form: {} + headers: + Accept: + - application/json + Content-Type: + - application/json + User-Agent: + - linodego 0.12.0 https://github.com/linode/linodego + url: https://api.linode.com/v4beta/networking/firewalls/223 + method: DELETE + response: + body: '{}' + headers: + Access-Control-Allow-Credentials: + - "true" + Access-Control-Allow-Headers: + - Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter + Access-Control-Allow-Methods: + - HEAD, GET, OPTIONS, POST, PUT, DELETE + Access-Control-Allow-Origin: + - '*' + Access-Control-Expose-Headers: + - X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status + Cache-Control: + - private, max-age=60, s-maxage=60 + Content-Length: + - "2" + Content-Security-Policy: + - default-src 'none' + Content-Type: + - application/json + Date: + - Tue, 19 May 2020 16:41:16 GMT + Retry-After: + - "91" + Server: + - nginx + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization, X-Filter + X-Accepted-Oauth-Scopes: + - firewall:read_write + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - DENY + - DENY + X-Oauth-Scopes: + - '*' + X-Ratelimit-Limit: + - "1600" + X-Ratelimit-Remaining: + - "1592" + X-Ratelimit-Reset: + - "1589906568" + X-Spec-Version: + - 4.65.1 + X-Xss-Protection: + - 1; mode=block + status: 200 OK + code: 200 + duration: ""