Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions firewall_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ func (c *Client) GetFirewallRules(ctx context.Context, firewallID int) (*Firewal
return doGETRequest[FirewallRuleSet](ctx, c, e)
}

// GetFirewallRulesExpansion gets the expanded FirewallRuleSet for the given Firewall.
func (c *Client) GetFirewallRulesExpansion(ctx context.Context, firewallID int) (*FirewallRuleSet, error) {
e := formatAPIPath("networking/firewalls/%d/rules/expansion", firewallID)
return doGETRequest[FirewallRuleSet](ctx, c, e)
}

// UpdateFirewallRules updates the FirewallRuleSet for the given Firewall
func (c *Client) UpdateFirewallRules(ctx context.Context, firewallID int, rules FirewallRuleSet) (*FirewallRuleSet, error) {
e := formatAPIPath("networking/firewalls/%d/rules", firewallID)
Expand Down
61 changes: 61 additions & 0 deletions prefixlists.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package linodego

import (
"context"
"encoding/json"
"time"

"github.com/linode/linodego/internal/parseabletime"
)

// PrefixList represents a network prefix list returned by the API.
type PrefixList struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Visibility string `json:"visibility"`
SourcePrefixListID *int `json:"source_prefixlist_id"`
IPv4 *[]string `json:"ipv4"`
IPv6 *[]string `json:"ipv6"`
Version int `json:"version"`

Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Deleted *time.Time `json:"-"`
}

// UnmarshalJSON implements custom timestamp parsing for PrefixList values.
func (p *PrefixList) UnmarshalJSON(data []byte) error {
type Mask PrefixList

aux := struct {
*Mask

Created *parseabletime.ParseableTime `json:"created"`
Updated *parseabletime.ParseableTime `json:"updated"`
Deleted *parseabletime.ParseableTime `json:"deleted"`
}{
Mask: (*Mask)(p),
}

if err := json.Unmarshal(data, &aux); err != nil {
return err
}

p.Created = (*time.Time)(aux.Created)
p.Updated = (*time.Time)(aux.Updated)
p.Deleted = (*time.Time)(aux.Deleted)

return nil
}

// ListPrefixLists returns a paginated collection of Prefix Lists.
func (c *Client) ListPrefixLists(ctx context.Context, opts *ListOptions) ([]PrefixList, error) {
return getPaginatedResults[PrefixList](ctx, c, "networking/prefixlists", opts)
}

// GetPrefixList fetches a single Prefix List by its ID.
func (c *Client) GetPrefixList(ctx context.Context, id int) (*PrefixList, error) {
endpoint := formatAPIPath("networking/prefixlists/%d", id)
return doGETRequest[PrefixList](ctx, c, endpoint)
}
135 changes: 108 additions & 27 deletions test/unit/firewall_rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,33 +45,6 @@ func TestFirewallRule_Get(t *testing.T) {
assert.ElementsMatch(t, []string{"2001:DB8::/128"}, *firewallRule.Outbound[0].Addresses.IPv6)
}

func TestFirewallRule_MarshalJSON(t *testing.T) {
ruleWithRuleset := linodego.FirewallRule{Ruleset: 51}
data, err := json.Marshal(ruleWithRuleset)
assert.NoError(t, err)
assert.JSONEq(t, `{"ruleset":51}`, string(data))

ipv4 := []string{"pl::vpcs:123"}
ruleWithoutRuleset := linodego.FirewallRule{
Action: "ACCEPT",
Label: "allow-vpc",
Ports: "443",
Protocol: linodego.NetworkProtocol("TCP"),
Addresses: linodego.NetworkAddresses{
IPv4: &ipv4,
},
}
data, err = json.Marshal(ruleWithoutRuleset)
assert.NoError(t, err)
assert.JSONEq(t, `{
"action":"ACCEPT",
"label":"allow-vpc",
"ports":"443",
"protocol":"TCP",
"addresses":{"ipv4":["pl::vpcs:123"]}
}`, string(data))
}

