1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
13
"github.com/Azure/azure-sdk-for-go/arm/authorization"
14
"github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions"
15
"github.com/Azure/go-autorest/autorest"
16
"github.com/Azure/go-autorest/autorest/azure"
17
"github.com/Azure/go-autorest/autorest/to"
18
"github.com/juju/errors"
19
"github.com/juju/loggo"
20
"github.com/juju/utils"
21
"github.com/juju/utils/clock"
23
"github.com/juju/juju/provider/azure/internal/ad"
24
"github.com/juju/juju/provider/azure/internal/errorutils"
25
"github.com/juju/juju/provider/azure/internal/tracing"
28
var logger = loggo.GetLogger("juju.provider.azure.internal.azureauth")
31
// jujuApplicationId is the ID of the Azure application that we use
32
// for interactive authentication. When the user logs in, a service
33
// principal will be created in their Active Directory tenant for
35
jujuApplicationId = "cbb548f1-5039-4836-af0b-727e8571f6a9"
37
// passwordExpiryDuration is how long the application password we
38
// set will remain valid.
39
passwordExpiryDuration = 365 * 24 * time.Hour
42
// InteractiveCreateServicePrincipalFunc is a function type for
43
// interactively creating service principals for a subscription.
44
type InteractiveCreateServicePrincipalFunc func(
46
sender autorest.Sender,
47
requestInspector autorest.PrepareDecorator,
48
resourceManagerEndpoint string,
50
subscriptionId string,
52
newUUID func() (utils.UUID, error),
53
) (appId, password string, _ error)
55
// InteractiveCreateServicePrincipal interactively creates service
56
// principals for a subscription.
57
func InteractiveCreateServicePrincipal(
59
sender autorest.Sender,
60
requestInspector autorest.PrepareDecorator,
61
resourceManagerEndpoint string,
63
subscriptionId string,
65
newUUID func() (utils.UUID, error),
66
) (appId, password string, _ error) {
68
subscriptionsClient := subscriptions.Client{
69
subscriptions.NewWithBaseURI(resourceManagerEndpoint),
71
subscriptionsClient.Sender = sender
72
setClientInspectors(&subscriptionsClient.Client, requestInspector, "azure.subscriptions")
74
oauthConfig, tenantId, err := OAuthConfig(
76
resourceManagerEndpoint,
80
return "", "", errors.Trace(err)
83
client := autorest.NewClientWithUserAgent("juju")
84
client.Sender = sender
85
setClientInspectors(&client, requestInspector, "azure.autorest")
87
// Perform the interactive authentication. The user will be prompted to
88
// open a URL and input a device code, after which they will have to
89
// enter their username and password if they are not already
90
// authenticated with Azure.
91
fmt.Fprintln(stderr, "Initiating interactive authentication.")
93
armResource := TokenResource(resourceManagerEndpoint)
94
clientId := jujuApplicationId
95
deviceCode, err := azure.InitiateDeviceAuth(&client, *oauthConfig, clientId, armResource)
97
return "", "", errors.Annotate(err, "initiating interactive authentication")
99
fmt.Fprintln(stderr, to.String(deviceCode.Message)+"\n")
100
token, err := azure.WaitForUserCompletion(&client, deviceCode)
102
return "", "", errors.Annotate(err, "waiting for interactive authentication to completed")
105
// Create service principal tokens that we can use to authorize API
106
// requests to Active Directory and Resource Manager. These tokens
107
// are only valid for a short amount of time, so we must create a
108
// service principal password that can be used to obtain new tokens.
109
armSpt, err := azure.NewServicePrincipalTokenFromManualToken(*oauthConfig, clientId, armResource, *token)
111
return "", "", errors.Annotate(err, "creating temporary ARM service principal token")
113
if client.Sender != nil {
114
armSpt.SetSender(client.Sender)
116
if err := armSpt.Refresh(); err != nil {
117
return "", "", errors.Trace(err)
120
// The application requires permissions for both ARM and AD, so we
121
// can use the token for both APIs.
122
graphResource := TokenResource(graphEndpoint)
123
graphToken := armSpt.Token
124
graphToken.Resource = graphResource
125
graphSpt, err := azure.NewServicePrincipalTokenFromManualToken(*oauthConfig, clientId, graphResource, graphToken)
127
return "", "", errors.Annotate(err, "creating temporary Graph service principal token")
129
if client.Sender != nil {
130
graphSpt.SetSender(client.Sender)
132
if err := graphSpt.Refresh(); err != nil {
133
return "", "", errors.Trace(err)
136
directoryURL, err := url.Parse(graphEndpoint)
138
return "", "", errors.Annotate(err, "parsing identity endpoint")
140
directoryURL.Path = path.Join(directoryURL.Path, tenantId)
141
directoryClient := ad.NewManagementClient(directoryURL.String())
142
authorizationClient := authorization.NewWithBaseURI(resourceManagerEndpoint, subscriptionId)
143
directoryClient.Authorizer = graphSpt
144
authorizationClient.Authorizer = armSpt
145
authorizationClient.Sender = client.Sender
146
directoryClient.Sender = client.Sender
147
setClientInspectors(&directoryClient.Client, requestInspector, "azure.directory")
148
setClientInspectors(&authorizationClient.Client, requestInspector, "azure.authorization")
150
userObject, err := ad.UsersClient{directoryClient}.GetCurrentUser()
152
return "", "", errors.Trace(err)
154
fmt.Fprintf(stderr, "Authenticated as %q.\n", userObject.DisplayName)
156
fmt.Fprintln(stderr, "Creating/updating service principal.")
157
servicePrincipalObjectId, password, err := createOrUpdateServicePrincipal(
158
ad.ServicePrincipalsClient{directoryClient},
164
return "", "", errors.Trace(err)
167
fmt.Fprintln(stderr, "Assigning Owner role to service principal.")
168
if err := createRoleAssignment(
171
servicePrincipalObjectId,
174
return "", "", errors.Trace(err)
176
return jujuApplicationId, password, nil
179
func setClientInspectors(
180
client *autorest.Client,
181
requestInspector autorest.PrepareDecorator,
182
loggingModule string,
184
logger := loggo.GetLogger(loggingModule)
185
client.ResponseInspector = tracing.RespondDecorator(logger)
186
client.RequestInspector = tracing.PrepareDecorator(logger)
187
if requestInspector != nil {
188
tracer := client.RequestInspector
189
client.RequestInspector = func(p autorest.Preparer) autorest.Preparer {
191
p = requestInspector(p)
197
func createOrUpdateServicePrincipal(
198
client ad.ServicePrincipalsClient,
199
subscriptionId string,
201
newUUID func() (utils.UUID, error),
202
) (servicePrincipalObjectId, password string, _ error) {
203
passwordCredential, err := preparePasswordCredential(clock, newUUID)
205
return "", "", errors.Annotate(err, "preparing password credential")
208
servicePrincipal, err := client.Create(
209
ad.ServicePrincipalCreateParameters{
210
ApplicationID: jujuApplicationId,
211
AccountEnabled: true,
212
PasswordCredentials: []ad.PasswordCredential{passwordCredential},
217
if !isMultipleObjectsWithSameKeyValueErr(err) {
218
return "", "", errors.Trace(err)
220
// The service principal already exists, so we'll fall out
221
// and update the service principal's password credentials.
223
// The service principal was created successfully, with the
224
// requested password credential.
225
return servicePrincipal.ObjectID, passwordCredential.Value, nil
228
// The service principal already exists, so we need to query
229
// its object ID, and fetch the existing password credentials
231
servicePrincipal, err = getServicePrincipal(client)
233
return "", "", errors.Trace(err)
235
if err := addServicePrincipalPasswordCredential(
236
client, servicePrincipal.ObjectID,
239
return "", "", errors.Annotate(err, "updating password credentials")
241
return servicePrincipal.ObjectID, passwordCredential.Value, nil
244
func isMultipleObjectsWithSameKeyValueErr(err error) bool {
245
if err, ok := errorutils.ServiceError(err); ok {
246
return err.Code == "Request_MultipleObjectsWithSameKeyValue"
251
func preparePasswordCredential(
253
newUUID func() (utils.UUID, error),
254
) (ad.PasswordCredential, error) {
255
password, err := newUUID()
257
return ad.PasswordCredential{}, errors.Annotate(err, "generating password")
259
passwordKeyUUID, err := newUUID()
261
return ad.PasswordCredential{}, errors.Annotate(err, "generating password key ID")
263
startDate := clock.Now().UTC()
264
endDate := startDate.Add(passwordExpiryDuration)
265
return ad.PasswordCredential{
266
CustomKeyIdentifier: []byte("juju-" + startDate.Format("20060102")),
267
KeyId: passwordKeyUUID.String(),
268
Value: password.String(),
269
StartDate: startDate,
274
func addServicePrincipalPasswordCredential(
275
client ad.ServicePrincipalsClient,
276
servicePrincipalObjectId string,
277
passwordCredential ad.PasswordCredential,
279
existing, err := client.ListPasswordCredentials(servicePrincipalObjectId)
281
return errors.Trace(err)
283
passwordCredentials := append(existing.Value, passwordCredential)
284
_, err = client.UpdatePasswordCredentials(
285
servicePrincipalObjectId,
286
ad.PasswordCredentialsUpdateParameters{passwordCredentials},
288
return errors.Trace(err)
291
func getServicePrincipal(client ad.ServicePrincipalsClient) (ad.ServicePrincipal, error) {
292
// TODO(axw) filter by Service Principal Name (SPN).
293
// It works without that, but the response is noisy.
294
result, err := client.List("")
296
return ad.ServicePrincipal{}, errors.Annotate(err, "listing service principals")
298
for _, sp := range result.Value {
299
if sp.ApplicationID == jujuApplicationId {
303
return ad.ServicePrincipal{}, errors.NotFoundf("service principal")
306
func createRoleAssignment(
307
authorizationClient authorization.ManagementClient,
308
subscriptionId string,
309
servicePrincipalObjectId string,
310
newUUID func() (utils.UUID, error),
312
// Find the role definition with the name "Owner".
313
roleScope := path.Join("subscriptions", subscriptionId)
314
roleDefinitionsClient := authorization.RoleDefinitionsClient{authorizationClient}
315
result, err := roleDefinitionsClient.List(roleScope, "roleName eq 'Owner'")
317
return errors.Annotate(err, "listing role definitions")
319
if result.Value == nil || len(*result.Value) == 0 {
320
return errors.NotFoundf("Owner role definition")
322
roleDefinitionId := (*result.Value)[0].ID
324
// The UUID value for the role assignment name is unimportant. Azure
325
// will prevent multiple role assignments for the same role definition
326
// and principal pair.
327
roleAssignmentUUID, err := newUUID()
329
return errors.Annotate(err, "generating role assignment ID")
331
roleAssignmentsClient := authorization.RoleAssignmentsClient{authorizationClient}
332
roleAssignmentName := roleAssignmentUUID.String()
333
if _, err := roleAssignmentsClient.Create(roleScope, roleAssignmentName, authorization.RoleAssignmentCreateParameters{
334
Properties: &authorization.RoleAssignmentProperties{
335
RoleDefinitionID: roleDefinitionId,
336
PrincipalID: to.StringPtr(servicePrincipalObjectId),
339
if err, ok := errorutils.ServiceError(err); ok {
340
const serviceErrorCodeRoleAssignmentExists = "RoleAssignmentExists"
341
if err.Code == serviceErrorCodeRoleAssignmentExists {
345
return errors.Annotate(err, "creating role assignment")