forked from linode/linodego
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request linode#131 from Jskobos/feature/cloud-firewalls
Adds support for cloud firewalls
- Loading branch information
Showing
9 changed files
with
506 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package linodego | ||
|
||
// NetworkProtocol enum type | ||
type NetworkProtocol string | ||
|
||
// NetworkProtocol enum values | ||
const ( | ||
TCP NetworkProtocol = "TCP" | ||
UDP NetworkProtocol = "UDP" | ||
ICMP NetworkProtocol = "ALL" | ||
) | ||
|
||
// NetworkAddresses are arrays of ipv4 and v6 addresses | ||
type NetworkAddresses struct { | ||
IPv4 []string `json:"ipv4"` | ||
IPv6 []string `json:"ipv6"` | ||
} | ||
|
||
// A FirewallRule is a whitelist of ports, protocols, and addresses for which traffic should be allowed. | ||
type FirewallRule struct { | ||
Ports string `json:"ports"` | ||
Protocol NetworkProtocol `json:"protocol"` | ||
Addresses NetworkAddresses `json:"addresses"` | ||
} | ||
|
||
// FirewallRuleSet is a pair of inbound and outbound rules that specify what network traffic should be allowed. | ||
type FirewallRuleSet struct { | ||
Inbound []FirewallRule `json:"inbound,omitempty"` | ||
Outbound []FirewallRule `json:"outbound,omitempty"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
package linodego | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/linode/linodego/internal/parseabletime" | ||
) | ||
|
||
// FirewallStatus enum type | ||
type FirewallStatus string | ||
|
||
// FirewallStatus enums start with Firewall | ||
const ( | ||
FirewallEnabled FirewallStatus = "enabled" | ||
FirewallDisabled FirewallStatus = "disabled" | ||
FirewallDeleted FirewallStatus = "deleted" | ||
) | ||
|
||
// A Firewall is a set of networking rules (iptables) applied to Devices with which it is associated | ||
type Firewall struct { | ||
ID int `json:"id"` | ||
Label string `json:"label"` | ||
Status FirewallStatus `json:"status"` | ||
Tags []string `json:"tags,omitempty"` | ||
Rules FirewallRuleSet `json:"rules"` | ||
Created *time.Time `json:"-"` | ||
Updated *time.Time `json:"-"` | ||
} | ||
|
||
// DevicesCreationOptions fields are used when adding devices during the Firewall creation process. | ||
type DevicesCreationOptions struct { | ||
Linodes []string `json:"linodes,omitempty"` | ||
NodeBalancers []string `json:"nodebalancers,omitempty"` | ||
} | ||
|
||
// FirewallCreateOptions fields are those accepted by CreateFirewall | ||
type FirewallCreateOptions struct { | ||
Label string `json:"label,omitempty"` | ||
Rules FirewallRuleSet `json:"rules"` | ||
Tags []string `json:"tags,omitempty"` | ||
Devices DevicesCreationOptions `json:"devices,omitempty"` | ||
} | ||
|
||
// UnmarshalJSON for Firewall responses | ||
func (i *Firewall) UnmarshalJSON(b []byte) error { | ||
type Mask Firewall | ||
|
||
p := struct { | ||
*Mask | ||
Created *parseabletime.ParseableTime `json:"created"` | ||
Updated *parseabletime.ParseableTime `json:"updated"` | ||
}{ | ||
Mask: (*Mask)(i), | ||
} | ||
|
||
if err := json.Unmarshal(b, &p); err != nil { | ||
return err | ||
} | ||
|
||
i.Created = (*time.Time)(p.Created) | ||
i.Updated = (*time.Time)(p.Updated) | ||
|
||
return nil | ||
} | ||
|
||
// FirewallsPagedResponse represents a Linode API response for listing of Cloud Firewalls | ||
type FirewallsPagedResponse struct { | ||
*PageOptions | ||
Data []Firewall `json:"data"` | ||
} | ||
|
||
func (FirewallsPagedResponse) endpoint(c *Client) string { | ||
endpoint, err := c.Firewalls.Endpoint() | ||
if err != nil { | ||
panic(err) | ||
} | ||
return endpoint | ||
} | ||
|
||
func (resp *FirewallsPagedResponse) appendData(r *FirewallsPagedResponse) { | ||
resp.Data = append(resp.Data, r.Data...) | ||
} | ||
|
||
// ListFirewalls returns a paginated list of Cloud Firewalls | ||
func (c *Client) ListFirewalls(ctx context.Context, opts *ListOptions) ([]Firewall, error) { | ||
response := FirewallsPagedResponse{} | ||
|
||
err := c.listHelper(ctx, &response, opts) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return response.Data, nil | ||
} | ||
|
||
// CreateFirewall creates a single Firewall with at least one set of inbound or outbound rules | ||
func (c *Client) CreateFirewall(ctx context.Context, createOpts FirewallCreateOptions) (*Firewall, error) { | ||
var body string | ||
e, err := c.Firewalls.Endpoint() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
req := c.R(ctx).SetResult(&Firewall{}) | ||
|
||
if bodyData, err := json.Marshal(createOpts); err == nil { | ||
body = string(bodyData) | ||
} else { | ||
return nil, NewError(err) | ||
} | ||
|
||
r, err := coupleAPIErrors(req. | ||
SetBody(body). | ||
Post(e)) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
return r.Result().(*Firewall), nil | ||
} | ||
|
||
// DeleteFirewall deletes a single Firewall with the provided ID | ||
func (c *Client) DeleteFirewall(ctx context.Context, id int) error { | ||
e, err := c.Firewalls.Endpoint() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
req := c.R(ctx) | ||
|
||
e = fmt.Sprintf("%s/%d", e, id) | ||
_, err = coupleAPIErrors(req.Delete(e)) | ||
return err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package integration | ||
|
||
import ( | ||
"github.com/linode/linodego" | ||
) | ||
|
||
var ( | ||
testFirewallRule = linodego.FirewallRule{ | ||
Ports: "22", | ||
Protocol: "TCP", | ||
Addresses: linodego.NetworkAddresses{ | ||
IPv4: []string{"0.0.0.0/0"}, | ||
IPv6: []string{"::0/0"}, | ||
}, | ||
} | ||
|
||
testFirewallRuleSet = linodego.FirewallRuleSet{ | ||
Inbound: []linodego.FirewallRule{testFirewallRule}, | ||
Outbound: []linodego.FirewallRule{testFirewallRule}, | ||
} | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package integration | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/linode/linodego" | ||
) | ||
|
||
var ( | ||
testFirewallCreateOpts = linodego.FirewallCreateOptions{ | ||
Label: "label", | ||
Rules: testFirewallRuleSet, // borrowed from firewall_rules.test.go | ||
Tags: []string{"testing"}, | ||
} | ||
) | ||
|
||
// TestListFirewalls should return a paginated list of Firewalls | ||
func TestListFirewalls(t *testing.T) { | ||
client, _, teardown, err := setupFirewall(t, []firewallModifier{ | ||
func(createOpts *linodego.FirewallCreateOptions) { | ||
createOpts.Label = randString(12, lowerBytes, digits) + "-linodego-testing" | ||
}, | ||
}, "fixtures/TestListFirewalls") | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
defer teardown() | ||
|
||
result, err := client.ListFirewalls(context.Background(), nil) | ||
if err != nil { | ||
t.Errorf("Error listing Firewalls, expected struct, got error %v", err) | ||
} | ||
|
||
if len(result) == 0 { | ||
t.Errorf("Expected a list of Firewalls, but got none: %v", err) | ||
} | ||
} | ||
|
||
type firewallModifier func(*linodego.FirewallCreateOptions) | ||
|
||
func setupFirewall(t *testing.T, firewallModifiers []firewallModifier, fixturesYaml string) (*linodego.Client, *linodego.Firewall, func(), error) { | ||
t.Helper() | ||
var fixtureTeardown func() | ||
client, fixtureTeardown := createTestClient(t, fixturesYaml) | ||
|
||
createOpts := testFirewallCreateOpts | ||
for _, modifier := range firewallModifiers { | ||
modifier(&createOpts) | ||
} | ||
|
||
firewall, err := client.CreateFirewall(context.Background(), createOpts) | ||
if err != nil { | ||
t.Errorf("Error creating Firewall, expected struct, got error %v", err) | ||
} | ||
|
||
teardown := func() { | ||
if err := client.DeleteFirewall(context.Background(), firewall.ID); err != nil { | ||
t.Errorf("Expected to delete a Firewall, but got %v", err) | ||
} | ||
fixtureTeardown() | ||
} | ||
return client, firewall, teardown, err | ||
} |
Oops, something went wrong.