pkg/authorizer/token_retriever.go (80 lines of code) (raw):
package authorizer
import (
"context"
"fmt"
"os"
"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/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
msiacrpullv1beta2 "github.com/Azure/msi-acrpull/api/v1beta2"
)
const (
defaultARMResource = "https://management.azure.com/"
customARMResourceEnvVar = "ARM_RESOURCE"
)
func AcquireARMToken(ctx context.Context, id azidentity.ManagedIDKind) (azcore.AccessToken, error) {
customARMResource := os.Getenv(customARMResourceEnvVar)
if customARMResource == "" {
customARMResource = defaultARMResource
}
cred, err := azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ID: id})
if err != nil {
return azcore.AccessToken{}, fmt.Errorf("failed to build managed identity credential: %w", err)
}
return cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{customARMResource}})
}
func ARMTokenForBinding(ctx context.Context, spec msiacrpullv1beta2.AcrPullBindingSpec, tenantId, clientId, serviceAccountToken string) (azcore.AccessToken, error) {
env := environment(spec.ACR.Environment, spec.ACR.CloudConfig)
var credential azcore.TokenCredential
var err error
switch {
case spec.Auth.ManagedIdentity != nil:
var id azidentity.ManagedIDKind
if spec.Auth.ManagedIdentity.ClientID != "" {
id = azidentity.ClientID(spec.Auth.ManagedIdentity.ClientID)
} else if spec.Auth.ManagedIdentity.ResourceID != "" {
id = azidentity.ResourceID(spec.Auth.ManagedIdentity.ResourceID)
}
credential, err = azidentity.NewManagedIdentityCredential(&azidentity.ManagedIdentityCredentialOptions{ID: id})
case spec.Auth.WorkloadIdentity != nil:
// n.b. the built-in azidentity.WorkloadIdentityCredential assumes we're loading a service account token
// from a file in a Pod, where the Kubernetes API server is rotating it, etc. Unfortunately that is not
// our use-case here, and we certainly don't want to centralize every service account token we ever mint
// in the filesystem of this controller, so we can use the lower-level client assertion credential instead.
credential, err = azidentity.NewClientAssertionCredential(tenantId, clientId, func(ctx context.Context) (string, error) {
return serviceAccountToken, nil
}, &azidentity.ClientAssertionCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: cloud.Configuration{
ActiveDirectoryAuthorityHost: env.ActiveDirectoryAuthorityHost,
},
},
DisableInstanceDiscovery: true,
})
}
if err != nil {
return azcore.AccessToken{}, fmt.Errorf("failed to build credential: %w", err)
}
if credential == nil {
// this should never happen with the validation we have on the CRD
panic(fmt.Errorf("programmer error: ACRPullBinding.Spec.Auth has no method: %#v", spec.Auth))
}
return credential.GetToken(ctx, policy.TokenRequestOptions{Scopes: []string{env.Services[cloud.ResourceManager].Audience + "/.default"}})
}
func environment(input msiacrpullv1beta2.AzureEnvironmentType, config *msiacrpullv1beta2.AirgappedCloudConfiguration) cloud.Configuration {
switch input {
case msiacrpullv1beta2.AzureEnvironmentPublicCloud:
return cloud.AzurePublic
case msiacrpullv1beta2.AzureEnvironmentUSGovernmentCloud:
return cloud.AzureGovernment
case msiacrpullv1beta2.AzureEnvironmentChinaCloud:
return cloud.AzureChina
case msiacrpullv1beta2.AzureEnvironmentAirgappedCloud:
return cloud.Configuration{
ActiveDirectoryAuthorityHost: config.EntraAuthorityHost,
Services: map[cloud.ServiceName]cloud.ServiceConfiguration{
cloud.ResourceManager: {
Audience: config.ResourceManagerAudience,
},
},
}
default:
panic(fmt.Errorf("unsupported msiacrpullv1beta2.AzureEnvironmentType: %s", input))
}
}