253 lines
6.6 KiB
Go
253 lines
6.6 KiB
Go
|
package lib
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"net/url"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
// FirewallGroup represents a firewall group on Vultr
|
||
|
type FirewallGroup struct {
|
||
|
ID string `json:"FIREWALLGROUPID"`
|
||
|
Description string `json:"description"`
|
||
|
Created string `json:"date_created"`
|
||
|
Modified string `json:"date_modified"`
|
||
|
InstanceCount int `json:"instance_count"`
|
||
|
RuleCount int `json:"rule_count"`
|
||
|
MaxRuleCount int `json:"max_rule_count"`
|
||
|
}
|
||
|
|
||
|
// FirewallRule represents a firewall rule on Vultr
|
||
|
type FirewallRule struct {
|
||
|
RuleNumber int `json:"rulenumber"`
|
||
|
Action string `json:"action"`
|
||
|
Protocol string `json:"protocol"`
|
||
|
Port string `json:"port"`
|
||
|
Network *net.IPNet
|
||
|
}
|
||
|
|
||
|
type firewallGroups []FirewallGroup
|
||
|
|
||
|
func (f firewallGroups) Len() int { return len(f) }
|
||
|
func (f firewallGroups) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||
|
func (f firewallGroups) Less(i, j int) bool {
|
||
|
// sort order: description
|
||
|
return strings.ToLower(f[i].Description) < strings.ToLower(f[j].Description)
|
||
|
}
|
||
|
|
||
|
type firewallRules []FirewallRule
|
||
|
|
||
|
func (r firewallRules) Len() int { return len(r) }
|
||
|
func (r firewallRules) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||
|
func (r firewallRules) Less(i, j int) bool {
|
||
|
// sort order: rule number
|
||
|
return r[i].RuleNumber < r[j].RuleNumber
|
||
|
}
|
||
|
|
||
|
// UnmarshalJSON implements json.Unmarshaller on FirewallRule.
|
||
|
// This is needed because the Vultr API is inconsistent in it's JSON responses.
|
||
|
// Some fields can change type, from JSON number to JSON string and vice-versa.
|
||
|
func (r *FirewallRule) UnmarshalJSON(data []byte) (err error) {
|
||
|
if r == nil {
|
||
|
*r = FirewallRule{}
|
||
|
}
|
||
|
|
||
|
var fields map[string]interface{}
|
||
|
if err := json.Unmarshal(data, &fields); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
value := fmt.Sprintf("%v", fields["rulenumber"])
|
||
|
if len(value) == 0 || value == "<nil>" {
|
||
|
value = "0"
|
||
|
}
|
||
|
number, err := strconv.ParseInt(value, 10, 64)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
r.RuleNumber = int(number)
|
||
|
|
||
|
value = fmt.Sprintf("%v", fields["subnet_size"])
|
||
|
if len(value) == 0 || value == "<nil>" {
|
||
|
value = "0"
|
||
|
}
|
||
|
subnetSize, err := strconv.ParseInt(value, 10, 64)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
r.Action = fmt.Sprintf("%v", fields["action"])
|
||
|
r.Protocol = fmt.Sprintf("%v", fields["protocol"])
|
||
|
r.Port = fmt.Sprintf("%v", fields["port"])
|
||
|
subnet := fmt.Sprintf("%v", fields["subnet"])
|
||
|
if subnet == "<nil>" {
|
||
|
subnet = ""
|
||
|
}
|
||
|
|
||
|
if len(subnet) > 0 {
|
||
|
_, r.Network, err = net.ParseCIDR(fmt.Sprintf("%s/%d", subnet, subnetSize))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("Failed to parse subnet from Vultr API")
|
||
|
}
|
||
|
} else {
|
||
|
// This case is used to create a valid default CIDR when the Vultr API does not return a subnet/subnet size at all, e.g. the response after creating a new rule.
|
||
|
_, r.Network, _ = net.ParseCIDR("0.0.0.0/0")
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// GetFirewallGroups returns a list of all available firewall groups on Vultr
|
||
|
func (c *Client) GetFirewallGroups() ([]FirewallGroup, error) {
|
||
|
var groupMap map[string]FirewallGroup
|
||
|
if err := c.get(`firewall/group_list`, &groupMap); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
var groupList []FirewallGroup
|
||
|
for _, g := range groupMap {
|
||
|
groupList = append(groupList, g)
|
||
|
}
|
||
|
sort.Sort(firewallGroups(groupList))
|
||
|
return groupList, nil
|
||
|
}
|
||
|
|
||
|
// GetFirewallGroup returns the firewall group with given ID
|
||
|
func (c *Client) GetFirewallGroup(id string) (FirewallGroup, error) {
|
||
|
groups, err := c.GetFirewallGroups()
|
||
|
if err != nil {
|
||
|
return FirewallGroup{}, err
|
||
|
}
|
||
|
|
||
|
for _, g := range groups {
|
||
|
if g.ID == id {
|
||
|
return g, nil
|
||
|
}
|
||
|
}
|
||
|
return FirewallGroup{}, fmt.Errorf("Firewall group with ID %v not found", id)
|
||
|
}
|
||
|
|
||
|
// CreateFirewallGroup creates a new firewall group in Vultr account
|
||
|
func (c *Client) CreateFirewallGroup(description string) (string, error) {
|
||
|
values := url.Values{}
|
||
|
|
||
|
// Optional description
|
||
|
if len(description) > 0 {
|
||
|
values.Add("description", description)
|
||
|
}
|
||
|
|
||
|
var result FirewallGroup
|
||
|
err := c.post(`firewall/group_create`, values, &result)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
return result.ID, nil
|
||
|
}
|
||
|
|
||
|
// DeleteFirewallGroup deletes an existing firewall group
|
||
|
func (c *Client) DeleteFirewallGroup(groupID string) error {
|
||
|
values := url.Values{
|
||
|
"FIREWALLGROUPID": {groupID},
|
||
|
}
|
||
|
|
||
|
if err := c.post(`firewall/group_delete`, values, nil); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// SetFirewallGroupDescription sets the description of an existing firewall group
|
||
|
func (c *Client) SetFirewallGroupDescription(groupID, description string) error {
|
||
|
values := url.Values{
|
||
|
"FIREWALLGROUPID": {groupID},
|
||
|
"description": {description},
|
||
|
}
|
||
|
|
||
|
if err := c.post(`firewall/group_set_description`, values, nil); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// GetFirewallRules returns a list of rules for the given firewall group
|
||
|
func (c *Client) GetFirewallRules(groupID string) ([]FirewallRule, error) {
|
||
|
var ruleMap map[string]FirewallRule
|
||
|
ipTypes := []string{"v4", "v6"}
|
||
|
for _, ipType := range ipTypes {
|
||
|
args := fmt.Sprintf("direction=in&FIREWALLGROUPID=%s&ip_type=%s",
|
||
|
groupID, ipType)
|
||
|
if err := c.get(`firewall/rule_list?`+args, &ruleMap); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var ruleList []FirewallRule
|
||
|
for _, r := range ruleMap {
|
||
|
ruleList = append(ruleList, r)
|
||
|
}
|
||
|
sort.Sort(firewallRules(ruleList))
|
||
|
return ruleList, nil
|
||
|
}
|
||
|
|
||
|
// CreateFirewallRule creates a new firewall rule in Vultr account.
|
||
|
// groupID is the ID of the firewall group to create the rule in
|
||
|
// protocol must be one of: "icmp", "tcp", "udp", "gre"
|
||
|
// port can be a port number or colon separated port range (TCP/UDP only)
|
||
|
func (c *Client) CreateFirewallRule(groupID, protocol, port string,
|
||
|
network *net.IPNet) (int, error) {
|
||
|
ip := network.IP.String()
|
||
|
maskBits, _ := network.Mask.Size()
|
||
|
if ip == "<nil>" {
|
||
|
return 0, fmt.Errorf("Invalid network")
|
||
|
}
|
||
|
|
||
|
var ipType string
|
||
|
if network.IP.To4() != nil {
|
||
|
ipType = "v4"
|
||
|
} else {
|
||
|
ipType = "v6"
|
||
|
}
|
||
|
|
||
|
values := url.Values{
|
||
|
"FIREWALLGROUPID": {groupID},
|
||
|
// possible values: "in"
|
||
|
"direction": {"in"},
|
||
|
// possible values: "icmp", "tcp", "udp", "gre"
|
||
|
"protocol": {protocol},
|
||
|
// possible values: "v4", "v6"
|
||
|
"ip_type": {ipType},
|
||
|
// IP address representing a subnet
|
||
|
"subnet": {ip},
|
||
|
// IP prefix size in bits
|
||
|
"subnet_size": {fmt.Sprintf("%v", maskBits)},
|
||
|
}
|
||
|
|
||
|
if len(port) > 0 {
|
||
|
values.Add("port", port)
|
||
|
}
|
||
|
|
||
|
var result FirewallRule
|
||
|
err := c.post(`firewall/rule_create`, values, &result)
|
||
|
if err != nil {
|
||
|
return 0, err
|
||
|
}
|
||
|
return result.RuleNumber, nil
|
||
|
}
|
||
|
|
||
|
// DeleteFirewallRule deletes an existing firewall rule
|
||
|
func (c *Client) DeleteFirewallRule(ruleNumber int, groupID string) error {
|
||
|
values := url.Values{
|
||
|
"FIREWALLGROUPID": {groupID},
|
||
|
"rulenumber": {fmt.Sprintf("%v", ruleNumber)},
|
||
|
}
|
||
|
|
||
|
if err := c.post(`firewall/rule_delete`, values, nil); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
return nil
|
||
|
}
|