1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
14
"github.com/Azure/azure-sdk-for-go/arm/authorization"
15
"github.com/Azure/go-autorest/autorest"
16
"github.com/Azure/go-autorest/autorest/azure"
17
"github.com/Azure/go-autorest/autorest/mocks"
18
"github.com/Azure/go-autorest/autorest/to"
19
"github.com/juju/testing"
20
jc "github.com/juju/testing/checkers"
21
"github.com/juju/utils"
22
gc "gopkg.in/check.v1"
24
"github.com/juju/juju/provider/azure/internal/ad"
25
"github.com/juju/juju/provider/azure/internal/azureauth"
26
"github.com/juju/juju/provider/azure/internal/azuretesting"
29
func clockStartTime() time.Time {
30
t, _ := time.Parse("2006-Jan-02 3:04am", "2016-Sep-19 9:47am")
34
type InteractiveSuite struct {
35
testing.IsolationSuite
37
newUUID func() (utils.UUID, error)
40
var _ = gc.Suite(&InteractiveSuite{})
42
func deviceCodeSender() autorest.Sender {
43
return azuretesting.NewSenderWithValue(azure.DeviceCode{
44
DeviceCode: to.StringPtr("device-code"),
45
Interval: to.Int64Ptr(1), // 1 second between polls
46
Message: to.StringPtr("open your browser, etc."),
50
func tokenSender() autorest.Sender {
51
return azuretesting.NewSenderWithValue(azure.Token{
52
RefreshToken: "refresh-token",
53
ExpiresOn: fmt.Sprint(time.Now().Add(time.Hour).Unix()),
57
func passwordCredentialsListSender() autorest.Sender {
58
return azuretesting.NewSenderWithValue(ad.PasswordCredentialsListResult{
59
Value: []ad.PasswordCredential{{
60
KeyId: "password-credential-key-id",
65
func updatePasswordCredentialsSender() autorest.Sender {
66
sender := mocks.NewSender()
67
sender.AppendResponse(mocks.NewResponseWithStatus("", http.StatusNoContent))
71
func currentUserSender() autorest.Sender {
72
return azuretesting.NewSenderWithValue(ad.AADObject{
73
DisplayName: "Foo Bar",
77
func createServicePrincipalSender() autorest.Sender {
78
return azuretesting.NewSenderWithValue(ad.ServicePrincipal{
79
ApplicationID: "cbb548f1-5039-4836-af0b-727e8571f6a9",
80
ObjectID: "sp-object-id",
84
func createServicePrincipalAlreadyExistsSender() autorest.Sender {
85
sender := mocks.NewSender()
86
body := mocks.NewBody(`{"odata.error":{"code":"Request_MultipleObjectsWithSameKeyValue"}}`)
87
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, ""))
91
func servicePrincipalListSender() autorest.Sender {
92
return azuretesting.NewSenderWithValue(ad.ServicePrincipalListResult{
93
Value: []ad.ServicePrincipal{{
94
ApplicationID: "cbb548f1-5039-4836-af0b-727e8571f6a9",
95
ObjectID: "sp-object-id",
100
func roleDefinitionListSender() autorest.Sender {
101
roleDefinitions := []authorization.RoleDefinition{{
102
ID: to.StringPtr("owner-role-id"),
103
Name: to.StringPtr("Owner"),
105
return azuretesting.NewSenderWithValue(authorization.RoleDefinitionListResult{
106
Value: &roleDefinitions,
110
func roleAssignmentSender() autorest.Sender {
111
return azuretesting.NewSenderWithValue(authorization.RoleAssignment{})
114
func roleAssignmentAlreadyExistsSender() autorest.Sender {
115
sender := mocks.NewSender()
116
body := mocks.NewBody(`{"error":{"code":"RoleAssignmentExists"}}`)
117
sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, ""))
121
func (s *InteractiveSuite) SetUpTest(c *gc.C) {
122
s.IsolationSuite.SetUpTest(c)
124
"33333333-3333-3333-3333-333333333333", // password
125
"44444444-4444-4444-4444-444444444444", // password key ID
126
"55555555-5555-5555-5555-555555555555", // role assignment ID
128
s.newUUID = func() (utils.UUID, error) {
129
uuid, err := utils.UUIDFromString(uuids[0])
131
return utils.UUID{}, err
136
s.clock = testing.NewClock(clockStartTime())
139
func (s *InteractiveSuite) TestInteractive(c *gc.C) {
141
var requests []*http.Request
142
senders := azuretesting.Senders{
145
tokenSender(), // CheckForUserCompletion returns a token.
147
// Token.Refresh returns a token. We do this
148
// twice: once for ARM, and once for AAD.
153
createServicePrincipalSender(),
154
roleDefinitionListSender(),
155
roleAssignmentSender(),
158
var stderr bytes.Buffer
159
subscriptionId := "22222222-2222-2222-2222-222222222222"
160
appId, password, err := azureauth.InteractiveCreateServicePrincipal(
163
azuretesting.RequestRecorder(&requests),
164
"https://arm.invalid",
165
"https://graph.invalid",
170
c.Assert(err, jc.ErrorIsNil)
171
c.Assert(appId, gc.Equals, "cbb548f1-5039-4836-af0b-727e8571f6a9")
172
c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333")
173
c.Assert(stderr.String(), gc.Equals, `
174
Initiating interactive authentication.
176
open your browser, etc.
178
Authenticated as "Foo Bar".
179
Creating/updating service principal.
180
Assigning Owner role to service principal.
183
// Token refreshes don't go through the inspectors.
184
c.Assert(requests, gc.HasLen, 7)
185
c.Check(requests[0].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222")
186
c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode")
187
c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token")
188
c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me")
189
c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals")
190
c.Check(requests[5].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions")
191
c.Check(requests[6].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555")
193
// The service principal creation includes the password. Check that the
194
// password returned from the function is the same as the one set in the
196
var params ad.ServicePrincipalCreateParameters
197
err = json.NewDecoder(requests[4].Body).Decode(¶ms)
198
c.Assert(err, jc.ErrorIsNil)
199
c.Assert(params.PasswordCredentials, gc.HasLen, 1)
200
assertPasswordCredential(c, params.PasswordCredentials[0])
203
func assertPasswordCredential(c *gc.C, cred ad.PasswordCredential) {
204
startDate := cred.StartDate
205
endDate := cred.EndDate
206
c.Assert(startDate, gc.Equals, clockStartTime())
207
c.Assert(endDate.Sub(startDate), gc.Equals, 365*24*time.Hour)
209
cred.StartDate = time.Time{}
210
cred.EndDate = time.Time{}
211
c.Assert(cred, jc.DeepEquals, ad.PasswordCredential{
212
CustomKeyIdentifier: []byte("juju-20160919"),
213
KeyId: "44444444-4444-4444-4444-444444444444",
214
Value: "33333333-3333-3333-3333-333333333333",
218
func (s *InteractiveSuite) TestInteractiveRoleAssignmentAlreadyExists(c *gc.C) {
219
var requests []*http.Request
220
senders := azuretesting.Senders{
227
createServicePrincipalSender(),
228
roleDefinitionListSender(),
229
roleAssignmentAlreadyExistsSender(),
231
_, _, err := azureauth.InteractiveCreateServicePrincipal(
234
azuretesting.RequestRecorder(&requests),
235
"https://arm.invalid",
236
"https://graph.invalid",
237
"22222222-2222-2222-2222-222222222222",
241
c.Assert(err, jc.ErrorIsNil)
244
func (s *InteractiveSuite) TestInteractiveServicePrincipalAlreadyExists(c *gc.C) {
245
var requests []*http.Request
246
senders := azuretesting.Senders{
253
createServicePrincipalAlreadyExistsSender(),
254
servicePrincipalListSender(),
255
passwordCredentialsListSender(),
256
updatePasswordCredentialsSender(),
257
roleDefinitionListSender(),
258
roleAssignmentAlreadyExistsSender(),
260
_, password, err := azureauth.InteractiveCreateServicePrincipal(
263
azuretesting.RequestRecorder(&requests),
264
"https://arm.invalid",
265
"https://graph.invalid",
266
"22222222-2222-2222-2222-222222222222",
270
c.Assert(err, jc.ErrorIsNil)
271
c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333")
273
c.Assert(requests, gc.HasLen, 10)
274
c.Check(requests[0].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222")
275
c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode")
276
c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token")
277
c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me")
278
c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // create
279
c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals") // list
280
c.Check(requests[6].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // list
281
c.Check(requests[7].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // update
282
c.Check(requests[8].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions")
283
c.Check(requests[9].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555")
285
// Make sure that we don't wipe existing password credentials, and that
286
// the new password credential matches the one returned from the
288
var params ad.PasswordCredentialsUpdateParameters
289
err = json.NewDecoder(requests[7].Body).Decode(¶ms)
290
c.Assert(err, jc.ErrorIsNil)
291
c.Assert(params.Value, gc.HasLen, 2)
292
c.Assert(params.Value[0], jc.DeepEquals, ad.PasswordCredential{
293
KeyId: "password-credential-key-id",
294
StartDate: time.Time{}.UTC(),
295
EndDate: time.Time{}.UTC(),
297
assertPasswordCredential(c, params.Value[1])