Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for autorest.BearerAuthorizer #120

Merged
merged 3 commits into from
Nov 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io/ioutil"
"strings"

"github.com/Azure/go-autorest/autorest"
"golang.org/x/crypto/pkcs12"
"golang.org/x/oauth2"

Expand Down Expand Up @@ -87,6 +88,11 @@ func (c *Config) NewAuthorizer(ctx context.Context, api Api) (Authorizer, error)
return nil, fmt.Errorf("no Authorizer could be configured, please check your configuration")
}

// NewAutorestAuthorizerWrapper returns an Authorizer that sources tokens from a supplied autorest.BearerAuthorizer
func NewAutorestAuthorizerWrapper(bearerAuthorizer *autorest.BearerAuthorizer) (Authorizer, error) {
return AutorestAuthorizerWrapper{bearerAuthorizer: bearerAuthorizer}, nil
}

// NewAzureCliAuthorizer returns an Authorizer which authenticates using the Azure CLI.
func NewAzureCliAuthorizer(ctx context.Context, api Api, tenantId string) (Authorizer, error) {
conf, err := NewAzureCliConfig(api, tenantId)
Expand Down
70 changes: 67 additions & 3 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"os"
"testing"

"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"golang.org/x/oauth2"

"github.com/manicminer/hamilton/auth"
Expand All @@ -20,6 +22,7 @@ var (
clientCertificatePath = os.Getenv("CLIENT_CERTIFICATE_PATH")
clientCertPassword = os.Getenv("CLIENT_CERTIFICATE_PASSWORD")
clientSecret = os.Getenv("CLIENT_SECRET")
environment = os.Getenv("AZURE_ENVIRONMENT")
msiEndpoint = os.Getenv("MSI_ENDPOINT")
msiToken = os.Getenv("MSI_TOKEN")
)
Expand All @@ -35,14 +38,21 @@ func TestClientCertificateAuthorizerV2(t *testing.T) {
}

func testClientCertificateAuthorizer(ctx context.Context, t *testing.T, tokenVersion auth.TokenVersion) (token *oauth2.Token) {
env, err := environments.EnvironmentFromString(environment)
if err != nil {
t.Fatal(err)
}

pfx := utils.Base64DecodeCertificate(clientCertificate)
auth, err := auth.NewClientCertificateAuthorizer(ctx, environments.Global, auth.MsGraph, tokenVersion, tenantId, clientId, pfx, clientCertificatePath, clientCertPassword)

auth, err := auth.NewClientCertificateAuthorizer(ctx, env, auth.MsGraph, tokenVersion, tenantId, clientId, pfx, clientCertificatePath, clientCertPassword)
if err != nil {
t.Fatalf("NewClientCertificateAuthorizer(): %v", err)
}
if auth == nil {
t.Fatal("auth is nil, expected Authorizer")
}

token, err = auth.Token()
if err != nil {
t.Fatalf("auth.Token(): %v", err)
Expand All @@ -53,6 +63,7 @@ func testClientCertificateAuthorizer(ctx context.Context, t *testing.T, tokenVer
if token.AccessToken == "" {
t.Fatal("token.AccessToken was empty")
}

return
}

Expand All @@ -67,13 +78,19 @@ func TestClientSecretAuthorizerV2(t *testing.T) {
}

func testClientSecretAuthorizer(ctx context.Context, t *testing.T, tokenVersion auth.TokenVersion) (token *oauth2.Token) {
auth, err := auth.NewClientSecretAuthorizer(ctx, environments.Global, auth.MsGraph, tokenVersion, tenantId, clientId, clientSecret)
env, err := environments.EnvironmentFromString(environment)
if err != nil {
t.Fatal(err)
}

auth, err := auth.NewClientSecretAuthorizer(ctx, env, auth.MsGraph, tokenVersion, tenantId, clientId, clientSecret)
if err != nil {
t.Fatalf("NewClientSecretAuthorizer(): %v", err)
}
if auth == nil {
t.Fatal("auth is nil, expected Authorizer")
}

token, err = auth.Token()
if err != nil {
t.Fatalf("auth.Token(): %v", err)
Expand All @@ -84,6 +101,7 @@ func testClientSecretAuthorizer(ctx context.Context, t *testing.T, tokenVersion
if token.AccessToken == "" {
t.Fatalf("token.AccessToken was empty")
}

return
}

Expand All @@ -100,6 +118,7 @@ func testAzureCliAuthorizer(ctx context.Context, t *testing.T) (token *oauth2.To
if auth == nil {
t.Fatal("auth is nil, expected Authorizer")
}

token, err = auth.Token()
if err != nil {
t.Fatalf("auth.Token(): %v", err)
Expand All @@ -110,6 +129,7 @@ func testAzureCliAuthorizer(ctx context.Context, t *testing.T) (token *oauth2.To
if token.AccessToken == "" {
t.Fatalf("token.AccessToken was empty")
}

return
}

Expand All @@ -122,13 +142,57 @@ func TestMsiAuthorizer(t *testing.T) {
done <- true
}()
}
auth, err := auth.NewMsiAuthorizer(ctx, environments.Global, auth.MsGraph, msiEndpoint, clientId)

env, err := environments.EnvironmentFromString(environment)
if err != nil {
t.Fatal(err)
}

auth, err := auth.NewMsiAuthorizer(ctx, env, auth.MsGraph, msiEndpoint, clientId)
if err != nil {
t.Fatalf("NewMsiAuthorizer(): %v", err)
}
if auth == nil {
t.Fatal("auth is nil, expected Authorizer")
}

token, err := auth.Token()
if err != nil {
t.Fatalf("auth.Token(): %v", err)
}
if token == nil {
t.Fatal("token was nil")
}
if token.AccessToken == "" {
t.Fatal("token.AccessToken was empty")
}
}

func TestAutorestAuthorizerWrapper(t *testing.T) {
env, err := environments.EnvironmentFromString(environment)
if err != nil {
t.Fatal(err)
}

// adal.ServicePrincipalToken.refreshInternal() doesn't support v2 tokens
oauthConfig, err := adal.NewOAuthConfigWithAPIVersion(string(env.AzureADEndpoint), tenantId, utils.StringPtr("1.0"))
if err != nil {
t.Fatalf("adal.NewOAuthConfig(): %v", err)
}

spt, err := adal.NewServicePrincipalToken(*oauthConfig, clientId, clientSecret, string(env.MsGraph.Endpoint))
if err != nil {
t.Fatalf("adal.NewServicePrincipalToken(): %v", err)
}

auth, err := auth.NewAutorestAuthorizerWrapper(autorest.NewBearerAuthorizer(spt))
if err != nil {
t.Fatalf("NewAutorestAuthorizerWrapper(): %v", err)
}
if auth == nil {
t.Fatal("auth is nil, expected Authorizer")
}

token, err := auth.Token()
if err != nil {
t.Fatalf("auth.Token(): %v", err)
Expand Down
40 changes: 40 additions & 0 deletions auth/autorest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package auth

import (
"fmt"

"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"golang.org/x/oauth2"
)

// AutorestAuthorizerWrapper is an Authorizer which sources tokens from an autorest.BearerAuthorizer
type AutorestAuthorizerWrapper struct {
bearerAuthorizer *autorest.BearerAuthorizer
}

// Token returns an access token using an autorest.BearerAuthorizer struct
func (a AutorestAuthorizerWrapper) Token() (*oauth2.Token, error) {
tokenProvider := a.bearerAuthorizer.TokenProvider()
if refresher, ok := tokenProvider.(adal.Refresher); ok {
if err := refresher.EnsureFresh(); err != nil {
return nil, err
}
}

var adalToken adal.Token
if spToken, ok := tokenProvider.(*adal.ServicePrincipalToken); ok {
adalToken = spToken.Token()
}

if adalToken.AccessToken == "" {
return nil, fmt.Errorf("could not obtain access token via supplied autorest.BearerAuthorizer")
}

return &oauth2.Token{
AccessToken: adalToken.AccessToken,
TokenType: adalToken.Type,
RefreshToken: adalToken.RefreshToken,
Expiry: adalToken.Expires(),
}, nil
}
24 changes: 24 additions & 0 deletions environments/environments.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package environments

import (
"fmt"
"strings"
)

// Environment represents a set of API configurations for a particular cloud.
type Environment struct {
// The Azure AD endpoint for acquiring access tokens.
Expand Down Expand Up @@ -48,3 +53,22 @@ var (
MsGraph: MsGraphCanary,
}
)

func EnvironmentFromString(env string) (Environment, error) {
switch strings.ToLower(env) {
case "", "public", "global":
return Global, nil
case "usgovernment", "usgovernmentl4":
return USGovernmentL4, nil
case "dod", "usgovernmentl5":
return USGovernmentL5, nil
case "canary":
return Canary, nil
case "china":
return China, nil
case "germany":
return Germany, nil
}

return Environment{}, fmt.Errorf("invalid environment specified: %s", env)
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/manicminer/hamilton
go 1.16

require (
github.com/Azure/go-autorest/autorest v0.11.21
github.com/Azure/go-autorest/autorest/adal v0.9.14
github.com/golang/protobuf v1.5.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0
Expand Down
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.21 h1:w77zY/9RnUAWcIQyDC0Fc89mCvwftR8F+zsR/OH6enk=
github.com/Azure/go-autorest/autorest v0.11.21/go.mod h1:Do/yuMSW/13ayUkcVREpsMHGG+MvV81uzSCFgYPj4tM=
github.com/Azure/go-autorest/autorest/adal v0.9.14 h1:G8hexQdV5D4khOXrWG2YuLCFKhWYmWD8bHYaXN5ophk=
github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg=
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand All @@ -45,6 +59,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
Expand Down Expand Up @@ -137,6 +153,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down
17 changes: 4 additions & 13 deletions internal/test/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,11 @@ type Connection struct {

// NewConnection configures and returns a Connection for use in tests.
func NewConnection(api auth.Api, tokenVersion auth.TokenVersion) *Connection {
env := environments.Global
switch environment {
case "usgovernment", "usgovernmentl4":
env = environments.USGovernmentL4
case "dod", "usgovernmentl5":
env = environments.USGovernmentL5
case "canary":
env = environments.Canary
case "china":
env = environments.China
case "germany":
env = environments.Germany
env, err := environments.EnvironmentFromString(environment)
if err != nil {
log.Fatal(err)
}

t := Connection{
AuthConfig: &auth.Config{
Environment: env,
Expand All @@ -62,7 +54,6 @@ func NewConnection(api auth.Api, tokenVersion auth.TokenVersion) *Connection {
DomainName: tenantDomain,
}

var err error
t.Authorizer, err = t.AuthConfig.NewAuthorizer(t.Context, api)
if err != nil {
log.Fatal(err)
Expand Down