github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "net/http" 10 "path" 11 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 are connected to, so that they can communicate 24 // with the controllers, and with each other. 25 // 26 // Each resource group is given its own subnet and network security 27 // group to manage. The first resource group will be assigned the 28 // subnet address prefix 10.0.0.0/16, the second 10.1.0.0/16, etc., 29 // which allows for up to 256 enviroments/resource groups. Azure 30 // only supports 100, but this can be extended by contacting support; 31 // we can make the address prefixes configurable if necessary. 32 internalNetworkName = "juju-internal" 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" 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.SecurityRuleProtocolTCP, 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 client network.ManagementClient, 78 controllerResourceGroup string, 79 location string, 80 tags map[string]string, 81 ) (*network.VirtualNetwork, error) { 82 addressPrefixes := make([]string, 256) 83 for i := range addressPrefixes { 84 addressPrefixes[i] = fmt.Sprintf("10.%d.0.0/16", i) 85 } 86 virtualNetworkParams := network.VirtualNetwork{ 87 Location: to.StringPtr(location), 88 Tags: toTagsPtr(tags), 89 Properties: &network.VirtualNetworkPropertiesFormat{ 90 AddressSpace: &network.AddressSpace{&addressPrefixes}, 91 }, 92 } 93 logger.Debugf("creating virtual network %q", internalNetworkName) 94 vnetClient := network.VirtualNetworksClient{client} 95 vnet, err := vnetClient.CreateOrUpdate( 96 controllerResourceGroup, internalNetworkName, virtualNetworkParams, 97 ) 98 if err != nil { 99 return nil, errors.Annotatef(err, "creating virtual network %q", internalNetworkName) 100 } 101 return &vnet, nil 102 } 103 104 // createInternalSubnet creates an internal subnet for the specified resource group, 105 // within the specified virtual network. 106 // 107 // Subnets are tied to the resource group of the virtual network, so we must create 108 // them all in the controller resource group. We create the network security group 109 // for the subnet in the environment's resource group. 110 // 111 // NOTE(axw) this method expects an up-to-date VirtualNetwork, and expects that are 112 // no concurrent subnet additions to the virtual network. At the moment we have only 113 // three places where we modify subnets: at bootstrap, when a new environment is 114 // created, and when an environment is destroyed. 115 func createInternalSubnet( 116 client network.ManagementClient, 117 resourceGroup, controllerResourceGroup string, 118 vnet *network.VirtualNetwork, 119 location string, 120 tags map[string]string, 121 ) (*network.Subnet, error) { 122 123 nextAddressPrefix := (*vnet.Properties.AddressSpace.AddressPrefixes)[0] 124 if vnet.Properties.Subnets != nil { 125 if len(*vnet.Properties.Subnets) == len(*vnet.Properties.AddressSpace.AddressPrefixes) { 126 return nil, errors.Errorf( 127 "no available address prefixes in vnet %q", 128 to.String(vnet.Name), 129 ) 130 } 131 addressPrefixesInUse := make(set.Strings) 132 for _, subnet := range *vnet.Properties.Subnets { 133 addressPrefixesInUse.Add(to.String(subnet.Properties.AddressPrefix)) 134 } 135 for _, addressPrefix := range *vnet.Properties.AddressSpace.AddressPrefixes { 136 if !addressPrefixesInUse.Contains(addressPrefix) { 137 nextAddressPrefix = addressPrefix 138 break 139 } 140 } 141 } 142 143 // Create a network security group for the environment. There is only 144 // one NSG per environment (there's a limit of 100 per subscription), 145 // in which we manage rules for each exposed machine. 146 securityRules := []network.SecurityRule{sshSecurityRule} 147 securityGroupParams := network.SecurityGroup{ 148 Location: to.StringPtr(location), 149 Tags: toTagsPtr(tags), 150 Properties: &network.SecurityGroupPropertiesFormat{ 151 SecurityRules: &securityRules, 152 }, 153 } 154 securityGroupClient := network.SecurityGroupsClient{client} 155 securityGroupName := internalSecurityGroupName 156 logger.Debugf("creating security group %q", securityGroupName) 157 _, err := securityGroupClient.CreateOrUpdate( 158 resourceGroup, securityGroupName, securityGroupParams, 159 ) 160 if err != nil { 161 return nil, errors.Annotatef(err, "creating security group %q", securityGroupName) 162 } 163 164 // Now create a subnet with the next available address prefix. The 165 // subnet must be created in the controller resource group, as it 166 // must be co-located with the vnet. 167 subnetName := resourceGroup 168 subnetParams := network.Subnet{ 169 Properties: &network.SubnetPropertiesFormat{ 170 AddressPrefix: to.StringPtr(nextAddressPrefix), 171 // NOTE(axw) we do NOT want to set the network security 172 // group as default for the subnet, because that will 173 // create a dependency from the controller resource 174 // group to environment resource groups. Instead, we 175 // set the NSG on NICs. 176 }, 177 } 178 logger.Debugf("creating subnet %q (%s)", subnetName, nextAddressPrefix) 179 subnetClient := network.SubnetsClient{client} 180 subnet, err := subnetClient.CreateOrUpdate( 181 controllerResourceGroup, internalNetworkName, subnetName, subnetParams, 182 ) 183 if err != nil { 184 return nil, errors.Annotatef(err, "creating subnet %q", subnetName) 185 } 186 return &subnet, nil 187 } 188 189 func newNetworkProfile( 190 client network.ManagementClient, 191 vmName string, 192 apiPort *int, 193 internalSubnet *network.Subnet, 194 nsgID string, 195 resourceGroup string, 196 location string, 197 tags map[string]string, 198 ) (*compute.NetworkProfile, error) { 199 logger.Debugf("creating network profile for %q", vmName) 200 201 // Create a public IP for the NIC. Public IP addresses are dynamic. 202 logger.Debugf("- allocating public IP address") 203 pipClient := network.PublicIPAddressesClient{client} 204 publicIPAddressParams := network.PublicIPAddress{ 205 Location: to.StringPtr(location), 206 Tags: toTagsPtr(tags), 207 Properties: &network.PublicIPAddressPropertiesFormat{ 208 PublicIPAllocationMethod: network.Dynamic, 209 }, 210 } 211 publicIPAddressName := vmName + "-public-ip" 212 publicIPAddress, err := pipClient.CreateOrUpdate(resourceGroup, publicIPAddressName, publicIPAddressParams) 213 if err != nil { 214 return nil, errors.Annotatef(err, "creating public IP address for %q", vmName) 215 } 216 217 // Determine the next available private IP address. 218 nicClient := network.InterfacesClient{client} 219 privateIPAddress, err := nextSubnetIPAddress(nicClient, resourceGroup, internalSubnet) 220 if err != nil { 221 return nil, errors.Annotatef(err, "querying private IP addresses") 222 } 223 224 // Create a primary NIC for the machine. This needs to be static, so 225 // that we can create security rules that don't become invalid. 226 logger.Debugf("- creating primary NIC") 227 ipConfigurations := []network.InterfaceIPConfiguration{{ 228 Name: to.StringPtr("primary"), 229 Properties: &network.InterfaceIPConfigurationPropertiesFormat{ 230 PrivateIPAddress: to.StringPtr(privateIPAddress), 231 PrivateIPAllocationMethod: network.Static, 232 Subnet: &network.SubResource{internalSubnet.ID}, 233 PublicIPAddress: &network.SubResource{publicIPAddress.ID}, 234 }, 235 }} 236 primaryNicName := vmName + "-primary" 237 primaryNicParams := network.Interface{ 238 Location: to.StringPtr(location), 239 Tags: toTagsPtr(tags), 240 Properties: &network.InterfacePropertiesFormat{ 241 IPConfigurations: &ipConfigurations, 242 // We set the network security group on the NIC, rather 243 // than the subnet, to avoid having the controller 244 // resource group dependent on the environment resource 245 // group. 246 NetworkSecurityGroup: &network.SubResource{to.StringPtr(nsgID)}, 247 }, 248 } 249 primaryNic, err := nicClient.CreateOrUpdate(resourceGroup, primaryNicName, primaryNicParams) 250 if err != nil { 251 return nil, errors.Annotatef(err, "creating network interface for %q", vmName) 252 } 253 254 // Create a network security rule for the machine if we need to open 255 // the API server port. 256 if apiPort != nil { 257 logger.Debugf("- querying network security group") 258 securityGroupClient := network.SecurityGroupsClient{client} 259 securityGroupName := internalSecurityGroupName 260 securityGroup, err := securityGroupClient.Get(resourceGroup, securityGroupName) 261 if err != nil { 262 return nil, errors.Annotate(err, "querying network security group") 263 } 264 265 // NOTE(axw) this looks like TOCTTOU race territory, but it's 266 // safe because we only allocate/deallocate rules in this 267 // range during machine (de)provisioning, which is managed by 268 // a single goroutine. Non-internal ports are managed by the 269 // firewaller exclusively. 270 nextPriority, err := nextSecurityRulePriority( 271 securityGroup, 272 securityRuleInternalSSHInbound+1, 273 securityRuleInternalMax, 274 ) 275 if err != nil { 276 return nil, errors.Trace(err) 277 } 278 279 apiSecurityRuleName := fmt.Sprintf("%s-api", vmName) 280 apiSecurityRule := network.SecurityRule{ 281 Name: to.StringPtr(apiSecurityRuleName), 282 Properties: &network.SecurityRulePropertiesFormat{ 283 Description: to.StringPtr("Allow API access to server machines"), 284 Protocol: network.SecurityRuleProtocolTCP, 285 SourceAddressPrefix: to.StringPtr("*"), 286 SourcePortRange: to.StringPtr("*"), 287 DestinationAddressPrefix: to.StringPtr(privateIPAddress), 288 DestinationPortRange: to.StringPtr(fmt.Sprint(*apiPort)), 289 Access: network.Allow, 290 Priority: to.IntPtr(nextPriority), 291 Direction: network.Inbound, 292 }, 293 } 294 logger.Debugf("- creating API network security rule") 295 securityRuleClient := network.SecurityRulesClient{client} 296 _, err = securityRuleClient.CreateOrUpdate( 297 resourceGroup, securityGroupName, apiSecurityRuleName, apiSecurityRule, 298 ) 299 if err != nil { 300 return nil, errors.Annotate(err, "creating API network security rule") 301 } 302 } 303 304 // For now we only attach a single, flat network to each machine. 305 networkInterfaces := []compute.NetworkInterfaceReference{{ 306 ID: primaryNic.ID, 307 Properties: &compute.NetworkInterfaceReferenceProperties{ 308 Primary: to.BoolPtr(true), 309 }, 310 }} 311 return &compute.NetworkProfile{&networkInterfaces}, nil 312 } 313 314 func (env *azureEnviron) deleteInternalSubnet() error { 315 client := network.SubnetsClient{env.network} 316 subnetName := env.resourceGroup 317 result, err := client.Delete( 318 env.controllerResourceGroup, internalNetworkName, subnetName, 319 ) 320 if err != nil { 321 if result.Response == nil || result.StatusCode != http.StatusNotFound { 322 return errors.Annotatef(err, "deleting subnet %q", subnetName) 323 } 324 } 325 return nil 326 } 327 328 // nextSecurityRulePriority returns the next available priority in the given 329 // security group within a specified range. 330 func nextSecurityRulePriority(group network.SecurityGroup, min, max int) (int, error) { 331 if group.Properties.SecurityRules == nil { 332 return min, nil 333 } 334 for p := min; p <= max; p++ { 335 var found bool 336 for _, rule := range *group.Properties.SecurityRules { 337 if to.Int(rule.Properties.Priority) == p { 338 found = true 339 break 340 } 341 } 342 if !found { 343 return p, nil 344 } 345 } 346 return -1, errors.Errorf( 347 "no priorities available in the range [%d, %d]", min, max, 348 ) 349 } 350 351 // nextSubnetIPAddress returns the next available IP address in the given subnet. 352 func nextSubnetIPAddress( 353 nicClient network.InterfacesClient, 354 resourceGroup string, 355 subnet *network.Subnet, 356 ) (string, error) { 357 _, ipnet, err := net.ParseCIDR(to.String(subnet.Properties.AddressPrefix)) 358 if err != nil { 359 return "", errors.Annotate(err, "parsing subnet prefix") 360 } 361 results, err := nicClient.List(resourceGroup) 362 if err != nil { 363 return "", errors.Annotate(err, "listing NICs") 364 } 365 // Azure reserves the first 4 addresses in the subnet. 366 var ipsInUse []net.IP 367 if results.Value != nil { 368 ipsInUse = make([]net.IP, 0, len(*results.Value)) 369 for _, item := range *results.Value { 370 if item.Properties.IPConfigurations == nil { 371 continue 372 } 373 for _, ipConfiguration := range *item.Properties.IPConfigurations { 374 if to.String(ipConfiguration.Properties.Subnet.ID) != to.String(subnet.ID) { 375 continue 376 } 377 ip := net.ParseIP(to.String(ipConfiguration.Properties.PrivateIPAddress)) 378 if ip != nil { 379 ipsInUse = append(ipsInUse, ip) 380 } 381 } 382 } 383 } 384 ip, err := iputils.NextSubnetIP(ipnet, ipsInUse) 385 if err != nil { 386 return "", errors.Trace(err) 387 } 388 return ip.String(), nil 389 } 390 391 // internalSubnetId returns the Azure resource ID of the internal network 392 // subnet for the specified resource group. 393 func internalSubnetId(resourceGroup, controllerResourceGroup, subscriptionId string) string { 394 return path.Join( 395 "/subscriptions", subscriptionId, 396 "resourceGroups", controllerResourceGroup, 397 "providers/Microsoft.Network/virtualNetworks", 398 internalNetworkName, "subnets", resourceGroup, 399 ) 400 }