Merge pull request #4556 from stack72/azurerm-network-security-group

provider/azurerm: add security group
This commit is contained in:
James Nugent 2016-01-07 13:47:50 -08:00
commit bf54811645
5 changed files with 681 additions and 0 deletions

View File

@ -43,6 +43,7 @@ func Provider() terraform.ResourceProvider {
"azurerm_virtual_network": resourceArmVirtualNetwork(),
"azurerm_local_network_gateway": resourceArmLocalNetworkGateway(),
"azurerm_availability_set": resourceArmAvailabilitySet(),
"azurerm_security_group": resourceArmSecurityGroup(),
},
ConfigureFunc: providerConfigure,

View File

@ -0,0 +1,310 @@
package azurerm
import (
"bytes"
"fmt"
"log"
"net/http"
"time"
"strings"
"github.com/Azure/azure-sdk-for-go/arm/network"
"github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
func resourceArmSecurityGroup() *schema.Resource {
return &schema.Resource{
Create: resourceArmSecurityGroupCreate,
Read: resourceArmSecurityGroupRead,
Update: resourceArmSecurityGroupCreate,
Delete: resourceArmSecurityGroupDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"location": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
StateFunc: azureRMNormalizeLocation,
},
"resource_group_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"security_rule": &schema.Schema{
Type: schema.TypeSet,
Optional: true,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
if len(value) > 140 {
errors = append(errors, fmt.Errorf(
"The security rule description can be no longer than 140 chars"))
}
return
},
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateSecurityRuleProtocol,
},
"source_port_range": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"destination_port_range": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"source_address_prefix": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"destination_address_prefix": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"access": &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateSecurityRuleAccess,
},
"priority": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(int)
if value < 100 || value > 4096 {
errors = append(errors, fmt.Errorf(
"The `priority` can only be between 100 and 4096"))
}
return
},
},
"direction": &schema.Schema{
Type: schema.TypeString,
Required: true,
ValidateFunc: validateSecurityRuleDirection,
},
},
},
Set: resourceArmSecurityGroupRuleHash,
},
},
}
}
func resourceArmSecurityGroupCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient)
secClient := client.secGroupClient
name := d.Get("name").(string)
location := d.Get("location").(string)
resGroup := d.Get("resource_group_name").(string)
sgRules, sgErr := expandAzureRmSecurityGroupRules(d)
if sgErr != nil {
return fmt.Errorf("Error Building list of Security Group Rules: %s", sgErr)
}
sg := network.SecurityGroup{
Name: &name,
Location: &location,
Properties: &network.SecurityGroupPropertiesFormat{
SecurityRules: &sgRules,
},
}
resp, err := secClient.CreateOrUpdate(resGroup, name, sg)
if err != nil {
return err
}
d.SetId(*resp.ID)
log.Printf("[DEBUG] Waiting for Security Group (%s) to become available", name)
stateConf := &resource.StateChangeConf{
Pending: []string{"Accepted", "Updating"},
Target: "Succeeded",
Refresh: securityGroupStateRefreshFunc(client, resGroup, name),
Timeout: 10 * time.Minute,
}
if _, err := stateConf.WaitForState(); err != nil {
return fmt.Errorf("Error waiting for Securty Group (%s) to become available: %s", name, err)
}
return resourceArmSecurityGroupRead(d, meta)
}
func resourceArmSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
secGroupClient := meta.(*ArmClient).secGroupClient
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resGroup := id.ResourceGroup
name := id.Path["networkSecurityGroups"]
resp, err := secGroupClient.Get(resGroup, name)
if resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
if err != nil {
return fmt.Errorf("Error making Read request on Azure Security Group %s: %s", name, err)
}
return nil
}
func resourceArmSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
secGroupClient := meta.(*ArmClient).secGroupClient
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resGroup := id.ResourceGroup
name := id.Path["networkSecurityGroups"]
_, err = secGroupClient.Delete(resGroup, name)
return err
}
func resourceArmSecurityGroupRuleHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf("%s-", m["protocol"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["source_port_range"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["destination_port_range"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["source_address_prefix"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["destination_address_prefix"].(string)))
buf.WriteString(fmt.Sprintf("%s-", m["access"].(string)))
buf.WriteString(fmt.Sprintf("%d-", m["priority"].(int)))
buf.WriteString(fmt.Sprintf("%s-", m["direction"].(string)))
return hashcode.String(buf.String())
}
func securityGroupStateRefreshFunc(client *ArmClient, resourceGroupName string, securityGroupName string) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
res, err := client.secGroupClient.Get(resourceGroupName, securityGroupName)
if err != nil {
return nil, "", fmt.Errorf("Error issuing read request in securityGroupStateRefreshFunc to Azure ARM for security group '%s' (RG: '%s'): %s", securityGroupName, resourceGroupName, err)
}
return res, *res.Properties.ProvisioningState, nil
}
}
func expandAzureRmSecurityGroupRules(d *schema.ResourceData) ([]network.SecurityRule, error) {
sgRules := d.Get("security_rule").(*schema.Set).List()
rules := make([]network.SecurityRule, 0, len(sgRules))
for _, sgRaw := range sgRules {
data := sgRaw.(map[string]interface{})
source_port_range := data["source_port_range"].(string)
destination_port_range := data["destination_port_range"].(string)
source_address_prefix := data["source_address_prefix"].(string)
destination_address_prefix := data["destination_address_prefix"].(string)
priority := data["priority"].(int)
properties := network.SecurityRulePropertiesFormat{
SourcePortRange: &source_port_range,
DestinationPortRange: &destination_port_range,
SourceAddressPrefix: &source_address_prefix,
DestinationAddressPrefix: &destination_address_prefix,
Priority: &priority,
Access: network.SecurityRuleAccess(data["access"].(string)),
Direction: network.SecurityRuleDirection(data["direction"].(string)),
Protocol: network.SecurityRuleProtocol(data["protocol"].(string)),
}
if v := data["description"].(string); v != "" {
properties.Description = &v
}
name := data["name"].(string)
rule := network.SecurityRule{
Name: &name,
Properties: &properties,
}
rules = append(rules, rule)
}
return rules, nil
}
func validateSecurityRuleProtocol(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
viewTypes := map[string]bool{
"tcp": true,
"udp": true,
"*": true,
}
if !viewTypes[value] {
errors = append(errors, fmt.Errorf("Security Rule Protocol can only be Tcp, Udp or *"))
}
return
}
func validateSecurityRuleAccess(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
viewTypes := map[string]bool{
"allow": true,
"deny": true,
}
if !viewTypes[value] {
errors = append(errors, fmt.Errorf("Security Rule Access can only be Allow or Deny"))
}
return
}
func validateSecurityRuleDirection(v interface{}, k string) (ws []string, errors []error) {
value := strings.ToLower(v.(string))
viewTypes := map[string]bool{
"inbound": true,
"outbound": true,
}
if !viewTypes[value] {
errors = append(errors, fmt.Errorf("Security Rule Directions can only be Inbound or Outbound"))
}
return
}

