Skip to content

Commit 4a27d62

Browse files
committed
Use Client Cert Auth for ARO HCP deployments
Use Client Certificate Authentication for ARO HCP deployments. HyperShift will pass the needed environment variables for this authentication method: ARO_HCP_MI_CLIENT_ID, ARO_HCP_TENANT_ID, and ARO_HCP_CLIENT_CERTIFICATE_PATH. Signed-off-by: Bryan Cox <[email protected]>
1 parent 36fa262 commit 4a27d62

File tree

2 files changed

+128
-7
lines changed

2 files changed

+128
-7
lines changed

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)