mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-09-19 15:09:56 +00:00
47b202554e
This commit adds the Azure SDK for Azure key vault KMS integration to the Ceph CSI driver. Signed-off-by: Praveen M <m.praveen@ibm.com>
132 lines
5.3 KiB
Go
132 lines
5.3 KiB
Go
//go:build go1.18
|
|
// +build go1.18
|
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
package azidentity
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
|
|
)
|
|
|
|
const credNameWorkloadIdentity = "WorkloadIdentityCredential"
|
|
|
|
// WorkloadIdentityCredential supports Azure workload identity on Kubernetes.
|
|
// See [Azure Kubernetes Service documentation] for more information.
|
|
//
|
|
// [Azure Kubernetes Service documentation]: https://learn.microsoft.com/azure/aks/workload-identity-overview
|
|
type WorkloadIdentityCredential struct {
|
|
assertion, file string
|
|
cred *ClientAssertionCredential
|
|
expires time.Time
|
|
mtx *sync.RWMutex
|
|
}
|
|
|
|
// WorkloadIdentityCredentialOptions contains optional parameters for WorkloadIdentityCredential.
|
|
type WorkloadIdentityCredentialOptions struct {
|
|
azcore.ClientOptions
|
|
|
|
// AdditionallyAllowedTenants specifies additional tenants for which the credential may acquire tokens.
|
|
// Add the wildcard value "*" to allow the credential to acquire tokens for any tenant in which the
|
|
// application is registered.
|
|
AdditionallyAllowedTenants []string
|
|
// ClientID of the service principal. Defaults to the value of the environment variable AZURE_CLIENT_ID.
|
|
ClientID string
|
|
// DisableInstanceDiscovery should be set true only by applications authenticating in disconnected clouds, or
|
|
// private clouds such as Azure Stack. It determines whether the credential requests Microsoft Entra instance metadata
|
|
// from https://login.microsoft.com before authenticating. Setting this to true will skip this request, making
|
|
// the application responsible for ensuring the configured authority is valid and trustworthy.
|
|
DisableInstanceDiscovery bool
|
|
// TenantID of the service principal. Defaults to the value of the environment variable AZURE_TENANT_ID.
|
|
TenantID string
|
|
// TokenFilePath is the path of a file containing a Kubernetes service account token. Defaults to the value of the
|
|
// environment variable AZURE_FEDERATED_TOKEN_FILE.
|
|
TokenFilePath string
|
|
}
|
|
|
|
// NewWorkloadIdentityCredential constructs a WorkloadIdentityCredential. Service principal configuration is read
|
|
// from environment variables as set by the Azure workload identity webhook. Set options to override those values.
|
|
func NewWorkloadIdentityCredential(options *WorkloadIdentityCredentialOptions) (*WorkloadIdentityCredential, error) {
|
|
if options == nil {
|
|
options = &WorkloadIdentityCredentialOptions{}
|
|
}
|
|
ok := false
|
|
clientID := options.ClientID
|
|
if clientID == "" {
|
|
if clientID, ok = os.LookupEnv(azureClientID); !ok {
|
|
return nil, errors.New("no client ID specified. Check pod configuration or set ClientID in the options")
|
|
}
|
|
}
|
|
file := options.TokenFilePath
|
|
if file == "" {
|
|
if file, ok = os.LookupEnv(azureFederatedTokenFile); !ok {
|
|
return nil, errors.New("no token file specified. Check pod configuration or set TokenFilePath in the options")
|
|
}
|
|
}
|
|
tenantID := options.TenantID
|
|
if tenantID == "" {
|
|
if tenantID, ok = os.LookupEnv(azureTenantID); !ok {
|
|
return nil, errors.New("no tenant ID specified. Check pod configuration or set TenantID in the options")
|
|
}
|
|
}
|
|
w := WorkloadIdentityCredential{file: file, mtx: &sync.RWMutex{}}
|
|
caco := ClientAssertionCredentialOptions{
|
|
AdditionallyAllowedTenants: options.AdditionallyAllowedTenants,
|
|
ClientOptions: options.ClientOptions,
|
|
DisableInstanceDiscovery: options.DisableInstanceDiscovery,
|
|
}
|
|
cred, err := NewClientAssertionCredential(tenantID, clientID, w.getAssertion, &caco)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// we want "WorkloadIdentityCredential" in log messages, not "ClientAssertionCredential"
|
|
cred.client.name = credNameWorkloadIdentity
|
|
w.cred = cred
|
|
return &w, nil
|
|
}
|
|
|
|
// GetToken requests an access token from Microsoft Entra ID. Azure SDK clients call this method automatically.
|
|
func (w *WorkloadIdentityCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) {
|
|
var err error
|
|
ctx, endSpan := runtime.StartSpan(ctx, credNameWorkloadIdentity+"."+traceOpGetToken, w.cred.client.azClient.Tracer(), nil)
|
|
defer func() { endSpan(err) }()
|
|
tk, err := w.cred.GetToken(ctx, opts)
|
|
return tk, err
|
|
}
|
|
|
|
// getAssertion returns the specified file's content, which is expected to be a Kubernetes service account token.
|
|
// Kubernetes is responsible for updating the file as service account tokens expire.
|
|
func (w *WorkloadIdentityCredential) getAssertion(context.Context) (string, error) {
|
|
w.mtx.RLock()
|
|
if w.expires.Before(time.Now()) {
|
|
// ensure only one goroutine at a time updates the assertion
|
|
w.mtx.RUnlock()
|
|
w.mtx.Lock()
|
|
defer w.mtx.Unlock()
|
|
// double check because another goroutine may have acquired the write lock first and done the update
|
|
if now := time.Now(); w.expires.Before(now) {
|
|
content, err := os.ReadFile(w.file)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
w.assertion = string(content)
|
|
// Kubernetes rotates service account tokens when they reach 80% of their total TTL. The shortest TTL
|
|
// is 1 hour. That implies the token we just read is valid for at least 12 minutes (20% of 1 hour),
|
|
// but we add some margin for safety.
|
|
w.expires = now.Add(10 * time.Minute)
|
|
}
|
|
} else {
|
|
defer w.mtx.RUnlock()
|
|
}
|
|
return w.assertion, nil
|
|
}
|