Skip to content

Commit

Permalink
Add firewall support for Loadbalancers (#911)
Browse files Browse the repository at this point in the history
* Pin godo dep to master for development

* Patch monitor alerts after godo upgrade (LB http alerts were split into 4xx and 5xx alerts)

* Add Firewall support to Load Balancer datasource & resource

* Bump godo to v1.92.0

* Run `go mod tidy`, update monitor alert doc

* Add regression test and documentation for setting LB firewalls.
  • Loading branch information
jrolheiser committed Dec 21, 2022
1 parent 29185c2 commit 8530567
Show file tree
Hide file tree
Showing 16 changed files with 392 additions and 56 deletions.
26 changes: 25 additions & 1 deletion digitalocean/datasource_digitalocean_loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,12 +220,32 @@ func dataSourceDigitalOceanLoadbalancer() *schema.Resource {
Computed: true,
Description: " Specifies the idle timeout for HTTPS connections on the load balancer.",
},

"project_id": {
Type: schema.TypeString,
Computed: true,
Description: "The ID of the project that the load balancer is associated with.",
},
"firewall": {
Type: schema.TypeList,
Computed: true,
Description: "the firewall rules for allowing/denying traffic to the load balancer",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"allow": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: 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,
Elem: &schema.Schema{Type: schema.TypeString},
Computed: 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 @@ -324,6 +344,10 @@ func dataSourceDigitalOceanLoadbalancerRead(ctx context.Context, d *schema.Resou
return diag.Errorf("[DEBUG] Error setting Load Balancer forwarding_rule - error: %#v", err)
}

if err := d.Set("firewall", flattenLBFirewall(foundLoadbalancer.Firewall)); err != nil {
return diag.Errorf("[DEBUG] Error setting Load Balancer firewall - error: %#v", err)
}

return nil
}

Expand Down
38 changes: 38 additions & 0 deletions digitalocean/loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ func expandStickySessions(config []interface{}) *godo.StickySessions {
return stickySession
}

func expandLBFirewall(config []interface{}) *godo.LBFirewall {
firewallConfig := config[0].(map[string]interface{})

firewall := &godo.LBFirewall{}

if v, ok := firewallConfig["allow"]; ok {
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 {
denies := make([]string, 0, len(v.([]interface{})))
for _, val := range v.([]interface{}) {
denies = append(denies, val.(string))
}
firewall.Deny = denies
}

return firewall
}

func expandHealthCheck(config []interface{}) *godo.HealthCheck {
healthcheckConfig := config[0].(map[string]interface{})

Expand Down Expand Up @@ -187,6 +211,20 @@ func flattenStickySessions(session *godo.StickySessions) []map[string]interface{
return result
}

func flattenLBFirewall(firewall *godo.LBFirewall) []map[string]interface{} {
result := make([]map[string]interface{}, 0, 1)

if firewall != nil {
r := make(map[string]interface{})
r["allow"] = (*firewall).Allow
r["deny"] = (*firewall).Deny

result = append(result, r)
}

return result
}

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

Expand Down
31 changes: 31 additions & 0 deletions digitalocean/resource_digitalocean_loadbalancer.go
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,29 @@ func resourceDigitalOceanLoadBalancerV0() *schema.Resource {
Optional: true,
Computed: true,
},

"firewall": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"allow": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
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.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
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 @@ -459,6 +482,10 @@ func buildLoadBalancerRequest(client *godo.Client, d *schema.ResourceData) (*god
opts.StickySessions = expandStickySessions(v.([]interface{}))
}

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

if v, ok := d.GetOk("vpc_uuid"); ok {
opts.VPCUUID = v.(string)
}
Expand Down Expand Up @@ -556,6 +583,10 @@ func resourceDigitalOceanLoadbalancerRead(ctx context.Context, d *schema.Resourc
return diag.Errorf("[DEBUG] Error setting Load Balancer forwarding_rule - error: %#v", err)
}

if err := d.Set("firewall", flattenLBFirewall(loadbalancer.Firewall)); err != nil {
return diag.Errorf("[DEBUG] Error setting Load Balancer firewall - error: %#v", err)
}

return nil

}
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)
}
6 changes: 4 additions & 2 deletions digitalocean/resource_digitalocean_monitor_alert.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@ func resourceDigitalOceanMonitorAlert() *schema.Resource {
godo.LoadBalancerConnectionUtilizationPercent,
godo.LoadBalancerDropletHealth,
godo.LoadBalancerTLSUtilizationPercent,
godo.LoadBalancerIncreaseInHTTPErrorRatePercentage,
godo.LoadBalancerIncreaseInHTTPErrorRateCount,
godo.LoadBalancerIncreaseInHTTPErrorRatePercentage4xx,
godo.LoadBalancerIncreaseInHTTPErrorRatePercentage5xx,
godo.LoadBalancerIncreaseInHTTPErrorRateCount4xx,
godo.LoadBalancerIncreaseInHTTPErrorRateCount5xx,
godo.LoadBalancerHighHttpResponseTime,
godo.LoadBalancerHighHttpResponseTime50P,
godo.LoadBalancerHighHttpResponseTime95P,
Expand Down
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
3 changes: 2 additions & 1 deletion docs/resources/monitor_alert.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ The following arguments are supported:
`v1/insights/droplet/private_outbound_bandwidth`, `v1/insights/droplet/private_inbound_bandwidth`,
`v1/insights/lbaas/avg_cpu_utilization_percent`, `v1/insights/lbaas/connection_utilization_percent`,
`v1/insights/lbaas/droplet_health`, `v1/insights/lbaas/tls_connections_per_second_utilization_percent`,
`v1/insights/lbaas/increase_in_http_error_rate_percentage`, `v1/insights/lbaas/increase_in_http_error_rate_count`,
`v1/insights/lbaas/increase_in_http_error_rate_percentage_5xx`, `v1/insights/lbaas/increase_in_http_error_rate_percentage_4xx`,
`v1/insights/lbaas/increase_in_http_error_rate_count_5xx`, `v1/insights/lbaas/increase_in_http_error_rate_count_4xx`,
`v1/insights/lbaas/high_http_request_response_time`, `v1/insights/lbaas/high_http_request_response_time_50p`,
`v1/insights/lbaas/high_http_request_response_time_95p`, `v1/insights/lbaas/high_http_request_response_time_99p`,
`v1/dbaas/alerts/load_15_alerts`, `v1/dbaas/alerts/cpu_alerts`, `v1/dbaas/alerts/memory_utilization_alerts`, or
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/digitalocean/terraform-provider-digitalocean

require (
github.com/aws/aws-sdk-go v1.42.18
github.com/digitalocean/godo v1.91.1
github.com/digitalocean/godo v1.92.0
github.com/hashicorp/awspolicyequivalence v1.5.0
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-version v1.3.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digitalocean/godo v1.91.1 h1:1o30VOCu1aC6488qBd0SkQiBeAZ35RSTvLwCA1pQMhc=
github.com/digitalocean/godo v1.91.1/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
github.com/digitalocean/godo v1.92.0 h1:eK9DgdLcjozZbywjh/9k2NF++ttbcidASOoCGRu48yQ=
github.com/digitalocean/godo v1.92.0/go.mod h1:NRpFznZFvhHjBoqZAaOD3khVzsJ3EibzKqFL4R60dmA=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
Expand Down
8 changes: 8 additions & 0 deletions vendor/github.com/digitalocean/godo/CHANGELOG.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8530567

Please sign in to comment.