github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/provider/azure/networking.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 "fmt" 8 "net" 9 "path" 10 11 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest" 12 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/to" 13 "github.com/Azure/azure-sdk-for-go/arm/compute" 14 "github.com/Azure/azure-sdk-for-go/arm/network" 15 "github.com/juju/errors" 16 "github.com/juju/utils/set" 17 18 "github.com/juju/juju/provider/azure/internal/iputils" 19 ) 20 21 const ( 22 // internalNetworkName is the name of the virtual network that all 23 // Juju machines within a resource group are connected to. 24 // 25 // Each resource group is given its own network, subnet and network 26 // security group to manage. Each resource group will have its own 27 // private 10.0.0.0/16 network. 28 internalNetworkName = "juju-internal-network" 29 30 // internalSubnetName is the name of the subnet that each machine's 31 // primary NIC is attached to. 32 internalSubnetName = "juju-internal-subnet" 33 34 // internalSecurityGroupName is the name of the network security 35 // group that each machine's primary (internal network) NIC is 36 // attached to. 37 internalSecurityGroupName = "juju-internal-nsg" 38 ) 39 40 const ( 41 // securityRuleInternalMin is the beginning of the range of 42 // internal security group rules defined by Juju. 43 securityRuleInternalMin = 100 44 45 // securityRuleInternalMax is the end of the range of internal 46 // security group rules defined by Juju. 47 securityRuleInternalMax = 199 48 49 // securityRuleMax is the maximum allowable security rule 50 // priority. 51 securityRuleMax = 4096 52 ) 53 54 const ( 55 // securityRuleInternalSSHInbound is the priority of the 56 // security rule that allows inbound SSH access to all 57 // machines. 58 securityRuleInternalSSHInbound = securityRuleInternalMin + iota 59 ) 60 61 var sshSecurityRule = network.SecurityRule{ 62 Name: to.StringPtr("SSHInbound"), 63 Properties: &network.SecurityRulePropertiesFormat{ 64 Description: to.StringPtr("Allow SSH access to all machines"), 65 Protocol: network.TCP, 66 SourceAddressPrefix: to.StringPtr("*"), 67 SourcePortRange: to.StringPtr("*"), 68 DestinationAddressPrefix: to.StringPtr("*"), 69 DestinationPortRange: to.StringPtr("22"), 70 Access: network.Allow, 71 Priority: to.IntPtr(securityRuleInternalSSHInbound), 72 Direction: network.Inbound, 73 }, 74 } 75 76 func createInternalVirtualNetwork( 77 callAPI callAPIFunc, 78 client network.ManagementClient, 79 resourceGroup string, 80 location string, 81 tags map[string]string, 82 ) (*network.VirtualNetwork, error) { 83 addressPrefixes := []string{"10.0.0.0/16"} 84 virtualNetworkParams := network.VirtualNetwork{ 85 Location: to.StringPtr(location), 86 Tags: toTagsPtr(tags), 87 Properties: &network.VirtualNetworkPropertiesFormat{ 88 AddressSpace: &network.AddressSpace{&addressPrefixes}, 89 }, 90 } 91 logger.Debugf("creating virtual network %q", internalNetworkName) 92 vnetClient := network.VirtualNetworksClient{client} 93 var vnet network.VirtualNetwork 94 if err := callAPI(func() (autorest.Response, error) { 95 var err error 96 vnet, err = vnetClient.CreateOrUpdate( 97 resourceGroup, internalNetworkName, virtualNetworkParams, 98 ) 99 return vnet.Response, err 100 }); err != nil { 101 return nil, errors.Annotatef(err, "creating virtual network %q", internalNetworkName) 102 } 103 return &vnet, nil 104 } 105 106 // createInternalSubnet creates an internal subnet for the specified resource group, 107 // within the specified virtual network. 108 // 109 // NOTE(axw) this method expects an up-to-date VirtualNetwork, and expects that are 110 // no concurrent subnet additions to the virtual network. At the moment we have only 111 // three places where we modify subnets: at bootstrap, when a new environment is 112 // created, and when an environment is destroyed. 113 func createInternalSubnet( 114 callAPI callAPIFunc, 115 client network.ManagementClient, 116 resourceGroup string, 117 vnet *network.VirtualNetwork, 118 location string, 119 tags map[string]string, 120 ) (*network.Subnet, error) { 121 122 nextAddressPrefix := (*vnet.Properties.AddressSpace.AddressPrefixes)[0] 123 if vnet.Properties.Subnets != nil { 124 if len(*vnet.Properties.Subnets) == len(*vnet.Properties.AddressSpace.AddressPrefixes) { 125 return nil, errors.Errorf( 126 "no available address prefixes in vnet %q", 127 to.String(vnet.Name), 128 ) 129 } 130 addressPrefixesInUse := make(set.Strings) 131 for _, subnet := range *vnet.Properties.Subnets { 132 addressPrefixesInUse.Add(to.String(subnet.Properties.AddressPrefix)) 133 } 134 for _, addressPrefix := range *vnet.Properties.AddressSpace.AddressPrefixes { 135 if !addressPrefixesInUse.Contains(addressPrefix) { 136 nextAddressPrefix = addressPrefix 137 break 138 } 139 } 140 } 141 142 // Create a network security group for the environment. There is only 143 // one NSG per environment (there's a limit of 100 per subscription), 144 // in which we manage rules for each exposed machine. 145 securityRules := []network.SecurityRule{sshSecurityRule} 146 securityGroupParams := network.SecurityGroup{ 147 Location: to.StringPtr(location), 148 Tags: toTagsPtr(tags), 149 Properties: &network.SecurityGroupPropertiesFormat{ 150 SecurityRules: &securityRules, 151 }, 152 } 153 securityGroupClient := network.SecurityGroupsClient{client} 154 securityGroupName := internalSecurityGroupName 155 logger.Debugf("creating security group %q", securityGroupName) 156 var nsg network.SecurityGroup 157 if err := callAPI(func() (autorest.Response, error) { 158 var err error 159 nsg, err = securityGroupClient.CreateOrUpdate( 160 resourceGroup, securityGroupName, securityGroupParams, 161 ) 162 return nsg.Response, err 163 }); err != nil { 164 return nil, errors.Annotatef(err, "creating security group %q", securityGroupName) 165 } 166 167 // Now create a subnet with the next available address prefix, and 168 // associate the subnet with the NSG created above. 169 subnetName := internalSubnetName 170 subnetParams := network.Subnet{ 171 Properties: &network.SubnetPropertiesFormat{ 172 AddressPrefix: to.StringPtr(nextAddressPrefix), 173 NetworkSecurityGroup: &network.SubResource{nsg.ID}, 174 }, 175 } 176 logger.Debugf("creating subnet %q (%s)", subnetName, nextAddressPrefix) 177 subnetClient := network.SubnetsClient{client} 178 var subnet network.Subnet 179 if err := callAPI(func() (autorest.Response, error) { 180 var err error 181 subnet, err = subnetClient.CreateOrUpdate( 182 resourceGroup, internalNetworkName, subnetName, subnetParams, 183 ) 184 return subnet.Response, err 185 }); err != nil { 186 return nil, errors.Annotatef(err, "creating subnet %q", subnetName) 187 } 188 return &subnet, nil 189 } 190 191 func newNetworkProfile( 192 callAPI callAPIFunc, 193 client network.ManagementClient, 194 vmName string, 195 apiPort *int, 196 internalSubnet *network.Subnet, 197 resourceGroup string, 198 location string, 199 tags map[string]string, 200 ) (*compute.NetworkProfile, error) { 201 logger.Debugf("creating network profile for %q", vmName) 202 203 // Create a public IP for the NIC. Public IP addresses are dynamic. 204 logger.Debugf("- allocating public IP address") 205 pipClient := network.PublicIPAddressesClient{client} 206 publicIPAddressParams := network.PublicIPAddress{ 207 Location: to.StringPtr(location), 208 Tags: toTagsPtr(tags), 209 Properties: &network.PublicIPAddressPropertiesFormat{ 210 PublicIPAllocationMethod: network.Dynamic, 211 }, 212 } 213 publicIPAddressName := vmName + "-public-ip" 214 var publicIPAddress network.PublicIPAddress 215 if err := callAPI(func() (autorest.Response, error) { 216 var err error 217 publicIPAddress, err = pipClient.CreateOrUpdate( 218 resourceGroup, publicIPAddressName, publicIPAddressParams, 219 ) 220 return publicIPAddress.Response, err 221 }); err != nil { 222 return nil, errors.Annotatef(err, "creating public IP address for %q", vmName) 223 } 224 225 // Determine the next available private IP address. 226 nicClient := network.InterfacesClient{client} 227 privateIPAddress, err := nextSubnetIPAddress(nicClient, resourceGroup, internalSubnet) 228 if err != nil { 229 return nil, errors.Annotatef(err, "querying private IP addresses") 230 } 231 232 // Create a primary NIC for the machine. This needs to be static, so 233 // that we can create security rules that don't become invalid. 234 logger.Debugf("- creating primary NIC") 235 ipConfigurations := []network.InterfaceIPConfiguration{{ 236 Name: to.StringPtr("primary"), 237 Properties: &network.InterfaceIPConfigurationPropertiesFormat{ 238 PrivateIPAddress: to.StringPtr(privateIPAddress), 239 PrivateIPAllocationMethod: network.Static, 240 Subnet: &network.SubResource{internalSubnet.ID}, 241 PublicIPAddress: &network.SubResource{publicIPAddress.ID}, 242 }, 243 }} 244 primaryNicName := vmName + "-primary" 245 primaryNicParams := network.Interface{ 246 Location: to.StringPtr(location), 247 Tags: toTagsPtr(tags), 248 Properties: &network.InterfacePropertiesFormat{ 249 IPConfigurations: &ipConfigurations, 250 }, 251 } 252 var primaryNic network.Interface 253 if err := callAPI(func() (autorest.Response, error) { 254 var err error 255 primaryNic, err = nicClient.CreateOrUpdate(resourceGroup, primaryNicName, primaryNicParams) 256 return primaryNic.Response, err 257 }); err != nil { 258 return nil, errors.Annotatef(err, "creating network interface for %q", vmName) 259 } 260 261 // Create a network security rule for the machine if we need to open 262 // the API server port. 263 if apiPort != nil { 264 logger.Debugf("- querying network security group") 265 securityGroupClient := network.SecurityGroupsClient{client} 266 securityGroupName := internalSecurityGroupName 267 var securityGroup network.SecurityGroup 268 if err := callAPI(func() (autorest.Response, error) { 269 var err error 270 securityGroup, err = securityGroupClient.Get(resourceGroup, securityGroupName) 271 return securityGroup.Response, err 272 }); err != nil { 273 return nil, errors.Annotate(err, "querying network security group") 274 } 275 276 // NOTE(axw) this looks like TOCTTOU race territory, but it's 277 // safe because we only allocate/deallocate rules in this 278 // range during machine (de)provisioning, which is managed by 279 // a single goroutine. Non-internal ports are managed by the 280 // firewaller exclusively. 281 nextPriority, err := nextSecurityRulePriority( 282 securityGroup, 283 securityRuleInternalSSHInbound+1, 284 securityRuleInternalMax, 285 ) 286 if err != nil { 287 return nil, errors.Trace(err) 288 } 289 290 apiSecurityRuleName := fmt.Sprintf("%s-api", vmName) 291 apiSecurityRule := network.SecurityRule{ 292 Name: to.StringPtr(apiSecurityRuleName), 293 Properties: &network.SecurityRulePropertiesFormat{ 294 Description: to.StringPtr("Allow API access to server machines"), 295 Protocol: network.TCP, 296 SourceAddressPrefix: to.StringPtr("*"), 297 SourcePortRange: to.StringPtr("*"), 298 DestinationAddressPrefix: to.StringPtr(privateIPAddress), 299 DestinationPortRange: to.StringPtr(fmt.Sprint(*apiPort)), 300 Access: network.Allow, 301 Priority: to.IntPtr(nextPriority), 302 Direction: network.Inbound, 303 }, 304 } 305 logger.Debugf("- creating API network security rule") 306 securityRuleClient := network.SecurityRulesClient{client} 307 if err := callAPI(func() (autorest.Response, error) { 308 result, err := securityRuleClient.CreateOrUpdate( 309 resourceGroup, securityGroupName, apiSecurityRuleName, apiSecurityRule, 310 ) 311 return result.Response, err 312 }); err != nil { 313 return nil, errors.Annotate(err, "creating API network security rule") 314 } 315 } 316 317 // For now we only attach a single, flat network to each machine. 318 networkInterfaces := []compute.NetworkInterfaceReference{{ 319 ID: primaryNic.ID, 320 Properties: &compute.NetworkInterfaceReferenceProperties{ 321 Primary: to.BoolPtr(true), 322 }, 323 }} 324 return &compute.NetworkProfile{&networkInterfaces}, nil 325 } 326 327 // nextSecurityRulePriority returns the next available priority in the given 328 // security group within a specified range. 329 func nextSecurityRulePriority(group network.SecurityGroup, min, max int) (int, error) { 330 if group.Properties.SecurityRules == nil { 331 return min, nil 332 } 333 for p := min; p <= max; p++ { 334 var found bool 335 for _, rule := range *group.Properties.SecurityRules { 336 if to.Int(rule.Properties.Priority) == p { 337 found = true 338 break 339 } 340 } 341 if !found { 342 return p, nil 343 } 344 } 345 return -1, errors.Errorf( 346 "no priorities available in the range [%d, %d]", min, max, 347 ) 348 } 349 350 // nextSubnetIPAddress returns the next available IP address in the given subnet. 351 func nextSubnetIPAddress( 352 nicClient network.InterfacesClient, 353 resourceGroup string, 354 subnet *network.Subnet, 355 ) (string, error) { 356 _, ipnet, err := net.ParseCIDR(to.String(subnet.Properties.AddressPrefix)) 357 if err != nil { 358 return "", errors.Annotate(err, "parsing subnet prefix") 359 } 360 results, err := nicClient.List(resourceGroup) 361 if err != nil { 362 return "", errors.Annotate(err, "listing NICs") 363 } 364 // Azure reserves the first 4 addresses in the subnet. 365 var ipsInUse []net.IP 366 if results.Value != nil { 367 ipsInUse = make([]net.IP, 0, len(*results.Value)) 368 for _, item := range *results.Value { 369 if item.Properties.IPConfigurations == nil { 370 continue 371 } 372 for _, ipConfiguration := range *item.Properties.IPConfigurations { 373 if to.String(ipConfiguration.Properties.Subnet.ID) != to.String(subnet.ID) { 374 continue 375 } 376 ip := net.ParseIP(to.String(ipConfiguration.Properties.PrivateIPAddress)) 377 if ip != nil { 378 ipsInUse = append(ipsInUse, ip) 379 } 380 } 381 } 382 } 383 ip, err := iputils.NextSubnetIP(ipnet, ipsInUse) 384 if err != nil { 385 return "", errors.Trace(err) 386 } 387 return ip.String(), nil 388 } 389 390 // internalSubnetId returns the Azure resource ID of the internal network 391 // subnet for the specified resource group. 392 func internalSubnetId(resourceGroup, subscriptionId string) string { 393 return path.Join( 394 "/subscriptions", subscriptionId, 395 "resourceGroups", resourceGroup, 396 "providers/Microsoft.Network/virtualNetworks", 397 internalNetworkName, "subnets", internalSubnetName, 398 ) 399 }