From 0dbcd5843ab39b6a52688d13e5a44b4f4f4a9520 Mon Sep 17 00:00:00 2001 From: Justin Judd Date: Wed, 4 May 2022 09:41:04 -0600 Subject: [PATCH] Added federated OIDC JWT support. (#680) * Added federated OIDC JWT support. * Fixing go formatting. * Made JWT field for federated identities internal Co-authored-by: Joel Hendrix --- autorest/adal/token.go | 46 ++++++++++++++++++++++++ autorest/adal/token_test.go | 72 +++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/autorest/adal/token.go b/autorest/adal/token.go index 310be07ec..0964a5881 100644 --- a/autorest/adal/token.go +++ b/autorest/adal/token.go @@ -365,6 +365,25 @@ func (secret ServicePrincipalAuthorizationCodeSecret) MarshalJSON() ([]byte, err }) } +// ServicePrincipalFederatedSecret implements ServicePrincipalSecret for Federated JWTs. +type ServicePrincipalFederatedSecret struct { + jwt string +} + +// SetAuthenticationValues is a method of the interface ServicePrincipalSecret. +// It will populate the form submitted during OAuth Token Acquisition using a JWT signed by an OIDC issuer. +func (secret *ServicePrincipalFederatedSecret) SetAuthenticationValues(spt *ServicePrincipalToken, v *url.Values) error { + + v.Set("client_assertion", secret.jwt) + v.Set("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (secret ServicePrincipalFederatedSecret) MarshalJSON() ([]byte, error) { + return nil, errors.New("marshalling ServicePrincipalFederatedSecret is not supported") +} + // ServicePrincipalToken encapsulates a Token created for a Service Principal. type ServicePrincipalToken struct { inner servicePrincipalToken @@ -419,6 +438,8 @@ func (spt *ServicePrincipalToken) UnmarshalJSON(data []byte) error { spt.inner.Secret = &ServicePrincipalUsernamePasswordSecret{} case "ServicePrincipalAuthorizationCodeSecret": spt.inner.Secret = &ServicePrincipalAuthorizationCodeSecret{} + case "ServicePrincipalFederatedSecret": + return errors.New("unmarshalling ServicePrincipalFederatedSecret is not supported") default: return fmt.Errorf("unrecognized token type '%s'", secret["type"]) } @@ -665,6 +686,31 @@ func NewServicePrincipalTokenFromAuthorizationCode(oauthConfig OAuthConfig, clie ) } +// NewServicePrincipalTokenFromFederatedToken creates a ServicePrincipalToken from the supplied federated OIDC JWT. +func NewServicePrincipalTokenFromFederatedToken(oauthConfig OAuthConfig, clientID string, jwt string, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { + if err := validateOAuthConfig(oauthConfig); err != nil { + return nil, err + } + if err := validateStringParam(clientID, "clientID"); err != nil { + return nil, err + } + if err := validateStringParam(resource, "resource"); err != nil { + return nil, err + } + if jwt == "" { + return nil, fmt.Errorf("parameter 'jwt' cannot be empty") + } + return NewServicePrincipalTokenWithSecret( + oauthConfig, + clientID, + resource, + &ServicePrincipalFederatedSecret{ + jwt: jwt, + }, + callbacks..., + ) +} + type msiType int const ( diff --git a/autorest/adal/token_test.go b/autorest/adal/token_test.go index 891706afc..aa9291135 100644 --- a/autorest/adal/token_test.go +++ b/autorest/adal/token_test.go @@ -700,6 +700,46 @@ func TestServicePrincipalTokenSecretRefreshSetsBody(t *testing.T) { }) } +func TestServicePrincipalTokenFederatedJwtRefreshSetsBody(t *testing.T) { + sptCert := newServicePrincipalTokenFederatedJwt(t) + testServicePrincipalTokenRefreshSetsBody(t, sptCert, func(t *testing.T, b []byte) { + body := string(b) + + values, _ := url.ParseQuery(body) + if values["client_assertion_type"][0] != "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" || + values["client_id"][0] != "id" || + values["grant_type"][0] != "client_credentials" || + values["resource"][0] != "resource" { + t.Fatalf("adal: ServicePrincipalTokenCertificate#Refresh did not correctly set the HTTP Request Body.") + } + + tok, _ := jwt.Parse(values["client_assertion"][0], nil) + if tok == nil { + t.Fatalf("adal: ServicePrincipalTokenCertificate#Expected client_assertion to be a JWT") + } + if _, ok := tok.Header["typ"]; !ok { + t.Fatalf("adal: ServicePrincipalTokenCertificate#Expected client_assertion to have an typ header") + } + + claims, ok := tok.Claims.(jwt.MapClaims) + if !ok { + t.Fatalf("expected MapClaims, got %T", tok.Claims) + } + if err := claims.Valid(); err != nil { + t.Fatalf("invalid claim: %v", err) + } + if aud := claims["aud"]; aud != "testAudience" { + t.Fatalf("unexpected aud: %s", aud) + } + if iss := claims["iss"]; iss != "id" { + t.Fatalf("unexpected iss: %s", iss) + } + if sub := claims["sub"]; sub != "id" { + t.Fatalf("unexpected sub: %s", sub) + } + }) +} + func TestServicePrincipalTokenRefreshClosesRequestBody(t *testing.T) { spt := newServicePrincipalToken() @@ -1266,6 +1306,19 @@ func TestMarshalServicePrincipalAuthorizationCodeSecret(t *testing.T) { } } +func TestMarshalServicePrincipalFederatedSecret(t *testing.T) { + spt := newServicePrincipalTokenFederatedJwt(t) + b, err := json.Marshal(spt) + if err == nil { + t.Fatal("expected error when marshalling certificate token") + } + var spt2 *ServicePrincipalToken + err = json.Unmarshal(b, &spt2) + if err == nil { + t.Fatal("expected error when unmarshalling certificate token") + } +} + func TestMarshalInnerToken(t *testing.T) { spt := newServicePrincipalTokenManual() tokenJSON, err := spt.MarshalTokenJSON() @@ -1465,3 +1518,22 @@ func newServicePrincipalTokenAuthorizationCode(t *testing.T) *ServicePrincipalTo spt, _ := NewServicePrincipalTokenFromAuthorizationCode(TestOAuthConfig, "id", "clientSecret", "code", "http://redirectUri/getToken", "resource") return spt } + +func newServicePrincipalTokenFederatedJwt(t *testing.T) *ServicePrincipalToken { + token := jwt.New(jwt.SigningMethodHS256) + token.Header["typ"] = "JWT" + token.Claims = jwt.MapClaims{ + "aud": "testAudience", + "iss": "id", + "sub": "id", + "nbf": time.Now().Unix(), + "exp": time.Now().Add(24 * time.Hour).Unix(), + } + + signedString, err := token.SignedString([]byte("test key")) + if err != nil { + t.Fatal(err) + } + spt, _ := NewServicePrincipalTokenFromFederatedToken(TestOAuthConfig, "id", signedString, "resource") + return spt +}