Skip to content

Commit 5e0bf6c

Browse files
Merge pull request #323 from bryan-cox/aro-hcp-cert-auth
OCPBUGS-42434: Add the ability to auth via certs without storing them in etcd secret
2 parents 63bdc30 + c12eb70 commit 5e0bf6c

File tree

7 files changed

+147
-27
lines changed

7 files changed

+147
-27
lines changed

api/v1beta1/azureclusteridentity_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ type AzureClusterIdentitySpec struct {
5959
// ClientSecret is a secret reference which should contain either a Service Principal password or certificate secret.
6060
// +optional
6161
ClientSecret corev1.SecretReference `json:"clientSecret,omitempty"`
62+
// CertPath is the path where certificates exist. When set, it takes precedence over ClientSecret for types that use certs like ServicePrincipalCertificate.
63+
// +optional
64+
CertPath string `json:"certPath,omitempty"`
6265
// TenantID is the service principal primary tenant id.
6366
TenantID string `json:"tenantID"`
6467
// AllowedNamespaces is used to identify the namespaces the clusters are allowed to use the identity from.

azure/scope/identity.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package scope
1818

1919
import (
2020
"context"
21+
"os"
2122
"reflect"
2223

2324
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
@@ -116,11 +117,23 @@ func (p *AzureCredentialsProvider) GetTokenCredential(ctx context.Context, resou
116117
cred, authErr = azidentity.NewClientSecretCredential(p.GetTenantID(), p.Identity.Spec.ClientID, clientSecret, &options)
117118

118119
case infrav1.ServicePrincipalCertificate:
119-
clientSecret, err := p.GetClientSecret(ctx)
120-
if err != nil {
121-
return nil, errors.Wrap(err, "failed to get client secret")
120+
var (
121+
certsContent []byte
122+
err error
123+
)
124+
if p.Identity.Spec.CertPath != "" {
125+
certsContent, err = os.ReadFile(p.Identity.Spec.CertPath)
126+
if err != nil {
127+
return nil, errors.Wrap(err, "failed to read certificate file")
128+
}
129+
} else {
130+
clientSecret, err := p.GetClientSecret(ctx)
131+
if err != nil {
132+
return nil, errors.Wrap(err, "failed to get client secret")
133+
}
134+
certsContent = []byte(clientSecret)
122135
}
123-
certs, key, err := azidentity.ParseCertificates([]byte(clientSecret), nil)
136+
certs, key, err := azidentity.ParseCertificates(certsContent, nil)
124137
if err != nil {
125138
return nil, errors.Wrap(err, "failed to parse certificate data")
126139
}
@@ -182,7 +195,7 @@ func (p *AzureCredentialsProvider) Type() infrav1.IdentityType {
182195
// This does not include managed identities.
183196
func (p *AzureCredentialsProvider) hasClientSecret() bool {
184197
switch p.Identity.Spec.Type {
185-
case infrav1.ServicePrincipal, infrav1.ManualServicePrincipal, infrav1.ServicePrincipalCertificate:
198+
case infrav1.ServicePrincipal, infrav1.ManualServicePrincipal:
186199
return true
187200
default:
188201
return false

azure/scope/identity_test.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,11 +162,10 @@ func TestHasClientSecret(t *testing.T) {
162162
name: "service principal with certificate",
163163
identity: &infrav1.AzureClusterIdentity{
164164
Spec: infrav1.AzureClusterIdentitySpec{
165-
Type: infrav1.ServicePrincipalCertificate,
166-
ClientSecret: corev1.SecretReference{Name: "my-client-secret"},
165+
Type: infrav1.ServicePrincipalCertificate,
167166
},
168167
},
169-
want: true,
168+
want: false,
170169
},
171170
{
172171
name: "manual service principal",
@@ -301,9 +300,7 @@ func TestGetTokenCredential(t *testing.T) {
301300
Spec: infrav1.AzureClusterIdentitySpec{
302301
Type: infrav1.ServicePrincipalCertificate,
303302
TenantID: fakeTenantID,
304-
ClientSecret: corev1.SecretReference{
305-
Name: "test-identity-secret",
306-
},
303+
CertPath: "../../test/setup/certificate",
307304
},
308305
},
309306
secret: &corev1.Secret{
@@ -315,6 +312,25 @@ func TestGetTokenCredential(t *testing.T) {
315312
},
316313
},
317314
},
315+
{
316+
name: "service principal certificate with certificate filepath",
317+
cluster: &infrav1.AzureCluster{
318+
Spec: infrav1.AzureClusterSpec{
319+
AzureClusterClassSpec: infrav1.AzureClusterClassSpec{
320+
IdentityRef: &corev1.ObjectReference{
321+
Kind: infrav1.AzureClusterIdentityKind,
322+
},
323+
},
324+
},
325+
},
326+
identity: &infrav1.AzureClusterIdentity{
327+
Spec: infrav1.AzureClusterIdentitySpec{
328+
Type: infrav1.ServicePrincipalCertificate,
329+
TenantID: fakeTenantID,
330+
CertPath: "../../test/setup/certificate",
331+
},
332+
},
333+
},
318334
{
319335
name: "user-assigned identity",
320336
cluster: &infrav1.AzureCluster{

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ spec:
123123
type: object
124124
x-kubernetes-map-type: atomic
125125
type: object
126+
certPath:
127+
description: CertPath is the path where certificates exist. When set,
128+
it takes precedence over ClientSecret for types that use certs like
129+
ServicePrincipalCertificate.
130+
type: string
126131
clientID:
127132
description: |-
128133
ClientID is the service principal client ID.

controllers/asosecret_controller.go

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package controllers
1919
import (
2020
"context"
2121
"fmt"
22+
"os"
2223

2324
asoconfig "github.com/Azure/azure-service-operator/v2/pkg/common/config"
2425
"github.com/pkg/errors"
@@ -288,23 +289,33 @@ func (asos *ASOSecretReconciler) createSecretFromClusterIdentity(ctx context.Con
288289
return newASOSecret, nil
289290
}
290291

291-
// Fetch identity secret, if it exists
292-
key = types.NamespacedName{
293-
Namespace: identity.Spec.ClientSecret.Namespace,
294-
Name: identity.Spec.ClientSecret.Name,
295-
}
296-
identitySecret := &corev1.Secret{}
297-
err := asos.Get(ctx, key, identitySecret)
298-
if err != nil {
299-
return nil, errors.Wrap(err, "failed to fetch AzureClusterIdentity secret")
300-
}
292+
if identity.Spec.CertPath != "" {
293+
certsContent, err := os.ReadFile(identity.Spec.CertPath)
294+
if err != nil {
295+
return nil, errors.Wrap(err, "failed to read certificate file")
296+
}
301297

302-
switch identity.Spec.Type {
303-
case infrav1.ServicePrincipal, infrav1.ManualServicePrincipal:
304-
newASOSecret.Data[asoconfig.AzureClientSecret] = identitySecret.Data[scope.AzureSecretKey]
305-
case infrav1.ServicePrincipalCertificate:
306-
newASOSecret.Data[asoconfig.AzureClientCertificate] = identitySecret.Data["certificate"]
307-
newASOSecret.Data[asoconfig.AzureClientCertificatePassword] = identitySecret.Data["password"]
298+
newASOSecret.Data[asoconfig.AzureClientCertificate] = certsContent
299+
newASOSecret.Data[asoconfig.AzureClientCertificatePassword] = []byte{}
300+
} else {
301+
// Fetch identity secret, if it exists
302+
key = types.NamespacedName{
303+
Namespace: identity.Spec.ClientSecret.Namespace,
304+
Name: identity.Spec.ClientSecret.Name,
305+
}
306+
identitySecret := &corev1.Secret{}
307+
err := asos.Get(ctx, key, identitySecret)
308+
if err != nil {
309+
return nil, errors.Wrap(err, "failed to fetch AzureClusterIdentity secret")
310+
}
311+
312+
switch identity.Spec.Type {
313+
case infrav1.ServicePrincipal, infrav1.ManualServicePrincipal:
314+
newASOSecret.Data[asoconfig.AzureClientSecret] = identitySecret.Data[scope.AzureSecretKey]
315+
case infrav1.ServicePrincipalCertificate:
316+
newASOSecret.Data[asoconfig.AzureClientCertificate] = identitySecret.Data["certificate"]
317+
newASOSecret.Data[asoconfig.AzureClientCertificatePassword] = identitySecret.Data["password"]
318+
}
308319
}
309320
return newASOSecret, nil
310321
}

controllers/asosecret_controller_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,31 @@ func TestASOSecretReconcile(t *testing.T) {
134134
}
135135
}),
136136
},
137+
"should reconcile normally for AzureManagedControlPlane with IdentityRef configured of type Service Principal with Certificate": {
138+
clusterName: defaultAzureManagedControlPlane.Name,
139+
objects: []runtime.Object{
140+
getASOAzureManagedControlPlane(func(c *infrav1.AzureManagedControlPlane) {
141+
c.Spec.IdentityRef = &corev1.ObjectReference{
142+
Name: "my-azure-cluster-identity",
143+
Namespace: "default",
144+
}
145+
}),
146+
getASOAzureClusterIdentity(func(identity *infrav1.AzureClusterIdentity) {
147+
identity.Spec.Type = infrav1.ServicePrincipalCertificate
148+
identity.Spec.CertPath = "../test/setup/certificate"
149+
}),
150+
defaultCluster,
151+
},
152+
asoSecret: getASOSecret(defaultAzureManagedControlPlane, func(s *corev1.Secret) {
153+
s.Data = map[string][]byte{
154+
"AZURE_SUBSCRIPTION_ID": []byte("fooSubscription"),
155+
"AZURE_TENANT_ID": []byte("fooTenant"),
156+
"AZURE_CLIENT_ID": []byte("fooClient"),
157+
"AZURE_CLIENT_CERTIFICATE_PASSWORD": []byte(""),
158+
"AZURE_CLIENT_CERTIFICATE": []byte("-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDjrdEr9P0TaUES\ndspE6cyo22NU8yhRrbYlV9VH2vWvnPsThXcxhnd+cUqdNEBswhwgFlUQcg/eSVxw\nrr+3nh+bFTZWPcY+1LQYxfpKGsrCXQfB82LDJIZDX4gHYrWf3Z272jXN1XeFAKti\nwDKgDXXuPH7r5lH7vC3RXeAffqLwQJhZf+NoHNtv9MH9IdUkQfmDFZtI/CQzCrb6\n+vOS6EmUD/Q2FNHBzgxCguGqgNyBcQbxJ9Qng+ZjIFuhGYXJlsyRUtexyzTR5/v0\nVNK8UsZgRBFhXqrBv/RoCCG+xVJYtmd0QsrvNzDqG6QnjUB21zVXqzKEkW2gRtjX\ncw4vYQehAgMBAAECggEAS6xtjg0nAokk0jS+ZOpKlkMZAFaza3ZvyHipkHDz4PMt\ntl7Rb5oQZGvWT2rbEOrxey7BBi7LHGhIu8ExQp/hRGPoBAETP7XlyCghWPkPtEtE\ndU/mXxLoN0NszHuf/2si7pmH8YqGZ6QB0tgr22ut60mbK+AJFsEEf4aSpBUspepJ\n2800sQHsqPE6L6kYkfZ2GRRY1V9vUrYEODKZpWzMhN3UA9nAKH9PB6xvP2OdyMNh\nhKgmUUMNIFtwr8pZlJn60cf0UrWrc5CvqQLuaGYlzDgUQGV4JEVjqm9F6lMfEPUw\neN70MVe1pcLeLq2rGCVWU3gakh/HvJqlR/sa546HgwKBgQDyf1vkyX4w5sboi6DJ\ncl5dMULtMMRpB1OaMFVOJjI9gZJ8mCdRjqXdYo5aS2KIqxie8tGG9+SohxDAWl4t\nlSUtDsE44fSmILqC5zIawNRQnnkv0X8LwmYu0Qd7YAjJMlLTWyDRsjD9XRq4nsR+\nmJVwrt85iSpS5UFyryEzPbFj0wKBgQDwWzraeN0Eccf1iIYmQsYy+yMEAlHNR5yi\ngPXuAhSybv2JReRhdUb39hLr/LvKw0ZeXiLWXmYUGpbyzPyXIm0s+PL3LWl65GTF\nl+cfV5wfAdDkk6rAdEPEE2pxN85ChyaPYPoYr0ohmV97VQcYc5FqY+j1tM6R1RDt\n/fWBSa8iOwKBgQCpa1dtWWTDj4gqUdrswu2wmEkU47xlUIwVLm164u64z/zi9X6K\n2WmCaWfhJ8fYigjyi9zdOfXT1EFc0gX4PLozZ5qRPjQpmLYV3KbB0DTFemJaiTgE\npDW1wa5DgQ3CW1lIduNP/fmCGfkgQTQw6jOF/XbRgMZEEg2OrVI5tYFopwKBgER9\niqjEth5VGejCjY+LiZTvcUvsKUk4tc6stueqmiE6dW7PhsOqup1f9oZej1i5Cm1L\nn9u8LJRf+1GWzgd3HOsqyXlb7GnDeV/A6HBK88b2KoNn/Mk4mDLgYX1/rHvSrU9A\nECRGlvY6ETZAxXPXQsGxVKnnatGtiFR5AKNlzs0PAoGAa5+X+DUqGh9aE5ID3wrv\njkjxQ2KLFJCNSq8f9GSuvpvgXstHh6wKoM6vMwIShjgXuURH8Ub4uhRsWnxMildF\n7EE+QaWU9jnCm2HQYArfXrAWw6DBudiSkBqgKc6HjDHun5fXlYUo8UesNMQOrg7b\nbydQZ5/4V/1oSWPETk7jSr0=\n-----END PRIVATE KEY-----\n-----BEGIN CERTIFICATE-----\nMIIDCTCCAfGgAwIBAgIUFSntEn+Tv6HM2xJReECJpJcC7iUwDQYJKoZIhvcNAQEL\nBQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDEwODE5NTQxNFoXDTM0MDEw\nNTE5NTQxNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF\nAAOCAQ8AMIIBCgKCAQEA463RK/T9E2lBEnbKROnMqNtjVPMoUa22JVfVR9r1r5z7\nE4V3MYZ3fnFKnTRAbMIcIBZVEHIP3klccK6/t54fmxU2Vj3GPtS0GMX6ShrKwl0H\nwfNiwySGQ1+IB2K1n92du9o1zdV3hQCrYsAyoA117jx+6+ZR+7wt0V3gH36i8ECY\nWX/jaBzbb/TB/SHVJEH5gxWbSPwkMwq2+vrzkuhJlA/0NhTRwc4MQoLhqoDcgXEG\n8SfUJ4PmYyBboRmFyZbMkVLXscs00ef79FTSvFLGYEQRYV6qwb/0aAghvsVSWLZn\ndELK7zcw6hukJ41Adtc1V6syhJFtoEbY13MOL2EHoQIDAQABo1MwUTAdBgNVHQ4E\nFgQUfry/KDtamwMlRQsFPbBhzdv2U5cwHwYDVR0jBBgwFoAUfry/KDtamwMlRQsF\nPbBhzdv2U5cwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAyYst\nVvewKRRpuYRWc4XG6WnYphUdyZLMoIlq0syZ1aj6YbqoK9NMHAYEnCvSov6zIZOa\ntrhuUcf9GFz5e0iJ2zIlDc312Iwsv41xiC/bs16kEn8Yf/SujEXasj7vmA3HrFWf\nwZTH/yFL5azo/f+lA1Q28YwqFpHmle0y0O53Uth4p0tmwlnu+CrO9fHp3kTlb7fD\n6mqfk9Nrt8tOC4aHYDoqtYUgZhx58xsHMOTetKeRlp8HMF9oROtriz4nYm6IhTwo\n5k1A13S3BjaxkZCyPXCgXssuXagNLasrr5Qq+Vgdb/nDhVehV8+Z4J0Ynzy9MZsE\nH1N1NfMtsA+PEqtPXA==\n-----END CERTIFICATE-----\n"),
159+
}
160+
}),
161+
},
137162
"should reconcile normally for AzureCluster with an IdentityRef of type WorkloadIdentity": {
138163
clusterName: defaultAzureCluster.Name,
139164
objects: []runtime.Object{

test/setup/certificate

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDjrdEr9P0TaUES
3+
dspE6cyo22NU8yhRrbYlV9VH2vWvnPsThXcxhnd+cUqdNEBswhwgFlUQcg/eSVxw
4+
rr+3nh+bFTZWPcY+1LQYxfpKGsrCXQfB82LDJIZDX4gHYrWf3Z272jXN1XeFAKti
5+
wDKgDXXuPH7r5lH7vC3RXeAffqLwQJhZf+NoHNtv9MH9IdUkQfmDFZtI/CQzCrb6
6+
+vOS6EmUD/Q2FNHBzgxCguGqgNyBcQbxJ9Qng+ZjIFuhGYXJlsyRUtexyzTR5/v0
7+
VNK8UsZgRBFhXqrBv/RoCCG+xVJYtmd0QsrvNzDqG6QnjUB21zVXqzKEkW2gRtjX
8+
cw4vYQehAgMBAAECggEAS6xtjg0nAokk0jS+ZOpKlkMZAFaza3ZvyHipkHDz4PMt
9+
tl7Rb5oQZGvWT2rbEOrxey7BBi7LHGhIu8ExQp/hRGPoBAETP7XlyCghWPkPtEtE
10+
dU/mXxLoN0NszHuf/2si7pmH8YqGZ6QB0tgr22ut60mbK+AJFsEEf4aSpBUspepJ
11+
2800sQHsqPE6L6kYkfZ2GRRY1V9vUrYEODKZpWzMhN3UA9nAKH9PB6xvP2OdyMNh
12+
hKgmUUMNIFtwr8pZlJn60cf0UrWrc5CvqQLuaGYlzDgUQGV4JEVjqm9F6lMfEPUw
13+
eN70MVe1pcLeLq2rGCVWU3gakh/HvJqlR/sa546HgwKBgQDyf1vkyX4w5sboi6DJ
14+
cl5dMULtMMRpB1OaMFVOJjI9gZJ8mCdRjqXdYo5aS2KIqxie8tGG9+SohxDAWl4t
15+
lSUtDsE44fSmILqC5zIawNRQnnkv0X8LwmYu0Qd7YAjJMlLTWyDRsjD9XRq4nsR+
16+
mJVwrt85iSpS5UFyryEzPbFj0wKBgQDwWzraeN0Eccf1iIYmQsYy+yMEAlHNR5yi
17+
gPXuAhSybv2JReRhdUb39hLr/LvKw0ZeXiLWXmYUGpbyzPyXIm0s+PL3LWl65GTF
18+
l+cfV5wfAdDkk6rAdEPEE2pxN85ChyaPYPoYr0ohmV97VQcYc5FqY+j1tM6R1RDt
19+
/fWBSa8iOwKBgQCpa1dtWWTDj4gqUdrswu2wmEkU47xlUIwVLm164u64z/zi9X6K
20+
2WmCaWfhJ8fYigjyi9zdOfXT1EFc0gX4PLozZ5qRPjQpmLYV3KbB0DTFemJaiTgE
21+
pDW1wa5DgQ3CW1lIduNP/fmCGfkgQTQw6jOF/XbRgMZEEg2OrVI5tYFopwKBgER9
22+
iqjEth5VGejCjY+LiZTvcUvsKUk4tc6stueqmiE6dW7PhsOqup1f9oZej1i5Cm1L
23+
n9u8LJRf+1GWzgd3HOsqyXlb7GnDeV/A6HBK88b2KoNn/Mk4mDLgYX1/rHvSrU9A
24+
ECRGlvY6ETZAxXPXQsGxVKnnatGtiFR5AKNlzs0PAoGAa5+X+DUqGh9aE5ID3wrv
25+
jkjxQ2KLFJCNSq8f9GSuvpvgXstHh6wKoM6vMwIShjgXuURH8Ub4uhRsWnxMildF
26+
7EE+QaWU9jnCm2HQYArfXrAWw6DBudiSkBqgKc6HjDHun5fXlYUo8UesNMQOrg7b
27+
bydQZ5/4V/1oSWPETk7jSr0=
28+
-----END PRIVATE KEY-----
29+
-----BEGIN CERTIFICATE-----
30+
MIIDCTCCAfGgAwIBAgIUFSntEn+Tv6HM2xJReECJpJcC7iUwDQYJKoZIhvcNAQEL
31+
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDEwODE5NTQxNFoXDTM0MDEw
32+
NTE5NTQxNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
33+
AAOCAQ8AMIIBCgKCAQEA463RK/T9E2lBEnbKROnMqNtjVPMoUa22JVfVR9r1r5z7
34+
E4V3MYZ3fnFKnTRAbMIcIBZVEHIP3klccK6/t54fmxU2Vj3GPtS0GMX6ShrKwl0H
35+
wfNiwySGQ1+IB2K1n92du9o1zdV3hQCrYsAyoA117jx+6+ZR+7wt0V3gH36i8ECY
36+
WX/jaBzbb/TB/SHVJEH5gxWbSPwkMwq2+vrzkuhJlA/0NhTRwc4MQoLhqoDcgXEG
37+
8SfUJ4PmYyBboRmFyZbMkVLXscs00ef79FTSvFLGYEQRYV6qwb/0aAghvsVSWLZn
38+
dELK7zcw6hukJ41Adtc1V6syhJFtoEbY13MOL2EHoQIDAQABo1MwUTAdBgNVHQ4E
39+
FgQUfry/KDtamwMlRQsFPbBhzdv2U5cwHwYDVR0jBBgwFoAUfry/KDtamwMlRQsF
40+
PbBhzdv2U5cwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAyYst
41+
VvewKRRpuYRWc4XG6WnYphUdyZLMoIlq0syZ1aj6YbqoK9NMHAYEnCvSov6zIZOa
42+
trhuUcf9GFz5e0iJ2zIlDc312Iwsv41xiC/bs16kEn8Yf/SujEXasj7vmA3HrFWf
43+
wZTH/yFL5azo/f+lA1Q28YwqFpHmle0y0O53Uth4p0tmwlnu+CrO9fHp3kTlb7fD
44+
6mqfk9Nrt8tOC4aHYDoqtYUgZhx58xsHMOTetKeRlp8HMF9oROtriz4nYm6IhTwo
45+
5k1A13S3BjaxkZCyPXCgXssuXagNLasrr5Qq+Vgdb/nDhVehV8+Z4J0Ynzy9MZsE
46+
H1N1NfMtsA+PEqtPXA==
47+
-----END CERTIFICATE-----

0 commit comments

Comments
 (0)