sigs.k8s.io/cluster-api-provider-azure@v1.17.0/azure/scope/identity.go (about)

     1  /*
     2  Copyright 2020 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 scope
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  
    23  	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
    24  	"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
    25  	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
    26  	"github.com/pkg/errors"
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api-provider-azure/util/tele"
    32  	"sigs.k8s.io/controller-runtime/pkg/client"
    33  )
    34  
    35  // AzureSecretKey is the value for they client secret key.
    36  const AzureSecretKey = "clientSecret"
    37  
    38  // CredentialsProvider defines the behavior for azure identity based credential providers.
    39  type CredentialsProvider interface {
    40  	GetClientID() string
    41  	GetClientSecret(ctx context.Context) (string, error)
    42  	GetTenantID() string
    43  	GetTokenCredential(ctx context.Context, resourceManagerEndpoint, activeDirectoryEndpoint, tokenAudience string) (azcore.TokenCredential, error)
    44  	Type() infrav1.IdentityType
    45  }
    46  
    47  // AzureCredentialsProvider represents a credential provider with azure cluster identity.
    48  type AzureCredentialsProvider struct {
    49  	Client   client.Client
    50  	Identity *infrav1.AzureClusterIdentity
    51  }
    52  
    53  // NewAzureCredentialsProvider creates a new AzureClusterCredentialsProvider from the supplied inputs.
    54  func NewAzureCredentialsProvider(ctx context.Context, kubeClient client.Client, identityRef *corev1.ObjectReference, defaultNamespace string) (*AzureCredentialsProvider, error) {
    55  	if identityRef == nil {
    56  		return nil, errors.New("failed to generate new AzureClusterCredentialsProvider from empty identityName")
    57  	}
    58  
    59  	// if the namespace isn't specified then assume it's in the same namespace as the AzureCluster
    60  	namespace := identityRef.Namespace
    61  	if namespace == "" {
    62  		namespace = defaultNamespace
    63  	}
    64  	identity := &infrav1.AzureClusterIdentity{}
    65  	key := client.ObjectKey{Name: identityRef.Name, Namespace: namespace}
    66  	if err := kubeClient.Get(ctx, key, identity); err != nil {
    67  		return nil, errors.Errorf("failed to retrieve AzureClusterIdentity external object %q/%q: %v", key.Namespace, key.Name, err)
    68  	}
    69  
    70  	return &AzureCredentialsProvider{
    71  		Client:   kubeClient,
    72  		Identity: identity,
    73  	}, nil
    74  }
    75  
    76  // GetTokenCredential returns an Azure TokenCredential based on the provided azure identity.
    77  func (p *AzureCredentialsProvider) GetTokenCredential(ctx context.Context, resourceManagerEndpoint, activeDirectoryEndpoint, tokenAudience string) (azcore.TokenCredential, error) {
    78  	ctx, log, done := tele.StartSpanWithLogger(ctx, "azure.scope.AzureCredentialsProvider.GetTokenCredential")
    79  	defer done()
    80  
    81  	var authErr error
    82  	var cred azcore.TokenCredential
    83  
    84  	switch p.Identity.Spec.Type {
    85  	case infrav1.WorkloadIdentity:
    86  		azwiCredOptions, err := NewWorkloadIdentityCredentialOptions().
    87  			WithTenantID(p.Identity.Spec.TenantID).
    88  			WithClientID(p.Identity.Spec.ClientID).
    89  			WithDefaults()
    90  		if err != nil {
    91  			return nil, errors.Wrapf(err, "failed to setup azwi options for identity %s", p.Identity.Name)
    92  		}
    93  		cred, authErr = NewWorkloadIdentityCredential(azwiCredOptions)
    94  
    95  	case infrav1.ManualServicePrincipal:
    96  		log.Info("Identity type ManualServicePrincipal is deprecated and will be removed in a future release. See https://capz.sigs.k8s.io/topics/identities to find a supported identity type.")
    97  		fallthrough
    98  	case infrav1.ServicePrincipal:
    99  		clientSecret, err := p.GetClientSecret(ctx)
   100  		if err != nil {
   101  			return nil, errors.Wrap(err, "failed to get client secret")
   102  		}
   103  		options := azidentity.ClientSecretCredentialOptions{
   104  			ClientOptions: azcore.ClientOptions{
   105  				Cloud: cloud.Configuration{
   106  					ActiveDirectoryAuthorityHost: activeDirectoryEndpoint,
   107  					Services: map[cloud.ServiceName]cloud.ServiceConfiguration{
   108  						cloud.ResourceManager: {
   109  							Audience: tokenAudience,
   110  							Endpoint: resourceManagerEndpoint,
   111  						},
   112  					},
   113  				},
   114  			},
   115  		}
   116  		cred, authErr = azidentity.NewClientSecretCredential(p.GetTenantID(), p.Identity.Spec.ClientID, clientSecret, &options)
   117  
   118  	case infrav1.ServicePrincipalCertificate:
   119  		clientSecret, err := p.GetClientSecret(ctx)
   120  		if err != nil {
   121  			return nil, errors.Wrap(err, "failed to get client secret")
   122  		}
   123  		certs, key, err := azidentity.ParseCertificates([]byte(clientSecret), nil)
   124  		if err != nil {
   125  			return nil, errors.Wrap(err, "failed to parse certificate data")
   126  		}
   127  		cred, authErr = azidentity.NewClientCertificateCredential(p.GetTenantID(), p.Identity.Spec.ClientID, certs, key, nil)
   128  
   129  	case infrav1.UserAssignedMSI:
   130  		options := azidentity.ManagedIdentityCredentialOptions{
   131  			ID: azidentity.ClientID(p.Identity.Spec.ClientID),
   132  		}
   133  		cred, authErr = azidentity.NewManagedIdentityCredential(&options)
   134  
   135  	default:
   136  		return nil, errors.Errorf("identity type %s not supported", p.Identity.Spec.Type)
   137  	}
   138  
   139  	if authErr != nil {
   140  		return nil, errors.Errorf("failed to create credential: %v", authErr)
   141  	}
   142  
   143  	return cred, nil
   144  }
   145  
   146  // GetClientID returns the Client ID associated with the AzureCredentialsProvider's Identity.
   147  func (p *AzureCredentialsProvider) GetClientID() string {
   148  	return p.Identity.Spec.ClientID
   149  }
   150  
   151  // GetClientSecret returns the Client Secret associated with the AzureCredentialsProvider's Identity.
   152  // NOTE: this only works if the Identity references a Service Principal Client Secret.
   153  // If using another type of credentials, such a Certificate, we return an empty string.
   154  func (p *AzureCredentialsProvider) GetClientSecret(ctx context.Context) (string, error) {
   155  	if p.hasClientSecret() {
   156  		secretRef := p.Identity.Spec.ClientSecret
   157  		key := types.NamespacedName{
   158  			Namespace: secretRef.Namespace,
   159  			Name:      secretRef.Name,
   160  		}
   161  		secret := &corev1.Secret{}
   162  
   163  		if err := p.Client.Get(ctx, key, secret); err != nil {
   164  			return "", errors.Wrap(err, "Unable to fetch ClientSecret")
   165  		}
   166  		return string(secret.Data[AzureSecretKey]), nil
   167  	}
   168  	return "", nil
   169  }
   170  
   171  // GetTenantID returns the Tenant ID associated with the AzureCredentialsProvider's Identity.
   172  func (p *AzureCredentialsProvider) GetTenantID() string {
   173  	return p.Identity.Spec.TenantID
   174  }
   175  
   176  // Type returns the auth mechanism used.
   177  func (p *AzureCredentialsProvider) Type() infrav1.IdentityType {
   178  	return p.Identity.Spec.Type
   179  }
   180  
   181  // hasClientSecret returns true if the identity has a Service Principal Client Secret.
   182  // This does not include managed identities.
   183  func (p *AzureCredentialsProvider) hasClientSecret() bool {
   184  	switch p.Identity.Spec.Type {
   185  	case infrav1.ServicePrincipal, infrav1.ManualServicePrincipal, infrav1.ServicePrincipalCertificate:
   186  		return true
   187  	default:
   188  		return false
   189  	}
   190  }
   191  
   192  // IsClusterNamespaceAllowed indicates if the cluster namespace is allowed.
   193  func IsClusterNamespaceAllowed(ctx context.Context, k8sClient client.Client, allowedNamespaces *infrav1.AllowedNamespaces, namespace string) bool {
   194  	if allowedNamespaces == nil {
   195  		return false
   196  	}
   197  
   198  	// empty value matches with all namespaces
   199  	if reflect.DeepEqual(*allowedNamespaces, infrav1.AllowedNamespaces{}) {
   200  		return true
   201  	}
   202  
   203  	for _, v := range allowedNamespaces.NamespaceList {
   204  		if v == namespace {
   205  			return true
   206  		}
   207  	}
   208  
   209  	// Check if clusterNamespace is in the namespaces selected by the identity's allowedNamespaces selector.
   210  	namespaces := &corev1.NamespaceList{}
   211  	selector, err := metav1.LabelSelectorAsSelector(allowedNamespaces.Selector)
   212  	if err != nil {
   213  		return false
   214  	}
   215  
   216  	// If a Selector has a nil or empty selector, it should match nothing.
   217  	if selector.Empty() {
   218  		return false
   219  	}
   220  
   221  	if err := k8sClient.List(ctx, namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil {
   222  		return false
   223  	}
   224  
   225  	for _, n := range namespaces.Items {
   226  		if n.Name == namespace {
   227  			return true
   228  		}
   229  	}
   230  
   231  	return false
   232  }