View File

@ -0,0 +1,283 @@
package azurerm
import (
"fmt"
"net/http"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestResourceAzureRMSecurityGroupProtocol_validation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "Random",
ErrCount: 1,
},
{
Value: "tcp",
ErrCount: 0,
},
{
Value: "TCP",
ErrCount: 0,
},
{
Value: "*",
ErrCount: 0,
},
{
Value: "Udp",
ErrCount: 0,
},
{
Value: "Tcp",
ErrCount: 0,
},
}
for _, tc := range cases {
_, errors := validateSecurityRuleProtocol(tc.Value, "azurerm_security_group")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Azure RM Security Group protocol to trigger a validation error")
}
}
}
func TestResourceAzureRMSecurityGroupAccess_validation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "Random",
ErrCount: 1,
},
{
Value: "Allow",
ErrCount: 0,
},
{
Value: "Deny",
ErrCount: 0,
},
{
Value: "ALLOW",
ErrCount: 0,
},
{
Value: "deny",
ErrCount: 0,
},
}
for _, tc := range cases {
_, errors := validateSecurityRuleAccess(tc.Value, "azurerm_security_group")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Azure RM Security Group access to trigger a validation error")
}
}
}
func TestResourceAzureRMSecurityGroupDirection_validation(t *testing.T) {
cases := []struct {
Value string
ErrCount int
}{
{
Value: "Random",
ErrCount: 1,
},
{
Value: "Inbound",
ErrCount: 0,
},
{
Value: "Outbound",
ErrCount: 0,
},
{
Value: "INBOUND",
ErrCount: 0,
},
{
Value: "Inbound",
ErrCount: 0,
},
}
for _, tc := range cases {
_, errors := validateSecurityRuleDirection(tc.Value, "azurerm_security_group")
if len(errors) != tc.ErrCount {
t.Fatalf("Expected the Azure RM Security Group direction to trigger a validation error")
}
}
}
func TestAccAzureRMSecurityGroup_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureRMSecurityGroup_basic,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMSecurityGroupExists("azurerm_security_group.test"),
),
},
},
})
}
func TestAccAzureRMSecurityGroup_addingExtraRules(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureRMSecurityGroup_basic,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMSecurityGroupExists("azurerm_security_group.test"),
resource.TestCheckResourceAttr(
"azurerm_security_group.test", "security_rule.#", "1"),
),
},
resource.TestStep{
Config: testAccAzureRMSecurityGroup_anotherRule,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMSecurityGroupExists("azurerm_security_group.test"),
resource.TestCheckResourceAttr(
"azurerm_security_group.test", "security_rule.#", "2"),
),
},
},
})
}
func testCheckAzureRMSecurityGroupExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
sgName := rs.Primary.Attributes["name"]
resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"]
if !hasResourceGroup {
return fmt.Errorf("Bad: no resource group found in state for security group: %s", sgName)
}
conn := testAccProvider.Meta().(*ArmClient).secGroupClient
resp, err := conn.Get(resourceGroup, sgName)
if err != nil {
return fmt.Errorf("Bad: Get on secGroupClient: %s", err)
}
if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("Bad: Security Group %q (resource group: %q) does not exist", name, resourceGroup)
}
return nil
}
}
func testCheckAzureRMSecurityGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*ArmClient).secGroupClient
for _, rs := range s.RootModule().Resources {
if rs.Type != "azurerm_security_group" {
continue
}
name := rs.Primary.Attributes["name"]
resourceGroup := rs.Primary.Attributes["resource_group_name"]
resp, err := conn.Get(resourceGroup, name)
if err != nil {
return nil
}
if resp.StatusCode != http.StatusNotFound {
return fmt.Errorf("Security Group still exists:\n%#v", resp.Properties)
}
}
return nil
}
var testAccAzureRMSecurityGroup_basic = `
resource "azurerm_resource_group" "test" {
name = "acceptanceTestResourceGroup1"
location = "West US"
}
resource "azurerm_security_group" "test" {
name = "acceptanceTestSecurityGroup1"
location = "West US"
resource_group_name = "${azurerm_resource_group.test.name}"
security_rule {
name = "test123"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
`
var testAccAzureRMSecurityGroup_anotherRule = `
resource "azurerm_resource_group" "test" {
name = "acceptanceTestResourceGroup1"
location = "West US"
}
resource "azurerm_security_group" "test" {
name = "acceptanceTestSecurityGroup1"
location = "West US"
resource_group_name = "${azurerm_resource_group.test.name}"
security_rule {
name = "test123"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
}
security_rule {
name = "testDeny"
priority = 101
direction = "Inbound"
access = "Deny"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
`