func TestFirewallRule_Update(t *testing.T) {
fixtureData, err := fixtures.GetFixture("firewall_rule_update")
assert.NoError(t, err)
Expand Down Expand Up @@ -139,3 +112,111 @@ func TestFirewallRule_Update(t *testing.T) {
assert.ElementsMatch(t, []string{"192.0.2.0/24", "198.51.100.2/32"}, *firewallRule.Outbound[0].Addresses.IPv4)
assert.ElementsMatch(t, []string{"2001:DB8::/128"}, *firewallRule.Outbound[0].Addresses.IPv6)
}

func TestFirewallRule_GetExpansion(t *testing.T) {
inboundIPv4 := []string{"pl::vpcs:1234"}
inboundIPv6 := []string{"pl::vpcs:<current>"}
outboundIPv4 := []string{"pl::vpcs:1234"}
outboundIPv6 := []string{"pl::vpcs:<current>"}

mockResponse := linodego.FirewallRuleSet{
Inbound: []linodego.FirewallRule{
{
Action: "ACCEPT",
Label: "accept-inbound-ssh",
Description: "Accept inbound SSH within the VPC",
Ports: "22",
Protocol: linodego.NetworkProtocol("TCP"),
Addresses: linodego.NetworkAddresses{
IPv4: &inboundIPv4,
IPv6: &inboundIPv6,
},
},
},
InboundPolicy: "DROP",
Outbound: []linodego.FirewallRule{
{
Action: "ACCEPT",
Label: "accept-outbound-ssh",
Description: "Accept outbound SSH within the VPC",
Ports: "22",
Protocol: linodego.NetworkProtocol("TCP"),
Addresses: linodego.NetworkAddresses{
IPv4: &outboundIPv4,
IPv6: &outboundIPv6,
},
},
},
OutboundPolicy: "DROP",
}

var base ClientBaseCase
base.SetUp(t)
defer base.TearDown(t)

firewallID := 456
base.MockGet(formatMockAPIPath("networking/firewalls/%d/rules/expansion", firewallID), mockResponse)

firewallRuleSet, err := base.Client.GetFirewallRulesExpansion(context.Background(), firewallID)
assert.NoError(t, err)
assert.NotNil(t, firewallRuleSet)

if assert.Len(t, firewallRuleSet.Inbound, 1) {
assert.Equal(t, "ACCEPT", firewallRuleSet.Inbound[0].Action)
assert.Equal(t, "accept-inbound-ssh", firewallRuleSet.Inbound[0].Label)
assert.Equal(t, "Accept inbound SSH within the VPC", firewallRuleSet.Inbound[0].Description)
assert.Equal(t, "22", firewallRuleSet.Inbound[0].Ports)
assert.Equal(t, linodego.NetworkProtocol("TCP"), firewallRuleSet.Inbound[0].Protocol)
if assert.NotNil(t, firewallRuleSet.Inbound[0].Addresses.IPv4) {
assert.ElementsMatch(t, []string{"pl::vpcs:1234"}, *firewallRuleSet.Inbound[0].Addresses.IPv4)
}
if assert.NotNil(t, firewallRuleSet.Inbound[0].Addresses.IPv6) {
assert.ElementsMatch(t, []string{"pl::vpcs:<current>"}, *firewallRuleSet.Inbound[0].Addresses.IPv6)
}
}

assert.Equal(t, "DROP", firewallRuleSet.InboundPolicy)

if assert.Len(t, firewallRuleSet.Outbound, 1) {
assert.Equal(t, "ACCEPT", firewallRuleSet.Outbound[0].Action)
assert.Equal(t, "accept-outbound-ssh", firewallRuleSet.Outbound[0].Label)
assert.Equal(t, "Accept outbound SSH within the VPC", firewallRuleSet.Outbound[0].Description)
assert.Equal(t, "22", firewallRuleSet.Outbound[0].Ports)
assert.Equal(t, linodego.NetworkProtocol("TCP"), firewallRuleSet.Outbound[0].Protocol)
if assert.NotNil(t, firewallRuleSet.Outbound[0].Addresses.IPv4) {
assert.ElementsMatch(t, []string{"pl::vpcs:1234"}, *firewallRuleSet.Outbound[0].Addresses.IPv4)
}
if assert.NotNil(t, firewallRuleSet.Outbound[0].Addresses.IPv6) {
assert.ElementsMatch(t, []string{"pl::vpcs:<current>"}, *firewallRuleSet.Outbound[0].Addresses.IPv6)
}
}

assert.Equal(t, "DROP", firewallRuleSet.OutboundPolicy)
}

func TestFirewallRule_MarshalJSON(t *testing.T) {
ruleWithRuleset := linodego.FirewallRule{Ruleset: 51}
data, err := json.Marshal(ruleWithRuleset)
assert.NoError(t, err)
assert.JSONEq(t, `{"ruleset":51}`, string(data))

ipv4 := []string{"pl::vpcs:123"}
ruleWithoutRuleset := linodego.FirewallRule{
Action: "ACCEPT",
Label: "allow-vpc",
Ports: "443",
Protocol: linodego.NetworkProtocol("TCP"),
Addresses: linodego.NetworkAddresses{
IPv4: &ipv4,
},
}
data, err = json.Marshal(ruleWithoutRuleset)
assert.NoError(t, err)
assert.JSONEq(t, `{
"action":"ACCEPT",
"label":"allow-vpc",
"ports":"443",
"protocol":"TCP",
"addresses":{"ipv4":["pl::vpcs:123"]}
}`, string(data))
}
121 changes: 121 additions & 0 deletions test/unit/prefixlists_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package unit

import (
"context"
"encoding/json"
"testing"
"time"

"github.com/linode/linodego"
"github.com/stretchr/testify/assert"
)

func TestPrefixLists_List(t *testing.T) {
var base ClientBaseCase
base.SetUp(t)
defer base.TearDown(t)

response := map[string]any{
"data": []map[string]any{
{
"id": 321,
"name": "pl:system:resolvers:us-iad:staging",
"description": "Resolver ACL",
"visibility": "restricted",
"source_prefixlist_id": nil,
"ipv4": []string{"139.144.192.62", "139.144.192.60"},
"ipv6": []string{"2600:3c05:e001:bc::1", "2600:3c05:e001:bc::2"},
"version": 4,
"created": "2018-01-01T00:01:01",
"updated": "2019-01-01T00:01:01",
"deleted": nil,
},
},
"page": 1,
"pages": 1,
"results": 1,
}

base.MockGet("networking/prefixlists", response)

prefixLists, err := base.Client.ListPrefixLists(context.Background(), nil)
assert.NoError(t, err)
assert.Len(t, prefixLists, 1)

pl := prefixLists[0]
assert.Equal(t, 321, pl.ID)
assert.Equal(t, "pl:system:resolvers:us-iad:staging", pl.Name)
assert.Equal(t, "restricted", pl.Visibility)
if assert.NotNil(t, pl.IPv4) {
assert.Equal(t, []string{"139.144.192.62", "139.144.192.60"}, *pl.IPv4)
}
if assert.NotNil(t, pl.IPv6) {
assert.Equal(t, []string{"2600:3c05:e001:bc::1", "2600:3c05:e001:bc::2"}, *pl.IPv6)
}
if assert.NotNil(t, pl.Created) {
assert.Equal(t, time.Date(2018, time.January, 1, 0, 1, 1, 0, time.UTC), pl.Created.UTC())
}
if assert.NotNil(t, pl.Updated) {
assert.Equal(t, time.Date(2019, time.January, 1, 0, 1, 1, 0, time.UTC), pl.Updated.UTC())
}
assert.Nil(t, pl.Deleted)
}

func TestPrefixLists_Get(t *testing.T) {
var base ClientBaseCase
base.SetUp(t)
defer base.TearDown(t)

prefixListID := 654

response := map[string]any{
"id": prefixListID,
"name": "pl::customer:example",
"description": "Example customer list",
"visibility": "account",
"source_prefixlist_id": nil,
"ipv4": []string{"198.51.100.0/24"},
"ipv6": []string{"2001:db8::/32"},
"version": 2,
"created": "2020-02-02T02:02:02",
"updated": "2020-03-03T03:03:03",
"deleted": nil,
}

base.MockGet(formatMockAPIPath("networking/prefixlists/%d", prefixListID), response)

prefixList, err := base.Client.GetPrefixList(context.Background(), prefixListID)
assert.NoError(t, err)
assert.Equal(t, prefixListID, prefixList.ID)
if assert.NotNil(t, prefixList.Created) {
assert.Equal(t, time.Date(2020, time.February, 2, 2, 2, 2, 0, time.UTC), prefixList.Created.UTC())
}
if assert.NotNil(t, prefixList.Updated) {
assert.Equal(t, time.Date(2020, time.March, 3, 3, 3, 3, 0, time.UTC), prefixList.Updated.UTC())
}
}

func TestPrefixList_UnmarshalJSON(t *testing.T) {
var prefixList linodego.PrefixList

raw := []byte(`{
"id": 1,
"name": "pl:test",
"visibility": "restricted",
"ipv4": ["203.0.113.0/24"],
"ipv6": ["2001:db8::/64"],
"version": 5,
"created": "2017-05-05T05:05:05",
"updated": "2017-06-06T06:06:06",
"deleted": null
}`)

assert.NoError(t, json.Unmarshal(raw, &prefixList))
if assert.NotNil(t, prefixList.Created) {
assert.Equal(t, time.Date(2017, time.May, 5, 5, 5, 5, 0, time.UTC), prefixList.Created.UTC())
}
if assert.NotNil(t, prefixList.Updated) {
assert.Equal(t, time.Date(2017, time.June, 6, 6, 6, 6, 0, time.UTC), prefixList.Updated.UTC())
}
assert.Nil(t, prefixList.Deleted)
}