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
This commit is contained in:
Sumit Kalra 2019-10-08 14:56:43 -07:00 committed by GitHub
parent ab5c24e3a7
commit 595b103bbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 729 additions and 10 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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) {

View File

@ -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 {

View File

@ -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)

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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'))]"
}
}

View File

@ -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())

View File

@ -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()

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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')]",

View File

@ -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'))]"
}
}

View File

@ -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')]",

View File

@ -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)
}
}

View File

@ -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"`
}

View File

@ -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)