resource_digitalocean_firewall.go: Add DO Cloud Firewalls (#15124)

https://www.digitalocean.com/products/cloud-firewalls/
https://blog.digitalocean.com/cloud-firewalls-secure-droplets-by-default/
4fa9e9d999

air$ go test -v -run TestAccDigitalOceanFirewall
=== RUN   TestAccDigitalOceanFirewall_importBasic
=== RUN   TestAccDigitalOceanFirewall_importBasic/only_allow_inbound_SSH(TCP/22)
=== RUN   TestAccDigitalOceanFirewall_importBasic/only_allow_outbound_SSH(TCP/22)
=== RUN   TestAccDigitalOceanFirewall_importBasic/only_allow_inbound_SSH(TCP/22)_and_HTTP(TCP/80)
=== RUN   TestAccDigitalOceanFirewall_importBasic/only_allow_outbound_SSH(TCP/22)_and_DNS(UDP/53)
=== RUN   TestAccDigitalOceanFirewall_importBasic/allow_inbound_and_outbound_HTTPS(TCP/443),_inbound_SSH(TCP/22),_and_outbound_DNS(UDP/53)
--- PASS: TestAccDigitalOceanFirewall_importBasic (19.14s)
--- PASS: TestAccDigitalOceanFirewall_importBasic/only_allow_inbound_SSH(TCP/22) (3.89s)
--- PASS: TestAccDigitalOceanFirewall_importBasic/only_allow_outbound_SSH(TCP/22) (4.61s)
--- PASS: TestAccDigitalOceanFirewall_importBasic/only_allow_inbound_SSH(TCP/22)_and_HTTP(TCP/80) (3.48s)
--- PASS: TestAccDigitalOceanFirewall_importBasic/only_allow_outbound_SSH(TCP/22)_and_DNS(UDP/53) (4.05s)
--- PASS: TestAccDigitalOceanFirewall_importBasic/allow_inbound_and_outbound_HTTPS(TCP/443),_inbound_SSH(TCP/22),_and_outbound_DNS(UDP/53) (3.11s)
=== RUN   TestAccDigitalOceanFirewall_Basic
=== RUN   TestAccDigitalOceanFirewall_Basic/only_allow_inbound_SSH(TCP/22)
=== RUN   TestAccDigitalOceanFirewall_Basic/only_allow_outbound_SSH(TCP/22)
=== RUN   TestAccDigitalOceanFirewall_Basic/only_allow_inbound_SSH(TCP/22)_and_HTTP(TCP/80)
=== RUN   TestAccDigitalOceanFirewall_Basic/only_allow_outbound_SSH(TCP/22)_and_DNS(UDP/53)
=== RUN   TestAccDigitalOceanFirewall_Basic/allow_inbound_and_outbound_HTTPS(TCP/443),_inbound_SSH(TCP/22),_and_outbound_DNS(UDP/53)
--- PASS: TestAccDigitalOceanFirewall_Basic (18.77s)
--- PASS: TestAccDigitalOceanFirewall_Basic/only_allow_inbound_SSH(TCP/22) (3.56s)
--- PASS: TestAccDigitalOceanFirewall_Basic/only_allow_outbound_SSH(TCP/22) (3.73s)
--- PASS: TestAccDigitalOceanFirewall_Basic/only_allow_inbound_SSH(TCP/22)_and_HTTP(TCP/80) (3.21s)
--- PASS: TestAccDigitalOceanFirewall_Basic/only_allow_outbound_SSH(TCP/22)_and_DNS(UDP/53) (3.84s)
--- PASS: TestAccDigitalOceanFirewall_Basic/allow_inbound_and_outbound_HTTPS(TCP/443),_inbound_SSH(TCP/22),_and_outbound_DNS(UDP/53) (4.43s)
PASS ok github.com/terraform-providers/terraform-provider-digitalocean/digitalocean 37.920s
air$
This commit is contained in:
Kei Nohguchi 2017-06-06 08:29:54 -07:00
parent 7a9ee7c2a1
commit f193630f42
6 changed files with 1114 additions and 0 deletions

View File

@ -0,0 +1,140 @@
package digitalocean
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
)
func TestAccDigitalOceanFirewall_importBasic(t *testing.T) {
tests := []struct {
description string
firewallName string
config string
}{
{
description: "only allow inbound SSH(TCP/22)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rules = [
{
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
},
]
}`,
},
{
description: "only allow outbound SSH(TCP/22)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
outbound_rules = [
{
protocol = "tcp"
port_range = "22"
destination_addresses = ["0.0.0.0/0", "::/0"]
},
]
}`,
},
{
description: "only allow inbound SSH(TCP/22) and HTTP(TCP/80)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rules = [
{
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
},
{
protocol = "tcp"
port_range = "80"
source_addresses = ["1.2.3.0/24", "2002::/16"]
},
]
}`,
},
{
description: "only allow outbound SSH(TCP/22) and DNS(UDP/53)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
outbound_rules = [
{
protocol = "tcp"
port_range = "22"
destination_addresses = ["192.168.1.0/24", "2002:1001::/48"]
},
{
protocol = "udp"
port_range = "53"
destination_addresses = ["1.2.3.0/24", "2002::/16"]
},
]
}`,
},
{
description: "allow inbound and outbound HTTPS(TCP/443), inbound SSH(TCP/22), and outbound DNS(UDP/53)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rules = [
{
protocol = "tcp"
port_range = "443"
source_addresses = ["192.168.1.0/24", "2002:1001:1:2::/64"]
},
{
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
},
]
outbound_rules = [
{
protocol = "tcp"
port_range = "443"
destination_addresses = ["192.168.1.0/24", "2002:1001:1:2::/64"]
},
{
protocol = "udp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
},
]
}`,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
config := fmt.Sprintf(tt.config, tt.firewallName)
resourceName := fmt.Sprintf("digitalocean_firewall.%s", tt.firewallName)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanFirewallDestroy,
Steps: []resource.TestStep{
{
Config: config,
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
})
}
}

View File

@ -25,6 +25,7 @@ func Provider() terraform.ResourceProvider {
"digitalocean_certificate": resourceDigitalOceanCertificate(),
"digitalocean_domain": resourceDigitalOceanDomain(),
"digitalocean_droplet": resourceDigitalOceanDroplet(),
"digitalocean_firewall": resourceDigitalOceanFirewall(),
"digitalocean_floating_ip": resourceDigitalOceanFloatingIp(),
"digitalocean_loadbalancer": resourceDigitalOceanLoadbalancer(),
"digitalocean_record": resourceDigitalOceanRecord(),

View File

@ -0,0 +1,574 @@
package digitalocean
import (
"bytes"
"context"
"fmt"
"log"
"strconv"
"strings"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceDigitalOceanFirewall() *schema.Resource {
return &schema.Resource{
Create: resourceDigitalOceanFirewallCreate,
Read: resourceDigitalOceanFirewallRead,
Update: resourceDigitalOceanFirewallUpdate,
Delete: resourceDigitalOceanFirewallDelete,
Exists: resourceDigitalOceanFirewallExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"status": {
Type: schema.TypeString,
Computed: true,
},
"created_at": {
Type: schema.TypeString,
Computed: true,
},
"pending_changes": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"droplet_id": {
Type: schema.TypeInt,
Optional: true,
},
"removing": {
Type: schema.TypeBool,
Optional: true,
},
"status": {
Type: schema.TypeString,
Optional: true,
},
},
},
},
"name": {
Type: schema.TypeString,
Required: true,
},
"droplet_ids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"tags": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"inbound_rules": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"protocol": {
Type: schema.TypeString,
Optional: true,
},
"port_range": {
Type: schema.TypeString,
Optional: true,
},
"source_addresses": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"source_tags": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"source_droplet_ids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeInt},
Optional: true,
},
"source_load_balancer_uids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
},
},
},
"outbound_rules": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"protocol": {
Type: schema.TypeString,
Optional: true,
},
"port_range": {
Type: schema.TypeString,
Optional: true,
},
"destination_addresses": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"destination_tags": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"destination_droplet_ids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeInt},
Optional: true,
},
"destination_load_balancer_uids": {
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
},
},
},
},
}
}
func resourceDigitalOceanFirewallCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
opts, err := firewallRequest(d, client)
if err != nil {
return fmt.Errorf("Error in firewall request: %s", err)
}
log.Printf("[DEBUG] Firewall create configuration: %#v", opts)
firewall, _, err := client.Firewalls.Create(context.Background(), opts)
if err != nil {
return fmt.Errorf("Error creating firewall: %s", err)
}
// Assign the firewall id
d.SetId(firewall.ID)
log.Printf("[INFO] Firewall ID: %s", d.Id())
return resourceDigitalOceanFirewallRead(d, meta)
}
func resourceDigitalOceanFirewallRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
// Retrieve the firewall properties for updating the state
firewall, resp, err := client.Firewalls.Get(context.Background(), d.Id())
if err != nil {
// check if the firewall no longer exists.
if resp != nil && resp.StatusCode == 404 {
log.Printf("[WARN] DigitalOcean Firewall (%s) not found", d.Id())
d.SetId("")
return nil
}
return fmt.Errorf("Error retrieving firewall: %s", err)
}
d.Set("status", firewall.Status)
d.Set("create_at", firewall.Created)
d.Set("pending_changes", firewallPendingChanges(d, firewall))
d.Set("name", firewall.Name)
d.Set("droplet_ids", firewall.DropletIDs)
d.Set("tags", firewall.Tags)
d.Set("inbound_rules", matchFirewallInboundRules(d, firewall))
d.Set("outbound_rules", matchFirewallOutboundRules(d, firewall))
return nil
}
func resourceDigitalOceanFirewallUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
opts, err := firewallRequest(d, client)
if err != nil {
return fmt.Errorf("Error in firewall request: %s", err)
}
log.Printf("[DEBUG] Firewall update configuration: %#v", opts)
_, _, err = client.Firewalls.Update(context.Background(), d.Id(), opts)
if err != nil {
return fmt.Errorf("Error updating firewall: %s", err)
}
return resourceDigitalOceanFirewallRead(d, meta)
}
func resourceDigitalOceanFirewallDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*godo.Client)
log.Printf("[INFO] Deleting firewall: %s", d.Id())
// Destroy the droplet
_, err := client.Firewalls.Delete(context.Background(), d.Id())
// Handle remotely destroyed droplets
if err != nil && strings.Contains(err.Error(), "404 Not Found") {
return nil
}
if err != nil {
return fmt.Errorf("Error deleting firewall: %s", err)
}
return nil
}
func resourceDigitalOceanFirewallExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*godo.Client)
log.Printf("[INFO] Exists firewall: %s", d.Id())
// Retrieve the firewall properties for updating the state
_, resp, err := client.Firewalls.Get(context.Background(), d.Id())
if err != nil {
// check if the firewall no longer exists.
if resp != nil && resp.StatusCode == 404 {
log.Printf("[WARN] DigitalOcean Firewall (%s) not found", d.Id())
d.SetId("")
return false, nil
}
return false, fmt.Errorf("Error retrieving firewall: %s", err)
}
return true, nil
}
func firewallRequest(d *schema.ResourceData, client *godo.Client) (*godo.FirewallRequest, error) {
// Build up our firewall request
opts := &godo.FirewallRequest{
Name: d.Get("name").(string),
}
if v, ok := d.GetOk("droplet_ids"); ok {
var droplets []int
for _, id := range v.([]interface{}) {
i, err := strconv.Atoi(id.(string))
if err != nil {
return nil, err
}
droplets = append(droplets, i)
}
opts.DropletIDs = droplets
}
if v, ok := d.GetOk("tags"); ok {
var tags []string
for _, tag := range v.([]interface{}) {
tags = append(tags, tag.(string))
}
opts.Tags = tags
}
// Get inbound_rules
opts.InboundRules = firewallInboundRules(d)
// Get outbound_rules
opts.OutboundRules = firewallOutboundRules(d)
return opts, nil
}
func firewallInboundRules(d *schema.ResourceData) []godo.InboundRule {
rules := make([]godo.InboundRule, 0, len(d.Get("inbound_rules").([]interface{})))
for i, rawRule := range d.Get("inbound_rules").([]interface{}) {
var src godo.Sources
rule := rawRule.(map[string]interface{})
key := fmt.Sprintf("inbound_rules.%d.source_addresses", i)
if vv, ok := d.GetOk(key); ok {
for _, v := range vv.([]interface{}) {
src.Addresses = append(src.Addresses, v.(string))
}
}
key = fmt.Sprintf("inbound_rules.%d.source_tags", i)
if vv, ok := d.GetOk(key); ok {
for _, v := range vv.([]interface{}) {
src.Tags = append(src.Tags, v.(string))
}
}
key = fmt.Sprintf("inbound_rules.%d.source_droplet_ids", i)
if vv, ok := d.GetOk(key); ok {
for _, v := range vv.([]interface{}) {
src.DropletIDs = append(src.DropletIDs, v.(int))
}
}
key = fmt.Sprintf("inbound_rules.%d.source_load_balancer_uids", i)
if vv, ok := d.GetOk(key); ok {
for _, v := range vv.([]interface{}) {
src.LoadBalancerUIDs = append(src.LoadBalancerUIDs, v.(string))
}
}
r := godo.InboundRule{
Protocol: rule["protocol"].(string),
PortRange: rule["port_range"].(string),
Sources: &src,
}
rules = append(rules, r)
}
return rules
}
func firewallOutboundRules(d *schema.ResourceData) []godo.OutboundRule {
rules := make([]godo.OutboundRule, 0, len(d.Get("outbound_rules").([]interface{})))
for i, rawRule := range d.Get("outbound_rules").([]interface{}) {
var dest godo.Destinations
rule := rawRule.(map[string]interface{})
key := fmt.Sprintf("outbound_rules.%d.destination_addresses", i)
if vv, ok := d.GetOk(key); ok {
for _, v := range vv.([]interface{}) {
dest.Addresses = append(dest.Addresses, v.(string))
}
}
key = fmt.Sprintf("outbound_rules.%d.destination_tags", i)
if vv, ok := d.GetOk(key); ok {
for _, v := range vv.([]interface{}) {
dest.Tags = append(dest.Tags, v.(string))
}
}
key = fmt.Sprintf("outbound_rules.%d.destination_droplet_ids", i)
if vv, ok := d.GetOk(key); ok {
for _, v := range vv.([]interface{}) {
dest.DropletIDs = append(dest.DropletIDs, v.(int))
}
}
key = fmt.Sprintf("outbound_rules.%d.destination_load_balancer_uids", i)
if vv, ok := d.GetOk(key); ok {
for _, v := range vv.([]interface{}) {
dest.LoadBalancerUIDs = append(dest.LoadBalancerUIDs, v.(string))
}
}
r := godo.OutboundRule{
Protocol: rule["protocol"].(string),
PortRange: rule["port_range"].(string),
Destinations: &dest,
}
rules = append(rules, r)
}
return rules
}
func firewallPendingChanges(d *schema.ResourceData, firewall *godo.Firewall) []interface{} {
remote := make([]interface{}, 0, len(firewall.PendingChanges))
for _, change := range firewall.PendingChanges {
rawChange := map[string]interface{}{
"droplet_id": change.DropletID,
"removing": change.Removing,
"status": change.Status,
}
remote = append(remote, rawChange)
}
return remote
}
func matchFirewallInboundRules(d *schema.ResourceData, firewall *godo.Firewall) []interface{} {
// Prepare the data.
local := d.Get("inbound_rules").([]interface{})
remote := make([]interface{}, 0, len(firewall.InboundRules))
remoteMap := make(map[int]map[string]interface{})
for _, rule := range firewall.InboundRules {
rawRule := map[string]interface{}{
"protocol": rule.Protocol,
"port_range": rule.PortRange,
"source_droplet_ids": rule.Sources.DropletIDs,
"source_tags": rule.Sources.Tags,
"source_addresses": rule.Sources.Addresses,
"source_load_balancer_uids": rule.Sources.LoadBalancerUIDs,
}
remote = append(remote, rawRule)
hash := hashFirewallRule(rule.Protocol, rule.PortRange)
remoteMap[hash] = rawRule
}
// Handle special cases, both using the remote rules.
if len(remote) == 0 || len(local) == 0 {
return remote
}
// Update the local rules to only contains rules match
// to the remote rules.
match := make([]interface{}, 0, len(firewall.InboundRules))
for _, rawRule := range local {
local := rawRule.(map[string]interface{})
protocol := local["protocol"].(string)
portRange := local["port_range"].(string)
hash := hashFirewallRule(protocol, portRange)
remote, ok := remoteMap[hash]
if !ok {
// No entry in the remote, remove it.
continue
}
// matches source lists.
key := "source_droplet_ids"
local[key] = matchFirewallIntLists(key, local, remote)
keys := []string{
"source_tags",
"source_addresses",
"source_load_balancer_uids",
}
for _, key := range keys {
local[key] = matchFirewallStringLists(key, local, remote)
}
match = append(match, local)
delete(remoteMap, hash)
}
// Append the remaining remote rules.
for _, rawRule := range remoteMap {
match = append(match, rawRule)
}
return match
}
func matchFirewallOutboundRules(d *schema.ResourceData, firewall *godo.Firewall) []interface{} {
// Prepare the data.
local := d.Get("outbound_rules").([]interface{})
remote := make([]interface{}, 0, len(firewall.OutboundRules))
remoteMap := make(map[int]map[string]interface{})
for _, rule := range firewall.OutboundRules {
rawRule := map[string]interface{}{
"protocol": rule.Protocol,
"port_range": rule.PortRange,
"destination_droplet_ids": rule.Destinations.DropletIDs,
"destination_tags": rule.Destinations.Tags,
"destination_addresses": rule.Destinations.Addresses,
"destination_load_balancer_uids": rule.Destinations.LoadBalancerUIDs,
}
remote = append(remote, rawRule)
hash := hashFirewallRule(rule.Protocol, rule.PortRange)
remoteMap[hash] = rawRule
}
// Handle special cases, both using the remote rules.
if len(remote) == 0 || len(local) == 0 {
return remote
}
// Update the local rules to only contains rules match
// to the remote rules.
match := make([]interface{}, 0, len(firewall.OutboundRules))
for _, rawRule := range local {
local := rawRule.(map[string]interface{})
protocol := local["protocol"].(string)
portRange := local["port_range"].(string)
hash := hashFirewallRule(protocol, portRange)
remote, ok := remoteMap[hash]
if !ok {
// No entry in the remote, remove it.
continue
}
// matches destination lists.
key := "destination_droplet_ids"
local[key] = matchFirewallIntLists(key, local, remote)
keys := []string{
"destination_tags",
"destination_addresses",
"destination_load_balancer_uids",
}
for _, key := range keys {
local[key] = matchFirewallStringLists(key, local, remote)
}
match = append(match, local)
delete(remoteMap, hash)
}
// Append the remaining remote rules.
for _, rawRule := range remoteMap {
match = append(match, rawRule)
}
return match
}
func matchFirewallIntLists(key string, local, remote map[string]interface{}) []interface{} {
remoteSize := len(remote[key].([]int))
remoteSet := make(map[int]bool)
matchedList := make([]interface{}, 0, remoteSize)
// Create a remote set out of the list for the quick comparison.
for _, i := range remote[key].([]int) {
remoteSet[i] = true
}
// Add only the item which exists in the remote list.
for _, i := range local[key].([]interface{}) {
if _, ok := remoteSet[i.(int)]; !ok {
continue
}
matchedList = append(matchedList, i)
delete(remoteSet, i.(int))
}
// Append items only exists in the remote list.
for i := range remoteSet {
matchedList = append(matchedList, i)
}
return matchedList
}
func matchFirewallStringLists(key string, local, remote map[string]interface{}) []interface{} {
remoteSize := len(remote[key].([]string))
remoteList := make([]interface{}, 0, remoteSize)
matchedList := make([]interface{}, 0, remoteSize)
// Create a remote set out of the list for the quick comparison.
for _, s := range remote[key].([]string) {
remoteList = append(remoteList, s)
}
remoteSet := schema.NewSet(schema.HashString, remoteList)
// Add only the item which exists in the remote list.
for _, s := range local[key].([]interface{}) {
if !remoteSet.Contains(s.(string)) {
continue
}
matchedList = append(matchedList, s)
remoteSet.Remove(s)
}
// Append items only exists in the remote list.
for _, s := range remoteSet.List() {
matchedList = append(matchedList, s)
}
return matchedList
}
func hashFirewallRule(protocol, portRange string) int {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("%s-%s", protocol, portRange))
return hashcode.String(buf.String())
}

View File

@ -0,0 +1,261 @@
package digitalocean
import (
"context"
"fmt"
"testing"
"github.com/digitalocean/godo"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccDigitalOceanFirewall_Basic(t *testing.T) {
tests := []struct {
description string
firewallName string
config string
checkers []resource.TestCheckFunc
}{
{
description: "only allow inbound SSH(TCP/22)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rules = [
{
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
},
]
}`,
checkers: []resource.TestCheckFunc{
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.#", "1"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.1", "::/0"),
},
},
{
description: "only allow outbound SSH(TCP/22)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
outbound_rules = [
{
protocol = "tcp"
port_range = "22"
destination_addresses = ["0.0.0.0/0", "::/0"]
},
]
}`,
checkers: []resource.TestCheckFunc{
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.#", "1"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.1", "::/0"),
},
},
{
description: "only allow inbound SSH(TCP/22) and HTTP(TCP/80)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rules = [
{
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
},
{
protocol = "tcp"
port_range = "80"
source_addresses = ["1.2.3.0/24", "2002::/16"]
},
]
}`,
checkers: []resource.TestCheckFunc{
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.1", "::/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.port_range", "80"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.source_addresses.0", "1.2.3.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.source_addresses.1", "2002::/16"),
},
},
{
description: "only allow outbound SSH(TCP/22) and DNS(UDP/53)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
outbound_rules = [
{
protocol = "tcp"
port_range = "22"
destination_addresses = ["192.168.1.0/24", "2002:1001::/48"]
},
{
protocol = "udp"
port_range = "53"
destination_addresses = ["1.2.3.0/24", "2002::/16"]
},
]
}`,
checkers: []resource.TestCheckFunc{
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.0", "192.168.1.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.1", "2002:1001::/48"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.port_range", "53"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.protocol", "udp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.destination_addresses.0", "1.2.3.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.destination_addresses.1", "2002::/16"),
},
},
{
description: "allow inbound and outbound HTTPS(TCP/443), inbound SSH(TCP/22), and outbound DNS(UDP/53)",
firewallName: fmt.Sprintf("foobar-test-terraform-firewall-%s", acctest.RandString(10)),
config: `
resource "digitalocean_firewall" "foobar" {
name = "%s"
inbound_rules = [
{
protocol = "tcp"
port_range = "443"
source_addresses = ["192.168.1.0/24", "2002:1001:1:2::/64"]
},
{
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
},
]
outbound_rules = [
{
protocol = "tcp"
port_range = "443"
destination_addresses = ["192.168.1.0/24", "2002:1001:1:2::/64"]
},
{
protocol = "udp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
},
]
}`,
checkers: []resource.TestCheckFunc{
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.port_range", "443"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.0", "192.168.1.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.0.source_addresses.1", "2002:1001:1:2::/64"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.port_range", "22"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.source_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "inbound_rules.1.source_addresses.1", "::/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.port_range", "443"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.0", "192.168.1.0/24"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.0.destination_addresses.1", "2002:1001:1:2::/64"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.port_range", "53"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.protocol", "udp"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.destination_addresses.#", "2"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.destination_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "outbound_rules.1.destination_addresses.1", "::/0"),
},
},
}
var firewall godo.Firewall
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
checkers := []resource.TestCheckFunc{
testAccCheckDigitalOceanFirewallExists("digitalocean_firewall.foobar", &firewall),
resource.TestCheckResourceAttr("digitalocean_firewall.foobar", "name", tt.firewallName),
}
checkers = append(checkers, tt.checkers...)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckDigitalOceanFirewallDestroy,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(tt.config, tt.firewallName),
Check: resource.ComposeTestCheckFunc(checkers...),
},
},
})
})
}
}
func testAccCheckDigitalOceanFirewallDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*godo.Client)
for _, rs := range s.RootModule().Resources {
if rs.Type != "digitalocean_firewall" {
continue
}
// Try to find the firewall
_, _, err := client.Firewalls.Get(context.Background(), rs.Primary.ID)
if err == nil {
return fmt.Errorf("Firewall still exists")
}
}
return nil
}
func testAccCheckDigitalOceanFirewallExists(n string, firewall *godo.Firewall) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Record ID is set")
}
client := testAccProvider.Meta().(*godo.Client)
foundFirewall, _, err := client.Firewalls.Get(context.Background(), rs.Primary.ID)
if err != nil {
return err
}
if foundFirewall.ID != rs.Primary.ID {
return fmt.Errorf("Record not found")
}
*firewall = *foundFirewall
return nil
}
}

View File

@ -33,6 +33,10 @@
<a href="/docs/providers/do/r/droplet.html">digitalocean_droplet</a>
</li>
<li<%= sidebar_current("docs-do-resource-firewall") %>>
<a href="/docs/providers/do/r/firewall.html">digitalocean_firewall</a>
</li>
<li<%= sidebar_current("docs-do-resource-floating-ip") %>>
<a href="/docs/providers/do/r/floating_ip.html">digitalocean_floating_ip</a>
</li>

View File

@ -0,0 +1,134 @@
---
layout: "digitalocean"
page_title: "DigitalOcean: digitalocean_firewall"
sidebar_current: "docs-do-resource-firewall"
description: |-
Provides a DigitalOcean Cloud Firewall resource. This can be used to create, modify, and delete Firewalls.
---
# digitalocean\_firewall
Provides a DigitalOcean Cloud Firewall resource. This can be used to create,
modify, and delete Firewalls.
## Example Usage
```hcl
resource "digitalocean_droplet" "web" {
name = "web-1"
size = "512mb"
image = "centos-7-x64"
region = "nyc3"
}
resource "digitalocean_firewall" "web" {
name = "only-22-80-and-443"
droplet_ids = ["${digitalocean_droplet.web.id}"]
inbound_rules = [
{
protocol = "tcp"
port_range = "22"
source_addresses = ["192.168.1.0/24", "2002:1:2::/48"]
},
{
protocol = "tcp"
port_range = "80"
source_addresses = ["0.0.0.0/0", "::/0"]
},
{
protocol = "tcp"
port_range = "443"
source_addresses = ["0.0.0.0/0", "::/0"]
},
]
outbound_rules = [
{
protocol = "udp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
},
]
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The Firewall name
* `droplet_ids` (Optional) - The list of the IDs of the Droplets assigned
to the Firewall.
* `tags` (Optional) - The names of the Tags assigned to the Firewall.
* `inbound_rules` - (Optional) The inbound access rule block for the Firewall.
The `inbound_rules` block is documented below.
* `outbound_rules` - (Optional) The outbound access rule block for the Firewall.
The `outbound_rules` block is documented below.
`inbound_rule` supports the following:
* `protocol` - (Optional) The type of traffic to be allowed.
This may be one of "tcp", "udp", or "icmp".
* `port_range` - (Optional) The ports on which traffic will be allowed
specified as a string containing a single port, a range (e.g. "8000-9000"),
or "all" to open all ports for a protocol.
* `source_addresses` - (Optional) An array of strings containing the IPv4
addresses, IPv6 addresses, IPv4 CIDRs, and/or IPv6 CIDRs from which the
inbound traffic will be accepted.
* `source_droplet_ids` - (Optional) An array containing the IDs of
the Droplets from which the inbound traffic will be accepted.
* `source_tags` - (Optional) An array containing the names of Tags
corresponding to groups of Droplets from which the inbound traffic
will be accepted.
* `source_load_balancer_uids` - (Optional) An array containing the IDs
of the Load Balancers from which the inbound traffic will be accepted.
`outbound_rule` supports the following:
* `protocol` - (Optional) The type of traffic to be allowed.
This may be one of "tcp", "udp", or "icmp".
* `port_range` - (Optional) The ports on which traffic will be allowed
specified as a string containing a single port, a range (e.g. "8000-9000"),
or "all" to open all ports for a protocol.
* `destination_addresses` - (Optional) An array of strings containing the IPv4
addresses, IPv6 addresses, IPv4 CIDRs, and/or IPv6 CIDRs to which the
outbound traffic will be allowed.
* `destination_droplet_ids` - (Optional) An array containing the IDs of
the Droplets to which the outbound traffic will be allowed.
* `destination_tags` - (Optional) An array containing the names of Tags
corresponding to groups of Droplets to which the outbound traffic will
be allowed.
traffic.
* `destination_load_balancer_uids` - (Optional) An array containing the IDs
of the Load Balancers to which the outbound traffic will be allowed.
## Attributes Reference
The following attributes are exported:
* `id` - A unique ID that can be used to identify and reference a Firewall.
* `status` - A status string indicating the current state of the Firewall.
This can be "waiting", "succeeded", or "failed".
* `created_at` - A time value given in ISO8601 combined date and time format
that represents when the Firewall was created.
* `pending_changes` - An list of object containing the fields, "droplet_id",
"removing", and "status". It is provided to detail exactly which Droplets
are having their security policies updated. When empty, all changes
have been successfully applied.
* `name` - The name of the Firewall.
* `droplet_ids` - The list of the IDs of the Droplets assigned to
the Firewall.
* `tags` - The names of the Tags assigned to the Firewall.
* `inbound_rules` - The inbound access rule block for the Firewall.
* `outbound_rules` - The outbound access rule block for the Firewall.
## Import
Firewalls can be imported using the firewall `id`, e.g.
```
terraform import digitalocean_firewall.myfirewall b8ecd2ab-2267-4a5e-8692-cbf1d32583e3
```