From 0f4d81e0917c51257d4d746c607f806913376356 Mon Sep 17 00:00:00 2001 From: Sumit Kalra <44450797+sumit-kalra@users.noreply.github.com> Date: Tue, 24 Sep 2019 09:34:19 -0700 Subject: [PATCH 1/7] Adding config to specify allowed inbound IP addresses and CIDR blocks (#1) * Adding config to specify allowed inbound IP addresses * Also allowing CIDR blocks in addition to IP addresses --- builder/azure/arm/config.go | 29 +++++- builder/azure/arm/config_test.go | 102 +++++++++++++++++++++ website/source/docs/builders/azure.html.md | 5 + 3 files changed, 131 insertions(+), 5 deletions(-) diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 5e9b8905c..aafd1bca4 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -10,6 +10,7 @@ import ( "fmt" "io/ioutil" "math/big" + "net" "regexp" "strings" "time" @@ -129,11 +130,12 @@ type Config struct { TempResourceGroupName string `mapstructure:"temp_resource_group_name"` BuildResourceGroupName string `mapstructure:"build_resource_group_name"` storageAccountBlobEndpoint string - PrivateVirtualNetworkWithPublicIp bool `mapstructure:"private_virtual_network_with_public_ip"` - VirtualNetworkName string `mapstructure:"virtual_network_name"` - VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"` - VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"` - CustomDataFile string `mapstructure:"custom_data_file"` + PrivateVirtualNetworkWithPublicIp bool `mapstructure:"private_virtual_network_with_public_ip"` + VirtualNetworkName string `mapstructure:"virtual_network_name"` + VirtualNetworkSubnetName string `mapstructure:"virtual_network_subnet_name"` + VirtualNetworkResourceGroupName string `mapstructure:"virtual_network_resource_group_name"` + AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses"` + CustomDataFile string `mapstructure:"custom_data_file"` customData string PlanInfo PlanInformation `mapstructure:"plan_info"` @@ -673,6 +675,12 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name")) } + if c.AllowedInboundIpAddresses != nil && len(c.AllowedInboundIpAddresses) >= 1 { + if ok, err := assertAllowedInboundIpAddresses(c.AllowedInboundIpAddresses, "allowed_inbound_ip_addresses"); !ok { + errs = packer.MultiErrorAppend(errs, err) + } + } + ///////////////////////////////////////////// // Plan Info if c.PlanInfo.PlanName != "" || c.PlanInfo.PlanProduct != "" || c.PlanInfo.PlanPublisher != "" || c.PlanInfo.PlanPromotionCode != "" { @@ -744,6 +752,17 @@ func assertManagedImageDataDiskSnapshotName(name, setting string) (bool, error) return true, nil } +func assertAllowedInboundIpAddresses(ipAddresses []string, setting string) (bool, error) { + for _, ipAddress := range ipAddresses { + if net.ParseIP(ipAddress) == nil { + if _, _, err := net.ParseCIDR(ipAddress); err != nil { + return false, fmt.Errorf("The setting %s must only contain valid IP addresses or CIDR blocks", setting) + } + } + } + return true, nil +} + func assertResourceGroupName(rgn, setting string) (bool, error) { if !isValidAzureName(reResourceGroupName, rgn) { return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validResourceGroupNameRe) diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index ef5c9fb93..9175c3d26 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -270,6 +270,108 @@ func TestConfigVirtualNetworkSubnetNameMustBeSetWithVirtualNetworkName(t *testin } } +func TestConfigAllowedInboundIpAddressesIsOptional(t *testing.T) { + config := map[string]string{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "virtual_network_name": "MyVirtualNetwork", + } + + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + if c.AllowedInboundIpAddresses != nil { + t.Errorf("Expected Config to set allowed_inbound_ip_addresses to nil, but got %v", c.AllowedInboundIpAddresses) + } +} + +func TestConfigShouldAcceptCorrectInboundIpAddresses(t *testing.T) { + ipValue0 := "127.0.0.1" + ipValue1 := "127.0.0.2" + cidrValue2 := "192.168.100.0/24" + cidrValue3 := "10.10.1.16/32" + config := map[string]interface{}{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "virtual_network_name": "MyVirtualNetwork", + } + + config["allowed_inbound_ip_addresses"] = ipValue0 + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 1 || + c.AllowedInboundIpAddresses[0] != ipValue0 { + t.Errorf("Expected 'allowed_inbound_ip_addresses' to have one element (%s), but got '%v'.", ipValue0, c.AllowedInboundIpAddresses) + } + + config["allowed_inbound_ip_addresses"] = cidrValue2 + c, _, err = newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 1 || + c.AllowedInboundIpAddresses[0] != cidrValue2 { + t.Errorf("Expected 'allowed_inbound_ip_addresses' to have one element (%s), but got '%v'.", cidrValue2, c.AllowedInboundIpAddresses) + } + + config["allowed_inbound_ip_addresses"] = []string{ipValue0, cidrValue2, ipValue1, cidrValue3} + c, _, err = newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 4 || + c.AllowedInboundIpAddresses[0] != ipValue0 || c.AllowedInboundIpAddresses[1] != cidrValue2 || + c.AllowedInboundIpAddresses[2] != ipValue1 || c.AllowedInboundIpAddresses[3] != cidrValue3 { + t.Errorf("Expected 'allowed_inbound_ip_addresses' to have four elements (%s %s %s %s), but got '%v'.", ipValue0, cidrValue2, ipValue1, + cidrValue3, c.AllowedInboundIpAddresses) + } +} + +func TestConfigShouldRejectIncorrectInboundIpAddresses(t *testing.T) { + config := map[string]interface{}{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "virtual_network_name": "MyVirtualNetwork", + } + + config["allowed_inbound_ip_addresses"] = []string{"127.0.0.1", "127.0.0.two"} + c, _, err := newConfig(config, getPackerConfiguration()) + if err == nil { + t.Errorf("Expected configuration creation to fail, but it succeeded with the malformed allowed_inbound_ip_addresses set to %v", c.AllowedInboundIpAddresses) + } + + config["allowed_inbound_ip_addresses"] = []string{"192.168.100.1000/24", "10.10.1.16/32"} + c, _, err = newConfig(config, getPackerConfiguration()) + if err == nil { + // 192.168.100.1000/24 is invalid + t.Errorf("Expected configuration creation to fail, but it succeeded with the malformed allowed_inbound_ip_addresses set to %v", c.AllowedInboundIpAddresses) + } +} + func TestConfigShouldDefaultToPublicCloud(t *testing.T) { c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) diff --git a/website/source/docs/builders/azure.html.md b/website/source/docs/builders/azure.html.md index aa741d675..37c62eda1 100644 --- a/website/source/docs/builders/azure.html.md +++ b/website/source/docs/builders/azure.html.md @@ -336,6 +336,11 @@ Providing `temp_resource_group_name` or `location` in combination with containing the virtual network. If the resource group cannot be found, or it cannot be disambiguated, this value should be set. +- `allowed_inbound_ip_addresses` (array of strings) list of IP addresses and + CIDR blocks that should be allowed access to the VM. If provided, an Azure + Network Security Group will be created with corresponding rules and be bound + to the NIC attached to the VM. + - `virtual_network_subnet_name` (string) If virtual\_network\_name is set, this value **may** also be set. If virtual\_network\_name is set, and this value is not set the builder attempts to determine the subnet to use with From 45840ffc3fd8be633f4a8eed3e7ba4a1f568f0a1 Mon Sep 17 00:00:00 2001 From: Sumit Kalra <44450797+sumit-kalra@users.noreply.github.com> Date: Thu, 26 Sep 2019 15:35:55 -0700 Subject: [PATCH 2/7] Ensuring that specifying allowed inbound IP and VNet are mutually exclusive (#2) --- builder/azure/arm/config.go | 8 +++++-- builder/azure/arm/config_test.go | 28 ++++++++++++++++++++-- website/source/docs/builders/azure.html.md | 3 +++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index aafd1bca4..9e7234e09 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -676,8 +676,12 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { } if c.AllowedInboundIpAddresses != nil && len(c.AllowedInboundIpAddresses) >= 1 { - if ok, err := assertAllowedInboundIpAddresses(c.AllowedInboundIpAddresses, "allowed_inbound_ip_addresses"); !ok { - errs = packer.MultiErrorAppend(errs, err) + if c.VirtualNetworkName != "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_name is specified, allowed_inbound_ip_addresses cannot be specified")) + } else { + if ok, err := assertAllowedInboundIpAddresses(c.AllowedInboundIpAddresses, "allowed_inbound_ip_addresses"); !ok { + errs = packer.MultiErrorAppend(errs, err) + } } } diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index 9175c3d26..a3617dc19 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -308,7 +308,6 @@ func TestConfigShouldAcceptCorrectInboundIpAddresses(t *testing.T) { "subscription_id": "ignore", "os_type": constants.Target_Linux, "communicator": "none", - "virtual_network_name": "MyVirtualNetwork", } config["allowed_inbound_ip_addresses"] = ipValue0 @@ -355,7 +354,6 @@ func TestConfigShouldRejectIncorrectInboundIpAddresses(t *testing.T) { "subscription_id": "ignore", "os_type": constants.Target_Linux, "communicator": "none", - "virtual_network_name": "MyVirtualNetwork", } config["allowed_inbound_ip_addresses"] = []string{"127.0.0.1", "127.0.0.two"} @@ -372,6 +370,32 @@ func TestConfigShouldRejectIncorrectInboundIpAddresses(t *testing.T) { } } +func TestConfigShouldRejectInboundIpAddressesWithVirtualNetwork(t *testing.T) { + config := map[string]interface{}{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "allowed_inbound_ip_addresses": "127.0.0.1", + } + + _, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + + config["virtual_network_name"] = "some_vnet_name" + _, _, err = newConfig(config, getPackerConfiguration()) + if err == nil { + t.Errorf("Expected configuration creation to fail, but it succeeded with allowed_inbound_ip_addresses and virtual_network_name both specified") + } +} + func TestConfigShouldDefaultToPublicCloud(t *testing.T) { c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) diff --git a/website/source/docs/builders/azure.html.md b/website/source/docs/builders/azure.html.md index 37c62eda1..6b6c09218 100644 --- a/website/source/docs/builders/azure.html.md +++ b/website/source/docs/builders/azure.html.md @@ -341,6 +341,9 @@ Providing `temp_resource_group_name` or `location` in combination with Network Security Group will be created with corresponding rules and be bound to the NIC attached to the VM. + Providing `allowed_inbound_ip_addresses` in combination with + `virtual_network_name` is not allowed. + - `virtual_network_subnet_name` (string) If virtual\_network\_name is set, this value **may** also be set. If virtual\_network\_name is set, and this value is not set the builder attempts to determine the subnet to use with From 132779c343676568778c56de6b4438664e55ab16 Mon Sep 17 00:00:00 2001 From: Sumit Kalra <44450797+sumit-kalra@users.noreply.github.com> Date: Tue, 24 Sep 2019 09:34:19 -0700 Subject: [PATCH 3/7] Adding config to specify allowed inbound IP addresses and CIDR blocks (#1) * Adding config to specify allowed inbound IP addresses * Also allowing CIDR blocks in addition to IP addresses --- builder/azure/arm/config.go | 23 +++++++ builder/azure/arm/config_test.go | 102 +++++++++++++++++++++++++++++++ 2 files changed, 125 insertions(+) diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 7243bd2a8..6744287cc 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -12,6 +12,7 @@ import ( "fmt" "io/ioutil" "math/big" + "net" "regexp" "strings" "time" @@ -342,6 +343,11 @@ type Config struct { // are None, ReadOnly, and ReadWrite. The default value is ReadWrite. DiskCachingType string `mapstructure:"disk_caching_type" required:"false"` diskCachingType compute.CachingTypes + // Specify the list of IP addresses and CIDR blocks that should be + // allowed access to the VM. If provided, an Azure Network Security + // Group will be created with corresponding rules and be bound to + // the NIC attached to the VM. + AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses"` // Runtime Values UserName string @@ -872,6 +878,12 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_subnet_name is specified, so must virtual_network_name")) } + if c.AllowedInboundIpAddresses != nil && len(c.AllowedInboundIpAddresses) >= 1 { + if ok, err := assertAllowedInboundIpAddresses(c.AllowedInboundIpAddresses, "allowed_inbound_ip_addresses"); !ok { + errs = packer.MultiErrorAppend(errs, err) + } + } + ///////////////////////////////////////////// // Plan Info if c.PlanInfo.PlanName != "" || c.PlanInfo.PlanProduct != "" || c.PlanInfo.PlanPublisher != "" || c.PlanInfo.PlanPromotionCode != "" { @@ -943,6 +955,17 @@ func assertManagedImageDataDiskSnapshotName(name, setting string) (bool, error) return true, nil } +func assertAllowedInboundIpAddresses(ipAddresses []string, setting string) (bool, error) { + for _, ipAddress := range ipAddresses { + if net.ParseIP(ipAddress) == nil { + if _, _, err := net.ParseCIDR(ipAddress); err != nil { + return false, fmt.Errorf("The setting %s must only contain valid IP addresses or CIDR blocks", setting) + } + } + } + return true, nil +} + func assertResourceGroupName(rgn, setting string) (bool, error) { if !isValidAzureName(reResourceGroupName, rgn) { return false, fmt.Errorf("The setting %s must match the regular expression %q, and not end with a '-' or '.'.", setting, validResourceGroupNameRe) diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index 9ed25fffa..ce446b246 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -270,6 +270,108 @@ func TestConfigVirtualNetworkSubnetNameMustBeSetWithVirtualNetworkName(t *testin } } +func TestConfigAllowedInboundIpAddressesIsOptional(t *testing.T) { + config := map[string]string{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "virtual_network_name": "MyVirtualNetwork", + } + + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + if c.AllowedInboundIpAddresses != nil { + t.Errorf("Expected Config to set allowed_inbound_ip_addresses to nil, but got %v", c.AllowedInboundIpAddresses) + } +} + +func TestConfigShouldAcceptCorrectInboundIpAddresses(t *testing.T) { + ipValue0 := "127.0.0.1" + ipValue1 := "127.0.0.2" + cidrValue2 := "192.168.100.0/24" + cidrValue3 := "10.10.1.16/32" + config := map[string]interface{}{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "virtual_network_name": "MyVirtualNetwork", + } + + config["allowed_inbound_ip_addresses"] = ipValue0 + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 1 || + c.AllowedInboundIpAddresses[0] != ipValue0 { + t.Errorf("Expected 'allowed_inbound_ip_addresses' to have one element (%s), but got '%v'.", ipValue0, c.AllowedInboundIpAddresses) + } + + config["allowed_inbound_ip_addresses"] = cidrValue2 + c, _, err = newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 1 || + c.AllowedInboundIpAddresses[0] != cidrValue2 { + t.Errorf("Expected 'allowed_inbound_ip_addresses' to have one element (%s), but got '%v'.", cidrValue2, c.AllowedInboundIpAddresses) + } + + config["allowed_inbound_ip_addresses"] = []string{ipValue0, cidrValue2, ipValue1, cidrValue3} + c, _, err = newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + if c.AllowedInboundIpAddresses == nil || len(c.AllowedInboundIpAddresses) != 4 || + c.AllowedInboundIpAddresses[0] != ipValue0 || c.AllowedInboundIpAddresses[1] != cidrValue2 || + c.AllowedInboundIpAddresses[2] != ipValue1 || c.AllowedInboundIpAddresses[3] != cidrValue3 { + t.Errorf("Expected 'allowed_inbound_ip_addresses' to have four elements (%s %s %s %s), but got '%v'.", ipValue0, cidrValue2, ipValue1, + cidrValue3, c.AllowedInboundIpAddresses) + } +} + +func TestConfigShouldRejectIncorrectInboundIpAddresses(t *testing.T) { + config := map[string]interface{}{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "virtual_network_name": "MyVirtualNetwork", + } + + config["allowed_inbound_ip_addresses"] = []string{"127.0.0.1", "127.0.0.two"} + c, _, err := newConfig(config, getPackerConfiguration()) + if err == nil { + t.Errorf("Expected configuration creation to fail, but it succeeded with the malformed allowed_inbound_ip_addresses set to %v", c.AllowedInboundIpAddresses) + } + + config["allowed_inbound_ip_addresses"] = []string{"192.168.100.1000/24", "10.10.1.16/32"} + c, _, err = newConfig(config, getPackerConfiguration()) + if err == nil { + // 192.168.100.1000/24 is invalid + t.Errorf("Expected configuration creation to fail, but it succeeded with the malformed allowed_inbound_ip_addresses set to %v", c.AllowedInboundIpAddresses) + } +} + func TestConfigShouldDefaultToPublicCloud(t *testing.T) { c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) From 91d19adcd7ea9eb1c154ea514301740060bc7e83 Mon Sep 17 00:00:00 2001 From: Sumit Kalra <44450797+sumit-kalra@users.noreply.github.com> Date: Thu, 26 Sep 2019 15:35:55 -0700 Subject: [PATCH 4/7] Ensuring that specifying allowed inbound IP and VNet are mutually exclusive (#2) --- builder/azure/arm/config.go | 8 ++++++-- builder/azure/arm/config_test.go | 28 ++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index 6744287cc..eef7599a0 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -879,8 +879,12 @@ func assertRequiredParametersSet(c *Config, errs *packer.MultiError) { } if c.AllowedInboundIpAddresses != nil && len(c.AllowedInboundIpAddresses) >= 1 { - if ok, err := assertAllowedInboundIpAddresses(c.AllowedInboundIpAddresses, "allowed_inbound_ip_addresses"); !ok { - errs = packer.MultiErrorAppend(errs, err) + if c.VirtualNetworkName != "" { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("If virtual_network_name is specified, allowed_inbound_ip_addresses cannot be specified")) + } else { + if ok, err := assertAllowedInboundIpAddresses(c.AllowedInboundIpAddresses, "allowed_inbound_ip_addresses"); !ok { + errs = packer.MultiErrorAppend(errs, err) + } } } diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index ce446b246..d90e31f44 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -308,7 +308,6 @@ func TestConfigShouldAcceptCorrectInboundIpAddresses(t *testing.T) { "subscription_id": "ignore", "os_type": constants.Target_Linux, "communicator": "none", - "virtual_network_name": "MyVirtualNetwork", } config["allowed_inbound_ip_addresses"] = ipValue0 @@ -355,7 +354,6 @@ func TestConfigShouldRejectIncorrectInboundIpAddresses(t *testing.T) { "subscription_id": "ignore", "os_type": constants.Target_Linux, "communicator": "none", - "virtual_network_name": "MyVirtualNetwork", } config["allowed_inbound_ip_addresses"] = []string{"127.0.0.1", "127.0.0.two"} @@ -372,6 +370,32 @@ func TestConfigShouldRejectIncorrectInboundIpAddresses(t *testing.T) { } } +func TestConfigShouldRejectInboundIpAddressesWithVirtualNetwork(t *testing.T) { + config := map[string]interface{}{ + "capture_name_prefix": "ignore", + "capture_container_name": "ignore", + "location": "ignore", + "image_url": "ignore", + "storage_account": "ignore", + "resource_group_name": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Linux, + "communicator": "none", + "allowed_inbound_ip_addresses": "127.0.0.1", + } + + _, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + + config["virtual_network_name"] = "some_vnet_name" + _, _, err = newConfig(config, getPackerConfiguration()) + if err == nil { + t.Errorf("Expected configuration creation to fail, but it succeeded with allowed_inbound_ip_addresses and virtual_network_name both specified") + } +} + func TestConfigShouldDefaultToPublicCloud(t *testing.T) { c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) From 595b103bbeefd556f1a71ebf2b7650cc610cdf5e Mon Sep 17 00:00:00 2001 From: Sumit Kalra <44450797+sumit-kalra@users.noreply.github.com> Date: Tue, 8 Oct 2019 14:56:43 -0700 Subject: [PATCH 5/7] Adding NSG to the ARM deployment template when needed (#3) * Adding NSG to the ARM eployment template when needed * Adding tests and fixing bugs * Removing denyall rule * Fixing logic to determine which port to open * Fixing config description --- builder/azure/arm/azure_client.go | 7 + builder/azure/arm/config.go | 6 +- builder/azure/arm/config_test.go | 4 + builder/azure/arm/step_deploy_template.go | 6 + builder/azure/arm/template_factory.go | 8 + ..._factory_test.TestPlanInfo01.approved.json | 4 + ..._factory_test.TestPlanInfo02.approved.json | 4 + ...stVirtualMachineDeployment03.approved.json | 4 + ...stVirtualMachineDeployment04.approved.json | 4 + ...stVirtualMachineDeployment05.approved.json | 4 + ...stVirtualMachineDeployment06.approved.json | 4 + ...stVirtualMachineDeployment07.approved.json | 4 + ...stVirtualMachineDeployment08.approved.json | 4 + ...stVirtualMachineDeployment09.approved.json | 4 + ...stVirtualMachineDeployment10.approved.json | 4 + ...stVirtualMachineDeployment11.approved.json | 4 + ...stVirtualMachineDeployment12.approved.json | 4 + ...stVirtualMachineDeployment13.approved.json | 227 ++++++++++++++++++ builder/azure/arm/template_factory_test.go | 34 +++ builder/azure/arm/tempname.go | 2 + builder/azure/arm/tempname_test.go | 8 + builder/azure/common/template/template.go | 9 +- .../azure/common/template/template_builder.go | 103 +++++++- ...uilder_test.TestBuildLinux00.approved.json | 4 + ...uilder_test.TestBuildLinux01.approved.json | 4 + ...uilder_test.TestBuildLinux02.approved.json | 4 + ...lder_test.TestBuildWindows00.approved.json | 4 + ...lder_test.TestBuildWindows01.approved.json | 4 + ...lder_test.TestBuildWindows02.approved.json | 4 + ...t.TestNetworkSecurityGroup00.approved.json | 212 ++++++++++++++++ ...est.TestSharedImageGallery00.approved.json | 4 + .../common/template/template_builder_test.go | 33 +++ .../common/template/template_parameters.go | 1 + .../template/template_parameters_test.go | 3 + 34 files changed, 729 insertions(+), 10 deletions(-) create mode 100644 builder/azure/arm/template_factory_test.TestVirtualMachineDeployment13.approved.json create mode 100644 builder/azure/common/template/template_builder_test.TestNetworkSecurityGroup00.approved.json diff --git a/builder/azure/arm/azure_client.go b/builder/azure/arm/azure_client.go index ab4330e37..22acda3f6 100644 --- a/builder/azure/arm/azure_client.go +++ b/builder/azure/arm/azure_client.go @@ -37,6 +37,7 @@ type AzureClient struct { network.InterfacesClient network.SubnetsClient network.VirtualNetworksClient + network.SecurityGroupsClient compute.ImagesClient compute.VirtualMachinesClient common.VaultClient @@ -182,6 +183,12 @@ func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) azureClient.VirtualNetworksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualNetworksClient.UserAgent) + azureClient.SecurityGroupsClient = network.NewSecurityGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) + azureClient.SecurityGroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) + azureClient.SecurityGroupsClient.RequestInspector = withInspection(maxlen) + azureClient.SecurityGroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) + azureClient.SecurityGroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SecurityGroupsClient.UserAgent) + azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) azureClient.PublicIPAddressesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen) diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index eef7599a0..bf072d5da 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -347,7 +347,9 @@ type Config struct { // allowed access to the VM. If provided, an Azure Network Security // Group will be created with corresponding rules and be bound to // the NIC attached to the VM. - AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses"` + // Providing `allowed_inbound_ip_addresses` in combination with + // `virtual_network_name` is not allowed. + AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses"` // Runtime Values UserName string @@ -363,6 +365,7 @@ type Config struct { tmpOSDiskName string tmpSubnetName string tmpVirtualNetworkName string + tmpNsgName string tmpWinRMCertificateUrl string // Authentication with the VM via SSH @@ -610,6 +613,7 @@ func setRuntimeValues(c *Config) { c.tmpOSDiskName = tempName.OSDiskName c.tmpSubnetName = tempName.SubnetName c.tmpVirtualNetworkName = tempName.VirtualNetworkName + c.tmpNsgName = tempName.NsgName c.tmpKeyVaultName = tempName.KeyVaultName } diff --git a/builder/azure/arm/config_test.go b/builder/azure/arm/config_test.go index d90e31f44..d541b2f9f 100644 --- a/builder/azure/arm/config_test.go +++ b/builder/azure/arm/config_test.go @@ -506,6 +506,10 @@ func TestSystemShouldDefineRuntimeValues(t *testing.T) { if c.tmpOSDiskName == "" { t.Errorf("Expected tmpOSDiskName to not be empty, but it was '%s'!", c.tmpOSDiskName) } + + if c.tmpNsgName == "" { + t.Errorf("Expected tmpNsgName to not be empty, but it was '%s'!", c.tmpNsgName) + } } func TestConfigShouldTransformToVirtualMachineCaptureParameters(t *testing.T) { diff --git a/builder/azure/arm/step_deploy_template.go b/builder/azure/arm/step_deploy_template.go index c20e4630d..759017526 100644 --- a/builder/azure/arm/step_deploy_template.go +++ b/builder/azure/arm/step_deploy_template.go @@ -115,6 +115,12 @@ func deleteResource(ctx context.Context, client *AzureClient, resourceType strin err = f.WaitForCompletionRef(ctx, client.VirtualNetworksClient.Client) } return err + case "Microsoft.Network/networkSecurityGroups": + f, err := client.SecurityGroupsClient.Delete(ctx, resourceGroupName, resourceName) + if err == nil { + err = f.WaitForCompletionRef(ctx, client.SecurityGroupsClient.Client) + } + return err case "Microsoft.Network/publicIPAddresses": f, err := client.PublicIPAddressesClient.Delete(ctx, resourceGroupName, resourceName) if err == nil { diff --git a/builder/azure/arm/template_factory.go b/builder/azure/arm/template_factory.go index 0401f9f9b..80dd296a5 100644 --- a/builder/azure/arm/template_factory.go +++ b/builder/azure/arm/template_factory.go @@ -40,6 +40,7 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) SubnetName: &template.TemplateParameter{Value: config.tmpSubnetName}, StorageAccountBlobEndpoint: &template.TemplateParameter{Value: config.storageAccountBlobEndpoint}, VirtualNetworkName: &template.TemplateParameter{Value: config.tmpVirtualNetworkName}, + NsgName: &template.TemplateParameter{Value: config.tmpNsgName}, VMSize: &template.TemplateParameter{Value: config.VMSize}, VMName: &template.TemplateParameter{Value: config.tmpComputeName}, } @@ -117,6 +118,13 @@ func GetVirtualMachineDeployment(config *Config) (*resources.Deployment, error) config.VirtualNetworkSubnetName) } + if config.AllowedInboundIpAddresses != nil && len(config.AllowedInboundIpAddresses) >= 1 && config.Comm.Port() != 0 { + err = builder.SetNetworkSecurityGroup(config.AllowedInboundIpAddresses, config.Comm.Port()) + if err != nil { + return nil, err + } + } + builder.SetTags(&config.AzureTags) doc, _ := builder.ToJSON() return createDeploymentParameters(*doc, params) diff --git a/builder/azure/arm/template_factory_test.TestPlanInfo01.approved.json b/builder/azure/arm/template_factory_test.TestPlanInfo01.approved.json index 392dc0ec9..30ede3ae4 100644 --- a/builder/azure/arm/template_factory_test.TestPlanInfo01.approved.json +++ b/builder/azure/arm/template_factory_test.TestPlanInfo01.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -189,6 +192,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestPlanInfo02.approved.json b/builder/azure/arm/template_factory_test.TestPlanInfo02.approved.json index b29616711..657af585d 100644 --- a/builder/azure/arm/template_factory_test.TestPlanInfo02.approved.json +++ b/builder/azure/arm/template_factory_test.TestPlanInfo02.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -194,6 +197,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment03.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment03.approved.json index 463a9e473..cda8c56dc 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment03.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment03.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -160,6 +163,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment04.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment04.approved.json index ee78c0825..8c922bfa5 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment04.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment04.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -158,6 +161,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment05.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment05.approved.json index 51301ea4e..9e66583fb 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment05.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment05.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -119,6 +122,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment06.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment06.approved.json index b7e8d9ba5..ce8b43ed8 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment06.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment06.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -178,6 +181,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment07.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment07.approved.json index 7208af852..66cf47288 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment07.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment07.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -159,6 +162,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json index d7f3aa148..cdabe3c93 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment08.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -158,6 +161,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json index 784e7ce4d..5af5b01bc 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment09.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -161,6 +164,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment10.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment10.approved.json index 0c7903a3d..a9c3d267c 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment10.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment10.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -139,6 +142,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment11.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment11.approved.json index bf94c8581..5ac3905c0 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment11.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment11.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -172,6 +175,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment12.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment12.approved.json index dc1ec1509..4339b1864 100644 --- a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment12.approved.json +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment12.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -173,6 +176,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment13.approved.json b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment13.approved.json new file mode 100644 index 000000000..9127c3bac --- /dev/null +++ b/builder/azure/arm/template_factory_test.TestVirtualMachineDeployment13.approved.json @@ -0,0 +1,227 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json", + "contentVersion": "1.0.0.0", + "parameters": { + "adminPassword": { + "type": "string" + }, + "adminUsername": { + "type": "string" + }, + "dnsNameForPublicIP": { + "type": "string" + }, + "nicName": { + "type": "string" + }, + "nsgName": { + "type": "string" + }, + "osDiskName": { + "type": "string" + }, + "publicIPAddressName": { + "type": "string" + }, + "storageAccountBlobEndpoint": { + "type": "string" + }, + "subnetName": { + "type": "string" + }, + "virtualNetworkName": { + "type": "string" + }, + "vmName": { + "type": "string" + }, + "vmSize": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "[variables('publicIPAddressApiVersion')]", + "location": "[variables('location')]", + "name": "[parameters('publicIPAddressName')]", + "properties": { + "dnsSettings": { + "domainNameLabel": "[parameters('dnsNameForPublicIP')]" + }, + "publicIPAllocationMethod": "[variables('publicIPAddressType')]" + }, + "type": "Microsoft.Network/publicIPAddresses" + }, + { + "apiVersion": "[variables('networkInterfacesApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" + ], + "location": "[variables('location')]", + "name": "[parameters('nicName')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" + } + } + } + ] + }, + "type": "Microsoft.Network/networkInterfaces" + }, + { + "apiVersion": "[variables('apiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]" + ], + "location": "[variables('location')]", + "name": "[parameters('vmName')]", + "properties": { + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": false + } + }, + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]" + } + ] + }, + "osProfile": { + "adminPassword": "[parameters('adminPassword')]", + "adminUsername": "[parameters('adminUsername')]", + "computerName": "[parameters('vmName')]", + "secrets": [ + { + "sourceVault": { + "id": "[resourceId(resourceGroup().name, 'Microsoft.KeyVault/vaults', '--keyvault-name--')]" + }, + "vaultCertificates": [ + { + "certificateStore": "My", + "certificateUrl": "" + } + ] + } + ], + "windowsConfiguration": { + "provisionVMAgent": true, + "winRM": { + "listeners": [ + { + "certificateUrl": "", + "protocol": "https" + } + ] + } + } + }, + "storageProfile": { + "imageReference": { + "offer": "--image-offer--", + "publisher": "--image-publisher--", + "sku": "--image-sku--", + "version": "--version--" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "managedDisk": { + "storageAccountType": "Standard_LRS" + }, + "name": "[parameters('osDiskName')]", + "osType": "Windows" + } + } + }, + "type": "Microsoft.Compute/virtualMachines" + }, + { + "apiVersion": "[variables('networkSecurityGroupsApiVersion')]", + "location": "[variables('location')]", + "name": "[parameters('nsgName')]", + "properties": { + "securityRules": [ + { + "name": "AllowIPsToSshWinRMInbound", + "properties": { + "access": "Allow", + "description": "Allow inbound traffic from specified IP addresses", + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRange": "5985", + "direction": "Inbound", + "priority": 100, + "protocol": "Tcp", + "sourceAddressPrefixes": [ + "127.0.0.1", + "192.168.100.0/24" + ], + "sourcePortRange": "*" + } + } + ] + }, + "type": "Microsoft.Network/networkSecurityGroups" + }, + { + "apiVersion": "[variables('virtualNetworksApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkSecurityGroups/', parameters('nsgName'))]" + ], + "location": "[variables('location')]", + "name": "[variables('virtualNetworkName')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('nsgName'))]" + } + } + } + ] + }, + "type": "Microsoft.Network/virtualNetworks" + } + ], + "variables": { + "addressPrefix": "10.0.0.0/16", + "apiVersion": "2017-03-30", + "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", + "publicIPAddressApiVersion": "2017-04-01", + "publicIPAddressType": "Dynamic", + "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", + "subnetAddressPrefix": "10.0.0.0/24", + "subnetName": "[parameters('subnetName')]", + "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", + "virtualNetworkName": "[parameters('virtualNetworkName')]", + "virtualNetworkResourceGroup": "[resourceGroup().name]", + "virtualNetworksApiVersion": "2017-04-01", + "vmStorageAccountContainerName": "images", + "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" + } +} \ No newline at end of file diff --git a/builder/azure/arm/template_factory_test.go b/builder/azure/arm/template_factory_test.go index 382a0b490..069415893 100644 --- a/builder/azure/arm/template_factory_test.go +++ b/builder/azure/arm/template_factory_test.go @@ -425,6 +425,40 @@ func TestVirtualMachineDeployment12(t *testing.T) { } } +// Ensure the VM template is correct when building with list of allowed IP addresses +func TestVirtualMachineDeployment13(t *testing.T) { + config := map[string]interface{}{ + "location": "ignore", + "subscription_id": "ignore", + "os_type": constants.Target_Windows, + "communicator": "winrm", + "winrm_username": "ignore", + "image_publisher": "--image-publisher--", + "image_offer": "--image-offer--", + "image_sku": "--image-sku--", + "image_version": "--version--", + "managed_image_name": "ManagedImageName", + "managed_image_resource_group_name": "ManagedImageResourceGroupName", + "allowed_inbound_ip_addresses": []string{"127.0.0.1", "192.168.100.0/24"}, + } + + c, _, err := newConfig(config, getPackerConfiguration()) + if err != nil { + t.Fatal(err) + } + c.tmpKeyVaultName = "--keyvault-name--" + + deployment, err := GetVirtualMachineDeployment(c) + if err != nil { + t.Fatal(err) + } + + err = approvaltests.VerifyJSONStruct(t, deployment.Properties.Template) + if err != nil { + t.Fatal(err) + } +} + // Ensure the link values are not set, and the concrete values are set. func TestKeyVaultDeployment00(t *testing.T) { c, _, _ := newConfig(getArmBuilderConfiguration(), getPackerConfiguration()) diff --git a/builder/azure/arm/tempname.go b/builder/azure/arm/tempname.go index b5289d450..45fb97e63 100644 --- a/builder/azure/arm/tempname.go +++ b/builder/azure/arm/tempname.go @@ -19,6 +19,7 @@ type TempName struct { SubnetName string PublicIPAddressName string VirtualNetworkName string + NsgName string } func NewTempName() *TempName { @@ -33,6 +34,7 @@ func NewTempName() *TempName { tempName.PublicIPAddressName = fmt.Sprintf("pkrip%s", suffix) tempName.SubnetName = fmt.Sprintf("pkrsn%s", suffix) tempName.VirtualNetworkName = fmt.Sprintf("pkrvn%s", suffix) + tempName.NsgName = fmt.Sprintf("pkrsg%s", suffix) tempName.ResourceGroupName = fmt.Sprintf("packer-Resource-Group-%s", suffix) tempName.AdminPassword = generatePassword() diff --git a/builder/azure/arm/tempname_test.go b/builder/azure/arm/tempname_test.go index 0b3608e18..e6a6fd236 100644 --- a/builder/azure/arm/tempname_test.go +++ b/builder/azure/arm/tempname_test.go @@ -41,6 +41,10 @@ func TestTempNameShouldCreatePrefixedRandomNames(t *testing.T) { if strings.Index(tempName.VirtualNetworkName, "pkrvn") != 0 { t.Errorf("Expected VirtualNetworkName to begin with 'pkrvn', but got '%s'!", tempName.VirtualNetworkName) } + + if strings.Index(tempName.NsgName, "pkrsg") != 0 { + t.Errorf("Expected NsgName to begin with 'pkrsg', but got '%s'!", tempName.NsgName) + } } func TestTempAdminPassword(t *testing.T) { @@ -92,4 +96,8 @@ func TestTempNameShouldHaveSameSuffix(t *testing.T) { if strings.HasSuffix(tempName.VirtualNetworkName, suffix) != true { t.Errorf("Expected VirtualNetworkName to end with '%s', but the value is '%s'!", suffix, tempName.VirtualNetworkName) } + + if strings.HasSuffix(tempName.NsgName, suffix) != true { + t.Errorf("Expected NsgName to end with '%s', but the value is '%s'!", suffix, tempName.NsgName) + } } diff --git a/builder/azure/common/template/template.go b/builder/azure/common/template/template.go index 6c4429c34..b90d0104b 100644 --- a/builder/azure/common/template/template.go +++ b/builder/azure/common/template/template.go @@ -91,10 +91,11 @@ type Properties struct { PublicIPAllocatedMethod *network.IPAllocationMethod `json:"publicIPAllocationMethod,omitempty"` Sku *Sku `json:"sku,omitempty"` //StorageProfile3 *compute.StorageProfile `json:"storageProfile,omitempty"` - StorageProfile *StorageProfileUnion `json:"storageProfile,omitempty"` - Subnets *[]network.Subnet `json:"subnets,omitempty"` - TenantId *string `json:"tenantId,omitempty"` - Value *string `json:"value,omitempty"` + StorageProfile *StorageProfileUnion `json:"storageProfile,omitempty"` + Subnets *[]network.Subnet `json:"subnets,omitempty"` + SecurityRules *[]network.SecurityRule `json:"securityRules,omitempty"` + TenantId *string `json:"tenantId,omitempty"` + Value *string `json:"value,omitempty"` } type AccessPolicies struct { diff --git a/builder/azure/common/template/template_builder.go b/builder/azure/common/template/template_builder.go index 188fdaf7b..1e1eb53d4 100644 --- a/builder/azure/common/template/template_builder.go +++ b/builder/azure/common/template/template_builder.go @@ -3,9 +3,11 @@ package template import ( "encoding/json" "fmt" + "strconv" "strings" "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute" + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-01-01/network" "github.com/Azure/go-autorest/autorest/to" ) @@ -13,11 +15,12 @@ const ( jsonPrefix = "" jsonIndent = " " - resourceKeyVaults = "Microsoft.KeyVault/vaults" - resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces" - resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses" - resourceVirtualMachine = "Microsoft.Compute/virtualMachines" - resourceVirtualNetworks = "Microsoft.Network/virtualNetworks" + resourceKeyVaults = "Microsoft.KeyVault/vaults" + resourceNetworkInterfaces = "Microsoft.Network/networkInterfaces" + resourcePublicIPAddresses = "Microsoft.Network/publicIPAddresses" + resourceVirtualMachine = "Microsoft.Compute/virtualMachines" + resourceVirtualNetworks = "Microsoft.Network/virtualNetworks" + resourceNetworkSecurityGroups = "Microsoft.Network/networkSecurityGroups" variableSshKeyPath = "sshKeyPath" ) @@ -309,6 +312,39 @@ func (s *TemplateBuilder) SetPrivateVirtualNetworkWithPublicIp(virtualNetworkRes return nil } +func (s *TemplateBuilder) SetNetworkSecurityGroup(ipAddresses []string, port int) error { + nsgResource, dependency, resourceId := s.createNsgResource(ipAddresses, port) + if err := s.addResource(nsgResource); err != nil { + return err + } + + vnetResource, err := s.getResourceByType(resourceVirtualNetworks) + if err != nil { + return err + } + s.deleteResourceByType(resourceVirtualNetworks) + + s.addResourceDependency(vnetResource, dependency) + + if vnetResource.Properties == nil || vnetResource.Properties.Subnets == nil || len(*vnetResource.Properties.Subnets) != 1 { + return fmt.Errorf("template: could not find virtual network/subnet to add default network security group to") + } + subnet := ((*vnetResource.Properties.Subnets)[0]) + if subnet.SubnetPropertiesFormat == nil { + subnet.SubnetPropertiesFormat = &network.SubnetPropertiesFormat{} + } + if subnet.SubnetPropertiesFormat.NetworkSecurityGroup != nil { + return fmt.Errorf("template: subnet already has an associated network security group") + } + subnet.SubnetPropertiesFormat.NetworkSecurityGroup = &network.SecurityGroup{ + ID: to.StringPtr(resourceId), + } + + s.addResource(vnetResource) + + return nil +} + func (s *TemplateBuilder) SetTags(tags *map[string]*string) error { if tags == nil || len(*tags) == 0 { return nil @@ -366,6 +402,18 @@ func (s *TemplateBuilder) toVariable(name string) string { return fmt.Sprintf("[variables('%s')]", name) } +func (s *TemplateBuilder) addResource(newResource *Resource) error { + for _, resource := range *s.template.Resources { + if *resource.Type == *newResource.Type { + return fmt.Errorf("template: found an existing resource of type %s", *resource.Type) + } + } + + resources := append(*s.template.Resources, *newResource) + s.template.Resources = &resources + return nil +} + func (s *TemplateBuilder) deleteResourceByType(resourceType string) { resources := make([]Resource, 0) @@ -379,6 +427,15 @@ func (s *TemplateBuilder) deleteResourceByType(resourceType string) { s.template.Resources = &resources } +func (s *TemplateBuilder) addResourceDependency(resource *Resource, dep string) { + if resource.DependsOn != nil { + deps := append(*resource.DependsOn, dep) + resource.DependsOn = &deps + } else { + resource.DependsOn = &[]string{dep} + } +} + func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate func(string) bool) { deps := make([]string, 0) @@ -391,6 +448,38 @@ func (s *TemplateBuilder) deleteResourceDependency(resource *Resource, predicate *resource.DependsOn = deps } +func (s *TemplateBuilder) createNsgResource(srcIpAddresses []string, port int) (*Resource, string, string) { + resource := &Resource{ + ApiVersion: to.StringPtr("[variables('networkSecurityGroupsApiVersion')]"), + Name: to.StringPtr("[parameters('nsgName')]"), + Type: to.StringPtr(resourceNetworkSecurityGroups), + Location: to.StringPtr("[variables('location')]"), + Properties: &Properties{ + SecurityRules: &[]network.SecurityRule{ + { + Name: to.StringPtr("AllowIPsToSshWinRMInbound"), + SecurityRulePropertiesFormat: &network.SecurityRulePropertiesFormat{ + Description: to.StringPtr("Allow inbound traffic from specified IP addresses"), + Protocol: network.SecurityRuleProtocolTCP, + Priority: to.Int32Ptr(100), + Access: network.SecurityRuleAccessAllow, + Direction: network.SecurityRuleDirectionInbound, + SourceAddressPrefixes: &srcIpAddresses, + SourcePortRange: to.StringPtr("*"), + DestinationAddressPrefix: to.StringPtr("VirtualNetwork"), + DestinationPortRange: to.StringPtr(strconv.Itoa(port)), + }, + }, + }, + }, + } + + dependency := fmt.Sprintf("[concat('%s/', parameters('nsgName'))]", resourceNetworkSecurityGroups) + resourceId := fmt.Sprintf("[resourceId('%s', parameters('nsgName'))]", resourceNetworkSecurityGroups) + + return resource, dependency, resourceId +} + // See https://github.com/Azure/azure-quickstart-templates for a extensive list of templates. // Template to deploy a KeyVault. @@ -496,6 +585,9 @@ const BasicTemplate = `{ "virtualNetworkName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "vmSize": { "type": "string" }, @@ -510,6 +602,7 @@ const BasicTemplate = `{ "networkInterfacesApiVersion": "2017-04-01", "publicIPAddressApiVersion": "2017-04-01", "virtualNetworksApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "location": "[resourceGroup().location]", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/common/template/template_builder_test.TestBuildLinux00.approved.json b/builder/azure/common/template/template_builder_test.TestBuildLinux00.approved.json index b1550d5b5..4ba861566 100644 --- a/builder/azure/common/template/template_builder_test.TestBuildLinux00.approved.json +++ b/builder/azure/common/template/template_builder_test.TestBuildLinux00.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -160,6 +163,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/common/template/template_builder_test.TestBuildLinux01.approved.json b/builder/azure/common/template/template_builder_test.TestBuildLinux01.approved.json index c8e1455e5..5e5b2ea05 100644 --- a/builder/azure/common/template/template_builder_test.TestBuildLinux01.approved.json +++ b/builder/azure/common/template/template_builder_test.TestBuildLinux01.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -158,6 +161,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/common/template/template_builder_test.TestBuildLinux02.approved.json b/builder/azure/common/template/template_builder_test.TestBuildLinux02.approved.json index a27074962..b3a2e1e73 100644 --- a/builder/azure/common/template/template_builder_test.TestBuildLinux02.approved.json +++ b/builder/azure/common/template/template_builder_test.TestBuildLinux02.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -120,6 +123,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/common/template/template_builder_test.TestBuildWindows00.approved.json b/builder/azure/common/template/template_builder_test.TestBuildWindows00.approved.json index 13a581f92..797081807 100644 --- a/builder/azure/common/template/template_builder_test.TestBuildWindows00.approved.json +++ b/builder/azure/common/template/template_builder_test.TestBuildWindows00.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -174,6 +177,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/common/template/template_builder_test.TestBuildWindows01.approved.json b/builder/azure/common/template/template_builder_test.TestBuildWindows01.approved.json index 3add40537..a7230706d 100644 --- a/builder/azure/common/template/template_builder_test.TestBuildWindows01.approved.json +++ b/builder/azure/common/template/template_builder_test.TestBuildWindows01.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -197,6 +200,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/common/template/template_builder_test.TestBuildWindows02.approved.json b/builder/azure/common/template/template_builder_test.TestBuildWindows02.approved.json index 7c44163c3..ad8d94bfb 100644 --- a/builder/azure/common/template/template_builder_test.TestBuildWindows02.approved.json +++ b/builder/azure/common/template/template_builder_test.TestBuildWindows02.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -190,6 +193,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/common/template/template_builder_test.TestNetworkSecurityGroup00.approved.json b/builder/azure/common/template/template_builder_test.TestNetworkSecurityGroup00.approved.json new file mode 100644 index 000000000..b318164a3 --- /dev/null +++ b/builder/azure/common/template/template_builder_test.TestNetworkSecurityGroup00.approved.json @@ -0,0 +1,212 @@ +{ + "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json", + "contentVersion": "1.0.0.0", + "parameters": { + "adminPassword": { + "type": "string" + }, + "adminUsername": { + "type": "string" + }, + "dnsNameForPublicIP": { + "type": "string" + }, + "nicName": { + "type": "string" + }, + "nsgName": { + "type": "string" + }, + "osDiskName": { + "type": "string" + }, + "publicIPAddressName": { + "type": "string" + }, + "storageAccountBlobEndpoint": { + "type": "string" + }, + "subnetName": { + "type": "string" + }, + "virtualNetworkName": { + "type": "string" + }, + "vmName": { + "type": "string" + }, + "vmSize": { + "type": "string" + } + }, + "resources": [ + { + "apiVersion": "[variables('publicIPAddressApiVersion')]", + "location": "[variables('location')]", + "name": "[parameters('publicIPAddressName')]", + "properties": { + "dnsSettings": { + "domainNameLabel": "[parameters('dnsNameForPublicIP')]" + }, + "publicIPAllocationMethod": "[variables('publicIPAddressType')]" + }, + "type": "Microsoft.Network/publicIPAddresses" + }, + { + "apiVersion": "[variables('networkInterfacesApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/publicIPAddresses/', parameters('publicIPAddressName'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" + ], + "location": "[variables('location')]", + "name": "[parameters('nicName')]", + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('publicIPAddressName'))]" + }, + "subnet": { + "id": "[variables('subnetRef')]" + } + } + } + ] + }, + "type": "Microsoft.Network/networkInterfaces" + }, + { + "apiVersion": "[variables('apiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkInterfaces/', parameters('nicName'))]" + ], + "location": "[variables('location')]", + "name": "[parameters('vmName')]", + "properties": { + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": false + } + }, + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]" + } + ] + }, + "osProfile": { + "adminPassword": "[parameters('adminPassword')]", + "adminUsername": "[parameters('adminUsername')]", + "computerName": "[parameters('vmName')]", + "linuxConfiguration": { + "ssh": { + "publicKeys": [ + { + "keyData": "--test-ssh-authorized-key--", + "path": "[variables('sshKeyPath')]" + } + ] + } + } + }, + "storageProfile": { + "imageReference": { + "offer": "UbuntuServer", + "publisher": "Canonical", + "sku": "16.04", + "version": "latest" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "name": "[parameters('osDiskName')]", + "vhd": { + "uri": "[concat(parameters('storageAccountBlobEndpoint'),variables('vmStorageAccountContainerName'),'/', parameters('osDiskName'),'.vhd')]" + } + } + } + }, + "type": "Microsoft.Compute/virtualMachines" + }, + { + "apiVersion": "[variables('networkSecurityGroupsApiVersion')]", + "location": "[variables('location')]", + "name": "[parameters('nsgName')]", + "properties": { + "securityRules": [ + { + "name": "AllowIPsToSshWinRMInbound", + "properties": { + "access": "Allow", + "description": "Allow inbound traffic from specified IP addresses", + "destinationAddressPrefix": "VirtualNetwork", + "destinationPortRange": "123", + "direction": "Inbound", + "priority": 100, + "protocol": "Tcp", + "sourceAddressPrefixes": [ + "127.0.0.1", + "192.168.100.0/24" + ], + "sourcePortRange": "*" + } + } + ] + }, + "type": "Microsoft.Network/networkSecurityGroups" + }, + { + "apiVersion": "[variables('virtualNetworksApiVersion')]", + "dependsOn": [ + "[concat('Microsoft.Network/networkSecurityGroups/', parameters('nsgName'))]" + ], + "location": "[variables('location')]", + "name": "[variables('virtualNetworkName')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[variables('addressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[variables('subnetName')]", + "properties": { + "addressPrefix": "[variables('subnetAddressPrefix')]", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('nsgName'))]" + } + } + } + ] + }, + "type": "Microsoft.Network/virtualNetworks" + } + ], + "variables": { + "addressPrefix": "10.0.0.0/16", + "apiVersion": "2017-03-30", + "location": "[resourceGroup().location]", + "managedDiskApiVersion": "2017-03-30", + "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", + "publicIPAddressApiVersion": "2017-04-01", + "publicIPAddressType": "Dynamic", + "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", + "subnetAddressPrefix": "10.0.0.0/24", + "subnetName": "[parameters('subnetName')]", + "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", + "virtualNetworkName": "[parameters('virtualNetworkName')]", + "virtualNetworkResourceGroup": "[resourceGroup().name]", + "virtualNetworksApiVersion": "2017-04-01", + "vmStorageAccountContainerName": "images", + "vnetID": "[resourceId(variables('virtualNetworkResourceGroup'), 'Microsoft.Network/virtualNetworks', variables('virtualNetworkName'))]" + } +} \ No newline at end of file diff --git a/builder/azure/common/template/template_builder_test.TestSharedImageGallery00.approved.json b/builder/azure/common/template/template_builder_test.TestSharedImageGallery00.approved.json index c180e1e16..49f2a72a4 100644 --- a/builder/azure/common/template/template_builder_test.TestSharedImageGallery00.approved.json +++ b/builder/azure/common/template/template_builder_test.TestSharedImageGallery00.approved.json @@ -14,6 +14,9 @@ "nicName": { "type": "string" }, + "nsgName": { + "type": "string" + }, "osDiskName": { "type": "string" }, @@ -155,6 +158,7 @@ "location": "[resourceGroup().location]", "managedDiskApiVersion": "2017-03-30", "networkInterfacesApiVersion": "2017-04-01", + "networkSecurityGroupsApiVersion": "2019-04-01", "publicIPAddressApiVersion": "2017-04-01", "publicIPAddressType": "Dynamic", "sshKeyPath": "[concat('/home/',parameters('adminUsername'),'/.ssh/authorized_keys')]", diff --git a/builder/azure/common/template/template_builder_test.go b/builder/azure/common/template/template_builder_test.go index 482107c34..b39af103f 100644 --- a/builder/azure/common/template/template_builder_test.go +++ b/builder/azure/common/template/template_builder_test.go @@ -210,3 +210,36 @@ func TestSharedImageGallery00(t *testing.T) { t.Fatal(err) } } + +// Linux build with Network Security Group +func TestNetworkSecurityGroup00(t *testing.T) { + testSubject, err := NewTemplateBuilder(BasicTemplate) + if err != nil { + t.Fatal(err) + } + + err = testSubject.BuildLinux("--test-ssh-authorized-key--") + if err != nil { + t.Fatal(err) + } + + err = testSubject.SetMarketPlaceImage("Canonical", "UbuntuServer", "16.04", "latest", compute.CachingTypesReadWrite) + if err != nil { + t.Fatal(err) + } + + err = testSubject.SetNetworkSecurityGroup([]string{"127.0.0.1", "192.168.100.0/24"}, 123) + if err != nil { + t.Fatal(err) + } + + doc, err := testSubject.ToJSON() + if err != nil { + t.Fatal(err) + } + + err = approvaltests.VerifyJSONBytes(t, []byte(*doc)) + if err != nil { + t.Fatal(err) + } +} diff --git a/builder/azure/common/template/template_parameters.go b/builder/azure/common/template/template_parameters.go index 65bc55e75..74b0562a7 100644 --- a/builder/azure/common/template/template_parameters.go +++ b/builder/azure/common/template/template_parameters.go @@ -31,6 +31,7 @@ type TemplateParameters struct { SubnetName *TemplateParameter `json:"subnetName,omitempty"` TenantId *TemplateParameter `json:"tenantId,omitempty"` VirtualNetworkName *TemplateParameter `json:"virtualNetworkName,omitempty"` + NsgName *TemplateParameter `json:"nsgName,omitempty"` VMSize *TemplateParameter `json:"vmSize,omitempty"` VMName *TemplateParameter `json:"vmName,omitempty"` } diff --git a/builder/azure/common/template/template_parameters_test.go b/builder/azure/common/template/template_parameters_test.go index 9dbb9f243..7a321128f 100644 --- a/builder/azure/common/template/template_parameters_test.go +++ b/builder/azure/common/template/template_parameters_test.go @@ -16,6 +16,7 @@ func TestTemplateParametersShouldHaveExpectedKeys(t *testing.T) { StorageAccountBlobEndpoint: &TemplateParameter{Value: "sentinel"}, VMName: &TemplateParameter{Value: "sentinel"}, VMSize: &TemplateParameter{Value: "sentinel"}, + NsgName: &TemplateParameter{Value: "sentinel"}, } bs, err := json.Marshal(params) @@ -38,6 +39,7 @@ func TestTemplateParametersShouldHaveExpectedKeys(t *testing.T) { "storageAccountBlobEndpoint", "vmSize", "vmName", + "nsgName", } for _, expectedKey := range expectedKeys { @@ -57,6 +59,7 @@ func TestParameterValuesShouldBeSet(t *testing.T) { StorageAccountBlobEndpoint: &TemplateParameter{Value: "storageaccountblobendpoint00"}, VMName: &TemplateParameter{Value: "vmname00"}, VMSize: &TemplateParameter{Value: "vmsize00"}, + NsgName: &TemplateParameter{Value: "nsgname00"}, } bs, err := json.Marshal(params) From 97f33647da2b0385e139494f2efd8eac2d3feb8a Mon Sep 17 00:00:00 2001 From: Sumit Kalra <44450797+sumit-kalra@users.noreply.github.com> Date: Tue, 8 Oct 2019 16:02:39 -0700 Subject: [PATCH 6/7] Adding auto-generated documentation (#5) --- .../builder/azure/arm/_Config-not-required.html.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/source/partials/builder/azure/arm/_Config-not-required.html.md b/website/source/partials/builder/azure/arm/_Config-not-required.html.md index af8816b87..5113e3db6 100644 --- a/website/source/partials/builder/azure/arm/_Config-not-required.html.md +++ b/website/source/partials/builder/azure/arm/_Config-not-required.html.md @@ -217,6 +217,13 @@ - `disk_caching_type` (string) - Specify the disk caching type. Valid values are None, ReadOnly, and ReadWrite. The default value is ReadWrite. +- `allowed_inbound_ip_addresses` ([]string) - Specify the list of IP addresses and CIDR blocks that should be + allowed access to the VM. If provided, an Azure Network Security + Group will be created with corresponding rules and be bound to + the NIC attached to the VM. + Providing `allowed_inbound_ip_addresses` in combination with + `virtual_network_name` is not allowed. + - `async_resourcegroup_delete` (bool) - If you want packer to delete the temporary resource group asynchronously set this value. It's a boolean value and defaults to false. Important Setting this true means that From 4bd09f381ef5da833a6ee78adb30db1fbf6268ae Mon Sep 17 00:00:00 2001 From: Sumit Kalra <44450797+sumit-kalra@users.noreply.github.com> Date: Thu, 10 Oct 2019 11:30:28 -0700 Subject: [PATCH 7/7] Fixing documentation (#6) --- builder/azure/arm/config.go | 2 +- .../partials/builder/azure/arm/_Config-not-required.html.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/azure/arm/config.go b/builder/azure/arm/config.go index bf072d5da..5ece275a7 100644 --- a/builder/azure/arm/config.go +++ b/builder/azure/arm/config.go @@ -346,7 +346,7 @@ type Config struct { // Specify the list of IP addresses and CIDR blocks that should be // allowed access to the VM. If provided, an Azure Network Security // Group will be created with corresponding rules and be bound to - // the NIC attached to the VM. + // the subnet of the VM. // Providing `allowed_inbound_ip_addresses` in combination with // `virtual_network_name` is not allowed. AllowedInboundIpAddresses []string `mapstructure:"allowed_inbound_ip_addresses"` diff --git a/website/source/partials/builder/azure/arm/_Config-not-required.html.md b/website/source/partials/builder/azure/arm/_Config-not-required.html.md index 5113e3db6..e70606434 100644 --- a/website/source/partials/builder/azure/arm/_Config-not-required.html.md +++ b/website/source/partials/builder/azure/arm/_Config-not-required.html.md @@ -220,7 +220,7 @@ - `allowed_inbound_ip_addresses` ([]string) - Specify the list of IP addresses and CIDR blocks that should be allowed access to the VM. If provided, an Azure Network Security Group will be created with corresponding rules and be bound to - the NIC attached to the VM. + the subnet of the VM. Providing `allowed_inbound_ip_addresses` in combination with `virtual_network_name` is not allowed.