This commit is contained in:
Joni Collinge 2025-05-23 06:53:08 +00:00 committed by GitHub
commit ec451b07ac
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 137 additions and 13 deletions

View File

@ -12,7 +12,7 @@ require (
)
require (
github.com/dapr/kit v0.15.3-0.20250516121556-bc7dc566c45d // indirect
github.com/dapr/kit v0.15.3-0.20250522135818-baea6263991d // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect

View File

@ -1,6 +1,6 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/dapr/kit v0.15.3-0.20250516121556-bc7dc566c45d h1:v+kZn9ami23xBsruyZmKErIOSlCdW9pR8wfHUg5+jys=
github.com/dapr/kit v0.15.3-0.20250516121556-bc7dc566c45d/go.mod h1:6w2Pr38zOAtBn+ld/jknwI4kgMfwanCIcFVnPykdPZQ=
github.com/dapr/kit v0.15.3-0.20250522135818-baea6263991d h1:8/Qhy5T6mb49KipoHnWQaG+uQ5Cjo9/tRaL8MFojG+g=
github.com/dapr/kit v0.15.3-0.20250522135818-baea6263991d/go.mod h1:6w2Pr38zOAtBn+ld/jknwI4kgMfwanCIcFVnPykdPZQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@ -139,6 +139,17 @@ func (s EnvironmentSettings) addWorkloadIdentityProvider(creds *[]azcore.TokenCr
}
}
func (s EnvironmentSettings) addSpiffeWorkloadIdentityProvider(creds *[]azcore.TokenCredential, errs *[]error) {
if c, e := s.GetSpiffeWorkloadIdentity(); e == nil {
cred, err := c.GetTokenCredential()
if err == nil {
*creds = append(*creds, cred)
} else {
*errs = append(*errs, err)
}
}
}
func (s EnvironmentSettings) addManagedIdentityProvider(timeout time.Duration, creds *[]azcore.TokenCredential, errs *[]error) {
c := s.GetMSI()
msiCred, err := c.GetTokenCredential()
@ -172,6 +183,8 @@ func (s EnvironmentSettings) addProviderByAuthMethodName(authMethod string, cred
s.addClientCertificateProvider(creds, errs)
case "workloadidentity", "wi":
s.addWorkloadIdentityProvider(creds, errs)
case "spiffeworkloadidentity", "spiffe":
s.addSpiffeWorkloadIdentityProvider(creds, errs)
case "managedidentity", "mi":
s.addManagedIdentityProvider(1*time.Second, creds, errs)
case "commandlineinterface", "cli":
@ -180,13 +193,13 @@ func (s EnvironmentSettings) addProviderByAuthMethodName(authMethod string, cred
}
func getAzureAuthMethods() []string {
return []string{"clientcredentials", "creds", "clientcertificate", "cert", "workloadidentity", "wi", "managedidentity", "mi", "commandlineinterface", "cli", "none"}
return []string{"clientcredentials", "creds", "clientcertificate", "cert", "workloadidentity", "wi", "spiffeworkloadidentity", "spiffe", "managedidentity", "mi", "commandlineinterface", "cli", "none"}
}
// GetTokenCredential returns an azcore.TokenCredential retrieved from the order specified via
// the azureAuthMethods component metadata property which denotes a comma-separated list of auth methods to try in order.
// The possible values contained are (case-insensitive):
// ServicePrincipal, Certificate, WorkloadIdentity, ManagedIdentity, CLI
// ServicePrincipal, Certificate, WorkloadIdentity, SPIFFEWorkloadIdentity, ManagedIdentity, CLI
// The string "None" can be used to disable Azure authentication.
//
// If the azureAuthMethods property is not present, the following order is used (which with the exception of step 5
@ -194,8 +207,9 @@ func getAzureAuthMethods() []string {
// 1. Client credentials
// 2. Client certificate
// 3. Workload identity
// 4. MSI (we use a timeout of 1 second when no compatible managed identity implementation is available)
// 5. Azure CLI
// 4. SPIFFE workload identity
// 5. MSI (we use a timeout of 1 second when no compatible managed identity implementation is available)
// 6. Azure CLI
func (s EnvironmentSettings) GetTokenCredential() (azcore.TokenCredential, error) {
// Create a chain
var creds []azcore.TokenCredential
@ -212,10 +226,13 @@ func (s EnvironmentSettings) GetTokenCredential() (azcore.TokenCredential, error
// 3. Workload identity
s.addWorkloadIdentityProvider(&creds, &errs)
// 4. MSI with timeout of 1 second (same as DefaultAzureCredential)
// 4. SPIFFE workload identity
s.addSpiffeWorkloadIdentityProvider(&creds, &errs)
// 5. MSI with timeout of 1 second (same as DefaultAzureCredential)
s.addManagedIdentityProvider(1*time.Second, &creds, &errs)
// 5. AzureCLICredential
// 6. AzureCLICredential
// We omit this if running in a cloud environment
if !isCloudServiceWithManagedIdentity() {
s.addCLIProvider(30*time.Second, &creds, &errs)

View File

@ -0,0 +1,91 @@
/*
Copyright 2025 The Dapr Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package azure
import (
"context"
"errors"
"fmt"
"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/spiffe/go-spiffe/v2/svid/jwtsvid"
spiffecontext "github.com/dapr/kit/crypto/spiffe/context"
)
const (
AzureADTokenExchangeAudience = "api://AzureADTokenExchange"
)
// SpiffeWorkloadIdentityConfig provides the options to get a bearer authorizer using SPIFFE-based workload identity.
type SpiffeWorkloadIdentityConfig struct {
TenantID string
ClientID string
AzureCloud *cloud.Configuration
}
// GetTokenCredential returns the azcore.TokenCredential object using a SPIFFE JWT token.
func (c SpiffeWorkloadIdentityConfig) GetTokenCredential() (azcore.TokenCredential, error) {
if c.TenantID == "" || c.ClientID == "" {
return nil, errors.New("parameters clientId and tenantId must be present for SPIFFE workload identity")
}
var opts *azidentity.ClientAssertionCredentialOptions
if c.AzureCloud != nil {
opts = &azidentity.ClientAssertionCredentialOptions{
ClientOptions: azcore.ClientOptions{
Cloud: *c.AzureCloud,
},
}
}
// Create a token provider function that retrieves the JWT from SPIFFE context
tokenProvider := func(ctx context.Context) (string, error) {
tknSource, ok := spiffecontext.JWTFrom(ctx)
if !ok {
return "", fmt.Errorf("failed to get JWT SVID source from context")
}
jwt, err := tknSource.FetchJWTSVID(ctx, jwtsvid.Params{
Audience: AzureADTokenExchangeAudience,
})
if err != nil {
return "", fmt.Errorf("failed to get JWT SVID: %w", err)
}
return jwt.Marshal(), nil
}
return azidentity.NewClientAssertionCredential(c.TenantID, c.ClientID, tokenProvider, opts)
}
// GetSpiffeWorkloadIdentity creates a config object from the available SPIFFE workload identity credentials.
// An error is returned if required credentials are not available.
func (s EnvironmentSettings) GetSpiffeWorkloadIdentity() (config SpiffeWorkloadIdentityConfig, err error) {
azureCloud, err := s.GetAzureEnvironment()
if err != nil {
return config, err
}
config.ClientID, _ = s.GetEnvironment("ClientID")
config.TenantID, _ = s.GetEnvironment("TenantID")
if config.ClientID == "" || config.TenantID == "" {
return config, errors.New("parameters clientId and tenantId must be present for SPIFFE workload identity")
}
config.AzureCloud = azureCloud
return config, nil
}

View File

@ -161,7 +161,6 @@ func (opts *ContainerClientOpts) InitContainerClient(azEnvSettings azauth.Enviro
if err != nil {
return nil, fmt.Errorf("cannot init blob storage container client with shared key: %w", err)
}
// Use Azure AD as fallback
default:
credential, tokenErr := azEnvSettings.GetTokenCredential()

2
go.mod
View File

@ -60,7 +60,7 @@ require (
github.com/cohesion-org/deepseek-go v1.2.0
github.com/cyphar/filepath-securejoin v0.2.4
github.com/dancannon/gorethink v4.0.0+incompatible
github.com/dapr/kit v0.15.3-0.20250516121556-bc7dc566c45d
github.com/dapr/kit v0.15.3-0.20250522135818-baea6263991d
github.com/didip/tollbooth/v7 v7.0.1
github.com/eclipse/paho.mqtt.golang v1.4.3
github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5

4
go.sum
View File

@ -509,8 +509,8 @@ github.com/dancannon/gorethink v4.0.0+incompatible h1:KFV7Gha3AuqT+gr0B/eKvGhbjm
github.com/dancannon/gorethink v4.0.0+incompatible/go.mod h1:BLvkat9KmZc1efyYwhz3WnybhRZtgF1K929FD8z1avU=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
github.com/dapr/kit v0.15.3-0.20250516121556-bc7dc566c45d h1:v+kZn9ami23xBsruyZmKErIOSlCdW9pR8wfHUg5+jys=
github.com/dapr/kit v0.15.3-0.20250516121556-bc7dc566c45d/go.mod h1:6w2Pr38zOAtBn+ld/jknwI4kgMfwanCIcFVnPykdPZQ=
github.com/dapr/kit v0.15.3-0.20250522135818-baea6263991d h1:8/Qhy5T6mb49KipoHnWQaG+uQ5Cjo9/tRaL8MFojG+g=
github.com/dapr/kit v0.15.3-0.20250522135818-baea6263991d/go.mod h1:6w2Pr38zOAtBn+ld/jknwI4kgMfwanCIcFVnPykdPZQ=
github.com/dave/jennifer v1.4.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

View File

@ -19,6 +19,23 @@ builtinAuthenticationProfiles:
sensitive: false
description: "The storage account name"
example: '"mystorageaccount"'
- name: "azuread.spiffe"
metadata:
- name: accountName
required: true
sensitive: false
description: "The storage account name"
example: '"mystorageaccount"'
- name: tenantId
required: true
sensitive: false
description: "The Azure AD tenant ID"
example: '"00000000-0000-0000-0000-000000000000"'
- name: clientId
required: true
sensitive: false
description: "The Azure AD client ID (application ID)"
example: '"00000000-0000-0000-0000-000000000000"'
authenticationProfiles:
- title: "Connection string"
description: "Authenticate using a connection string."