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 }