github.com/openshift/installer@v1.4.17/pkg/infrastructure/azure/network.go (about)

     1  package azure
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"path"
     7  
     8  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
     9  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4"
    10  	"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2"
    11  	"k8s.io/utils/ptr"
    12  )
    13  
    14  type lbInput struct {
    15  	loadBalancerName       string
    16  	infraID                string
    17  	region                 string
    18  	resourceGroup          string
    19  	subscriptionID         string
    20  	frontendIPConfigName   string
    21  	backendAddressPoolName string
    22  	idPrefix               string
    23  	lbClient               *armnetwork.LoadBalancersClient
    24  	tags                   map[string]*string
    25  }
    26  
    27  type pipInput struct {
    28  	infraID       string
    29  	name          string
    30  	region        string
    31  	resourceGroup string
    32  	pipClient     *armnetwork.PublicIPAddressesClient
    33  	tags          map[string]*string
    34  }
    35  
    36  type vmInput struct {
    37  	infraID       string
    38  	resourceGroup string
    39  	ids           []string
    40  	bap           *armnetwork.BackendAddressPool
    41  	vmClient      *armcompute.VirtualMachinesClient
    42  	nicClient     *armnetwork.InterfacesClient
    43  }
    44  
    45  type securityGroupInput struct {
    46  	resourceGroupName    string
    47  	securityGroupName    string
    48  	securityRuleName     string
    49  	securityRulePort     string
    50  	securityRulePriority int32
    51  	networkClientFactory *armnetwork.ClientFactory
    52  }
    53  
    54  type inboundNatRuleInput struct {
    55  	resourceGroupName    string
    56  	loadBalancerName     string
    57  	bootstrapNicName     string
    58  	frontendIPConfigID   string
    59  	inboundNatRuleID     string
    60  	inboundNatRuleName   string
    61  	inboundNatRulePort   int32
    62  	networkClientFactory *armnetwork.ClientFactory
    63  }
    64  
    65  func createPublicIP(ctx context.Context, in *pipInput) (*armnetwork.PublicIPAddress, error) {
    66  	pollerResp, err := in.pipClient.BeginCreateOrUpdate(
    67  		ctx,
    68  		in.resourceGroup,
    69  		in.name,
    70  		armnetwork.PublicIPAddress{
    71  			Name:     to.Ptr(in.name),
    72  			Location: to.Ptr(in.region),
    73  			SKU: &armnetwork.PublicIPAddressSKU{
    74  				Name: to.Ptr(armnetwork.PublicIPAddressSKUNameStandard),
    75  				Tier: to.Ptr(armnetwork.PublicIPAddressSKUTierRegional),
    76  			},
    77  			Properties: &armnetwork.PublicIPAddressPropertiesFormat{
    78  				PublicIPAddressVersion:   to.Ptr(armnetwork.IPVersionIPv4),
    79  				PublicIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodStatic),
    80  				DNSSettings: &armnetwork.PublicIPAddressDNSSettings{
    81  					DomainNameLabel: to.Ptr(in.infraID),
    82  				},
    83  			},
    84  			Tags: in.tags,
    85  		},
    86  		nil,
    87  	)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	resp, err := pollerResp.PollUntilDone(ctx, nil)
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	return &resp.PublicIPAddress, nil
    97  }
    98  
    99  func createAPILoadBalancer(ctx context.Context, pip *armnetwork.PublicIPAddress, in *lbInput) (*armnetwork.LoadBalancer, error) {
   100  	probeName := "api-probe"
   101  
   102  	pollerResp, err := in.lbClient.BeginCreateOrUpdate(ctx,
   103  		in.resourceGroup,
   104  		in.loadBalancerName,
   105  		armnetwork.LoadBalancer{
   106  			Location: to.Ptr(in.region),
   107  			SKU: &armnetwork.LoadBalancerSKU{
   108  				Name: to.Ptr(armnetwork.LoadBalancerSKUNameStandard),
   109  				Tier: to.Ptr(armnetwork.LoadBalancerSKUTierRegional),
   110  			},
   111  			Properties: &armnetwork.LoadBalancerPropertiesFormat{
   112  				FrontendIPConfigurations: []*armnetwork.FrontendIPConfiguration{
   113  					{
   114  						Name: &in.frontendIPConfigName,
   115  						Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{
   116  							PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
   117  							PublicIPAddress:           pip,
   118  						},
   119  					},
   120  				},
   121  				BackendAddressPools: []*armnetwork.BackendAddressPool{
   122  					{
   123  						Name: &in.backendAddressPoolName,
   124  					},
   125  				},
   126  				Probes: []*armnetwork.Probe{
   127  					{
   128  						Name: &probeName,
   129  						Properties: &armnetwork.ProbePropertiesFormat{
   130  							Protocol:          to.Ptr(armnetwork.ProbeProtocolHTTPS),
   131  							Port:              to.Ptr[int32](6443),
   132  							IntervalInSeconds: to.Ptr[int32](5),
   133  							NumberOfProbes:    to.Ptr[int32](2),
   134  							RequestPath:       to.Ptr("/readyz"),
   135  						},
   136  					},
   137  				},
   138  				LoadBalancingRules: []*armnetwork.LoadBalancingRule{
   139  					{
   140  						Name: to.Ptr("api-v4"),
   141  						Properties: &armnetwork.LoadBalancingRulePropertiesFormat{
   142  							Protocol:             to.Ptr(armnetwork.TransportProtocolTCP),
   143  							FrontendPort:         to.Ptr[int32](6443),
   144  							BackendPort:          to.Ptr[int32](6443),
   145  							IdleTimeoutInMinutes: to.Ptr[int32](30),
   146  							EnableFloatingIP:     to.Ptr(false),
   147  							LoadDistribution:     to.Ptr(armnetwork.LoadDistributionDefault),
   148  							FrontendIPConfiguration: &armnetwork.SubResource{
   149  								ID: to.Ptr(fmt.Sprintf("/%s/%s/frontendIPConfigurations/%s", in.idPrefix, in.loadBalancerName, in.frontendIPConfigName)),
   150  							},
   151  							BackendAddressPool: &armnetwork.SubResource{
   152  								ID: to.Ptr(fmt.Sprintf("/%s/%s/backendAddressPools/%s", in.idPrefix, in.loadBalancerName, in.backendAddressPoolName)),
   153  							},
   154  							Probe: &armnetwork.SubResource{
   155  								ID: to.Ptr(fmt.Sprintf("/%s/%s/probes/%s", in.idPrefix, in.loadBalancerName, probeName)),
   156  							},
   157  						},
   158  					},
   159  				},
   160  			},
   161  			Tags: in.tags,
   162  		}, nil)
   163  
   164  	if err != nil {
   165  		return nil, fmt.Errorf("cannot create load balancer: %w", err)
   166  	}
   167  
   168  	resp, err := pollerResp.PollUntilDone(ctx, nil)
   169  	if err != nil {
   170  		return nil, err
   171  	}
   172  	return &resp.LoadBalancer, nil
   173  }
   174  
   175  func updateOutboundLoadBalancerToAPILoadBalancer(ctx context.Context, pip *armnetwork.PublicIPAddress, in *lbInput) (*armnetwork.LoadBalancer, error) {
   176  	probeName := "api-probe"
   177  
   178  	// Get the CAPI-created outbound load balancer so we can modify it.
   179  	extLB, err := in.lbClient.Get(ctx, in.resourceGroup, in.loadBalancerName, nil)
   180  	if err != nil {
   181  		return nil, fmt.Errorf("failed to get external load balancer: %w", err)
   182  	}
   183  
   184  	// Get the existing frontend configuration and backend address pool and
   185  	// create an additional frontend configuration and backend address
   186  	// pool. Use the newly created public IP address with the additional
   187  	// configuration so we can setup load balancing rules for the external
   188  	// API server.
   189  	extLB.Properties.FrontendIPConfigurations = append(extLB.Properties.FrontendIPConfigurations,
   190  		&armnetwork.FrontendIPConfiguration{
   191  			Name: &in.frontendIPConfigName,
   192  			Properties: &armnetwork.FrontendIPConfigurationPropertiesFormat{
   193  				PrivateIPAllocationMethod: to.Ptr(armnetwork.IPAllocationMethodDynamic),
   194  				PublicIPAddress:           pip,
   195  			},
   196  		})
   197  	extLB.Properties.BackendAddressPools = append(extLB.Properties.BackendAddressPools,
   198  		&armnetwork.BackendAddressPool{
   199  			Name: &in.backendAddressPoolName,
   200  		})
   201  
   202  	pollerResp, err := in.lbClient.BeginCreateOrUpdate(ctx,
   203  		in.resourceGroup,
   204  		in.loadBalancerName,
   205  		armnetwork.LoadBalancer{
   206  			Location: to.Ptr(in.region),
   207  			SKU: &armnetwork.LoadBalancerSKU{
   208  				Name: to.Ptr(armnetwork.LoadBalancerSKUNameStandard),
   209  				Tier: to.Ptr(armnetwork.LoadBalancerSKUTierRegional),
   210  			},
   211  			Properties: &armnetwork.LoadBalancerPropertiesFormat{
   212  				FrontendIPConfigurations: extLB.Properties.FrontendIPConfigurations,
   213  				BackendAddressPools:      extLB.Properties.BackendAddressPools,
   214  				Probes: []*armnetwork.Probe{
   215  					{
   216  						Name: &probeName,
   217  						Properties: &armnetwork.ProbePropertiesFormat{
   218  							Protocol:          to.Ptr(armnetwork.ProbeProtocolHTTPS),
   219  							Port:              to.Ptr[int32](6443),
   220  							IntervalInSeconds: to.Ptr[int32](5),
   221  							NumberOfProbes:    to.Ptr[int32](2),
   222  							RequestPath:       to.Ptr("/readyz"),
   223  						},
   224  					},
   225  				},
   226  				LoadBalancingRules: []*armnetwork.LoadBalancingRule{
   227  					{
   228  						Name: to.Ptr("api-v4"),
   229  						Properties: &armnetwork.LoadBalancingRulePropertiesFormat{
   230  							Protocol:             to.Ptr(armnetwork.TransportProtocolTCP),
   231  							FrontendPort:         to.Ptr[int32](6443),
   232  							BackendPort:          to.Ptr[int32](6443),
   233  							IdleTimeoutInMinutes: to.Ptr[int32](30),
   234  							EnableFloatingIP:     to.Ptr(false),
   235  							LoadDistribution:     to.Ptr(armnetwork.LoadDistributionDefault),
   236  							FrontendIPConfiguration: &armnetwork.SubResource{
   237  								ID: to.Ptr(fmt.Sprintf("/%s/%s/frontendIPConfigurations/%s", in.idPrefix, in.loadBalancerName, in.frontendIPConfigName)),
   238  							},
   239  							BackendAddressPool: &armnetwork.SubResource{
   240  								ID: to.Ptr(fmt.Sprintf("/%s/%s/backendAddressPools/%s", in.idPrefix, in.loadBalancerName, in.backendAddressPoolName)),
   241  							},
   242  							Probe: &armnetwork.SubResource{
   243  								ID: to.Ptr(fmt.Sprintf("/%s/%s/probes/%s", in.idPrefix, in.loadBalancerName, probeName)),
   244  							},
   245  						},
   246  					},
   247  				},
   248  				OutboundRules: extLB.Properties.OutboundRules,
   249  			},
   250  			Tags: in.tags,
   251  		}, nil)
   252  
   253  	if err != nil {
   254  		return nil, fmt.Errorf("cannot update load balancer: %w", err)
   255  	}
   256  
   257  	resp, err := pollerResp.PollUntilDone(ctx, nil)
   258  	if err != nil {
   259  		return nil, err
   260  	}
   261  	return &resp.LoadBalancer, nil
   262  }
   263  
   264  func updateInternalLoadBalancer(ctx context.Context, in *lbInput) (*armnetwork.LoadBalancer, error) {
   265  	mcsProbeName := "sint-probe"
   266  
   267  	// Get the CAPI-created internal load balancer so we can modify it.
   268  	lbResp, err := in.lbClient.Get(ctx, in.resourceGroup, in.loadBalancerName, nil)
   269  	if err != nil {
   270  		return nil, fmt.Errorf("could not get internal load balancer: %w", err)
   271  	}
   272  	intLB := lbResp.LoadBalancer
   273  
   274  	mcsProbe := &armnetwork.Probe{
   275  		Name: to.Ptr(mcsProbeName),
   276  		Properties: &armnetwork.ProbePropertiesFormat{
   277  			Protocol:          to.Ptr(armnetwork.ProbeProtocolHTTPS),
   278  			Port:              to.Ptr[int32](22623),
   279  			IntervalInSeconds: to.Ptr[int32](5),
   280  			NumberOfProbes:    to.Ptr[int32](2),
   281  			RequestPath:       to.Ptr("/healthz"),
   282  		},
   283  	}
   284  
   285  	existingFrontEndIPConfig := intLB.Properties.FrontendIPConfigurations
   286  	if len(existingFrontEndIPConfig) == 0 {
   287  		return nil, fmt.Errorf("could not get frontEndIPConfig for internal LB %s", *intLB.Name)
   288  	}
   289  	existingFrontEndIPConfigName := *(existingFrontEndIPConfig[0].Name)
   290  
   291  	mcsRule := &armnetwork.LoadBalancingRule{
   292  		Name: to.Ptr("sint-v4"),
   293  		Properties: &armnetwork.LoadBalancingRulePropertiesFormat{
   294  			Protocol:             to.Ptr(armnetwork.TransportProtocolTCP),
   295  			FrontendPort:         to.Ptr[int32](22623),
   296  			BackendPort:          to.Ptr[int32](22623),
   297  			IdleTimeoutInMinutes: to.Ptr[int32](30),
   298  			EnableFloatingIP:     to.Ptr(false),
   299  			LoadDistribution:     to.Ptr(armnetwork.LoadDistributionDefault),
   300  			FrontendIPConfiguration: &armnetwork.SubResource{
   301  				ID: to.Ptr(fmt.Sprintf("/%s/%s/frontendIPConfigurations/%s", in.idPrefix, in.loadBalancerName, existingFrontEndIPConfigName)),
   302  			},
   303  			BackendAddressPool: &armnetwork.SubResource{
   304  				ID: to.Ptr(fmt.Sprintf("/%s/%s/backendAddressPools/%s", in.idPrefix, in.loadBalancerName, in.backendAddressPoolName)),
   305  			},
   306  			Probe: &armnetwork.SubResource{
   307  				ID: to.Ptr(fmt.Sprintf("/%s/%s/probes/%s", in.idPrefix, in.loadBalancerName, mcsProbeName)),
   308  			},
   309  		},
   310  	}
   311  
   312  	intLB.Properties.Probes = append(intLB.Properties.Probes, mcsProbe)
   313  	intLB.Properties.LoadBalancingRules = append(intLB.Properties.LoadBalancingRules, mcsRule)
   314  	pollerResp, err := in.lbClient.BeginCreateOrUpdate(ctx,
   315  		in.resourceGroup,
   316  		in.loadBalancerName,
   317  		intLB,
   318  		nil)
   319  	if err != nil {
   320  		return nil, fmt.Errorf("cannot update load balancer: %w", err)
   321  	}
   322  
   323  	resp, err := pollerResp.PollUntilDone(ctx, nil)
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	return &resp.LoadBalancer, nil
   328  }
   329  
   330  func associateVMToBackendPool(ctx context.Context, in vmInput) error {
   331  	for _, id := range in.ids {
   332  		vmName := path.Base(id)
   333  		vm, err := in.vmClient.Get(ctx, in.resourceGroup, vmName, nil)
   334  		if err != nil {
   335  			return fmt.Errorf("failed to get vm %s: %w", vmName, err)
   336  		}
   337  
   338  		if nics := vm.Properties.NetworkProfile.NetworkInterfaces; len(nics) == 1 {
   339  			nicRef := nics[0]
   340  
   341  			nicName := path.Base(*nicRef.ID)
   342  			nic, err := in.nicClient.Get(ctx, in.resourceGroup, nicName, nil)
   343  			if err != nil {
   344  				return fmt.Errorf("failed to get nic for vm %s: %w", vmName, err)
   345  			}
   346  			for _, ipconfig := range nic.Properties.IPConfigurations {
   347  				ipconfig.Properties.LoadBalancerBackendAddressPools = append(ipconfig.Properties.LoadBalancerBackendAddressPools, in.bap)
   348  			}
   349  			pollerResp, err := in.nicClient.BeginCreateOrUpdate(ctx, in.resourceGroup, nicName, nic.Interface, nil)
   350  			if err != nil {
   351  				return fmt.Errorf("failed to update nic for %s: %w", vmName, err)
   352  			}
   353  			_, err = pollerResp.PollUntilDone(ctx, nil)
   354  			if err != nil {
   355  				return fmt.Errorf("failed to update nic for vm %s: %w", vmName, err)
   356  			}
   357  		} else {
   358  			return fmt.Errorf("vm %s does not have a single nic: %w", vmName, err)
   359  		}
   360  	}
   361  	return nil
   362  }
   363  
   364  func addSecurityGroupRule(ctx context.Context, in *securityGroupInput) error {
   365  	securityRulesClient := in.networkClientFactory.NewSecurityRulesClient()
   366  
   367  	// Assume inbound tcp connections from any port to destination port for now
   368  	securityRuleResp, err := securityRulesClient.BeginCreateOrUpdate(ctx,
   369  		in.resourceGroupName,
   370  		in.securityGroupName,
   371  		in.securityRuleName,
   372  		armnetwork.SecurityRule{
   373  			Name: ptr.To(in.securityRuleName),
   374  			Properties: &armnetwork.SecurityRulePropertiesFormat{
   375  				Access:                   ptr.To(armnetwork.SecurityRuleAccessAllow),
   376  				Direction:                ptr.To(armnetwork.SecurityRuleDirectionInbound),
   377  				Protocol:                 ptr.To(armnetwork.SecurityRuleProtocolTCP),
   378  				DestinationAddressPrefix: ptr.To("*"),
   379  				DestinationPortRange:     ptr.To(in.securityRulePort),
   380  				Priority:                 ptr.To[int32](in.securityRulePriority),
   381  				SourceAddressPrefix:      ptr.To("*"),
   382  				SourcePortRange:          ptr.To("*"),
   383  			},
   384  		},
   385  		nil,
   386  	)
   387  	if err != nil {
   388  		return fmt.Errorf("failed to add security rule: %w", err)
   389  	}
   390  	_, err = securityRuleResp.PollUntilDone(ctx, nil)
   391  	if err != nil {
   392  		return fmt.Errorf("failed to add security rule: %w", err)
   393  	}
   394  
   395  	return nil
   396  }
   397  
   398  func deleteSecurityGroupRule(ctx context.Context, in *securityGroupInput) error {
   399  	securityRulesClient := in.networkClientFactory.NewSecurityRulesClient()
   400  	securityRulesPoller, err := securityRulesClient.BeginDelete(ctx, in.resourceGroupName, in.securityGroupName, in.securityRuleName, nil)
   401  	if err != nil {
   402  		return fmt.Errorf("failed to delete security rule: %w", err)
   403  	}
   404  	_, err = securityRulesPoller.PollUntilDone(ctx, nil)
   405  	if err != nil {
   406  		return fmt.Errorf("failed to delete security rule: %w", err)
   407  	}
   408  	return nil
   409  }
   410  
   411  func addInboundNatRuleToLoadBalancer(ctx context.Context, in *inboundNatRuleInput) (*armnetwork.InboundNatRule, error) {
   412  	inboundNatRulesClient := in.networkClientFactory.NewInboundNatRulesClient()
   413  	inboundNatRulesPoller, err := inboundNatRulesClient.BeginCreateOrUpdate(ctx,
   414  		in.resourceGroupName,
   415  		in.loadBalancerName,
   416  		in.inboundNatRuleName,
   417  		armnetwork.InboundNatRule{
   418  			Properties: &armnetwork.InboundNatRulePropertiesFormat{
   419  				BackendPort: to.Ptr[int32](in.inboundNatRulePort),
   420  				FrontendIPConfiguration: &armnetwork.SubResource{
   421  					ID: to.Ptr(in.frontendIPConfigID),
   422  				},
   423  				FrontendPort: to.Ptr[int32](in.inboundNatRulePort),
   424  				Protocol:     to.Ptr(armnetwork.TransportProtocolTCP), // assume TCP for now
   425  			},
   426  		},
   427  		nil,
   428  	)
   429  	if err != nil {
   430  		return nil, fmt.Errorf("failed to add inbound nat rule to load balancer: %w", err)
   431  	}
   432  	inboundNatRuleResp, err := inboundNatRulesPoller.PollUntilDone(ctx, nil)
   433  	if err != nil {
   434  		return nil, fmt.Errorf("failed to add inbound nat rule to load balancer: %w", err)
   435  	}
   436  
   437  	return &inboundNatRuleResp.InboundNatRule, nil
   438  }
   439  
   440  func deleteInboundNatRule(ctx context.Context, in *inboundNatRuleInput) error {
   441  	inboundNatRulesClient := in.networkClientFactory.NewInboundNatRulesClient()
   442  	inboundNatRulesPoller, err := inboundNatRulesClient.BeginDelete(ctx,
   443  		in.resourceGroupName,
   444  		in.loadBalancerName,
   445  		in.inboundNatRuleName,
   446  		nil,
   447  	)
   448  	if err != nil {
   449  		return fmt.Errorf("failed to delete inbound nat rule: %w", err)
   450  	}
   451  	_, err = inboundNatRulesPoller.PollUntilDone(ctx, nil)
   452  	if err != nil {
   453  		return fmt.Errorf("failed to delete inbound nat rule: %w", err)
   454  	}
   455  	return nil
   456  }
   457  
   458  func associateInboundNatRuleToInterface(ctx context.Context, in *inboundNatRuleInput) (*armnetwork.Interface, error) {
   459  	interfacesClient := in.networkClientFactory.NewInterfacesClient()
   460  	interfaceResp, err := interfacesClient.Get(ctx,
   461  		in.resourceGroupName,
   462  		in.bootstrapNicName,
   463  		nil,
   464  	)
   465  	if err != nil {
   466  		return nil, fmt.Errorf("failed to get interface: %w", err)
   467  	}
   468  	bootstrapInterface := interfaceResp.Interface
   469  
   470  	inboundNatRulesClient := in.networkClientFactory.NewInboundNatRulesClient()
   471  	inboundNatRulesResp, err := inboundNatRulesClient.Get(ctx,
   472  		in.resourceGroupName,
   473  		in.loadBalancerName,
   474  		in.inboundNatRuleName,
   475  		nil,
   476  	)
   477  	if err != nil {
   478  		return nil, fmt.Errorf("failed to get inbound nat rule: %w", err)
   479  	}
   480  	inboundNatRule := inboundNatRulesResp.InboundNatRule
   481  
   482  	for i, ipConfig := range bootstrapInterface.Properties.IPConfigurations {
   483  		ipConfig.Properties.LoadBalancerInboundNatRules = append(ipConfig.Properties.LoadBalancerInboundNatRules,
   484  			&inboundNatRule,
   485  		)
   486  		bootstrapInterface.Properties.IPConfigurations[i] = ipConfig
   487  	}
   488  
   489  	interfacesPollerResp, err := interfacesClient.BeginCreateOrUpdate(ctx,
   490  		in.resourceGroupName,
   491  		in.bootstrapNicName,
   492  		bootstrapInterface,
   493  		nil,
   494  	)
   495  	if err != nil {
   496  		return nil, fmt.Errorf("failed to add inbound nat rule to interface: %w", err)
   497  	}
   498  
   499  	interfacesResp, err := interfacesPollerResp.PollUntilDone(ctx, nil)
   500  	if err != nil {
   501  		return nil, fmt.Errorf("failed to add inbound nat rule to interface: %w", err)
   502  	}
   503  	return &interfacesResp.Interface, nil
   504  }