diff --git a/backend/azureblob/azureblob.go b/backend/azureblob/azureblob.go index 2185e2cfc..31abcae54 100644 --- a/backend/azureblob/azureblob.go +++ b/backend/azureblob/azureblob.go @@ -984,6 +984,38 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e if err != nil { return nil, fmt.Errorf("failed to acquire MSI token: %w", err) } + case opt.ClientID != "" && opt.Tenant != "" && opt.MSIClientID != "": + // Workload Identity based authentication + var options azidentity.ManagedIdentityCredentialOptions + options.ID = azidentity.ClientID(opt.MSIClientID) + + msiCred, err := azidentity.NewManagedIdentityCredential(&options) + if err != nil { + return nil, fmt.Errorf("failed to acquire MSI token: %w", err) + } + + getClientAssertions := func(context.Context) (string, error) { + token, err := msiCred.GetToken(context.Background(), policy.TokenRequestOptions{ + Scopes: []string{"api://AzureADTokenExchange"}, + }) + + if err != nil { + return "", fmt.Errorf("failed to acquire MSI token: %w", err) + } + + return token.Token, nil + } + + assertOpts := &azidentity.ClientAssertionCredentialOptions{} + f.cred, err = azidentity.NewClientAssertionCredential( + opt.Tenant, + opt.ClientID, + getClientAssertions, + assertOpts) + + if err != nil { + return nil, fmt.Errorf("failed to acquire client assertion token: %w", err) + } case opt.UseAZ: var options = azidentity.AzureCLICredentialOptions{} f.cred, err = azidentity.NewAzureCLICredential(&options) diff --git a/backend/azurefiles/azurefiles.go b/backend/azurefiles/azurefiles.go index 5bb7a6da8..cb9dd2793 100644 --- a/backend/azurefiles/azurefiles.go +++ b/backend/azurefiles/azurefiles.go @@ -569,6 +569,38 @@ func newFsFromOptions(ctx context.Context, name, root string, opt *Options) (fs. if err != nil { return nil, fmt.Errorf("failed to acquire MSI token: %w", err) } + case opt.ClientID != "" && opt.Tenant != "" && opt.MSIClientID != "": + // Workload Identity based authentication + var options azidentity.ManagedIdentityCredentialOptions + options.ID = azidentity.ClientID(opt.MSIClientID) + + msiCred, err := azidentity.NewManagedIdentityCredential(&options) + if err != nil { + return nil, fmt.Errorf("failed to acquire MSI token: %w", err) + } + + getClientAssertions := func(context.Context) (string, error) { + token, err := msiCred.GetToken(context.Background(), policy.TokenRequestOptions{ + Scopes: []string{"api://AzureADTokenExchange"}, + }) + + if err != nil { + return "", fmt.Errorf("failed to acquire MSI token: %w", err) + } + + return token.Token, nil + } + + assertOpts := &azidentity.ClientAssertionCredentialOptions{} + cred, err = azidentity.NewClientAssertionCredential( + opt.Tenant, + opt.ClientID, + getClientAssertions, + assertOpts) + + if err != nil { + return nil, fmt.Errorf("failed to acquire client assertion token: %w", err) + } default: return nil, errors.New("no authentication method configured") } diff --git a/docs/content/azureblob.md b/docs/content/azureblob.md index 9e8970438..a08900c44 100644 --- a/docs/content/azureblob.md +++ b/docs/content/azureblob.md @@ -297,6 +297,16 @@ be explicitly specified using exactly one of the `msi_object_id`, If none of `msi_object_id`, `msi_client_id`, or `msi_mi_res_id` is set, this is is equivalent to using `env_auth`. +#### Fedrated Identity Credentials + +If these variables are set, rclone will authenticate with fedrated identity. + +- `tenant_id`: tenant_id to authenticate in storage +- `client_id`: client ID of the application the user will authenticate to storage +- `msi_client_id`: managed identity client ID of the application the user will authenticate to + +By default "api://AzureADTokenExchange" is used as scope for token retrieval over MSI. This token is then exchanged for actual storage token using 'tenant_id' and 'client_id'. + #### Azure CLI tool `az` {#use_az} Set to use the [Azure CLI tool `az`](https://learn.microsoft.com/en-us/cli/azure/) diff --git a/docs/content/azurefiles.md b/docs/content/azurefiles.md index a08e8d9ea..fe28662e3 100644 --- a/docs/content/azurefiles.md +++ b/docs/content/azurefiles.md @@ -295,6 +295,16 @@ be explicitly specified using exactly one of the `msi_object_id`, If none of `msi_object_id`, `msi_client_id`, or `msi_mi_res_id` is set, this is is equivalent to using `env_auth`. + +#### Fedrated Identity Credentials + +If these variables are set, rclone will authenticate with fedrated identity. + +- `tenant_id`: tenant_id to authenticate in storage +- `client_id`: client ID of the application the user will authenticate to storage +- `msi_client_id`: managed identity client ID of the application the user will authenticate to + +By default "api://AzureADTokenExchange" is used as scope for token retrieval over MSI. This token is then exchanged for actual storage token using 'tenant_id' and 'client_id'. #### Azure CLI tool `az` {#use_az} Set to use the [Azure CLI tool `az`](https://learn.microsoft.com/en-us/cli/azure/)