-
Notifications
You must be signed in to change notification settings - Fork 189
/
authorizers.go
320 lines (259 loc) · 8.3 KB
/
authorizers.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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package iam
import (
"fmt"
"net/url"
"strings"
"github.com/Azure/azure-service-operator/pkg/resourcemanager/config"
"github.com/Azure/go-autorest/autorest"
"github.com/Azure/go-autorest/autorest/adal"
"github.com/Azure/go-autorest/autorest/azure/auth"
)
// This file was adapted from https://github.com/Azure-Samples/azure-sdk-for-go-samples/blob/master/internal/iam/authorizers.go
// A possible alternative would be to use NewAuthorizerFromEnvironment from https://github.com/Azure/go-autorest/blob/master/autorest/azure/auth/auth.go,
// but that may not be doable because of our need to support multiple managed identities and select between them.
var (
armAuthorizer autorest.Authorizer
batchAuthorizer autorest.Authorizer
graphAuthorizer autorest.Authorizer
groupsAuthorizer autorest.Authorizer
keyvaultAuthorizer autorest.Authorizer
)
// OAuthGrantType specifies which grant type to use.
type OAuthGrantType int
const (
// OAuthGrantTypeServicePrincipal for client credentials flow
OAuthGrantTypeServicePrincipal OAuthGrantType = iota
// OAuthGrantTypeDeviceFlow for device flow
OAuthGrantTypeDeviceFlow
// OAuthGrantTypeManagedIdentity for aad-pod-identity
OAuthGrantTypeManagedIdentity
)
// GrantType returns what grant type has been configured.
func grantType(creds config.Credentials) OAuthGrantType {
if config.UseDeviceFlow() {
return OAuthGrantTypeDeviceFlow
}
if creds.UseManagedIdentity() {
return OAuthGrantTypeManagedIdentity
}
return OAuthGrantTypeServicePrincipal
}
// GetResourceManagementAuthorizer gets an OAuthTokenAuthorizer for Azure Resource Manager
func GetResourceManagementAuthorizer(creds config.Credentials) (autorest.Authorizer, error) {
if armAuthorizer != nil {
return armAuthorizer, nil
}
var a autorest.Authorizer
var err error
a, err = getAuthorizerForResource(config.Environment().ResourceManagerEndpoint, creds)
if err == nil {
// cache
armAuthorizer = a
} else {
// clear cache
armAuthorizer = nil
}
return armAuthorizer, err
}
// GetBatchAuthorizer gets an OAuthTokenAuthorizer for Azure Batch.
func GetBatchAuthorizer(creds config.Credentials) (autorest.Authorizer, error) {
if batchAuthorizer != nil {
return batchAuthorizer, nil
}
var a autorest.Authorizer
var err error
a, err = getAuthorizerForResource(config.Environment().BatchManagementEndpoint, creds)
if err == nil {
// cache
batchAuthorizer = a
} else {
// clear cache
batchAuthorizer = nil
}
return batchAuthorizer, err
}
// GetGraphAuthorizer gets an OAuthTokenAuthorizer for graphrbac API.
func GetGraphAuthorizer(creds config.Credentials) (autorest.Authorizer, error) {
if graphAuthorizer != nil {
return graphAuthorizer, nil
}
var a autorest.Authorizer
var err error
a, err = getAuthorizerForResource(config.Environment().GraphEndpoint, creds)
if err == nil {
// cache
graphAuthorizer = a
} else {
graphAuthorizer = nil
}
return graphAuthorizer, err
}
// GetGroupsAuthorizer gets an OAuthTokenAuthorizer for resource group API.
func GetGroupsAuthorizer(creds config.Credentials) (autorest.Authorizer, error) {
if groupsAuthorizer != nil {
return groupsAuthorizer, nil
}
var a autorest.Authorizer
var err error
a, err = getAuthorizerForResource(config.Environment().TokenAudience, creds)
if err == nil {
// cache
groupsAuthorizer = a
} else {
groupsAuthorizer = nil
}
return groupsAuthorizer, err
}
// GetKeyvaultAuthorizer gets an OAuthTokenAuthorizer for use with Key Vault
// keys and secrets. Note that Key Vault *Vaults* are managed by Azure Resource
// Manager.
func GetKeyvaultAuthorizer(creds config.Credentials) (autorest.Authorizer, error) {
if keyvaultAuthorizer != nil {
return keyvaultAuthorizer, nil
}
// BUG: default value for KeyVaultEndpoint is wrong
vaultEndpoint := strings.TrimSuffix(config.Environment().KeyVaultEndpoint, "/")
// BUG: alternateEndpoint replaces other endpoints in the configs below
alternateEndpoint, _ := url.Parse(
"https://login.windows.net/" + creds.TenantID() + "/oauth2/token")
var a autorest.Authorizer
var err error
switch grantType(creds) {
case OAuthGrantTypeServicePrincipal:
oauthconfig, err := adal.NewOAuthConfig(
config.Environment().ActiveDirectoryEndpoint, creds.TenantID())
if err != nil {
return a, err
}
oauthconfig.AuthorizeEndpoint = *alternateEndpoint
token, err := adal.NewServicePrincipalToken(
*oauthconfig, creds.ClientID(), creds.ClientSecret(), vaultEndpoint)
if err != nil {
return a, err
}
a = autorest.NewBearerAuthorizer(token)
case OAuthGrantTypeManagedIdentity:
MIEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, err
}
token, err := adal.NewServicePrincipalTokenFromMSI(MIEndpoint, vaultEndpoint)
if err != nil {
return nil, err
}
a = autorest.NewBearerAuthorizer(token)
case OAuthGrantTypeDeviceFlow:
// TODO: Remove this - it's an interactive authentication
// method and doesn't make sense in an operator. Maybe it was
// useful for early testing?
deviceConfig := auth.NewDeviceFlowConfig(creds.ClientID(), creds.TenantID())
deviceConfig.Resource = vaultEndpoint
deviceConfig.AADEndpoint = alternateEndpoint.String()
a, err = deviceConfig.Authorizer()
default:
return a, fmt.Errorf("invalid grant type specified")
}
if err == nil {
keyvaultAuthorizer = a
} else {
keyvaultAuthorizer = nil
}
return keyvaultAuthorizer, err
}
func getAuthorizerForResource(resource string, creds config.Credentials) (autorest.Authorizer, error) {
var a autorest.Authorizer
var err error
switch grantType(creds) {
case OAuthGrantTypeServicePrincipal:
oauthConfig, err := adal.NewOAuthConfig(
config.Environment().ActiveDirectoryEndpoint, creds.TenantID())
if err != nil {
return nil, err
}
token, err := adal.NewServicePrincipalToken(
*oauthConfig, creds.ClientID(), creds.ClientSecret(), resource)
if err != nil {
return nil, err
}
a = autorest.NewBearerAuthorizer(token)
case OAuthGrantTypeManagedIdentity:
MIEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, err
}
token, err := adal.NewServicePrincipalTokenFromMSI(MIEndpoint, resource)
if err != nil {
return nil, err
}
a = autorest.NewBearerAuthorizer(token)
case OAuthGrantTypeDeviceFlow:
deviceconfig := auth.NewDeviceFlowConfig(creds.ClientID(), creds.TenantID())
deviceconfig.Resource = resource
a, err = deviceconfig.Authorizer()
if err != nil {
return nil, err
}
default:
return a, fmt.Errorf("invalid grant type specified")
}
return a, err
}
// GetMSITokenForResource returns the MSI token for a resource
func GetMSITokenForResource(resource string) (*adal.ServicePrincipalToken, error) {
miEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, err
}
token, err := adal.NewServicePrincipalTokenFromMSI(miEndpoint, resource)
if err != nil {
return nil, err
}
return token, err
}
// GetMSITokenForResourceByClientID returns the MSI token for a resource by the client ID
func GetMSITokenForResourceByClientID(resource string, clientID string) (*adal.ServicePrincipalToken, error) {
miEndpoint, err := adal.GetMSIVMEndpoint()
if err != nil {
return nil, err
}
token, err := adal.NewServicePrincipalTokenFromMSIWithUserAssignedID(miEndpoint, resource, clientID)
if err != nil {
return nil, err
}
return token, err
}
// GetMSITokenProviderForResource gets a token provider for the given resource.
// A token provider is just a function that returns the OAuth token when called
func GetMSITokenProviderForResource(resource string) (func() (string, error), error) {
msi, err := GetMSITokenForResource(resource)
if err != nil {
return nil, err
}
return makeTokenProvider(msi), nil
}
func makeTokenProvider(msi *adal.ServicePrincipalToken) func() (string, error) {
return func() (string, error) {
err := msi.EnsureFresh()
if err != nil {
return "", err
}
token := msi.OAuthToken()
return token, nil
}
}
func GetMSITokenProviderForResourceByClientID(resource string, clientID string) (func() (string, error), error) {
msi, err := GetMSITokenForResourceByClientID(resource, clientID)
if err != nil {
return nil, err
}
return func() (string, error) {
err = msi.EnsureFresh()
if err != nil {
return "", err
}
token := msi.OAuthToken()
return token, nil
}, nil
}