diff --git a/go.mod b/go.mod index d4ade8bbe..a557a8f48 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-uuid v1.0.1 github.com/hashicorp/terraform-plugin-sdk/v2 v2.0.3 - github.com/manicminer/hamilton v0.0.0-20201214022947-cbad180256ed + github.com/manicminer/hamilton v0.0.0-20201214113833-69b33b22d310 ) go 1.14 diff --git a/go.sum b/go.sum index cb1b1911b..e4e4ced9a 100644 --- a/go.sum +++ b/go.sum @@ -248,8 +248,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= 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/manicminer/hamilton v0.0.0-20201214022947-cbad180256ed h1:2YMBfabym01GCls5y8+QmcPj8XbbfFHSv1hVL+BrlFY= -github.com/manicminer/hamilton v0.0.0-20201214022947-cbad180256ed/go.mod h1:3+kIF2LZiDNEUtnO+r6FyFhYPebiZ3galn6Wwm5wKUU= +github.com/manicminer/hamilton v0.0.0-20201214113833-69b33b22d310 h1:xQ5fjkWxxPLWQCrwGs9GP1j2FNmrn618vOuHHzUGGzI= +github.com/manicminer/hamilton v0.0.0-20201214113833-69b33b22d310/go.mod h1:3+kIF2LZiDNEUtnO+r6FyFhYPebiZ3galn6Wwm5wKUU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= diff --git a/internal/clients/builder.go b/internal/clients/builder.go index ec0d3befc..788559114 100644 --- a/internal/clients/builder.go +++ b/internal/clients/builder.go @@ -31,7 +31,7 @@ func (b *ClientBuilder) Build(ctx context.Context) (*Client, error) { if getAuthenticatedObjectID := b.AadAuthConfig.GetAuthenticatedObjectID; getAuthenticatedObjectID != nil { v, err := getAuthenticatedObjectID(ctx) if err != nil { - return nil, fmt.Errorf("Error getting authenticated object ID: %v", err) + return nil, fmt.Errorf("getting authenticated object ID: %v", err) } objectID = v } @@ -77,10 +77,19 @@ func (b *ClientBuilder) Build(ctx context.Context) (*Client, error) { if err != nil { return nil, err } + + // Obtain the tenant ID from Azure CLI + if cli, ok := o.MsGraphAuthorizer.(auth.AzureCliAuthorizer); ok { + if cli.TenantID == "" { + return nil, fmt.Errorf("azure-cli could not determine tenant ID to use") + } + + // TODO: v2.0 set the provider tenantId from here, for now we use the one returned by go-azure-helpers + } } if err := client.build(ctx, o); err != nil { - return nil, fmt.Errorf("Error building Client: %+v", err) + return nil, fmt.Errorf("building client: %+v", err) } return &client, nil diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 418821320..352735a1f 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -207,14 +207,14 @@ func providerConfigure(p *schema.Provider) schema.ConfigureContextFunc { authConfig = &auth.Config{ TenantID: d.Get("tenant_id").(string), ClientID: d.Get("client_id").(string), + ClientCertPassword: d.Get("client_certificate_password").(string), + ClientCertPath: d.Get("client_certificate_path").(string), + ClientSecret: d.Get("client_secret").(string), EnableAzureCliToken: true, - EnableMsiAuth: true, - MsiEndpoint: d.Get("msi_endpoint").(string), EnableClientCertAuth: true, - ClientCertPath: d.Get("client_certificate_path").(string), - ClientCertPassword: d.Get("client_certificate_password").(string), EnableClientSecretAuth: true, - ClientSecret: d.Get("client_secret").(string), + EnableMsiAuth: true, // TODO: not yet supported + MsiEndpoint: d.Get("msi_endpoint").(string), } } diff --git a/vendor/github.com/manicminer/hamilton/auth/auth.go b/vendor/github.com/manicminer/hamilton/auth/auth.go index 4a81c74a6..d63a1813d 100644 --- a/vendor/github.com/manicminer/hamilton/auth/auth.go +++ b/vendor/github.com/manicminer/hamilton/auth/auth.go @@ -22,7 +22,6 @@ type Config struct { ClientID string // Azure CLI Tokens Auth - // TODO: NOT YET SUPPORTED EnableAzureCliToken bool // Managed Service Identity Auth @@ -46,7 +45,7 @@ type Authorizer interface { } func (c *Config) NewAuthorizer(ctx context.Context) (Authorizer, error) { - if c.EnableClientCertAuth && strings.TrimSpace(c.ClientID) != "" && strings.TrimSpace(c.ClientCertPath) != "" { + if c.EnableClientCertAuth && strings.TrimSpace(c.TenantID) == "" && strings.TrimSpace(c.ClientID) != "" && strings.TrimSpace(c.ClientCertPath) != "" { a, err := NewClientCertificateAuthorizer(ctx, c.TenantID, c.ClientID, c.ClientCertPath, c.ClientCertPassword) if err != nil { return nil, fmt.Errorf("could not configure ClientCertificate Authorizer: %s", err) @@ -56,7 +55,7 @@ func (c *Config) NewAuthorizer(ctx context.Context) (Authorizer, error) { } } - if c.EnableClientSecretAuth && strings.TrimSpace(c.ClientID) != "" && strings.TrimSpace(c.ClientSecret) != "" { + if c.EnableClientSecretAuth && strings.TrimSpace(c.TenantID) == "" && strings.TrimSpace(c.ClientID) != "" && strings.TrimSpace(c.ClientSecret) != "" { a, err := NewClientSecretAuthorizer(ctx, c.TenantID, c.ClientID, c.ClientSecret) if err != nil { return nil, fmt.Errorf("could not configure ClientCertificate Authorizer: %s", err) @@ -66,17 +65,21 @@ func (c *Config) NewAuthorizer(ctx context.Context) (Authorizer, error) { } } + if c.EnableAzureCliToken { + a, err := NewAzureCliAuthorizer(ctx, c.TenantID) + if err != nil { + return nil, fmt.Errorf("could not configure AzureCli Authorizer: %s", err) + } + if a != nil { + return a, nil + } + } + return nil, fmt.Errorf("no Authorizer could be configured, please check your configuration") } -func NewClientSecretAuthorizer(ctx context.Context, tenantId, clientId, clientSecret string) (Authorizer, error) { - conf := clientcredentials.Config{ - AuthStyle: oauth2.AuthStyleInParams, - ClientID: clientId, - ClientSecret: clientSecret, - Scopes: []string{"https://graph.microsoft.com/.default"}, - TokenURL: endpoints.AzureAD(tenantId).TokenURL, - } +func NewAzureCliAuthorizer(ctx context.Context, tenantId string) (Authorizer, error) { + conf := AzureCliConfig{TenantID: tenantId} return conf.TokenSource(ctx), nil } @@ -105,3 +108,14 @@ func NewClientCertificateAuthorizer(ctx context.Context, tenantId, clientId, pfx } return conf.TokenSource(ctx), nil } + +func NewClientSecretAuthorizer(ctx context.Context, tenantId, clientId, clientSecret string) (Authorizer, error) { + conf := clientcredentials.Config{ + AuthStyle: oauth2.AuthStyleInParams, + ClientID: clientId, + ClientSecret: clientSecret, + Scopes: []string{"https://graph.microsoft.com/.default"}, + TokenURL: endpoints.AzureAD(tenantId).TokenURL, + } + return conf.TokenSource(ctx), nil +} diff --git a/vendor/github.com/manicminer/hamilton/auth/azcli.go b/vendor/github.com/manicminer/hamilton/auth/azcli.go new file mode 100644 index 000000000..8a67b36e6 --- /dev/null +++ b/vendor/github.com/manicminer/hamilton/auth/azcli.go @@ -0,0 +1,99 @@ +package auth + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "golang.org/x/oauth2" + "os/exec" + "regexp" + "strings" + "time" +) + +type AzureCliAuthorizer struct { + ctx context.Context + conf *AzureCliConfig + TenantID string +} + +func (a AzureCliAuthorizer) Token() (*oauth2.Token, error) { + // We don't need to handle token caching and refreshing since az-cli does that for us + var token struct { + AccessToken string `json:"accessToken"` + ExpiresOn string `json:"expiresOn"` + Tenant string `json:"tenant"` + TokenType string `json:"tokenType"` + } + cmd := []string{"account", "get-access-token", "--resource-type=ms-graph", "--tenant", a.TenantID, "-o=json"} + err := jsonUnmarshalAzCmd(&token, cmd...) + if err != nil { + return nil, err + } + + return &oauth2.Token{ + AccessToken: token.AccessToken, + TokenType: token.TokenType, + Expiry: time.Time{}, + }, nil +} + +type AzureCliConfig struct { + TenantID string +} + +func (c *AzureCliConfig) TokenSource(ctx context.Context) Authorizer { + var tenantId string + if validTenantId, err := regexp.MatchString("^[a-zA-Z0-9._-]+$", c.TenantID); err == nil && validTenantId { + tenantId = c.TenantID + } else { + var account struct { + ID string `json:"id"` + TenantID string `json:"tenantId"` + } + cmd := []string{"account", "show", "-o=json"} + err := jsonUnmarshalAzCmd(&account, cmd...) + if err == nil { + tenantId = account.TenantID + } + } + + return &AzureCliAuthorizer{ + ctx: ctx, + conf: c, + TenantID: tenantId, + } +} + +func jsonUnmarshalAzCmd(i interface{}, arg ...string) error { + var stderr bytes.Buffer + var stdout bytes.Buffer + + cmd := exec.Command("az", arg...) + + cmd.Stderr = &stderr + cmd.Stdout = &stdout + + if err := cmd.Start(); err != nil { + err := fmt.Errorf("launching Azure CLI: %+v", err) + if stdErrStr := stderr.String(); stdErrStr != "" { + err = fmt.Errorf("%s: %s", err, strings.TrimSpace(stdErrStr)) + } + return err + } + + if err := cmd.Wait(); err != nil { + err := fmt.Errorf("waiting for the Azure CLI: %+v", err) + if stdErrStr := stderr.String(); stdErrStr != "" { + err = fmt.Errorf("%s: %s", err, strings.TrimSpace(stdErrStr)) + } + return err + } + + if err := json.Unmarshal([]byte(stdout.String()), &i); err != nil { + return fmt.Errorf("unmarshaling the result of Azure CLI: %v", err) + } + + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 902aecb73..328e95c49 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -249,7 +249,7 @@ github.com/jstemmer/go-junit-report/formatter github.com/jstemmer/go-junit-report/parser # github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd github.com/kevinburke/ssh_config -# github.com/manicminer/hamilton v0.0.0-20201214022947-cbad180256ed +# github.com/manicminer/hamilton v0.0.0-20201214113833-69b33b22d310 ## explicit github.com/manicminer/hamilton/auth github.com/manicminer/hamilton/auth/microsoft