View File

@ -0,0 +1,83 @@
---
layout: "azurerm"
page_title: "Azure Resource Manager: azurerm_security_group"
sidebar_current: "docs-azurerm-resource-security-group"
description: |-
Create a network security group that contains a list of network security rules. Network security groups enable inbound or outbound traffic to be enabled or denied.
---
# azurerm\_security\_group
Create a network security group that contains a list of network security rules.
## Example Usage
```
resource "azurerm_resource_group" "test" {
name = "acceptanceTestResourceGroup1"
location = "West US"
}
resource "azurerm_security_group" "test" {
name = "acceptanceTestSecurityGroup1"
location = "West US"
resource_group_name = "${azurerm_resource_group.test.name}"
security_rule {
name = "test123"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "*"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) Specifies the name of the availability set. Changing this forces a
new resource to be created.
* `resource_group_name` - (Required) The name of the resource group in which to
create the availability set.
* `location` - (Required) Specifies the supported Azure location where the resource exists. Changing this forces a new resource to be created.
* `security_rule` - (Optional) Can be specified multiple times to define multiple
security rules. Each `security_rule` block supports fields documented below.
The `security_rule` block supports:
* `name` - (Required) The name of the security rule.
* `description` - (Optional) A description for this rule. Restricted to 140 characters.
* `protocol` - (Required) Network protocol this rule applies to. Can be Tcp, Udp or * to match both.
* `source_port_range` - (Required) Source Port or Range. Integer or range between 0 and 65535 or * to match any.
* `destination_port_range` - (Required) Destination Port or Range. Integer or range between 0 and 65535 or * to match any.
* `source_address_prefix` - (Required) CIDR or source IP range or * to match any IP. Tags such as VirtualNetwork, AzureLoadBalancer and Internet can also be used.
* `destination_address_prefix` - (Required) CIDR or destination IP range or * to match any IP. Tags such as VirtualNetwork, AzureLoadBalancer and Internet can also be used.
* `access` - (Required) Specifies whether network traffic is allowed or denied. Possible values are “Allow” and “Deny”.
* `priority` - (Required) Specifies the priority of the rule. The value can be between 100 and 4096. The priority number must be unique for each rule in the collection. The lower the priority number, the higher the priority of the rule.
* `direction` - (Required) The direction specifies if rule will be evaluated on incoming or outgoing traffic. Possible values are “Inbound” and “Outbound”.
## Attributes Reference
The following attributes are exported:
* `id` - The virtual AvailabilitySet ID.

View File

@ -29,6 +29,10 @@
<a href="/docs/providers/azurerm/r/availability_set.html">azurerm_availability_set</a>
</li>
<li<%= sidebar_current("docs-azurerm-resource-security-group") %>>
<a href="/docs/providers/azurerm/r/security_group.html">azurerm_security_group</a>
</li>
</ul>
</li>
</ul>