Skip to content

Commit 42c829a

Browse files
author
Cecile Robert-Michon
committed
Add support for Service Principal with Certificate auth using AAD pod identity
1 parent f0fbfac commit 42c829a

File tree

6 files changed

+103
-27
lines changed

6 files changed

+103
-27
lines changed

api/v1beta1/azureclusteridentity_types.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,20 @@ type AllowedNamespaces struct {
4343

4444
// AzureClusterIdentitySpec defines the parameters that are used to create an AzureIdentity.
4545
type AzureClusterIdentitySpec struct {
46-
// UserAssignedMSI or Service Principal
46+
// Type is the type of Azure Identity used.
47+
// ServicePrincipal, ServicePrincipalCertificate, or ManualServicePrincipal.
4748
Type IdentityType `json:"type"`
48-
// User assigned MSI resource id.
49+
// ResourceID is the Azure resource ID for the User Assigned MSI resource.
50+
// Not currently supported.
4951
// +optional
5052
ResourceID string `json:"resourceID,omitempty"`
53+
// ClientID is the service principal client ID.
5154
// Both User Assigned MSI and SP can use this field.
5255
ClientID string `json:"clientID"`
5356
// ClientSecret is a secret reference which should contain either a Service Principal password or certificate secret.
5457
// +optional
5558
ClientSecret corev1.SecretReference `json:"clientSecret,omitempty"`
56-
// Service principal primary tenant id.
59+
// TenantID is the service principal primary tenant id.
5760
TenantID string `json:"tenantID"`
5861
// AllowedNamespaces is used to identify the namespaces the clusters are allowed to use the identity from.
5962
// Namespaces can be selected either using an array of namespaces or with label selector.

api/v1beta1/types.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,18 +422,21 @@ const (
422422
)
423423

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

428428
const (
429429
// UserAssignedMSI represents a user-assigned identity.
430430
UserAssignedMSI IdentityType = "UserAssignedMSI"
431431

432-
// ServicePrincipal represents a service principal.
432+
// ServicePrincipal represents a service principal using a client password as secret.
433433
ServicePrincipal IdentityType = "ServicePrincipal"
434434

435435
// ManualServicePrincipal represents a manual service principal.
436436
ManualServicePrincipal IdentityType = "ManualServicePrincipal"
437+
438+
// ServicePrincipalCertificate represents a service principal using a certificate as secret.
439+
ServicePrincipalCertificate IdentityType = "ServicePrincipalCertificate"
437440
)
438441

439442
// OSDisk defines the operating system disk for a VM.

azure/scope/identity.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"reflect"
2323

24+
aadpodid "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity"
2425
aadpodv1 "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity/v1"
2526
"github.com/Azure/go-autorest/autorest"
2627
"github.com/Azure/go-autorest/autorest/adal"
@@ -87,10 +88,6 @@ func NewAzureClusterCredentialsProvider(ctx context.Context, kubeClient client.C
8788
return nil, errors.Errorf("failed to retrieve AzureClusterIdentity external object %q/%q: %v", key.Namespace, key.Name, err)
8889
}
8990

90-
if identity.Spec.Type != infrav1.ServicePrincipal {
91-
return nil, errors.New("AzureClusterIdentity is not of type Service Principal")
92-
}
93-
9491
return &AzureClusterCredentialsProvider{
9592
AzureCredentialsProvider{
9693
Client: kubeClient,
@@ -123,10 +120,6 @@ func NewManagedControlPlaneCredentialsProvider(ctx context.Context, kubeClient c
123120
return nil, errors.Errorf("failed to retrieve AzureClusterIdentity external object %q/%q: %v", key.Namespace, key.Name, err)
124121
}
125122

126-
if identity.Spec.Type != infrav1.ServicePrincipal {
127-
return nil, errors.New("AzureClusterIdentity is not of type Service Principal")
128-
}
129-
130123
return &ManagedControlPlaneCredentialsProvider{
131124
AzureCredentialsProvider{
132125
Client: kubeClient,
@@ -145,7 +138,7 @@ func (p *ManagedControlPlaneCredentialsProvider) GetAuthorizer(ctx context.Conte
145138
func (p *AzureCredentialsProvider) GetAuthorizer(ctx context.Context, resourceManagerEndpoint, activeDirectoryEndpoint string, clusterMeta metav1.ObjectMeta) (autorest.Authorizer, error) {
146139
var spt *adal.ServicePrincipalToken
147140
switch p.Identity.Spec.Type {
148-
case infrav1.ServicePrincipal:
141+
case infrav1.ServicePrincipal, infrav1.ServicePrincipalCertificate:
149142
if err := createAzureIdentityWithBindings(ctx, p.Identity, resourceManagerEndpoint, activeDirectoryEndpoint, clusterMeta, p.Client); err != nil {
150143
return nil, err
151144
}
@@ -283,13 +276,15 @@ func createAzureIdentityWithBindings(ctx context.Context, azureIdentity *infrav1
283276

284277
func getAzureIdentityType(identity *infrav1.AzureClusterIdentity) (aadpodv1.IdentityType, error) {
285278
switch identity.Spec.Type {
286-
case infrav1.ServicePrincipal:
287-
return aadpodv1.ServicePrincipal, nil
288279
case infrav1.UserAssignedMSI:
289280
return aadpodv1.UserAssignedMSI, nil
281+
case infrav1.ServicePrincipal:
282+
return aadpodv1.ServicePrincipal, nil
283+
case infrav1.ServicePrincipalCertificate:
284+
return aadpodv1.IdentityType(aadpodid.ServicePrincipalCertificate), nil
290285
}
291286

292-
return 0, errors.New("AzureIdentity does not have a vaild type")
287+
return -1, errors.New("AzureIdentity does not have a valid type")
293288
}
294289

295290
// IsClusterNamespaceAllowed indicates if the cluster namespace is allowed.

azure/scope/identity_test.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"context"
2121
"testing"
2222

23+
aadpodid "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity"
2324
aadpodv1 "github.com/Azure/aad-pod-identity/pkg/apis/aadpodidentity/v1"
2425
. "github.com/onsi/gomega"
2526
corev1 "k8s.io/api/core/v1"
@@ -212,6 +213,36 @@ func TestCreateAzureIdentityWithBindings(t *testing.T) {
212213
Namespace: "capz-system",
213214
},
214215
},
216+
{
217+
name: "create service principal with certificate identity",
218+
identity: &infrav1.AzureClusterIdentity{
219+
ObjectMeta: metav1.ObjectMeta{
220+
Name: "test-identity",
221+
},
222+
Spec: infrav1.AzureClusterIdentitySpec{
223+
Type: infrav1.ServicePrincipalCertificate,
224+
ResourceID: "my-resource-id",
225+
ClientID: "my-client-id",
226+
ClientSecret: corev1.SecretReference{Name: "my-client-secret"},
227+
TenantID: "my-tenant-id",
228+
},
229+
},
230+
identityType: aadpodv1.IdentityType(aadpodid.ServicePrincipalCertificate),
231+
resourceManagerEndpoint: "public-cloud-endpoint",
232+
activeDirectoryEndpoint: "active-directory-endpoint",
233+
clusterMeta: metav1.ObjectMeta{
234+
Name: "cluster-name",
235+
Namespace: "my-namespace",
236+
},
237+
copiedIdentity: metav1.ObjectMeta{
238+
Name: "cluster-name-my-namespace-test-identity",
239+
Namespace: "capz-system",
240+
},
241+
binding: metav1.ObjectMeta{
242+
Name: "cluster-name-my-namespace-test-identity-binding",
243+
Namespace: "capz-system",
244+
},
245+
},
215246
{
216247
name: "invalid identity type",
217248
identity: &infrav1.AzureClusterIdentity{
@@ -226,7 +257,7 @@ func TestCreateAzureIdentityWithBindings(t *testing.T) {
226257
TenantID: "my-tenant-id",
227258
},
228259
},
229-
identityType: 0,
260+
identityType: -1,
230261
expectedErr: true,
231262
},
232263
}

config/crd/bases/infrastructure.cluster.x-k8s.io_azureclusteridentities.yaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ spec:
403403
type: object
404404
type: object
405405
clientID:
406-
description: Both User Assigned MSI and SP can use this field.
406+
description: ClientID is the service principal client ID. Both User
407+
Assigned MSI and SP can use this field.
407408
type: string
408409
clientSecret:
409410
description: ClientSecret is a secret reference which should contain
@@ -419,17 +420,19 @@ spec:
419420
type: string
420421
type: object
421422
resourceID:
422-
description: User assigned MSI resource id.
423+
description: ResourceID is the Azure resource ID for the User Assigned
424+
MSI resource. Not currently supported.
423425
type: string
424426
tenantID:
425-
description: Service principal primary tenant id.
427+
description: TenantID is the service principal primary tenant id.
426428
type: string
427429
type:
428-
description: UserAssignedMSI or Service Principal
430+
description: Type is the type of Azure Identity used. ServicePrincipal,
431+
ServicePrincipalCertificate, or ManualServicePrincipal.
429432
enum:
430433
- ServicePrincipal
431434
- ManualServicePrincipal
432-
- UserAssignedMSI
435+
- ServicePrincipalCertificate
433436
type: string
434437
required:
435438
- clientID

docs/book/src/topics/multitenancy.md

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
To enable single controller multi-tenancy, a different Identity can be added to the Azure Cluster that will be used as the Azure Identity when creating Azure resources related to that cluster.
44

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

7-
## Service Principal Identity
7+
## Service Principal With Client Password
88

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

@@ -22,9 +22,15 @@ spec:
2222
allowedNamespaces:
2323
list:
2424
- <cluster-namespace>
25+
```
26+
27+
A Kubernetes Secret should also be created to store the client password:
2528
29+
```bash
30+
kubectl create secret generic "${AZURE_CLUSTER_IDENTITY_SECRET_NAME}" --from-literal=clientSecret="${AZURE_CLIENT_SECRET}"
2631
```
27-
The password will need to be added in a secret similar to the following example:
32+
33+
The resulting Secret should look similar to the following example:
2834

2935
```yaml
3036
apiVersion: v1
@@ -36,7 +42,39 @@ data:
3642
clientSecret: <client-secret-of-SP-identity>
3743
```
3844
39-
OR the password can also be added as a Certificate:
45+
## Service Principal With Certificate
46+
47+
Once a new SP Identity is created in Azure, the corresponding values should be used to create an `AzureClusterIdentity` resource:
48+
49+
```yaml
50+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
51+
kind: AzureClusterIdentity
52+
metadata:
53+
name: example-identity
54+
namespace: default
55+
spec:
56+
type: ServicePrincipalCertificate
57+
tenantID: <azure-tenant-id>
58+
clientID: <client-id-of-SP-identity>
59+
clientSecret: {"name":"<secret-name-for-client-password>","namespace":"default"}
60+
allowedNamespaces:
61+
list:
62+
- <cluster-namespace>
63+
```
64+
65+
If needed, convert the PEM file to PKCS12 and set a password:
66+
67+
```bash
68+
openssl pkcs12 -export -in fileWithCertAndPrivateKey.pem -out ad-sp-cert.pfx -passout pass:<password>
69+
```
70+
71+
Create a k8s secret with the certificate and password:
72+
73+
```bash
74+
kubectl create secret generic "${AZURE_CLUSTER_IDENTITY_SECRET_NAME}" --from-file=certificate=ad-sp-cert.pfx --from-literal=password=<password>
75+
```
76+
77+
The resulting Secret should look similar to the following example:
4078

4179
```yaml
4280
apiVersion: v1
@@ -53,6 +91,7 @@ data:
5391

5492
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.
5593
To use this type of identity, set the identity type as `ManualServicePrincipal` in `AzureClusterIdentity`. For example,
94+
5695
```yaml
5796
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
5897
kind: AzureClusterIdentity
@@ -68,9 +107,11 @@ spec:
68107
list:
69108
- <cluster-namespace>
70109
```
110+
71111
The rest of the configuration is the same as that of service principal identity. This useful in scenarios where you don't want to have a dependency on [aad-pod-identity](https://azure.github.io/aad-pod-identity).
72112

73113
## allowedNamespaces
114+
74115
AllowedNamespaces is used to identify the namespaces the clusters are allowed to use the identity from. Namespaces can be selected either using an array of namespaces or with label selector.
75116
An empty allowedNamespaces object indicates that AzureClusters can use this identity from any namespace.
76117
If this object is nil, no namespaces will be allowed (default behaviour, if this field is not provided)

0 commit comments

Comments
 (0)