sigs.k8s.io/cluster-api-provider-azure@v1.14.3/util/azure/azure.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package azure
    18  
    19  import (
    20  	"context"
    21  	"os"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
    26  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
    27  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
    28  	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
    29  	"github.com/Azure/go-autorest/autorest"
    30  	azureautorest "github.com/Azure/go-autorest/autorest/azure"
    31  	"github.com/Azure/go-autorest/autorest/azure/auth"
    32  	"github.com/jongio/azidext/go/azidext"
    33  	"github.com/pkg/errors"
    34  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  )
    37  
    38  // AzureSystemNodeLabelPrefix is a standard node label prefix for Azure features, e.g., kubernetes.azure.com/scalesetpriority.
    39  const AzureSystemNodeLabelPrefix = "kubernetes.azure.com"
    40  
    41  const (
    42  	// ProviderIDPrefix will be appended to the beginning of Azure resource IDs to form the Kubernetes Provider ID.
    43  	// NOTE: this format matches the 2 slashes format used in cloud-provider and cluster-autoscaler.
    44  	ProviderIDPrefix = "azure://"
    45  )
    46  
    47  // IsAzureSystemNodeLabelKey is a helper function that determines whether a node label key is an Azure "system" label.
    48  func IsAzureSystemNodeLabelKey(labelKey string) bool {
    49  	return strings.HasPrefix(labelKey, AzureSystemNodeLabelPrefix)
    50  }
    51  
    52  func getCloudConfig(environment azureautorest.Environment) cloud.Configuration {
    53  	var config cloud.Configuration
    54  	switch environment.Name {
    55  	case "AzureStackCloud":
    56  		config = cloud.Configuration{
    57  			ActiveDirectoryAuthorityHost: environment.ActiveDirectoryEndpoint,
    58  			Services: map[cloud.ServiceName]cloud.ServiceConfiguration{
    59  				cloud.ResourceManager: {
    60  					Audience: environment.TokenAudience,
    61  					Endpoint: environment.ResourceManagerEndpoint,
    62  				},
    63  			},
    64  		}
    65  	case "AzureChinaCloud":
    66  		config = cloud.AzureChina
    67  	case "AzureUSGovernmentCloud":
    68  		config = cloud.AzureGovernment
    69  	default:
    70  		config = cloud.AzurePublic
    71  	}
    72  	return config
    73  }
    74  
    75  // GetAuthorizer returns an autorest.Authorizer-compatible object from MSAL.
    76  func GetAuthorizer(settings auth.EnvironmentSettings) (autorest.Authorizer, error) {
    77  	// azidentity uses different envvars for certificate authentication:
    78  	//  azidentity: AZURE_CLIENT_CERTIFICATE_{PATH,PASSWORD}
    79  	//  autorest: AZURE_CERTIFICATE_{PATH,PASSWORD}
    80  	// Let's set them according to the envvars used by autorest, in case they are present
    81  	_, azidSet := os.LookupEnv("AZURE_CLIENT_CERTIFICATE_PATH")
    82  	path, autorestSet := os.LookupEnv("AZURE_CERTIFICATE_PATH")
    83  	if !azidSet && autorestSet {
    84  		os.Setenv("AZURE_CLIENT_CERTIFICATE_PATH", path)
    85  		os.Setenv("AZURE_CLIENT_CERTIFICATE_PASSWORD", os.Getenv("AZURE_CERTIFICATE_PASSWORD"))
    86  	}
    87  
    88  	options := azidentity.DefaultAzureCredentialOptions{
    89  		ClientOptions: azcore.ClientOptions{
    90  			Cloud: getCloudConfig(settings.Environment),
    91  		},
    92  	}
    93  	cred, err := azidentity.NewDefaultAzureCredential(&options)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	// We must use TokenAudience for StackCloud, otherwise we get an
    99  	// AADSTS500011 error from the API
   100  	scope := settings.Environment.TokenAudience
   101  	if !strings.HasSuffix(scope, "/.default") {
   102  		scope += "/.default"
   103  	}
   104  	return azidext.NewTokenCredentialAdapter(cred, []string{scope}), nil
   105  }
   106  
   107  // FindParentMachinePool finds the parent MachinePool for the AzureMachinePool.
   108  func FindParentMachinePool(ampName string, cli client.Client) (*expv1.MachinePool, error) {
   109  	ctx := context.Background()
   110  	machinePoolList := &expv1.MachinePoolList{}
   111  	if err := cli.List(ctx, machinePoolList); err != nil {
   112  		return nil, errors.Wrapf(err, "failed to list MachinePools for %s", ampName)
   113  	}
   114  	for _, mp := range machinePoolList.Items {
   115  		if mp.Spec.Template.Spec.InfrastructureRef.Name == ampName {
   116  			return &mp, nil
   117  		}
   118  	}
   119  	return nil, errors.Errorf("failed to get MachinePool for %s", ampName)
   120  }
   121  
   122  // FindParentMachinePoolWithRetry finds the parent MachinePool for the AzureMachinePool with retry.
   123  func FindParentMachinePoolWithRetry(ampName string, cli client.Client, maxAttempts int) (*expv1.MachinePool, error) {
   124  	for i := 1; ; i++ {
   125  		p, err := FindParentMachinePool(ampName, cli)
   126  		if err != nil {
   127  			if i >= maxAttempts {
   128  				return nil, errors.Wrap(err, "failed to find parent MachinePool")
   129  			}
   130  			time.Sleep(1 * time.Second)
   131  			continue
   132  		}
   133  		return p, nil
   134  	}
   135  }
   136  
   137  // ParseResourceID parses a string to an *arm.ResourceID, first removing any "azure://" prefix.
   138  func ParseResourceID(id string) (*arm.ResourceID, error) {
   139  	return arm.ParseResourceID(strings.TrimPrefix(id, ProviderIDPrefix))
   140  }