sigs.k8s.io/cluster-api-provider-azure@v1.14.3/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 "strings" 23 24 "github.com/Azure/azure-sdk-for-go/sdk/azcore" 25 "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" 26 "github.com/Azure/azure-sdk-for-go/sdk/azidentity" 27 "github.com/Azure/go-autorest/autorest" 28 "github.com/jongio/azidext/go/azidext" 29 "github.com/pkg/errors" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/types" 33 infrav1 "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" 34 "sigs.k8s.io/cluster-api-provider-azure/util/tele" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 ) 37 38 // AzureSecretKey is the value for they client secret key. 39 const AzureSecretKey = "clientSecret" 40 41 // CredentialsProvider defines the behavior for azure identity based credential providers. 42 type CredentialsProvider interface { 43 GetAuthorizer(ctx context.Context, tokenCredential azcore.TokenCredential, tokenAudience string) (autorest.Authorizer, error) 44 GetClientID() string 45 GetClientSecret(ctx context.Context) (string, error) 46 GetTenantID() string 47 GetTokenCredential(ctx context.Context, resourceManagerEndpoint, activeDirectoryEndpoint, tokenAudience string) (azcore.TokenCredential, error) 48 } 49 50 // AzureCredentialsProvider represents a credential provider with azure cluster identity. 51 type AzureCredentialsProvider struct { 52 Client client.Client 53 Identity *infrav1.AzureClusterIdentity 54 } 55 56 // AzureClusterCredentialsProvider wraps AzureCredentialsProvider with AzureCluster. 57 type AzureClusterCredentialsProvider struct { 58 AzureCredentialsProvider 59 AzureCluster *infrav1.AzureCluster 60 } 61 62 // ManagedControlPlaneCredentialsProvider wraps AzureCredentialsProvider with AzureManagedControlPlane. 63 type ManagedControlPlaneCredentialsProvider struct { 64 AzureCredentialsProvider 65 AzureManagedControlPlane *infrav1.AzureManagedControlPlane 66 } 67 68 var _ CredentialsProvider = (*AzureClusterCredentialsProvider)(nil) 69 var _ CredentialsProvider = (*ManagedControlPlaneCredentialsProvider)(nil) 70 71 // NewAzureClusterCredentialsProvider creates a new AzureClusterCredentialsProvider from the supplied inputs. 72 func NewAzureClusterCredentialsProvider(ctx context.Context, kubeClient client.Client, azureCluster *infrav1.AzureCluster) (*AzureClusterCredentialsProvider, error) { 73 if azureCluster.Spec.IdentityRef == nil { 74 return nil, errors.New("failed to generate new AzureClusterCredentialsProvider from empty identityName") 75 } 76 77 ref := azureCluster.Spec.IdentityRef 78 // if the namespace isn't specified then assume it's in the same namespace as the AzureCluster 79 namespace := ref.Namespace 80 if namespace == "" { 81 namespace = azureCluster.Namespace 82 } 83 identity := &infrav1.AzureClusterIdentity{} 84 key := client.ObjectKey{Name: ref.Name, Namespace: namespace} 85 if err := kubeClient.Get(ctx, key, identity); err != nil { 86 return nil, errors.Errorf("failed to retrieve AzureClusterIdentity external object %q/%q: %v", key.Namespace, key.Name, err) 87 } 88 89 return &AzureClusterCredentialsProvider{ 90 AzureCredentialsProvider{ 91 Client: kubeClient, 92 Identity: identity, 93 }, 94 azureCluster, 95 }, nil 96 } 97 98 // GetAuthorizer returns an Azure authorizer based on the provided azure identity. It delegates to AzureCredentialsProvider with AzureCluster metadata. 99 func (p *AzureClusterCredentialsProvider) GetAuthorizer(ctx context.Context, tokenCredential azcore.TokenCredential, tokenAudience string) (autorest.Authorizer, error) { 100 return p.AzureCredentialsProvider.GetAuthorizer(ctx, tokenCredential, tokenAudience) 101 } 102 103 // GetTokenCredential returns an Azure TokenCredential based on the provided azure identity. 104 func (p *AzureClusterCredentialsProvider) GetTokenCredential(ctx context.Context, resourceManagerEndpoint, activeDirectoryEndpoint, tokenAudience string) (azcore.TokenCredential, error) { 105 return p.AzureCredentialsProvider.GetTokenCredential(ctx, resourceManagerEndpoint, activeDirectoryEndpoint, tokenAudience, p.AzureCluster.ObjectMeta) 106 } 107 108 // NewManagedControlPlaneCredentialsProvider creates a new ManagedControlPlaneCredentialsProvider from the supplied inputs. 109 func NewManagedControlPlaneCredentialsProvider(ctx context.Context, kubeClient client.Client, managedControlPlane *infrav1.AzureManagedControlPlane) (*ManagedControlPlaneCredentialsProvider, error) { 110 if managedControlPlane.Spec.IdentityRef == nil { 111 return nil, errors.New("failed to generate new ManagedControlPlaneCredentialsProvider from empty identityName") 112 } 113 114 ref := managedControlPlane.Spec.IdentityRef 115 // if the namespace isn't specified then assume it's in the same namespace as the AzureManagedControlPlane 116 namespace := ref.Namespace 117 if namespace == "" { 118 namespace = managedControlPlane.Namespace 119 } 120 identity := &infrav1.AzureClusterIdentity{} 121 key := client.ObjectKey{Name: ref.Name, Namespace: namespace} 122 if err := kubeClient.Get(ctx, key, identity); err != nil { 123 return nil, errors.Errorf("failed to retrieve AzureClusterIdentity external object %q/%q: %v", key.Namespace, key.Name, err) 124 } 125 126 return &ManagedControlPlaneCredentialsProvider{ 127 AzureCredentialsProvider{ 128 Client: kubeClient, 129 Identity: identity, 130 }, 131 managedControlPlane, 132 }, nil 133 } 134 135 // GetAuthorizer returns an Azure authorizer based on the provided azure identity. It delegates to AzureCredentialsProvider with AzureManagedControlPlane metadata. 136 func (p *ManagedControlPlaneCredentialsProvider) GetAuthorizer(ctx context.Context, tokenCredential azcore.TokenCredential, tokenAudience string) (autorest.Authorizer, error) { 137 return p.AzureCredentialsProvider.GetAuthorizer(ctx, tokenCredential, tokenAudience) 138 } 139 140 // GetTokenCredential returns an Azure TokenCredential based on the provided azure identity. 141 func (p *ManagedControlPlaneCredentialsProvider) GetTokenCredential(ctx context.Context, resourceManagerEndpoint, activeDirectoryEndpoint, tokenAudience string) (azcore.TokenCredential, error) { 142 return p.AzureCredentialsProvider.GetTokenCredential(ctx, resourceManagerEndpoint, activeDirectoryEndpoint, tokenAudience, p.AzureManagedControlPlane.ObjectMeta) 143 } 144 145 // GetTokenCredential returns an Azure TokenCredential based on the provided azure identity. 146 func (p *AzureCredentialsProvider) GetTokenCredential(ctx context.Context, resourceManagerEndpoint, activeDirectoryEndpoint, tokenAudience string, clusterMeta metav1.ObjectMeta) (azcore.TokenCredential, error) { 147 ctx, log, done := tele.StartSpanWithLogger(ctx, "azure.scope.AzureCredentialsProvider.GetTokenCredential") 148 defer done() 149 150 var authErr error 151 var cred azcore.TokenCredential 152 153 switch p.Identity.Spec.Type { 154 case infrav1.WorkloadIdentity: 155 azwiCredOptions, err := NewWorkloadIdentityCredentialOptions(). 156 WithTenantID(p.Identity.Spec.TenantID). 157 WithClientID(p.Identity.Spec.ClientID). 158 WithDefaults() 159 if err != nil { 160 return nil, errors.Wrapf(err, "failed to setup azwi options for identity %s", p.Identity.Name) 161 } 162 cred, authErr = NewWorkloadIdentityCredential(azwiCredOptions) 163 164 case infrav1.ManualServicePrincipal: 165 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.") 166 fallthrough 167 case infrav1.ServicePrincipal: 168 clientSecret, err := p.GetClientSecret(ctx) 169 if err != nil { 170 return nil, errors.Wrap(err, "failed to get client secret") 171 } 172 options := azidentity.ClientSecretCredentialOptions{ 173 ClientOptions: azcore.ClientOptions{ 174 Cloud: cloud.Configuration{ 175 ActiveDirectoryAuthorityHost: activeDirectoryEndpoint, 176 Services: map[cloud.ServiceName]cloud.ServiceConfiguration{ 177 cloud.ResourceManager: { 178 Audience: tokenAudience, 179 Endpoint: resourceManagerEndpoint, 180 }, 181 }, 182 }, 183 }, 184 } 185 cred, authErr = azidentity.NewClientSecretCredential(p.GetTenantID(), p.Identity.Spec.ClientID, clientSecret, &options) 186 187 case infrav1.ServicePrincipalCertificate: 188 clientSecret, err := p.GetClientSecret(ctx) 189 if err != nil { 190 return nil, errors.Wrap(err, "failed to get client secret") 191 } 192 certs, key, err := azidentity.ParseCertificates([]byte(clientSecret), nil) 193 if err != nil { 194 return nil, errors.Wrap(err, "failed to parse certificate data") 195 } 196 cred, authErr = azidentity.NewClientCertificateCredential(p.GetTenantID(), p.Identity.Spec.ClientID, certs, key, nil) 197 198 case infrav1.UserAssignedMSI: 199 options := azidentity.ManagedIdentityCredentialOptions{ 200 ID: azidentity.ClientID(p.Identity.Spec.ClientID), 201 } 202 cred, authErr = azidentity.NewManagedIdentityCredential(&options) 203 204 default: 205 return nil, errors.Errorf("identity type %s not supported", p.Identity.Spec.Type) 206 } 207 208 if authErr != nil { 209 return nil, errors.Errorf("failed to create credential: %v", authErr) 210 } 211 212 return cred, nil 213 } 214 215 // GetAuthorizer returns an Azure authorizer based on the provided azure identity, cluster metadata, and tokenCredential. 216 func (p *AzureCredentialsProvider) GetAuthorizer(ctx context.Context, cred azcore.TokenCredential, tokenAudience string) (autorest.Authorizer, error) { 217 // We must use TokenAudience for StackCloud, otherwise we get an 218 // AADSTS500011 error from the API 219 scope := tokenAudience 220 if !strings.HasSuffix(scope, "/.default") { 221 scope += "/.default" 222 } 223 authorizer := azidext.NewTokenCredentialAdapter(cred, []string{scope}) 224 return authorizer, nil 225 } 226 227 // GetClientID returns the Client ID associated with the AzureCredentialsProvider's Identity. 228 func (p *AzureCredentialsProvider) GetClientID() string { 229 return p.Identity.Spec.ClientID 230 } 231 232 // GetClientSecret returns the Client Secret associated with the AzureCredentialsProvider's Identity. 233 // NOTE: this only works if the Identity references a Service Principal Client Secret. 234 // If using another type of credentials, such a Certificate, we return an empty string. 235 func (p *AzureCredentialsProvider) GetClientSecret(ctx context.Context) (string, error) { 236 if p.hasClientSecret() { 237 secretRef := p.Identity.Spec.ClientSecret 238 key := types.NamespacedName{ 239 Namespace: secretRef.Namespace, 240 Name: secretRef.Name, 241 } 242 secret := &corev1.Secret{} 243 244 if err := p.Client.Get(ctx, key, secret); err != nil { 245 return "", errors.Wrap(err, "Unable to fetch ClientSecret") 246 } 247 return string(secret.Data[AzureSecretKey]), nil 248 } 249 return "", nil 250 } 251 252 // GetTenantID returns the Tenant ID associated with the AzureCredentialsProvider's Identity. 253 func (p *AzureCredentialsProvider) GetTenantID() string { 254 return p.Identity.Spec.TenantID 255 } 256 257 // hasClientSecret returns true if the identity has a Service Principal Client Secret. 258 // This does not include managed identities. 259 func (p *AzureCredentialsProvider) hasClientSecret() bool { 260 switch p.Identity.Spec.Type { 261 case infrav1.ServicePrincipal, infrav1.ManualServicePrincipal, infrav1.ServicePrincipalCertificate: 262 return true 263 default: 264 return false 265 } 266 } 267 268 // IsClusterNamespaceAllowed indicates if the cluster namespace is allowed. 269 func IsClusterNamespaceAllowed(ctx context.Context, k8sClient client.Client, allowedNamespaces *infrav1.AllowedNamespaces, namespace string) bool { 270 if allowedNamespaces == nil { 271 return false 272 } 273 274 // empty value matches with all namespaces 275 if reflect.DeepEqual(*allowedNamespaces, infrav1.AllowedNamespaces{}) { 276 return true 277 } 278 279 for _, v := range allowedNamespaces.NamespaceList { 280 if v == namespace { 281 return true 282 } 283 } 284 285 // Check if clusterNamespace is in the namespaces selected by the identity's allowedNamespaces selector. 286 namespaces := &corev1.NamespaceList{} 287 selector, err := metav1.LabelSelectorAsSelector(allowedNamespaces.Selector) 288 if err != nil { 289 return false 290 } 291 292 // If a Selector has a nil or empty selector, it should match nothing. 293 if selector.Empty() { 294 return false 295 } 296 297 if err := k8sClient.List(ctx, namespaces, client.MatchingLabelsSelector{Selector: selector}); err != nil { 298 return false 299 } 300 301 for _, n := range namespaces.Items { 302 if n.Name == namespace { 303 return true 304 } 305 } 306 307 return false 308 }