Skip to content

Commit

Permalink
use the adal library for spn when --legacy is specified (#338)
Browse files Browse the repository at this point in the history
* use the adal library for spn when --legacy is specified

* simplified validation

* addressed feedbacks

* added basic validation UTs
  • Loading branch information
weinong committed Sep 15, 2023
1 parent 8e19a20 commit c495a51
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/google/uuid v1.3.1
github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.8.2
golang.org/x/crypto v0.12.0
gopkg.in/dnaeon/go-vcr.v3 v3.1.2
gopkg.in/retry.v1 v1.0.3
Expand Down Expand Up @@ -59,6 +60,7 @@ require (
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/xlab/treeprint v1.1.0 // indirect
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect
golang.org/x/net v0.10.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions pkg/token/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ func newTokenProvider(o *Options) (TokenProvider, error) {
case InteractiveLogin:
return newInteractiveTokenProvider(*oAuthConfig, o.ClientID, o.ServerID, o.TenantID, popClaimsMap)
case ServicePrincipalLogin:
if o.IsLegacy {
return newLegacyServicePrincipalToken(*oAuthConfig, o.ClientID, o.ClientSecret, o.ClientCert, o.ClientCertPassword, o.ServerID, o.TenantID)
}
return newServicePrincipalTokenProvider(cloudConfiguration, o.ClientID, o.ClientSecret, o.ClientCert, o.ClientCertPassword, o.ServerID, o.TenantID, popClaimsMap)
case ROPCLogin:
return newResourceOwnerToken(*oAuthConfig, o.ClientID, o.Username, o.Password, o.ServerID, o.TenantID)
Expand Down
113 changes: 113 additions & 0 deletions pkg/token/serviceprincipaltoken_legacy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package token

import (
"errors"
"fmt"
"os"

"github.com/Azure/go-autorest/autorest/adal"
)

var errInvalidOAuthConfig = errors.New("OAuthConfig needs to be configured with api-version=1.0")

type legacyServicePrincipalToken struct {
clientID string
clientSecret string
clientCert string
clientCertPassword string
resourceID string
tenantID string
oAuthConfig adal.OAuthConfig
}

func newLegacyServicePrincipalToken(oAuthConfig adal.OAuthConfig, clientID, clientSecret, clientCert, clientCertPassword, resourceID, tenantID string) (TokenProvider, error) {
if err := validateOAuthConfig(oAuthConfig); err != nil {
return nil, err
}
if clientID == "" {
return nil, errors.New("clientID cannot be empty")
}
if clientSecret == "" && clientCert == "" {
return nil, errors.New("both clientSecret and clientcert cannot be empty")
}
if clientSecret != "" && clientCert != "" {
return nil, errors.New("client secret and client certificate cannot be set at the same time. Only one has to be specified")
}
if resourceID == "" {
return nil, errors.New("resourceID cannot be empty")
}
if tenantID == "" {
return nil, errors.New("tenantID cannot be empty")
}

return &legacyServicePrincipalToken{
clientID: clientID,
clientSecret: clientSecret,
clientCert: clientCert,
clientCertPassword: clientCertPassword,
resourceID: resourceID,
tenantID: tenantID,
oAuthConfig: oAuthConfig,
}, nil
}

func (p *legacyServicePrincipalToken) Token() (adal.Token, error) {
emptyToken := adal.Token{}

var (
spt *adal.ServicePrincipalToken
err error
)

if p.clientSecret != "" {
spt, err = adal.NewServicePrincipalToken(
p.oAuthConfig,
p.clientID,
p.clientSecret,
p.resourceID)
if err != nil {
return emptyToken, fmt.Errorf("failed to create service principal token using secret: %s", err)
}
} else if p.clientCert != "" {
certData, err := os.ReadFile(p.clientCert)
if err != nil {
return emptyToken, fmt.Errorf("failed to read the certificate file (%s): %w", p.clientCert, err)
}

// Get the certificate and private key from pfx file
cert, rsaPrivateKey, err := decodePkcs12(certData, p.clientCertPassword)
if err != nil {
return emptyToken, fmt.Errorf("failed to decode pkcs12 certificate while creating spt: %w", err)
}

spt, err = adal.NewServicePrincipalTokenFromCertificate(
p.oAuthConfig,
p.clientID,
cert,
rsaPrivateKey,
p.resourceID)
if err != nil {
return emptyToken, fmt.Errorf("failed to create service principal token using cert: %s", err)
}
}

err = spt.EnsureFresh()
if err != nil {
return emptyToken, err
}
return spt.Token(), nil
}

func validateOAuthConfig(config adal.OAuthConfig) error {
v := config.AuthorizeEndpoint.Query().Get("api-version")
if v != "1.0" {
return errInvalidOAuthConfig
}

v = config.TokenEndpoint.Query().Get("api-version")
if v != "1.0" {
return errInvalidOAuthConfig
}

return nil
}
31 changes: 31 additions & 0 deletions pkg/token/serviceprincipaltoken_legacy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package token

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestNewLegacyServicePrincipalToken(t *testing.T) {
t.Run("new spn token provider with legacy should not result in error", func(t *testing.T) {
_, err := newTokenProvider(&Options{
LoginMethod: ServicePrincipalLogin,
IsLegacy: true,
TenantID: "tenantID",
ClientID: "client-id",
ClientSecret: "foobar",
ServerID: "server-id",
Environment: "AzurePublicCloud",
})

require.NoError(t, err)
})

t.Run("legacy spn token provider with incorrectly setup oauth config should result in error", func(t *testing.T) {
oathConfig, err := getOAuthConfig("AzurePublicCloud", "tenantID", false)
require.NoError(t, err)

_, err = newLegacyServicePrincipalToken(*oathConfig, "client-id", "client-secret", "", "", "server-id", "tenant-id")
require.ErrorIs(t, err, errInvalidOAuthConfig)
})
}

0 comments on commit c495a51

Please sign in to comment.