Skip to content

Commit 4c7e4ef

Browse files
Merge pull request #1131 from bryan-cox/HOSTEDCP-1994
HOSTEDCP-2019: Use Client Cert Auth for ARO HCP deployments
2 parents acc522a + 4a27d62 commit 4c7e4ef

File tree

7 files changed

+430
-8
lines changed

7 files changed

+430
-8
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/IBM/platform-services-go-sdk v0.55.0
2323
github.com/aws/aws-sdk-go v1.50.35
2424
github.com/davecgh/go-spew v1.1.1
25+
github.com/fsnotify/fsnotify v1.7.0
2526
github.com/ghodss/yaml v1.0.0
2627
github.com/golang-jwt/jwt v3.2.2+incompatible
2728
github.com/google/go-cmp v0.6.0
@@ -49,6 +50,7 @@ require (
4950
k8s.io/apimachinery v0.30.1
5051
k8s.io/client-go v0.30.1
5152
k8s.io/klog/v2 v2.120.1
53+
k8s.io/kubernetes v1.30.2
5254
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
5355
)
5456

@@ -80,7 +82,6 @@ require (
8082
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
8183
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
8284
github.com/felixge/httpsnoop v1.0.4 // indirect
83-
github.com/fsnotify/fsnotify v1.7.0 // indirect
8485
github.com/go-logr/logr v1.4.1 // indirect
8586
github.com/go-logr/stdr v1.2.2 // indirect
8687
github.com/go-openapi/errors v0.20.3 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,8 @@ k8s.io/kube-aggregator v0.30.1 h1:ymR2BsxDacTKwzKTuNhGZttuk009c+oZbSeD+IPX5q4=
17211721
k8s.io/kube-aggregator v0.30.1/go.mod h1:SFbqWsM6ea8dHd3mPLsZFzJHbjBOS5ykIgJh4znZ5iQ=
17221722
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
17231723
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
1724+
k8s.io/kubernetes v1.30.2 h1:11WhS78OYX/lnSy6TXxPO6Hk+E5K9ZNrEsk9JgMSX8I=
1725+
k8s.io/kubernetes v1.30.2/go.mod h1:yPbIk3MhmhGigX62FLJm+CphNtjxqCvAIFQXup6RKS0=
17241726
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
17251727
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
17261728
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=

pkg/storage/azure/azure.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"k8s.io/apimachinery/pkg/util/rand"
3030
kcorelisters "k8s.io/client-go/listers/core/v1"
3131
"k8s.io/klog/v2"
32+
"k8s.io/kubernetes/pkg/util/interrupt"
3233

3334
configv1 "github.com/openshift/api/config/v1"
3435
imageregistryv1 "github.com/openshift/api/imageregistry/v1"
@@ -373,15 +374,44 @@ func (d *driver) storageAccountsClient(cfg *Azure, environment autorestazure.Env
373374
// Managed Identity Override for ARO HCP
374375
managedIdentityClientID := os.Getenv("ARO_HCP_MI_CLIENT_ID")
375376
if managedIdentityClientID != "" {
376-
options := azidentity.ManagedIdentityCredentialOptions{
377+
klog.V(2).Info("Using client certification Azure authentication for ARO HCP")
378+
options := &azidentity.ClientCertificateCredentialOptions{
377379
ClientOptions: azcore.ClientOptions{
378380
Cloud: cloudConfig,
379381
},
380-
ID: azidentity.ClientID(managedIdentityClientID),
382+
SendCertificateChain: true,
381383
}
382384

383-
var err error
384-
cred, err = azidentity.NewManagedIdentityCredential(&options)
385+
tenantID := os.Getenv("ARO_HCP_TENANT_ID")
386+
certPath := os.Getenv("ARO_HCP_CLIENT_CERTIFICATE_PATH")
387+
388+
certData, err := os.ReadFile(certPath)
389+
if err != nil {
390+
return storage.AccountsClient{}, fmt.Errorf(`failed to read certificate file "%s": %v`, certPath, err)
391+
}
392+
393+
certs, key, err := azidentity.ParseCertificates(certData, []byte{})
394+
if err != nil {
395+
return storage.AccountsClient{}, fmt.Errorf(`failed to parse certificate data "%s": %v`, certPath, err)
396+
}
397+
398+
// Set up a watch on our config file; if it changes, we should exit -
399+
// (we don't have the ability to dynamically reload config changes).
400+
stopCh := make(chan struct{})
401+
err = interrupt.New(func(s os.Signal) {
402+
_, _ = fmt.Fprintf(os.Stderr, "interrupt: Gracefully shutting down ...\n")
403+
close(stopCh)
404+
}).Run(func() error {
405+
if err := azureclient.WatchForChanges(certPath, stopCh); err != nil {
406+
return err
407+
}
408+
return nil
409+
})
410+
if err != nil {
411+
return storage.AccountsClient{}, err
412+
}
413+
414+
cred, err = azidentity.NewClientCertificateCredential(tenantID, managedIdentityClientID, certs, key, options)
385415
if err != nil {
386416
return storage.AccountsClient{}, err
387417
}

pkg/storage/azure/azureclient/azureclient.go

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net/http"
88
"os"
9+
"path/filepath"
910
"strings"
1011

1112
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
@@ -21,6 +22,9 @@ import (
2122
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
2223
autorestazure "github.com/Azure/go-autorest/autorest/azure"
2324
"github.com/Azure/go-autorest/autorest/to"
25+
"github.com/fsnotify/fsnotify"
26+
"k8s.io/klog/v2"
27+
"k8s.io/kubernetes/pkg/util/interrupt"
2428
)
2529

2630
const (
@@ -105,13 +109,44 @@ func (c *Client) getCreds() (azcore.TokenCredential, error) {
105109
// Managed Identity Override for ARO HCP
106110
managedIdentityClientID := os.Getenv("ARO_HCP_MI_CLIENT_ID")
107111
if managedIdentityClientID != "" {
108-
options := azidentity.ManagedIdentityCredentialOptions{
112+
klog.V(2).Info("Using client certification Azure authentication for ARO HCP")
113+
options := &azidentity.ClientCertificateCredentialOptions{
109114
ClientOptions: azcore.ClientOptions{
110115
Cloud: c.clientOpts.Cloud,
111116
},
112-
ID: azidentity.ClientID(managedIdentityClientID),
117+
SendCertificateChain: true,
113118
}
114-
creds, err = azidentity.NewManagedIdentityCredential(&options)
119+
120+
tenantID := os.Getenv("ARO_HCP_TENANT_ID")
121+
certPath := os.Getenv("ARO_HCP_CLIENT_CERTIFICATE_PATH")
122+
123+
certData, err := os.ReadFile(certPath)
124+
if err != nil {
125+
return nil, fmt.Errorf(`failed to read certificate file "%s": %v`, certPath, err)
126+
}
127+
128+
certs, key, err := azidentity.ParseCertificates(certData, []byte{})
129+
if err != nil {
130+
return nil, fmt.Errorf(`failed to parse certificate data "%s": %v`, certPath, err)
131+
}
132+
133+
// Set up a watch on our config file; if it changes, we should exit -
134+
// (we don't have the ability to dynamically reload config changes).
135+
stopCh := make(chan struct{})
136+
err = interrupt.New(func(s os.Signal) {
137+
_, _ = fmt.Fprintf(os.Stderr, "interrupt: Gracefully shutting down ...\n")
138+
close(stopCh)
139+
}).Run(func() error {
140+
if err := WatchForChanges(certPath, stopCh); err != nil {
141+
return err
142+
}
143+
return nil
144+
})
145+
if err != nil {
146+
return nil, err
147+
}
148+
149+
creds, err = azidentity.NewClientCertificateCredential(tenantID, managedIdentityClientID, certs, key, options)
115150
if err != nil {
116151
return nil, err
117152
}
@@ -904,3 +939,59 @@ func (client *BlobClient) DeleteStorageContainer(ctx context.Context, containerN
904939
_, err := client.client.DeleteContainer(ctx, containerName, &azblob.DeleteContainerOptions{})
905940
return err
906941
}
942+
943+
// WatchForChanges closes stopCh if the configuration file changed.
944+
func WatchForChanges(configPath string, stopCh chan struct{}) error {
945+
configPath, err := filepath.Abs(configPath)
946+
if err != nil {
947+
return err
948+
}
949+
watcher, err := fsnotify.NewWatcher()
950+
if err != nil {
951+
return err
952+
}
953+
// Watch all symlinks for changes
954+
p := configPath
955+
maxDepth := 100
956+
for depth := 0; depth < maxDepth; depth++ {
957+
if err := watcher.Add(p); err != nil {
958+
return err
959+
}
960+
klog.V(2).Infof("Watching config file %s for changes", p)
961+
stat, err := os.Lstat(p)
962+
if err != nil {
963+
return err
964+
}
965+
// configmaps are usually symlinks
966+
if stat.Mode()&os.ModeSymlink > 0 {
967+
p, err = filepath.EvalSymlinks(p)
968+
if err != nil {
969+
return err
970+
}
971+
} else {
972+
break
973+
}
974+
}
975+
go func() {
976+
for {
977+
select {
978+
case <-stopCh:
979+
case event, ok := <-watcher.Events:
980+
if !ok {
981+
klog.Errorf("failed to watch config file %s", p)
982+
return
983+
}
984+
klog.V(2).Infof("Configuration file %s changed, exiting...", event.Name)
985+
close(stopCh)
986+
return
987+
case err, ok := <-watcher.Errors:
988+
if !ok {
989+
klog.Errorf("failed to watch config file %s", p)
990+
return
991+
}
992+
klog.Errorf("fsnotify error: %v", err)
993+
}
994+
}
995+
}()
996+
return nil
997+
}

0 commit comments

Comments
 (0)