From 2077a4f956686dce1021a2879b11fc78cecc8481 Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 14:58:23 -0700 Subject: [PATCH 1/9] allow login with resourceID --- autorest/adal/cmd/adal.go | 71 +++++++++++++++++++++++++++++++++++---- autorest/adal/token.go | 23 ++++++++++--- 2 files changed, 84 insertions(+), 10 deletions(-) diff --git a/autorest/adal/cmd/adal.go b/autorest/adal/cmd/adal.go index 7214dcabb..cdcdb6d1b 100644 --- a/autorest/adal/cmd/adal.go +++ b/autorest/adal/cmd/adal.go @@ -31,10 +31,13 @@ import ( ) const ( - deviceMode = "device" - clientSecretMode = "secret" - clientCertMode = "cert" - refreshMode = "refresh" + deviceMode = "device" + clientSecretMode = "secret" + clientCertMode = "cert" + refreshMode = "refresh" + msiDefaultMode = "msiDefault" + msiClientIDMode = "msiClientID" + msiResourceIDMode = "msiResourceID" activeDirectoryEndpoint = "https://login.microsoftonline.com/" ) @@ -48,8 +51,9 @@ var ( mode string resource string - tenantID string - applicationID string + tenantID string + applicationID string + identityResourceID string applicationSecret string certificatePath string @@ -82,10 +86,28 @@ func init() { flag.StringVar(&applicationSecret, "secret", "", "application secret") flag.StringVar(&certificatePath, "certificatePath", "", "path to pk12/PFC application certificate") flag.StringVar(&tokenCachePath, "tokenCachePath", defaultTokenCachePath(), "location of oath token cache") + flag.StringVar(&identityResourceID, "identityResourceID", "", "managedIdentity azure resource id") flag.Parse() switch mode = strings.TrimSpace(mode); mode { + case msiDefaultMode: + checkMandatoryOptions(clientSecretMode, + option{name: "resource", value: resource}, + option{name: "tenantId", value: tenantID}, + ) + case msiClientIDMode: + checkMandatoryOptions(clientSecretMode, + option{name: "resource", value: resource}, + option{name: "tenantId", value: tenantID}, + option{name: "applicationId", value: applicationID}, + ) + case msiResourceIDMode: + checkMandatoryOptions(clientSecretMode, + option{name: "resource", value: resource}, + option{name: "tenantId", value: tenantID}, + option{name: "identityResourceID", value: applicationID}, + ) case clientSecretMode: checkMandatoryOptions(clientSecretMode, option{name: "resource", value: resource}, @@ -150,6 +172,43 @@ func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.Private return certificate, rsaPrivateKey, nil } +func acquireTokenMSIFlow(oauthConfig adal.OAuthConfig, + applicationID string, + identityResourceID string, + resource string, + callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { + + // only one of them can be present: + if applicationID != "" && identityResourceID != "" { + return nil, fmt.Errorf("didn't expect applicationID and identityResourceID at same time") + } + + msiEndpoint, _ := adal.GetMSIVMEndpoint() + var spt *adal.ServicePrincipalToken + var err error + + // both can be empty, systemAssignedMSI scenario + if applicationID == "" && identityResourceID == "" { + spt, err = adal.NewServicePrincipalTokenFromMSI(msiEndpoint, resource, callbacks...) + } + + // msi login with clientID + if applicationID != "" { + spt, err = adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource, applicationID, callbacks...) + } + + // msi login with resourceID + if identityResourceID != "" { + spt, err = adal.NewServicePrincipalTokenFromMSIWithIdentityResourceID(msiEndpoint, resource, identityResourceID, callbacks...) + } + + if err != nil { + return nil, err + } + + return spt, spt.Refresh() +} + func acquireTokenClientCertFlow(oauthConfig adal.OAuthConfig, applicationID string, applicationCertPath string, diff --git a/autorest/adal/token.go b/autorest/adal/token.go index c026f7d12..83a67d179 100644 --- a/autorest/adal/token.go +++ b/autorest/adal/token.go @@ -678,16 +678,22 @@ func GetMSIEndpoint() (string, error) { // NewServicePrincipalTokenFromMSI creates a ServicePrincipalToken via the MSI VM Extension. // It will use the system assigned identity when creating the token. func NewServicePrincipalTokenFromMSI(msiEndpoint, resource string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, callbacks...) + return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, nil, callbacks...) } // NewServicePrincipalTokenFromMSIWithUserAssignedID creates a ServicePrincipalToken via the MSI VM Extension. -// It will use the specified user assigned identity when creating the token. +// It will use the clientID of specified user assigned identity when creating the token. func NewServicePrincipalTokenFromMSIWithUserAssignedID(msiEndpoint, resource string, userAssignedID string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { - return newServicePrincipalTokenFromMSI(msiEndpoint, resource, &userAssignedID, callbacks...) + return newServicePrincipalTokenFromMSI(msiEndpoint, resource, &userAssignedID, nil, callbacks...) } -func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedID *string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { +// NewServicePrincipalTokenFromMSIWithIdentityResourceID creates a ServicePrincipalToken via the MSI VM Extension. +// It will use the azure resource id of user assigned identity when creating the token. +func NewServicePrincipalTokenFromMSIWithIdentityResourceID(msiEndpoint, resource string, identityResourceID string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { + return newServicePrincipalTokenFromMSI(msiEndpoint, resource, nil, &identityResourceID, callbacks...) +} + +func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedID *string, identityResourceID *string, callbacks ...TokenRefreshCallback) (*ServicePrincipalToken, error) { if err := validateStringParam(msiEndpoint, "msiEndpoint"); err != nil { return nil, err } @@ -699,6 +705,11 @@ func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedI return nil, err } } + if identityResourceID != nil { + if err := validateStringParam(*identityResourceID, "identityResourceID"); err != nil { + return nil, err + } + } // We set the oauth config token endpoint to be MSI's endpoint msiEndpointURL, err := url.Parse(msiEndpoint) if err != nil { @@ -716,6 +727,10 @@ func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedI if userAssignedID != nil { v.Set("client_id", *userAssignedID) } + if identityResourceID != nil { + fmt.Println("mi_res_id set to ", *userAssignedID) + v.Set("mi_res_id", *userAssignedID) + } msiEndpointURL.RawQuery = v.Encode() spt := &ServicePrincipalToken{ From fc34975f62ac7a00b9dbc06cee07c64d0672ae6e Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 15:18:05 -0700 Subject: [PATCH 2/9] test --- autorest/adal/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autorest/adal/go.mod b/autorest/adal/go.mod index 02a3d39ff..1a178a819 100644 --- a/autorest/adal/go.mod +++ b/autorest/adal/go.mod @@ -1,4 +1,4 @@ -module github.com/Azure/go-autorest/autorest/adal +module github.com/haitch/go-autorest/autorest/adal go 1.12 From 2f91460aeed1affa6faf0c40fc53fee44d88535c Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 15:34:29 -0700 Subject: [PATCH 3/9] tweaks --- autorest/adal/cmd/adal.go | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/autorest/adal/cmd/adal.go b/autorest/adal/cmd/adal.go index cdcdb6d1b..7c0231fdb 100644 --- a/autorest/adal/cmd/adal.go +++ b/autorest/adal/cmd/adal.go @@ -26,7 +26,7 @@ import ( "net/http" "os/user" - "github.com/Azure/go-autorest/autorest/adal" + "github.com/haitch/go-autorest/autorest/adal" "golang.org/x/crypto/pkcs12" ) @@ -92,18 +92,18 @@ func init() { switch mode = strings.TrimSpace(mode); mode { case msiDefaultMode: - checkMandatoryOptions(clientSecretMode, + checkMandatoryOptions(msiDefaultMode, option{name: "resource", value: resource}, option{name: "tenantId", value: tenantID}, ) case msiClientIDMode: - checkMandatoryOptions(clientSecretMode, + checkMandatoryOptions(msiClientIDMode, option{name: "resource", value: resource}, option{name: "tenantId", value: tenantID}, option{name: "applicationId", value: applicationID}, ) case msiResourceIDMode: - checkMandatoryOptions(clientSecretMode, + checkMandatoryOptions(msiResourceIDMode, option{name: "resource", value: resource}, option{name: "tenantId", value: tenantID}, option{name: "identityResourceID", value: applicationID}, @@ -172,8 +172,7 @@ func decodePkcs12(pkcs []byte, password string) (*x509.Certificate, *rsa.Private return certificate, rsaPrivateKey, nil } -func acquireTokenMSIFlow(oauthConfig adal.OAuthConfig, - applicationID string, +func acquireTokenMSIFlow(applicationID string, identityResourceID string, resource string, callbacks ...adal.TokenRefreshCallback) (*adal.ServicePrincipalToken, error) { @@ -342,6 +341,20 @@ func main() { if err == nil { err = saveToken(spt.Token()) } + case msiResourceIDMode: + fallthrough + case msiClientIDMode: + fallthrough + case msiDefaultMode: + var spt *adal.ServicePrincipalToken + spt, err = acquireTokenMSIFlow( + applicationID, + identityResourceID, + resource, + callback) + if err == nil { + err = saveToken(spt.Token()) + } case refreshMode: _, err = refreshToken( *oauthConfig, From 1f59cbcc17187eabe0a4b4f69f014f5466ba4b3b Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 15:39:55 -0700 Subject: [PATCH 4/9] fix --- autorest/adal/cmd/adal.go | 2 +- autorest/adal/token.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/autorest/adal/cmd/adal.go b/autorest/adal/cmd/adal.go index 7c0231fdb..624fb15c2 100644 --- a/autorest/adal/cmd/adal.go +++ b/autorest/adal/cmd/adal.go @@ -106,7 +106,7 @@ func init() { checkMandatoryOptions(msiResourceIDMode, option{name: "resource", value: resource}, option{name: "tenantId", value: tenantID}, - option{name: "identityResourceID", value: applicationID}, + option{name: "identityResourceID", value: identityResourceID}, ) case clientSecretMode: checkMandatoryOptions(clientSecretMode, diff --git a/autorest/adal/token.go b/autorest/adal/token.go index 83a67d179..b3c7acf62 100644 --- a/autorest/adal/token.go +++ b/autorest/adal/token.go @@ -728,8 +728,8 @@ func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedI v.Set("client_id", *userAssignedID) } if identityResourceID != nil { - fmt.Println("mi_res_id set to ", *userAssignedID) - v.Set("mi_res_id", *userAssignedID) + fmt.Println("mi_res_id set to ", *identityResourceID) + v.Set("mi_res_id", *identityResourceID) } msiEndpointURL.RawQuery = v.Encode() From 7db3b4c23ef0ee94d8f776650250edaadef7b5e8 Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 15:43:25 -0700 Subject: [PATCH 5/9] tested with cmd --- autorest/adal/cmd/adal.go | 2 +- autorest/adal/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/autorest/adal/cmd/adal.go b/autorest/adal/cmd/adal.go index 624fb15c2..6c2aa1272 100644 --- a/autorest/adal/cmd/adal.go +++ b/autorest/adal/cmd/adal.go @@ -26,7 +26,7 @@ import ( "net/http" "os/user" - "github.com/haitch/go-autorest/autorest/adal" + "github.com/Azure/go-autorest/autorest/adal" "golang.org/x/crypto/pkcs12" ) diff --git a/autorest/adal/go.mod b/autorest/adal/go.mod index 1a178a819..02a3d39ff 100644 --- a/autorest/adal/go.mod +++ b/autorest/adal/go.mod @@ -1,4 +1,4 @@ -module github.com/haitch/go-autorest/autorest/adal +module github.com/Azure/go-autorest/autorest/adal go 1.12 From a0dc8b0fdb5520e19a03f66cc3ce936881cead87 Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 16:15:30 -0700 Subject: [PATCH 6/9] fix unittest --- autorest/adal/token_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autorest/adal/token_test.go b/autorest/adal/token_test.go index d123ac012..e002ea526 100644 --- a/autorest/adal/token_test.go +++ b/autorest/adal/token_test.go @@ -895,7 +895,7 @@ func TestMarshalServicePrincipalCertificateSecret(t *testing.T) { } func TestMarshalServicePrincipalMSISecret(t *testing.T) { - spt, err := newServicePrincipalTokenFromMSI("http://msiendpoint/", "https://resource", nil) + spt, err := newServicePrincipalTokenFromMSI("http://msiendpoint/", "https://resource", nil, nil) if err != nil { t.Fatalf("failed to get MSI SPT: %+v", err) } From e955b9ac7a8be6a7cbe147c841809ac00997fa95 Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 16:36:11 -0700 Subject: [PATCH 7/9] add new test, remove debug trace --- autorest/adal/token.go | 1 - autorest/adal/token_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/autorest/adal/token.go b/autorest/adal/token.go index b3c7acf62..d45a3fa57 100644 --- a/autorest/adal/token.go +++ b/autorest/adal/token.go @@ -728,7 +728,6 @@ func newServicePrincipalTokenFromMSI(msiEndpoint, resource string, userAssignedI v.Set("client_id", *userAssignedID) } if identityResourceID != nil { - fmt.Println("mi_res_id set to ", *identityResourceID) v.Set("mi_res_id", *identityResourceID) } msiEndpointURL.RawQuery = v.Encode() diff --git a/autorest/adal/token_test.go b/autorest/adal/token_test.go index e002ea526..45fd63169 100644 --- a/autorest/adal/token_test.go +++ b/autorest/adal/token_test.go @@ -744,6 +744,32 @@ func TestNewServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) { } } +func TestNewServicePrincipalTokenFromMSIWithIdentityResourceID(t *testing.T) { + resource := "https://resource" + identityResourceId := "/subscriptions/testSub/resourceGroups/testGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" + cb := func(token Token) error { return nil } + + spt, err := NewServicePrincipalTokenFromMSIWithIdentityResourceID("http://msiendpoint/", resource, identityResourceId, cb) + if err != nil { + t.Fatalf("Failed to get MSI SPT: %v", err) + } + + // check some of the SPT fields + if _, ok := spt.inner.Secret.(*ServicePrincipalMSISecret); !ok { + t.Fatal("SPT secret was not of MSI type") + } + + if spt.inner.Resource != resource { + t.Fatal("SPT came back with incorrect resource") + } + + if len(spt.refreshCallbacks) != 1 { + t.Fatal("SPT had incorrect refresh callbacks.") + } + + strings.Contains(spt.inner.OauthConfig.TokenEndpoint.RawQuery, fmt.Sprintf("mi_res_id=%s", identityResourceId)) +} + func TestNewServicePrincipalTokenFromManualTokenSecret(t *testing.T) { token := newToken() secret := &ServicePrincipalAuthorizationCodeSecret{ From f3166348ea1e25a210970d3d1053cd61ea7d008f Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 16:44:07 -0700 Subject: [PATCH 8/9] fix unittest --- autorest/adal/token_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/autorest/adal/token_test.go b/autorest/adal/token_test.go index 45fd63169..4df92e4fd 100644 --- a/autorest/adal/token_test.go +++ b/autorest/adal/token_test.go @@ -746,10 +746,10 @@ func TestNewServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) { func TestNewServicePrincipalTokenFromMSIWithIdentityResourceID(t *testing.T) { resource := "https://resource" - identityResourceId := "/subscriptions/testSub/resourceGroups/testGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" + identityResourceID := "/subscriptions/testSub/resourceGroups/testGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" cb := func(token Token) error { return nil } - spt, err := NewServicePrincipalTokenFromMSIWithIdentityResourceID("http://msiendpoint/", resource, identityResourceId, cb) + spt, err := NewServicePrincipalTokenFromMSIWithIdentityResourceID("http://msiendpoint/", resource, identityResourceID, cb) if err != nil { t.Fatalf("Failed to get MSI SPT: %v", err) } @@ -767,7 +767,9 @@ func TestNewServicePrincipalTokenFromMSIWithIdentityResourceID(t *testing.T) { t.Fatal("SPT had incorrect refresh callbacks.") } - strings.Contains(spt.inner.OauthConfig.TokenEndpoint.RawQuery, fmt.Sprintf("mi_res_id=%s", identityResourceId)) + if !strings.Contains(spt.inner.OauthConfig.TokenEndpoint.RawQuery, fmt.Sprintf("mi_res_id=%s", identityResourceID)) { + t.Fatal("SPT tokenEndpoint should contains mi_res_id") + } } func TestNewServicePrincipalTokenFromManualTokenSecret(t *testing.T) { From b19a6bb23658f4fcd3d7a7fff9ab7648c26133ba Mon Sep 17 00:00:00 2001 From: Haitao Chen Date: Thu, 6 Aug 2020 16:49:56 -0700 Subject: [PATCH 9/9] fix with url encode --- autorest/adal/token_test.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/autorest/adal/token_test.go b/autorest/adal/token_test.go index 4df92e4fd..931504a1b 100644 --- a/autorest/adal/token_test.go +++ b/autorest/adal/token_test.go @@ -694,7 +694,7 @@ func TestServicePrincipalTokenManualRefreshFailsWithoutRefresh(t *testing.T) { } func TestNewServicePrincipalTokenFromMSI(t *testing.T) { - resource := "https://resource" + const resource = "https://resource" cb := func(token Token) error { return nil } spt, err := NewServicePrincipalTokenFromMSI("http://msiendpoint/", resource, cb) @@ -717,8 +717,10 @@ func TestNewServicePrincipalTokenFromMSI(t *testing.T) { } func TestNewServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) { - resource := "https://resource" - userID := "abc123" + const ( + resource = "https://resource" + userID = "abc123" + ) cb := func(token Token) error { return nil } spt, err := NewServicePrincipalTokenFromMSIWithUserAssignedID("http://msiendpoint/", resource, userID, cb) @@ -745,8 +747,10 @@ func TestNewServicePrincipalTokenFromMSIWithUserAssignedID(t *testing.T) { } func TestNewServicePrincipalTokenFromMSIWithIdentityResourceID(t *testing.T) { - resource := "https://resource" - identityResourceID := "/subscriptions/testSub/resourceGroups/testGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" + const ( + resource = "https://resource" + identityResourceID = "/subscriptions/testSub/resourceGroups/testGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/test-identity" + ) cb := func(token Token) error { return nil } spt, err := NewServicePrincipalTokenFromMSIWithIdentityResourceID("http://msiendpoint/", resource, identityResourceID, cb) @@ -767,7 +771,10 @@ func TestNewServicePrincipalTokenFromMSIWithIdentityResourceID(t *testing.T) { t.Fatal("SPT had incorrect refresh callbacks.") } - if !strings.Contains(spt.inner.OauthConfig.TokenEndpoint.RawQuery, fmt.Sprintf("mi_res_id=%s", identityResourceID)) { + urlPathParameter := url.Values{} + urlPathParameter.Set("mi_res_id", identityResourceID) + + if !strings.Contains(spt.inner.OauthConfig.TokenEndpoint.RawQuery, urlPathParameter.Encode()) { t.Fatal("SPT tokenEndpoint should contains mi_res_id") } }