Skip to content

Commit

Permalink
Enable Azure Workload Identity authentication.
Browse files Browse the repository at this point in the history
Authenticate with an Azure Workload Identity (AZWI) serviceaccount token
when client secret is absent from auth config.
  • Loading branch information
jstuever committed Jun 1, 2023
1 parent 8e64b2f commit b37da5a
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 20 deletions.
2 changes: 2 additions & 0 deletions manifests/00-ingress-credentials-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ metadata:
include.release.openshift.io/self-managed-high-availability: "true"
include.release.openshift.io/single-node-developer: "true"
spec:
serviceAccountNames:
- ingress-operator
secretRef:
name: cloud-credentials
namespace: openshift-ingress-operator
Expand Down
66 changes: 59 additions & 7 deletions pkg/dns/azure/client/auth.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package client

import (
"errors"
"os"
"strings"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
Expand Down Expand Up @@ -35,14 +37,64 @@ func getAuthorizerForResource(config Config) (autorest.Authorizer, error) {
},
}
}
options := azidentity.ClientSecretCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: cloudConfig,
},

// Fallback to using tenant ID from env variable if not set.
if strings.TrimSpace(config.TenantID) == "" {
config.TenantID = os.Getenv("AZURE_TENANT_ID")
if strings.TrimSpace(config.TenantID) == "" {
return nil, errors.New("empty tenant ID")
}
}

// Fallback to using client ID from env variable if not set.
if strings.TrimSpace(config.ClientID) == "" {
config.ClientID = os.Getenv("AZURE_CLIENT_ID")
if strings.TrimSpace(config.ClientID) == "" {
return nil, errors.New("empty client ID")
}
}
cred, err := azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, &options)
if err != nil {
return nil, err

// Fallback to using client secret from env variable if not set.
if strings.TrimSpace(config.ClientSecret) == "" {
config.ClientSecret = os.Getenv("AZURE_CLIENT_SECRET")
// Skip validation; fallback to token (below) if env variable is also not set.
}

// Fallback to using federated token file from env variable if not set.
if strings.TrimSpace(config.FederatedTokenFile) == "" {
config.FederatedTokenFile = os.Getenv("AZURE_FEDERATED_TOKEN_FILE")
if strings.TrimSpace(config.FederatedTokenFile) == "" {
// Default to a generic token file location.
config.FederatedTokenFile = "/var/run/secrets/openshift/serviceaccount/token"
}
}

var cred azcore.TokenCredential
if strings.TrimSpace(config.ClientSecret) == "" {
options := azidentity.WorkloadIdentityCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: cloudConfig,
},
ClientID: config.ClientID,
TenantID: config.TenantID,
TokenFilePath: config.FederatedTokenFile,
}
var err error
cred, err = azidentity.NewWorkloadIdentityCredential(&options)
if err != nil {
return nil, err
}
} else {
options := azidentity.ClientSecretCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: cloudConfig,
},
}
var err error
cred, err = azidentity.NewClientSecretCredential(config.TenantID, config.ClientID, config.ClientSecret, &options)
if err != nil {
return nil, err
}
}

scope := endpointToScope(config.Environment.TokenAudience)
Expand Down
21 changes: 17 additions & 4 deletions pkg/dns/azure/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,24 @@ type DNSClient interface {
}

type Config struct {
Environment azure.Environment
// Environment describes the Azure environment: ChinaCloud,
// USGovernmentCloud, PublicCloud, or AzureStackCloud. If empty,
// AzureStackCloud is assumed.
Environment azure.Environment
// SubscriptionID is the subscription id for the Azure identity.
SubscriptionID string
ClientID string
ClientSecret string
TenantID string
// ClientID is an Azure application client id.
ClientID string
// ClientSecret is an Azure application client secret. It is required
// if Azure workload identity is not used.
ClientSecret string
// FederatedTokenFile is the path to a file containing a workload
// identity token. If FederatedTokenFile is specified and
// AzureWorkloadIdentityEnabled is true, then Azure workload identity is
// used instead of using a client secret.
FederatedTokenFile string
// TenantID is the Azure tenant ID.
TenantID string
}

// ARecord is a DNS A record.
Expand Down
13 changes: 8 additions & 5 deletions pkg/dns/azure/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ type Config struct {
ClientID string
// ClientSecret is an azure service principal's credential.
ClientSecret string
// FederatedTokenFile is an azure federated token file.
FederatedTokenFile string
// TenantID is the azure identity's tenant ID.
TenantID string
// SubscriptionID is the azure identity's subscription ID.
Expand Down Expand Up @@ -73,11 +75,12 @@ func NewProvider(config Config, operatorReleaseVersion string) (dns.Provider, er
return nil, fmt.Errorf("could not determine cloud environment: %w", err)
}
c, err := client.New(client.Config{
Environment: env,
SubscriptionID: config.SubscriptionID,
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
TenantID: config.TenantID,
Environment: env,
SubscriptionID: config.SubscriptionID,
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
FederatedTokenFile: config.FederatedTokenFile,
TenantID: config.TenantID,
}, userAgent(operatorReleaseVersion))
if err != nil {
return nil, err
Expand Down
8 changes: 4 additions & 4 deletions pkg/manifests/bindata.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit b37da5a

Please sign in to comment.