Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/v1beta1/azureclusteridentity_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ type AllowedNamespaces struct {
// AzureClusterIdentitySpec defines the parameters that are used to create an AzureIdentity.
type AzureClusterIdentitySpec struct {
// Type is the type of Azure Identity used.
// ServicePrincipal, ServicePrincipalCertificate, or ManualServicePrincipal.
// ServicePrincipal, ServicePrincipalCertificate, UserAssignedMSI or ManualServicePrincipal.
Type IdentityType `json:"type"`
// ResourceID is the Azure resource ID for the User Assigned MSI resource.
// Not currently supported.
// Only applicable when type is UserAssignedMSI.
// +optional
ResourceID string `json:"resourceID,omitempty"`
// ClientID is the service principal client ID.
Expand Down
4 changes: 2 additions & 2 deletions api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,11 +422,11 @@ const (
)

// IdentityType represents different types of identities.
// +kubebuilder:validation:Enum=ServicePrincipal;ManualServicePrincipal;ServicePrincipalCertificate
// +kubebuilder:validation:Enum=ServicePrincipal;UserAssignedMSI;ManualServicePrincipal;ServicePrincipalCertificate
type IdentityType string

const (
// UserAssignedMSI represents a user-assigned identity.
// UserAssignedMSI represents a user-assigned managed identity.
UserAssignedMSI IdentityType = "UserAssignedMSI"

// ServicePrincipal represents a service principal using a client password as secret.
Expand Down
29 changes: 19 additions & 10 deletions azure/scope/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ func (p *ManagedControlPlaneCredentialsProvider) GetAuthorizer(ctx context.Conte
func (p *AzureCredentialsProvider) GetAuthorizer(ctx context.Context, resourceManagerEndpoint, activeDirectoryEndpoint string, clusterMeta metav1.ObjectMeta) (autorest.Authorizer, error) {
var spt *adal.ServicePrincipalToken
switch p.Identity.Spec.Type {
case infrav1.ServicePrincipal, infrav1.ServicePrincipalCertificate:
case infrav1.ServicePrincipal, infrav1.ServicePrincipalCertificate, infrav1.UserAssignedMSI:
if err := createAzureIdentityWithBindings(ctx, p.Identity, resourceManagerEndpoint, activeDirectoryEndpoint, clusterMeta, p.Client); err != nil {
return nil, err
}
Expand Down Expand Up @@ -185,24 +185,33 @@ func (p *AzureCredentialsProvider) GetClientID() string {
// NOTE: this only works if the Identity references a Service Principal Client Secret.
// If using another type of credentials, such a Certificate, we return an empty string.
func (p *AzureCredentialsProvider) GetClientSecret(ctx context.Context) (string, error) {
secretRef := p.Identity.Spec.ClientSecret
key := types.NamespacedName{
Namespace: secretRef.Namespace,
Name: secretRef.Name,
}
secret := &corev1.Secret{}
if p.hasClientSecret() {
secretRef := p.Identity.Spec.ClientSecret
key := types.NamespacedName{
Namespace: secretRef.Namespace,
Name: secretRef.Name,
}
secret := &corev1.Secret{}

if err := p.Client.Get(ctx, key, secret); err != nil {
return "", errors.Wrap(err, "Unable to fetch ClientSecret")
if err := p.Client.Get(ctx, key, secret); err != nil {
return "", errors.Wrap(err, "Unable to fetch ClientSecret")
}
return string(secret.Data[azureSecretKey]), nil
}
return string(secret.Data[azureSecretKey]), nil
return "", nil
}

// GetTenantID returns the Tenant ID associated with the AzureCredentialsProvider's Identity.
func (p *AzureCredentialsProvider) GetTenantID() string {
return p.Identity.Spec.TenantID
}

// hasClientSecret returns true if the identity has a Service Principal Client Secret.
// This does not include service principals with certificates or managed identities.
func (p *AzureCredentialsProvider) hasClientSecret() bool {
return p.Identity.Spec.Type == infrav1.ServicePrincipal || p.Identity.Spec.Type == infrav1.ManualServicePrincipal
}

func createAzureIdentityWithBindings(ctx context.Context, azureIdentity *infrav1.AzureClusterIdentity, resourceManagerEndpoint, activeDirectoryEndpoint string, clusterMeta metav1.ObjectMeta,
kubeClient client.Client) error {
azureIdentityType, err := getAzureIdentityType(azureIdentity)
Expand Down
59 changes: 59 additions & 0 deletions azure/scope/identity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,62 @@ func TestCreateAzureIdentityWithBindings(t *testing.T) {
})
}
}

func TestHasClientSecret(t *testing.T) {
tests := []struct {
name string
identity *infrav1.AzureClusterIdentity
want bool
}{
{
name: "user assigned identity",
identity: &infrav1.AzureClusterIdentity{
Spec: infrav1.AzureClusterIdentitySpec{
Type: infrav1.UserAssignedMSI,
ResourceID: "my-resource-id",
},
},
want: false,
},
{
name: "service principal with secret",
identity: &infrav1.AzureClusterIdentity{
Spec: infrav1.AzureClusterIdentitySpec{
Type: infrav1.ServicePrincipal,
ClientSecret: corev1.SecretReference{Name: "my-client-secret"},
},
},
want: true,
},
{
name: "service principal with certificate",
identity: &infrav1.AzureClusterIdentity{
Spec: infrav1.AzureClusterIdentitySpec{
Type: infrav1.ServicePrincipalCertificate,
ClientSecret: corev1.SecretReference{Name: "my-client-secret"},
},
},
want: false,
},
{
name: "manual service principal",
identity: &infrav1.AzureClusterIdentity{
Spec: infrav1.AzureClusterIdentitySpec{
Type: infrav1.ManualServicePrincipal,
ClientSecret: corev1.SecretReference{Name: "my-client-secret"},
},
},
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &AzureCredentialsProvider{
Identity: tt.identity,
}
if got := p.hasClientSecret(); got != tt.want {
t.Errorf("AzureCredentialsProvider.hasClientSecret() = %v, want %v", got, tt.want)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -421,16 +421,17 @@ spec:
type: object
resourceID:
description: ResourceID is the Azure resource ID for the User Assigned
MSI resource. Not currently supported.
MSI resource. Only applicable when type is UserAssignedMSI.
type: string
tenantID:
description: TenantID is the service principal primary tenant id.
type: string
type:
description: Type is the type of Azure Identity used. ServicePrincipal,
ServicePrincipalCertificate, or ManualServicePrincipal.
ServicePrincipalCertificate, UserAssignedMSI or ManualServicePrincipal.
enum:
- ServicePrincipal
- UserAssignedMSI
- ManualServicePrincipal
- ServicePrincipalCertificate
type: string
Expand Down
52 changes: 45 additions & 7 deletions docs/book/src/topics/multitenancy.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ To enable single controller multi-tenancy, a different Identity can be added to

This is achieved using the [aad-pod-identity](https://azure.github.io/aad-pod-identity) library.

## Service Principal With Client Password
## Identity Types

### Service Principal With Client Password

Once a new SP Identity is created in Azure, the corresponding values should be used to create an `AzureClusterIdentity` resource:

Expand Down Expand Up @@ -42,7 +44,7 @@ data:
clientSecret: <client-secret-of-SP-identity>
```

## Service Principal With Certificate
### Service Principal With Certificate

Once a new SP Identity is created in Azure, the corresponding values should be used to create an `AzureClusterIdentity` resource:

Expand Down Expand Up @@ -87,7 +89,47 @@ data:
password: PASSWORD
```

## Manual Service Principal Identity
### User-Assigned Managed Identity

<aside class="note">

<h1> Note </h1>

This option is only available when the cluster is managed from a Kubernetes cluster running on Azure.

</aside>

#### Prerequisites

1. [Create](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-manage-user-assigned-managed-identities?pivots=identity-mi-methods-azp#create-a-user-assigned-managed-identity) a user-assigned managed identity in Azure.
2. [Create a role assignment](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/howto-assign-access-portal#use-azure-rbac-to-assign-a-managed-identity-access-to-another-resource) to give the identity Contributor access to the Azure subscription where the workload cluster will be created.
3. [Configure] the identity on the management cluster nodes by adding it to each worker node VM. If using AKS as the management cluster see [these instructions](https://docs.microsoft.com/en-us/azure/aks/use-managed-identity).

#### Creating the AzureClusterIdentity

After a user-assigned managed identity is created in Azure and assigned to the management cluster, the corresponding values should be used to create an `AzureClusterIdentity` resource:

```yaml
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
kind: AzureClusterIdentity
metadata:
name: example-identity
namespace: default
spec:
type: ServicePrincipal
tenantID: <azure-tenant-id>
clientID: <client-id-of-user-assigned-identity>
resourceID: <resource-id-of-user-assigned-identity>
allowedNamespaces:
list:
- <cluster-namespace>
```

#### Assigning VM identities for cloud-provider authentication

When using a user-assigned managed identity to create the workload cluster, a VM identity should also be assigned to each control-plane machine in the workload cluster for Cloud Provider to use. See [here](../topics/vm-identity.md#managed-identities) for more information.

### Manual Service Principal Identity

Manual Service Principal Identity is similar to [Service Principal Identity](https://capz.sigs.k8s.io/topics/multitenancy.html#service-principal-identity) except that the service principal's `clientSecret` is directly fetched from the secret containing it.
To use this type of identity, set the identity type as `ManualServicePrincipal` in `AzureClusterIdentity`. For example,
Expand Down Expand Up @@ -143,7 +185,3 @@ spec:
```

For more details on how aad-pod-identity works, please check the guide [here](https://azure.github.io/aad-pod-identity/docs/).

## User Assigned Identity

_will be supported in a future release_
Loading