Skip to content

Commit

Permalink
Use Client Cert Auth for ARO HCP deployments
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
bryan-cox committed Nov 14, 2024
1 parent 9305d20 commit f8e2bcd
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 6 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/aws/aws-sdk-go v1.38.49
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/florianl/go-nfqueue v1.3.2
github.com/fsnotify/fsnotify v1.7.0
github.com/go-logr/logr v1.4.2
github.com/go-logr/zapr v1.3.0
github.com/google/go-cmp v0.6.0
Expand Down Expand Up @@ -70,7 +71,6 @@ require (
github.com/evanphx/json-patch v5.7.0+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-openapi/errors v0.21.0 // indirect
Expand Down
41 changes: 36 additions & 5 deletions pkg/dns/azure/client/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@ package client

import (
"errors"
"fmt"
"os"
"strings"
"sync"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/azure"
"github.com/jongio/azidext/go/azidext"

"github.com/openshift/cluster-ingress-operator/pkg/util/filewatcher"
)

var watchCertificateFileOnce sync.Once

func getAuthorizerForResource(config Config) (autorest.Authorizer, error) {
var cloudConfig cloud.Configuration
switch config.Environment {
Expand Down Expand Up @@ -70,18 +76,43 @@ func getAuthorizerForResource(config Config) (autorest.Authorizer, error) {
}

var cred azcore.TokenCredential
// Managed Identity Override for ARO HCP
// Managed Identity Override for ARO HCP. In ARO HCP, we ignore the values provided for AZURE_TENANT_ID and
// AZURE_CLIENT_ID and use ARO_HCP_TENANT_ID and ARO_HCP_MI_CLIENT_ID instead.
managedIdentityClientID := os.Getenv("ARO_HCP_MI_CLIENT_ID")
if managedIdentityClientID != "" {
options := azidentity.ManagedIdentityCredentialOptions{
options := &azidentity.ClientCertificateCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: cloudConfig,
},
ID: azidentity.ClientID(managedIdentityClientID),
SendCertificateChain: true,
}

var err error
cred, err = azidentity.NewManagedIdentityCredential(&options)
tenantID := os.Getenv("ARO_HCP_TENANT_ID")
certPath := os.Getenv("ARO_HCP_CLIENT_CERTIFICATE_PATH")

certData, err := os.ReadFile(certPath)
if err != nil {
return nil, fmt.Errorf("failed to read certificate file %q: %w", certPath, err)
}

certs, key, err := azidentity.ParseCertificates(certData, nil)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate data %q: %w", certPath, err)
}

// Watch the certificate for changes; if the certificate changes, the pod will be restarted.
// This starts only one occurrence of the file watcher, which watches the file, certPath.
var fileWatcherError error
watchCertificateFileOnce.Do(func() {
if err = filewatcher.WatchFileForChanges(certPath); err != nil {
fileWatcherError = err
}
})
if fileWatcherError != nil {
return nil, fmt.Errorf("failed to watch certificate file %q: %w", certPath, fileWatcherError)
}

cred, err = azidentity.NewClientCertificateCredential(tenantID, managedIdentityClientID, certs, key, options)
if err != nil {
return nil, err
}
Expand Down
65 changes: 65 additions & 0 deletions pkg/util/filewatcher/filewatcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package filewatcher

import (
"os"
"path/filepath"

"github.com/fsnotify/fsnotify"
logf "github.com/openshift/cluster-ingress-operator/pkg/log"
)

var log = logf.Logger.WithName("filewatcher")

// WatchFileForChanges watches the file, fileToWatch, for changes. If the file contents have changed, the pod this
// function is running on will be restarted.
func WatchFileForChanges(fileToWatch string) error {
log.Info("Starting the file change watcher")

// Update the file path to watch in case this is a symlink
fileToWatch, err := filepath.EvalSymlinks(fileToWatch)
if err != nil {
return err
}
log.Info("Watching file", "file", fileToWatch)

// Start the file watcher to monitor file changes
go func() {
if err := checkForFileChanges(fileToWatch); err != nil {
log.Error(err, "Error checking for file changes")
}
}()

return nil
}

// checkForFileChanges starts a new file watcher. If the file is changed, the pod running this function will exit.
func checkForFileChanges(path string) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}

go func() {
for {
select {
case event, ok := <-watcher.Events:
if ok && (event.Has(fsnotify.Write) || event.Has(fsnotify.Chmod) || event.Has(fsnotify.Remove)) {
log.Info("file was modified, exiting...", "file", event.Name)
os.Exit(0)
}
case err, ok := <-watcher.Errors:
if ok {
log.Error(err, "file watcher error")
} else {
log.Error(err, "failed to retrieve watcher error")
}
}
}
}()

if err = watcher.Add(path); err != nil {
return err
}

return nil
}

0 comments on commit f8e2bcd

Please sign in to comment.