diff --git a/examples/identitybinding-msal-go/Dockerfile b/examples/identitybinding-msal-go/Dockerfile index 5dd12e23b..6f1d147e3 100644 --- a/examples/identitybinding-msal-go/Dockerfile +++ b/examples/identitybinding-msal-go/Dockerfile @@ -10,7 +10,6 @@ RUN go mod download # Copy the go source COPY main.go main.go -COPY token_credential.go token_credential.go # Build ARG TARGETARCH diff --git a/examples/identitybinding-msal-go/go.mod b/examples/identitybinding-msal-go/go.mod index 0906be29e..540ee9404 100644 --- a/examples/identitybinding-msal-go/go.mod +++ b/examples/identitybinding-msal-go/go.mod @@ -3,18 +3,23 @@ module github.com/Azure/azure-workload-identity/examples/identitybinding-msal-go go 1.23.10 require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0-beta.2 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 k8s.io/klog/v2 v2.130.1 ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.1-0.20250811211210-f7ac5a70412a // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/go-logr/logr v1.4.1 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/text v0.27.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + golang.org/x/crypto v0.41.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect ) - -replace github.com/Azure/azure-sdk-for-go/sdk/azidentity => github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.1-0.20250811231238-0ec7258063bb diff --git a/examples/identitybinding-msal-go/go.sum b/examples/identitybinding-msal-go/go.sum index 3c0c69df6..18fd917df 100644 --- a/examples/identitybinding-msal-go/go.sum +++ b/examples/identitybinding-msal-go/go.sum @@ -1,40 +1,46 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2 h1:Hr5FTipp7SL07o2FvoVOX9HRiRH3CR3Mj8pxqCcdD5A= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.2/go.mod h1:QyVsSSN64v5TGltphKLQ2sQxe4OBQg0J1eKRcVBnfgE= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.1-0.20250811231238-0ec7258063bb h1:uwniPeVuMgjek09SI9NnvJ6sQjTKP4tlTtLXsUWZ1yE= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.11.1-0.20250811231238-0ec7258063bb/go.mod h1:okZ+ZURbArNdlJ+ptXoyHNuOETzOl1Oww19rm8I2WLA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0-beta.2 h1:6px3Q+rQYyJkBmchJK7VGsoCbwLpWQlfdmEehl3unns= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0-beta.2/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0 h1:xnO4sFyG8UH2fElBkcqLTOZsAajvKfnSlgBBW8dXYjw= github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets v0.12.0/go.mod h1:XD3DIOOVgBCO03OleB1fHjgktVRFxlT++KwKgIOewdM= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= diff --git a/examples/identitybinding-msal-go/main.go b/examples/identitybinding-msal-go/main.go index 859a70ece..ff64acf39 100644 --- a/examples/identitybinding-msal-go/main.go +++ b/examples/identitybinding-msal-go/main.go @@ -5,47 +5,11 @@ import ( "os" "time" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" "github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets" "k8s.io/klog/v2" ) -func createCredentialFromEnv() (azcore.TokenCredential, error) { - // Azure AD Workload Identity webhook will inject the following env vars - // AZURE_CLIENT_ID with the clientID set in the service account annotation - // AZURE_FEDERATED_TOKEN_FILE is the service account token path - // AZURE_KUBERNETES_TOKEN_PROXY is the identity binding token endpoint - // AZURE_KUBERNETES_SNI_NAME is the SNI name for token endpoint - // AZURE_KUBERNETES_CA_FILE is the CA file for the token endpoint - clientID := os.Getenv("AZURE_CLIENT_ID") - tokenFilePath := os.Getenv("AZURE_FEDERATED_TOKEN_FILE") - tokenEndpoint := os.Getenv("AZURE_KUBERNETES_TOKEN_PROXY") - sni := os.Getenv("AZURE_KUBERNETES_SNI_NAME") - caFile := os.Getenv("AZURE_KUBERNETES_CA_FILE") - - if clientID == "" { - klog.Fatal("AZURE_CLIENT_ID environment variable is not set") - } - if tokenFilePath == "" { - klog.Fatal("AZURE_FEDERATED_TOKEN_FILE environment variable is not set") - } - if tokenEndpoint == "" { - klog.Fatal("AZURE_KUBERNETES_TOKEN_PROXY environment variable is not set") - } - if sni == "" { - klog.Fatal("AZURE_KUBERNETES_SNI_NAME environment variable is not set") - } - if caFile == "" { - klog.Fatal("AZURE_KUBERNETES_CA_FILE environment variable is not set") - } - - cred, err := newClientAssertionCredential(clientID, tokenEndpoint, sni, caFile, tokenFilePath, nil) - if err != nil { - return nil, err - } - return cred, nil -} - func main() { keyvaultURL := os.Getenv("KEYVAULT_URL") if keyvaultURL == "" { @@ -56,8 +20,9 @@ func main() { klog.Fatal("SECRET_NAME environment variable is not set") } - var cred azcore.TokenCredential - cred, err := createCredentialFromEnv() + cred, err := azidentity.NewWorkloadIdentityCredential(&azidentity.WorkloadIdentityCredentialOptions{ + EnableAzureTokenProxy: true, + }) if err != nil { klog.Fatal(err) } diff --git a/examples/identitybinding-msal-go/token_credential.go b/examples/identitybinding-msal-go/token_credential.go deleted file mode 100644 index 3bb5f5922..000000000 --- a/examples/identitybinding-msal-go/token_credential.go +++ /dev/null @@ -1,143 +0,0 @@ -package main - -import ( - "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "fmt" - "net/http" - "net/url" - "os" - "strings" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" -) - -// clientAssertionCredential authenticates an application with assertions provided by a callback function. -type clientAssertionCredential struct { - assertion string - tokenFile string - tokenEndpoint string - lastRead time.Time - clientID string - tokenClient *http.Client -} - -// clientAssertionCredentialOptions contains optional parameters for ClientAssertionCredential. -type clientAssertionCredentialOptions struct { - azcore.ClientOptions -} - -func createTokenHTTPClient(sni string, caFile string) (*http.Client, error) { - caCert, err := os.ReadFile(caFile) - if err != nil { - return nil, fmt.Errorf("failed to read CA file %q: %w", caFile, err) - } - caCertPool := x509.NewCertPool() - if !caCertPool.AppendCertsFromPEM(caCert) { - return nil, fmt.Errorf("failed to append CA certs from PEM from %q", caFile) - } - - tlsConfig := &tls.Config{ - ServerName: sni, - RootCAs: caCertPool, - } - - defaultTransport, ok := http.DefaultTransport.(*http.Transport) - if !ok { - return nil, fmt.Errorf("default transport is not of type *http.Transport") - } - transportWithTLSConfigOverride := defaultTransport.Clone() - transportWithTLSConfigOverride.TLSClientConfig = tlsConfig - - return &http.Client{ - Transport: transportWithTLSConfigOverride, - }, nil -} - -// newClientAssertionCredential constructs a clientAssertionCredential. Pass nil for options to accept defaults. -func newClientAssertionCredential( - clientID string, tokenEndpoint string, sni string, - caFile string, tokenFile string, - options *clientAssertionCredentialOptions, -) (*clientAssertionCredential, error) { - tokenHTTPClient, err := createTokenHTTPClient(sni, caFile) - if err != nil { - return nil, fmt.Errorf("failed to create token transport: %w", err) - } - - c := &clientAssertionCredential{ - clientID: clientID, - tokenFile: tokenFile, - tokenEndpoint: tokenEndpoint, - tokenClient: tokenHTTPClient, - } - - if options == nil { - options = &clientAssertionCredentialOptions{} - } - - return c, nil -} - -// GetToken implements the TokenCredential interface -func (c *clientAssertionCredential) GetToken(ctx context.Context, opts policy.TokenRequestOptions) (azcore.AccessToken, error) { - assertion, err := c.getAssertion(ctx) - if err != nil { - return azcore.AccessToken{}, fmt.Errorf("failed to get assertion: %w", err) - } - - req, err := http.NewRequest(http.MethodPost, c.tokenEndpoint, nil) - if err != nil { - return azcore.AccessToken{}, fmt.Errorf("failed to create request: %w", err) - } - q := url.Values{} - q.Add("grant_type", "client_credentials") - q.Add("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - q.Add("scope", strings.Join(opts.Scopes, " ")) - q.Add("client_assertion", assertion) - q.Add("client_id", c.clientID) - req.URL.RawQuery = q.Encode() - - resp, err := c.tokenClient.Do(req) - if err != nil { - return azcore.AccessToken{}, fmt.Errorf("failed to send request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return azcore.AccessToken{}, fmt.Errorf("unexpected status code %d from token endpoint", resp.StatusCode) - } - - // see oauth/ops.TokenResponse - var tokenResponse struct { - AccessToken string `json:"access_token"` - ExpiresIn int64 `json:"expires_in"` - } - - if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil { - return azcore.AccessToken{}, fmt.Errorf("failed to decode token response: %w", err) - } - - return azcore.AccessToken{ - Token: tokenResponse.AccessToken, - ExpiresOn: time.Now().Add(time.Duration(tokenResponse.ExpiresIn) * time.Second), - }, nil -} - -// getAssertion reads the assertion from the file and returns it -// if the file has not been read in the last 5 minutes -func (c *clientAssertionCredential) getAssertion(context.Context) (string, error) { - if now := time.Now(); c.lastRead.Add(5 * time.Minute).Before(now) { - content, err := os.ReadFile(c.tokenFile) - if err != nil { - return "", err - } - c.assertion = string(content) - c.lastRead = now - } - return c.assertion, nil -}