-
Notifications
You must be signed in to change notification settings - Fork 40
/
azure_cli_authorizer.go
205 lines (167 loc) · 6.26 KB
/
azure_cli_authorizer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package auth
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"strings"
"github.com/hashicorp/go-azure-sdk/sdk/environments"
"github.com/hashicorp/go-azure-sdk/sdk/internal/azurecli"
"golang.org/x/oauth2"
)
// Copyright (c) HashiCorp Inc. All rights reserved.
// Licensed under the MIT License. See NOTICE.txt in the project root for license information.
type AzureCliAuthorizerOptions struct {
// Api describes the Azure API being used
Api environments.Api
// TenantId is the tenant to authenticate against
TenantId string
// AuxTenantIds lists additional tenants to authenticate against, currently only
// used for Resource Manager when auxiliary tenants are needed.
// e.g. https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/authenticate-multi-tenant
AuxTenantIds []string
}
// NewAzureCliAuthorizer returns an Authorizer which authenticates using the Azure CLI.
func NewAzureCliAuthorizer(ctx context.Context, options AzureCliAuthorizerOptions) (Authorizer, error) {
conf, err := newAzureCliConfig(options.Api, options.TenantId, options.AuxTenantIds)
if err != nil {
return nil, err
}
return conf.TokenSource(ctx)
}
var _ Authorizer = &AzureCliAuthorizer{}
// AzureCliAuthorizer is an Authorizer which supports the Azure CLI.
type AzureCliAuthorizer struct {
// TenantID is the specified tenant ID, or the auto-detected tenant ID if none was specified
TenantID string
// DefaultSubscriptionID is the default subscription, when detected
DefaultSubscriptionID string
conf *azureCliConfig
}
// Token returns an access token using the Azure CLI as an authentication mechanism.
func (a *AzureCliAuthorizer) Token(_ context.Context, _ *http.Request) (*oauth2.Token, error) {
if a.conf == nil {
return nil, fmt.Errorf("could not request token: conf is nil")
}
azArgs := []string{"account", "get-access-token"}
// verify that the Azure CLI supports MSAL - ADAL is no longer supported
err := azurecli.CheckAzVersion(azurecli.MsalVersion, nil)
if err != nil {
return nil, fmt.Errorf("checking the version of the Azure CLI: %+v", err)
}
scope, err := environments.Scope(a.conf.Api)
if err != nil {
return nil, fmt.Errorf("determining scope for %q: %+v", a.conf.Api.Name(), err)
}
azArgs = append(azArgs, "--scope", *scope)
// Try to detect if we're running in Cloud Shell
if cloudShell := os.Getenv("AZUREPS_HOST_ENVIRONMENT"); !strings.HasPrefix(cloudShell, "cloud-shell/") {
// Seemingly not, so we'll append the tenant ID to the az args
azArgs = append(azArgs, "--tenant", a.conf.TenantID)
}
var token azureCliToken
if err := azurecli.JSONUnmarshalAzCmd(&token, azArgs...); err != nil {
return nil, err
}
return &oauth2.Token{
AccessToken: token.AccessToken,
TokenType: token.TokenType,
}, nil
}
// AuxiliaryTokens returns additional tokens for auxiliary tenant IDs, for use in multi-tenant scenarios
func (a *AzureCliAuthorizer) AuxiliaryTokens(_ context.Context, _ *http.Request) ([]*oauth2.Token, error) {
if a.conf == nil {
return nil, fmt.Errorf("could not request token: conf is nil")
}
// Return early if no auxiliary tenants are configured
if len(a.conf.AuxiliaryTenantIDs) == 0 {
return []*oauth2.Token{}, nil
}
// Try to detect if we're running in Cloud Shell
if cloudShell := os.Getenv("AZUREPS_HOST_ENVIRONMENT"); strings.HasPrefix(cloudShell, "cloud-shell/") {
return nil, fmt.Errorf("auxiliary tokens not supported in Cloud Shell")
}
azArgs := []string{"account", "get-access-token"}
// verify that the Azure CLI supports MSAL - ADAL is no longer supported
err := azurecli.CheckAzVersion(AzureCliMsalVersion, nil)
if err != nil {
return nil, fmt.Errorf("checking the version of the Azure CLI: %+v", err)
}
scope, err := environments.Scope(a.conf.Api)
if err != nil {
return nil, fmt.Errorf("determining scope for %q: %+v", a.conf.Api.Name(), err)
}
azArgs = append(azArgs, "--scope", *scope)
tokens := make([]*oauth2.Token, 0)
for _, tenantId := range a.conf.AuxiliaryTenantIDs {
argsWithTenant := append(azArgs, "--tenant", tenantId)
var token azureCliToken
if err := azurecli.JSONUnmarshalAzCmd(&token, argsWithTenant...); err != nil {
return nil, err
}
tokens = append(tokens, &oauth2.Token{
AccessToken: token.AccessToken,
TokenType: token.TokenType,
})
}
return tokens, nil
}
const (
AzureCliMinimumVersion = "2.0.81"
AzureCliMsalVersion = "2.30.0"
AzureCliNextMajorVersion = "3.0.0"
)
// azureCliConfig configures an AzureCliAuthorizer.
type azureCliConfig struct {
Api environments.Api
// TenantID is the required tenant ID for the primary token
TenantID string
// AuxiliaryTenantIDs is an optional list of tenant IDs for which to obtain additional tokens
AuxiliaryTenantIDs []string
// DefaultSubscriptionID is the optional default subscription ID
DefaultSubscriptionID string
}
// newAzureCliConfig validates the supplied tenant ID and returns a new azureCliConfig.
func newAzureCliConfig(api environments.Api, tenantId string, auxiliaryTenantIds []string) (*azureCliConfig, error) {
var err error
// check az-cli version
nextMajor := azurecli.NextMajorVersion
if err = azurecli.CheckAzVersion(azurecli.MinimumVersion, &nextMajor); err != nil {
return nil, err
}
// check tenant ID
tenantId, err = azurecli.CheckTenantID(tenantId)
if err != nil {
return nil, err
}
if tenantId == "" {
return nil, errors.New("invalid tenantId or unable to determine tenantId")
}
// get the default subscription ID
subscriptionId, err := azurecli.GetDefaultSubscriptionID()
if err != nil {
return nil, err
}
return &azureCliConfig{
Api: api,
TenantID: tenantId,
AuxiliaryTenantIDs: auxiliaryTenantIds,
DefaultSubscriptionID: subscriptionId,
}, nil
}
// TokenSource provides a source for obtaining access tokens using AzureCliAuthorizer.
func (c *azureCliConfig) TokenSource(ctx context.Context) (Authorizer, error) {
// Cache access tokens internally to avoid unnecessary `az` invocations
return NewCachedAuthorizer(&AzureCliAuthorizer{
TenantID: c.TenantID,
DefaultSubscriptionID: c.DefaultSubscriptionID,
conf: c,
})
}
type azureCliToken struct {
AccessToken string `json:"accessToken"`
ExpiresOn string `json:"expiresOn"`
Tenant string `json:"tenant"`
TokenType string `json:"tokenType"`
}