github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/provider/azure/instance.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 stdcontext "context" 8 "fmt" 9 "strings" 10 11 "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" 12 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork" 13 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" 14 "github.com/juju/collections/set" 15 "github.com/juju/errors" 16 "github.com/juju/names/v5" 17 18 "github.com/juju/juju/core/instance" 19 corenetwork "github.com/juju/juju/core/network" 20 "github.com/juju/juju/core/network/firewall" 21 "github.com/juju/juju/core/status" 22 "github.com/juju/juju/environs/context" 23 "github.com/juju/juju/provider/azure/internal/errorutils" 24 ) 25 26 type azureInstance struct { 27 vmName string 28 provisioningState armresources.ProvisioningState 29 provisioningError string 30 env *azureEnviron 31 networkInterfaces []*armnetwork.Interface 32 publicIPAddresses []*armnetwork.PublicIPAddress 33 } 34 35 // Id is specified in the Instance interface. 36 func (inst *azureInstance) Id() instance.Id { 37 // Note: we use Name and not Id, since all VM operations are in 38 // terms of the VM name (qualified by resource group). The ID is 39 // an internal detail. 40 return instance.Id(inst.vmName) 41 } 42 43 // Status is specified in the Instance interface. 44 func (inst *azureInstance) Status(ctx context.ProviderCallContext) instance.Status { 45 var instanceStatus status.Status 46 message := string(inst.provisioningState) 47 switch inst.provisioningState { 48 case armresources.ProvisioningStateSucceeded: 49 // TODO(axw) once a VM has been started, we should 50 // start using its power state to show if it's 51 // really running or not. This is just a nice to 52 // have, since we should not expect a VM to ever 53 // be stopped. 54 instanceStatus = status.Running 55 message = "" 56 case armresources.ProvisioningStateDeleting, armresources.ProvisioningStateFailed: 57 instanceStatus = status.ProvisioningError 58 message = inst.provisioningError 59 case armresources.ProvisioningStateCreating: 60 message = "" 61 fallthrough 62 default: 63 instanceStatus = status.Provisioning 64 } 65 return instance.Status{ 66 Status: instanceStatus, 67 Message: message, 68 } 69 } 70 71 // setInstanceAddresses queries Azure for the NICs and public IPs associated 72 // with the given set of instances. This assumes that the instances' 73 // VirtualMachines are up-to-date, and that there are no concurrent accesses 74 // to the instances. 75 func (env *azureEnviron) setInstanceAddresses( 76 ctx context.ProviderCallContext, 77 resourceGroup string, 78 instances []*azureInstance, 79 ) (err error) { 80 instanceNics, err := env.instanceNetworkInterfaces(ctx, resourceGroup) 81 if err != nil { 82 return errors.Annotate(err, "listing network interfaces") 83 } 84 instancePips, err := env.instancePublicIPAddresses(ctx, resourceGroup) 85 if err != nil { 86 return errors.Annotate(err, "listing public IP addresses") 87 } 88 for _, inst := range instances { 89 inst.networkInterfaces = instanceNics[inst.Id()] 90 inst.publicIPAddresses = instancePips[inst.Id()] 91 } 92 return nil 93 } 94 95 // instanceNetworkInterfaces lists all network interfaces in the resource 96 // group, and returns a mapping from instance ID to the network interfaces 97 // associated with that instance. 98 func (env *azureEnviron) instanceNetworkInterfaces( 99 ctx context.ProviderCallContext, 100 resourceGroup string, 101 ) (map[instance.Id][]*armnetwork.Interface, error) { 102 nicClient, err := env.interfacesClient() 103 if err != nil { 104 return nil, errors.Trace(err) 105 } 106 pager := nicClient.NewListPager(resourceGroup, nil) 107 instanceNics := make(map[instance.Id][]*armnetwork.Interface) 108 for pager.More() { 109 next, err := pager.NextPage(ctx) 110 if err != nil { 111 return nil, errorutils.HandleCredentialError(errors.Annotate(err, "listing network interfaces"), ctx) 112 } 113 for _, nic := range next.Value { 114 instanceId := instance.Id(toValue(nic.Tags[jujuMachineNameTag])) 115 instanceNics[instanceId] = append(instanceNics[instanceId], nic) 116 } 117 } 118 return instanceNics, nil 119 } 120 121 // interfacePublicIPAddresses lists all public IP addresses in the resource 122 // group, and returns a mapping from instance ID to the public IP addresses 123 // associated with that instance. 124 func (env *azureEnviron) instancePublicIPAddresses( 125 ctx context.ProviderCallContext, 126 resourceGroup string, 127 ) (map[instance.Id][]*armnetwork.PublicIPAddress, error) { 128 pipClient, err := env.publicAddressesClient() 129 if err != nil { 130 return nil, errors.Trace(err) 131 } 132 pager := pipClient.NewListPager(resourceGroup, nil) 133 instancePips := make(map[instance.Id][]*armnetwork.PublicIPAddress) 134 for pager.More() { 135 next, err := pager.NextPage(ctx) 136 if err != nil { 137 return nil, errorutils.HandleCredentialError(errors.Annotate(err, "listing public IP addresses"), ctx) 138 } 139 for _, pip := range next.Value { 140 instanceId := instance.Id(toValue(pip.Tags[jujuMachineNameTag])) 141 instancePips[instanceId] = append(instancePips[instanceId], pip) 142 } 143 } 144 return instancePips, nil 145 } 146 147 // Addresses is specified in the Instance interface. 148 func (inst *azureInstance) Addresses(ctx context.ProviderCallContext) (corenetwork.ProviderAddresses, error) { 149 addresses := make([]corenetwork.ProviderAddress, 0, len(inst.networkInterfaces)+len(inst.publicIPAddresses)) 150 for _, nic := range inst.networkInterfaces { 151 if nic.Properties == nil { 152 continue 153 } 154 for _, ipConfiguration := range nic.Properties.IPConfigurations { 155 if ipConfiguration.Properties == nil || ipConfiguration.Properties.PrivateIPAddress == nil { 156 continue 157 } 158 privateIpAddress := ipConfiguration.Properties.PrivateIPAddress 159 addresses = append(addresses, corenetwork.NewMachineAddress( 160 toValue(privateIpAddress), 161 corenetwork.WithScope(corenetwork.ScopeCloudLocal), 162 ).AsProviderAddress()) 163 } 164 } 165 for _, pip := range inst.publicIPAddresses { 166 if pip.Properties == nil || pip.Properties.IPAddress == nil { 167 continue 168 } 169 addresses = append(addresses, corenetwork.NewMachineAddress( 170 toValue(pip.Properties.IPAddress), 171 corenetwork.WithScope(corenetwork.ScopePublic), 172 ).AsProviderAddress()) 173 } 174 return addresses, nil 175 } 176 177 type securityGroupInfo struct { 178 resourceGroup string 179 securityGroup *armnetwork.SecurityGroup 180 primaryAddress corenetwork.SpaceAddress 181 } 182 183 // primarySecurityGroupInfo returns info for the NIC's primary corenetwork.Address 184 // for the internal virtual network, and any security group on the subnet. 185 // The address is used to identify the machine in network security rules. 186 func primarySecurityGroupInfo(ctx stdcontext.Context, env *azureEnviron, nic *armnetwork.Interface) (*securityGroupInfo, error) { 187 if nic == nil || nic.Properties == nil { 188 return nil, errors.NotFoundf("internal network address or security group") 189 } 190 subnets, err := env.subnetsClient() 191 if err != nil { 192 return nil, errors.Trace(err) 193 } 194 for _, ipConfiguration := range nic.Properties.IPConfigurations { 195 if ipConfiguration.Properties == nil { 196 continue 197 } 198 if !toValue(ipConfiguration.Properties.Primary) { 199 continue 200 } 201 privateIpAddress := ipConfiguration.Properties.PrivateIPAddress 202 if privateIpAddress == nil { 203 continue 204 } 205 securityGroup := nic.Properties.NetworkSecurityGroup 206 if securityGroup == nil && ipConfiguration.Properties.Subnet != nil { 207 idParts := strings.Split(toValue(ipConfiguration.Properties.Subnet.ID), "/") 208 lenParts := len(idParts) 209 subnet, err := subnets.Get(ctx, idParts[lenParts-7], idParts[lenParts-3], idParts[lenParts-1], &armnetwork.SubnetsClientGetOptions{ 210 Expand: to.Ptr("networkSecurityGroup"), 211 }) 212 if err != nil { 213 return nil, errors.Trace(err) 214 } 215 if subnet.Properties != nil { 216 securityGroup = subnet.Properties.NetworkSecurityGroup 217 } 218 } 219 if securityGroup == nil { 220 continue 221 } 222 223 idParts := strings.Split(toValue(securityGroup.ID), "/") 224 resourceGroup := idParts[len(idParts)-5] 225 return &securityGroupInfo{ 226 resourceGroup: resourceGroup, 227 securityGroup: securityGroup, 228 primaryAddress: corenetwork.NewSpaceAddress( 229 toValue(privateIpAddress), 230 corenetwork.WithScope(corenetwork.ScopeCloudLocal), 231 ), 232 }, nil 233 } 234 return nil, errors.NotFoundf("internal network address or security group") 235 } 236 237 // getSecurityGroupInfo gets the security group information for 238 // each NIC on the instance. 239 func (inst *azureInstance) getSecurityGroupInfo(ctx stdcontext.Context) ([]securityGroupInfo, error) { 240 return getSecurityGroupInfoForInterfaces(ctx, inst.env, inst.networkInterfaces) 241 } 242 243 func getSecurityGroupInfoForInterfaces(ctx stdcontext.Context, env *azureEnviron, networkInterfaces []*armnetwork.Interface) ([]securityGroupInfo, error) { 244 groupsByName := make(map[string]securityGroupInfo) 245 for _, nic := range networkInterfaces { 246 info, err := primarySecurityGroupInfo(ctx, env, nic) 247 if errors.IsNotFound(err) { 248 continue 249 } 250 if err != nil { 251 return nil, errors.Trace(err) 252 } 253 name := toValue(info.securityGroup.Name) 254 if _, ok := groupsByName[name]; ok { 255 continue 256 } 257 groupsByName[name] = *info 258 } 259 var result []securityGroupInfo 260 for _, sg := range groupsByName { 261 result = append(result, sg) 262 } 263 return result, nil 264 } 265 266 // OpenPorts is specified in the Instance interface. 267 func (inst *azureInstance) OpenPorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error { 268 securityGroupInfos, err := inst.getSecurityGroupInfo(ctx) 269 if err != nil { 270 return errors.Trace(err) 271 } 272 for _, info := range securityGroupInfos { 273 if err := inst.openPortsOnGroup(ctx, machineId, info, rules); err != nil { 274 return errors.Annotatef(err, 275 "opening ports on security group %q on machine %q", toValue(info.securityGroup.Name), machineId) 276 } 277 } 278 return nil 279 } 280 281 func (inst *azureInstance) openPortsOnGroup( 282 ctx context.ProviderCallContext, 283 machineId string, nsgInfo securityGroupInfo, rules firewall.IngressRules, 284 ) error { 285 nsg := nsgInfo.securityGroup 286 if nsg.Properties == nil { 287 nsg.Properties = &armnetwork.SecurityGroupPropertiesFormat{} 288 } 289 290 // Create rules one at a time; this is necessary to avoid trampling 291 // on changes made by the provisioner. We still record rules in the 292 // NSG in memory, so we can easily tell which priorities are available. 293 vmName := resourceName(names.NewMachineTag(machineId)) 294 prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) 295 296 securityRules, err := inst.env.securityRulesClient() 297 if err != nil { 298 return errors.Trace(err) 299 } 300 singleSourceIngressRules := explodeIngressRules(rules) 301 for _, rule := range singleSourceIngressRules { 302 ruleName := securityRuleName(prefix, rule) 303 304 // Check if the rule already exists; OpenPorts must be idempotent. 305 var found bool 306 for _, rule := range nsg.Properties.SecurityRules { 307 if toValue(rule.Name) == ruleName { 308 found = true 309 break 310 } 311 } 312 if found { 313 logger.Debugf("security rule %q already exists", ruleName) 314 continue 315 } 316 logger.Debugf("creating security rule %q", ruleName) 317 318 priority, err := nextSecurityRulePriority(nsg, securityRuleInternalMax+1, securityRuleMax) 319 if err != nil { 320 return errors.Annotatef(err, "getting security rule priority for %q", rule) 321 } 322 323 var protocol armnetwork.SecurityRuleProtocol 324 switch rule.PortRange.Protocol { 325 case "tcp": 326 protocol = armnetwork.SecurityRuleProtocolTCP 327 case "udp": 328 protocol = armnetwork.SecurityRuleProtocolUDP 329 default: 330 return errors.Errorf("invalid protocol %q", rule.PortRange.Protocol) 331 } 332 333 var portRange string 334 if rule.PortRange.FromPort != rule.PortRange.ToPort { 335 portRange = fmt.Sprintf("%d-%d", rule.PortRange.FromPort, rule.PortRange.ToPort) 336 } else { 337 portRange = fmt.Sprint(rule.PortRange.FromPort) 338 } 339 340 // rule has a single source CIDR 341 from := rule.SourceCIDRs.SortedValues()[0] 342 securityRule := armnetwork.SecurityRule{ 343 Properties: &armnetwork.SecurityRulePropertiesFormat{ 344 Description: to.Ptr(rule.String()), 345 Protocol: to.Ptr(protocol), 346 SourcePortRange: to.Ptr("*"), 347 DestinationPortRange: to.Ptr(portRange), 348 SourceAddressPrefix: to.Ptr(from), 349 DestinationAddressPrefix: to.Ptr(nsgInfo.primaryAddress.Value), 350 Access: to.Ptr(armnetwork.SecurityRuleAccessAllow), 351 Priority: to.Ptr(priority), 352 Direction: to.Ptr(armnetwork.SecurityRuleDirectionInbound), 353 }, 354 } 355 poller, err := securityRules.BeginCreateOrUpdate( 356 ctx, 357 nsgInfo.resourceGroup, toValue(nsg.Name), ruleName, securityRule, 358 nil, 359 ) 360 if err == nil { 361 _, err = poller.PollUntilDone(ctx, nil) 362 } 363 if err != nil { 364 return errorutils.HandleCredentialError(errors.Annotatef(err, "creating security rule for %q", ruleName), ctx) 365 } 366 nsg.Properties.SecurityRules = append(nsg.Properties.SecurityRules, to.Ptr(securityRule)) 367 } 368 return nil 369 } 370 371 // ClosePorts is specified in the Instance interface. 372 func (inst *azureInstance) ClosePorts(ctx context.ProviderCallContext, machineId string, rules firewall.IngressRules) error { 373 securityGroupInfos, err := inst.getSecurityGroupInfo(ctx) 374 if err != nil { 375 return errors.Trace(err) 376 } 377 for _, info := range securityGroupInfos { 378 if err := inst.closePortsOnGroup(ctx, machineId, info, rules); err != nil { 379 return errors.Annotatef(err, 380 "closing ports on security group %q on machine %q", toValue(info.securityGroup.Name), machineId) 381 } 382 } 383 return nil 384 } 385 386 func (inst *azureInstance) closePortsOnGroup( 387 ctx context.ProviderCallContext, 388 machineId string, nsgInfo securityGroupInfo, rules firewall.IngressRules, 389 ) error { 390 // Delete rules one at a time; this is necessary to avoid trampling 391 // on changes made by the provisioner. 392 vmName := resourceName(names.NewMachineTag(machineId)) 393 prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) 394 395 securityRules, err := inst.env.securityRulesClient() 396 if err != nil { 397 return errors.Trace(err) 398 } 399 singleSourceIngressRules := explodeIngressRules(rules) 400 for _, rule := range singleSourceIngressRules { 401 ruleName := securityRuleName(prefix, rule) 402 logger.Debugf("deleting security rule %q", ruleName) 403 poller, err := securityRules.BeginDelete( 404 ctx, 405 nsgInfo.resourceGroup, toValue(nsgInfo.securityGroup.Name), ruleName, 406 nil, 407 ) 408 if err == nil { 409 _, err = poller.PollUntilDone(ctx, nil) 410 } 411 if err != nil && !errorutils.IsNotFoundError(err) { 412 return errorutils.HandleCredentialError(errors.Annotatef(err, "deleting security rule %q", ruleName), ctx) 413 } 414 } 415 return nil 416 } 417 418 // IngressRules is specified in the Instance interface. 419 func (inst *azureInstance) IngressRules(ctx context.ProviderCallContext, machineId string) (firewall.IngressRules, error) { 420 // The rules to use will be those on the primary network interface. 421 var info *securityGroupInfo 422 for _, nic := range inst.networkInterfaces { 423 if nic.Properties == nil || !toValue(nic.Properties.Primary) { 424 continue 425 } 426 var err error 427 info, err = primarySecurityGroupInfo(ctx, inst.env, nic) 428 if errors.IsNotFound(err) { 429 continue 430 } 431 if err != nil { 432 return nil, errors.Trace(err) 433 } 434 break 435 } 436 if info == nil { 437 return nil, nil 438 } 439 rules, err := inst.ingressRulesForGroup(ctx, machineId, info) 440 if err != nil { 441 return rules, errors.Trace(err) 442 } 443 rules.Sort() 444 return rules, nil 445 } 446 447 func (inst *azureInstance) ingressRulesForGroup(ctx context.ProviderCallContext, machineId string, nsgInfo *securityGroupInfo) (rules firewall.IngressRules, err error) { 448 securityGroups, err := inst.env.securityGroupsClient() 449 if err != nil { 450 return nil, errors.Trace(err) 451 } 452 nsg, err := securityGroups.Get(ctx, nsgInfo.resourceGroup, toValue(nsgInfo.securityGroup.Name), nil) 453 if err != nil { 454 return nil, errorutils.HandleCredentialError(errors.Annotate(err, "querying network security group"), ctx) 455 } 456 if nsg.Properties == nil || len(nsg.Properties.SecurityRules) == 0 { 457 return nil, nil 458 } 459 460 vmName := resourceName(names.NewMachineTag(machineId)) 461 prefix := instanceNetworkSecurityRulePrefix(instance.Id(vmName)) 462 463 // Keep track of all the SourceAddressPrefixes for each port range. 464 portSourceCIDRs := make(map[corenetwork.PortRange]*[]string) 465 for _, rule := range nsg.Properties.SecurityRules { 466 if rule.Properties == nil { 467 continue 468 } 469 if toValue(rule.Properties.Direction) != armnetwork.SecurityRuleDirectionInbound { 470 continue 471 } 472 if toValue(rule.Properties.Access) != armnetwork.SecurityRuleAccessAllow { 473 continue 474 } 475 if toValue(rule.Properties.Priority) <= securityRuleInternalMax { 476 continue 477 } 478 if !strings.HasPrefix(toValue(rule.Name), prefix) { 479 continue 480 } 481 482 var portRange corenetwork.PortRange 483 if toValue(rule.Properties.DestinationPortRange) == "*" { 484 portRange.FromPort = 1 485 portRange.ToPort = 65535 486 } else { 487 portRange, err = corenetwork.ParsePortRange( 488 toValue(rule.Properties.DestinationPortRange), 489 ) 490 if err != nil { 491 return nil, errors.Annotatef( 492 err, "parsing port range for security rule %q", 493 toValue(rule.Name), 494 ) 495 } 496 } 497 498 var protocols []string 499 switch toValue(rule.Properties.Protocol) { 500 case armnetwork.SecurityRuleProtocolTCP: 501 protocols = []string{"tcp"} 502 case armnetwork.SecurityRuleProtocolUDP: 503 protocols = []string{"udp"} 504 default: 505 protocols = []string{"tcp", "udp"} 506 } 507 508 // Record the SourceAddressPrefix for the port range. 509 remotePrefix := toValue(rule.Properties.SourceAddressPrefix) 510 if remotePrefix == "" || remotePrefix == "*" { 511 remotePrefix = "0.0.0.0/0" 512 } 513 for _, protocol := range protocols { 514 portRange.Protocol = protocol 515 sourceCIDRs, ok := portSourceCIDRs[portRange] 516 if !ok { 517 sourceCIDRs = &[]string{} 518 portSourceCIDRs[portRange] = sourceCIDRs 519 } 520 *sourceCIDRs = append(*sourceCIDRs, remotePrefix) 521 } 522 } 523 // Combine all the port ranges and remote prefixes. 524 for portRange, sourceCIDRs := range portSourceCIDRs { 525 rules = append(rules, firewall.NewIngressRule(portRange, *sourceCIDRs...)) 526 } 527 if err := rules.Validate(); err != nil { 528 return nil, errors.Trace(err) 529 } 530 return rules, nil 531 } 532 533 // deleteInstanceNetworkSecurityRules deletes network security rules in the 534 // internal network security group that correspond to the specified machine. 535 // 536 // This is expected to delete *all* security rules related to the instance, 537 // i.e. both the ones opened by OpenPorts above, and the ones opened for API 538 // access. 539 func deleteInstanceNetworkSecurityRules( 540 ctx context.ProviderCallContext, 541 env *azureEnviron, id instance.Id, 542 networkInterfaces []*armnetwork.Interface, 543 ) error { 544 securityGroupInfos, err := getSecurityGroupInfoForInterfaces(ctx, env, networkInterfaces) 545 if err != nil { 546 return errors.Trace(err) 547 } 548 securityRules, err := env.securityRulesClient() 549 if err != nil { 550 return errors.Trace(err) 551 } 552 553 for _, info := range securityGroupInfos { 554 if err := deleteSecurityRules( 555 ctx, id, info, 556 securityRules, 557 ); err != nil { 558 return errors.Trace(err) 559 } 560 } 561 return nil 562 } 563 564 func deleteSecurityRules( 565 ctx context.ProviderCallContext, 566 id instance.Id, 567 nsgInfo securityGroupInfo, 568 securityRuleClient *armnetwork.SecurityRulesClient, 569 ) error { 570 nsg := nsgInfo.securityGroup 571 if nsg.Properties == nil { 572 return nil 573 } 574 prefix := instanceNetworkSecurityRulePrefix(id) 575 for _, rule := range nsg.Properties.SecurityRules { 576 ruleName := toValue(rule.Name) 577 if !strings.HasPrefix(ruleName, prefix) { 578 continue 579 } 580 poller, err := securityRuleClient.BeginDelete( 581 ctx, 582 nsgInfo.resourceGroup, 583 *nsg.Name, 584 ruleName, 585 nil, 586 ) 587 if err != nil { 588 return errors.Annotatef(err, "deleting security rule %q", ruleName) 589 } 590 _, err = poller.PollUntilDone(ctx, nil) 591 if err != nil && !errorutils.IsNotFoundError(err) { 592 return errorutils.HandleCredentialError(errors.Annotatef(err, "deleting security rule %q", ruleName), ctx) 593 } 594 } 595 return nil 596 } 597 598 // instanceNetworkSecurityRulePrefix returns the unique prefix for network 599 // security rule names that relate to the instance with the given ID. 600 func instanceNetworkSecurityRulePrefix(id instance.Id) string { 601 return string(id) + "-" 602 } 603 604 // securityRuleName returns the security rule name for the given ingress rule, 605 // and prefix returned by instanceNetworkSecurityRulePrefix. 606 func securityRuleName(prefix string, rule firewall.IngressRule) string { 607 ruleName := fmt.Sprintf("%s%s-%d", prefix, rule.PortRange.Protocol, rule.PortRange.FromPort) 608 if rule.PortRange.FromPort != rule.PortRange.ToPort { 609 ruleName += fmt.Sprintf("-%d", rule.PortRange.ToPort) 610 } 611 // The rule parameter must have a single source cidr. 612 // Ensure the rule name can be a valid URL path component. 613 var cidr string 614 if rule.SourceCIDRs.IsEmpty() { 615 cidr = firewall.AllNetworksIPV4CIDR 616 } else { 617 cidr = rule.SourceCIDRs.SortedValues()[0] 618 } 619 if cidr != firewall.AllNetworksIPV4CIDR && cidr != "*" { 620 cidr = strings.Replace(cidr, ".", "-", -1) 621 cidr = strings.Replace(cidr, "::", "-", -1) 622 cidr = strings.Replace(cidr, "/", "-", -1) 623 ruleName = fmt.Sprintf("%s-cidr-%s", ruleName, cidr) 624 } 625 return ruleName 626 } 627 628 // explodeIngressRules creates a slice of ingress rules, each rule in the 629 // result having a single source CIDR. The results contain a copy of each 630 // specified rule with each copy having one of the source CIDR values, 631 func explodeIngressRules(inRules firewall.IngressRules) firewall.IngressRules { 632 // If any rule has an empty source CIDR slice, a default 633 // source value of "*" is used. 634 var singleSourceIngressRules firewall.IngressRules 635 for _, rule := range inRules { 636 sourceCIDRs := rule.SourceCIDRs 637 if len(sourceCIDRs) == 0 { 638 sourceCIDRs = set.NewStrings("*") 639 } 640 for _, sr := range sourceCIDRs.SortedValues() { 641 singleSourceIngressRules = append(singleSourceIngressRules, firewall.NewIngressRule(rule.PortRange, sr)) 642 } 643 } 644 return singleSourceIngressRules 645 }