Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add firewall support for Loadbalancers #911

Merged
merged 6 commits into from
Dec 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Add regression test and documentation for setting LB firewalls.
  • Loading branch information
jrolheiser committed Dec 20, 2022
commit 93deeb16271281ff3bf8339acf4d202157ef9b3e
25 changes: 12 additions & 13 deletions digitalocean/loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,19 @@ func expandLBFirewall(config []interface{}) *godo.LBFirewall {
firewall := &godo.LBFirewall{}

if v, ok := firewallConfig["allow"]; ok {
firewall.Allow = v.([]string)
allows := make([]string, 0, len(v.([]interface{})))
for _, val := range v.([]interface{}) {
allows = append(allows, val.(string))
}
firewall.Allow = allows
}

if v, ok := firewallConfig["deny"]; ok {
firewall.Deny = v.([]string)
denies := make([]string, 0, len(v.([]interface{})))
for _, val := range v.([]interface{}) {
denies = append(denies, val.(string))
}
firewall.Deny = denies
}

return firewall
Expand Down Expand Up @@ -208,24 +216,15 @@ func flattenLBFirewall(firewall *godo.LBFirewall) []map[string]interface{} {

if firewall != nil {
r := make(map[string]interface{})
// TODO: Unsure if nested rules requiring flattening? (jrolheiser)
r["allow"] = flattenFirewallRules((*firewall).Allow)
r["deny"] = flattenFirewallRules((*firewall).Deny)
r["allow"] = (*firewall).Allow
r["deny"] = (*firewall).Deny

result = append(result, r)
}

return result
}

func flattenFirewallRules(rules []string) *schema.Set {
flatSet := schema.NewSet(schema.HashString, []interface{}{})
for _, v := range rules {
flatSet.Add(v)
}
return flatSet
}

func flattenForwardingRules(client *godo.Client, rules []godo.ForwardingRule) ([]map[string]interface{}, error) {
result := make([]map[string]interface{}, 0, 1)

Expand Down
12 changes: 6 additions & 6 deletions digitalocean/resource_digitalocean_loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,22 +379,22 @@ func resourceDigitalOceanLoadBalancerV0() *schema.Resource {
},

"firewall": {
Type: schema.TypeList,
Type: schema.TypeSet,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"allow": {
Type: schema.TypeSet,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
Optional: true,
Description: "the rules for ALLOWING traffic to the LB (strings in the form: 'ip:1.2.3.4' or 'cidr:1.2.0.0/16')",
},
"deny": {
Type: schema.TypeSet,
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: true,
Optional: true,
Description: "the rules for DENYING traffic to the LB (strings in the form: 'ip:1.2.3.4' or 'cidr:1.2.0.0/16')",
},
},
Expand Down Expand Up @@ -483,7 +483,7 @@ func buildLoadBalancerRequest(client *godo.Client, d *schema.ResourceData) (*god
}

if v, ok := d.GetOk("firewall"); ok {
opts.Firewall = expandLBFirewall(v.([]interface{}))
opts.Firewall = expandLBFirewall(v.(*schema.Set).List())
}

if v, ok := d.GetOk("vpc_uuid"); ok {
Expand Down
62 changes: 62 additions & 0 deletions digitalocean/resource_digitalocean_loadbalancer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,37 @@ func TestAccDigitalOceanLoadbalancer_WithVPC(t *testing.T) {
})
}

func TestAccDigitalOceanLoadbalancer_Firewall(t *testing.T) {
var loadbalancer godo.LoadBalancer
lbName := randomTestName()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactories,
CheckDestroy: testAccCheckDigitalOceanLoadbalancerDestroy,
Steps: []resource.TestStep{
{
Config: testAccCheckDigitalOceanLoadbalancerConfig_Firewall(lbName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckDigitalOceanLoadbalancerExists("digitalocean_loadbalancer.foobar", &loadbalancer),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "name", lbName),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "firewall.#", "1"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "firewall.0.deny.0", "cidr:1.2.0.0/16"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "firewall.0.deny.1", "ip:2.3.4.5"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "firewall.0.allow.0", "ip:1.2.3.4"),
resource.TestCheckResourceAttr(
"digitalocean_loadbalancer.foobar", "firewall.0.allow.1", "cidr:2.3.4.0/24"),
),
},
},
})
}

func testAccCheckDigitalOceanLoadbalancerDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*CombinedConfig).godoClient()

Expand Down Expand Up @@ -1122,3 +1153,34 @@ resource "digitalocean_loadbalancer" "foobar" {
droplet_ids = [digitalocean_droplet.foobar.id]
}`, randomTestName(), randomTestName(), name)
}

func testAccCheckDigitalOceanLoadbalancerConfig_Firewall(name string) string {
return fmt.Sprintf(`
resource "digitalocean_droplet" "foobar" {
name = "%s"
size = "s-1vcpu-1gb"
image = "ubuntu-22-04-x64"
region = "nyc3"
}

resource "digitalocean_loadbalancer" "foobar" {
name = "%s"
region = "nyc3"
size = "lb-small"

forwarding_rule {
entry_port = 80
entry_protocol = "http"

target_port = 80
target_protocol = "http"
}

firewall {
deny = ["cidr:1.2.0.0/16", "ip:2.3.4.5"]
allow = ["ip:1.2.3.4", "cidr:2.3.4.0/24"]
}

droplet_ids = [digitalocean_droplet.foobar.id]
}`, randomTestName(), name)
}
9 changes: 8 additions & 1 deletion docs/resources/loadbalancer.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ the backend service. Default value is `false`.
* `vpc_uuid` - (Optional) The ID of the VPC where the load balancer will be located.
* `droplet_ids` (Optional) - A list of the IDs of each droplet to be attached to the Load Balancer.
* `droplet_tag` (Optional) - The name of a Droplet tag corresponding to Droplets to be assigned to the Load Balancer.
* `firewall` (Optional) - A block containing rules for allowing/denying traffic to the Load Balancer. The `firewall` block is documented below. Only 1 firewall is allowed.

`forwarding_rule` supports the following:

Expand All @@ -137,11 +138,17 @@ the backend service. Default value is `false`.
* `protocol` - (Required) The protocol used for health checks sent to the backend Droplets. The possible values are `http`, `https` or `tcp`.
* `port` - (Optional) An integer representing the port on the backend Droplets on which the health check will attempt a connection.
* `path` - (Optional) The path on the backend Droplets to which the Load Balancer instance will send a request.
* `check_interval_seconds` - (Optional) The number of seconds between between two consecutive health checks. If not specified, the default value is `10`.
* `check_interval_seconds` - (Optional) The number of seconds between two consecutive health checks. If not specified, the default value is `10`.
* `response_timeout_seconds` - (Optional) The number of seconds the Load Balancer instance will wait for a response until marking a health check as failed. If not specified, the default value is `5`.
* `unhealthy_threshold` - (Optional) The number of times a health check must fail for a backend Droplet to be marked "unhealthy" and be removed from the pool. If not specified, the default value is `3`.
* `healthy_threshold` - (Optional) The number of times a health check must pass for a backend Droplet to be marked "healthy" and be re-added to the pool. If not specified, the default value is `5`.

`firewall` supports the following:

* `deny` - (Optional) A list of strings describing deny rules. Must be colon delimited strings of the form `{type}:{source}`
* `allow` - (Optional) A list of strings describing allow rules. Must be colon delimited strings of the form `{type}:{source}`
* Ex. `deny = ["cidr:1.2.0.0/16", "ip:2.3.4.5"]` or `allow = ["ip:1.2.3.4", "cidr:2.3.4.0/24"]`


## Attributes Reference

Expand Down