Skip to content

Commit 79a185a

Browse files
committed
Create task execution role for registry creds on 'registry-creds up'
1 parent 9648c63 commit 79a185a

File tree

15 files changed

+927
-78
lines changed

15 files changed

+927
-78
lines changed

ecs-cli/Gopkg.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package regcreds
15+
16+
import (
17+
"fmt"
18+
"time"
19+
20+
iamClient "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/iam"
21+
kmsClient "github.com/aws/amazon-ecs-cli/ecs-cli/modules/clients/aws/kms"
22+
"github.com/aws/amazon-ecs-cli/ecs-cli/modules/utils"
23+
"github.com/aws/aws-sdk-go/aws"
24+
"github.com/aws/aws-sdk-go/service/iam"
25+
log "github.com/sirupsen/logrus"
26+
)
27+
28+
const (
29+
assumeRolePolicyDocString = `{"Version":"2008-10-17","Statement":[{"Sid":"","Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"}]}`
30+
roleDescriptionString = "Role generated by the ecs-cli"
31+
)
32+
33+
type executionRoleParams struct {
34+
CredEntries map[string]CredsOutputEntry
35+
RoleName string
36+
Region string
37+
}
38+
39+
func createTaskExecutionRole(params executionRoleParams, iamClient iamClient.Client, kmsClient kmsClient.Client) error {
40+
log.Infof("Creating resources for task execution role %s...", params.RoleName)
41+
42+
// create role
43+
roleName, err := createOrFindRole(params.RoleName, iamClient)
44+
if err != nil {
45+
return err
46+
}
47+
48+
// generate policy document
49+
policyDoc, err := generateSecretsPolicy(params.CredEntries, kmsClient)
50+
if err != nil {
51+
return err
52+
}
53+
54+
// create the new policy
55+
newPolicy, err := createRegistryCredentialsPolicy(params.RoleName, policyDoc, iamClient)
56+
if err != nil {
57+
return err
58+
}
59+
log.Infof("Created new task execution role policy %s", aws.StringValue(newPolicy.Arn))
60+
61+
// attach managed execution role policy & new credentials policy to role
62+
err = attachRolePolicies(*newPolicy.Arn, roleName, params.Region, iamClient)
63+
if err != nil {
64+
return err
65+
}
66+
67+
return nil
68+
}
69+
70+
func createRegistryCredentialsPolicy(roleName, policyDoc string, client iamClient.Client) (*iam.Policy, error) {
71+
newPolicyName := generateECSResourceName(roleName + "-policy")
72+
policyDescriptionFmtString := "Policy generated by the ecs-cli for role: %s"
73+
74+
createPolicyRequest := iam.CreatePolicyInput{
75+
PolicyName: newPolicyName,
76+
PolicyDocument: aws.String(policyDoc),
77+
Description: aws.String(fmt.Sprintf(policyDescriptionFmtString, roleName)),
78+
}
79+
80+
policyResult, err := client.CreatePolicy(createPolicyRequest)
81+
// if policy already exists, create new one with timestamp
82+
// TODO: consider instead updating existing policy with CreatePolicyVersion()
83+
if utils.EntityAlreadyExists(err) {
84+
createPolicyRequest := iam.CreatePolicyInput{
85+
PolicyName: generateECSResourceName(roleName + "-policy-" + time.Now().UTC().Format("20060102150405")),
86+
PolicyDocument: aws.String(policyDoc),
87+
Description: aws.String(fmt.Sprintf(policyDescriptionFmtString, roleName)),
88+
}
89+
policyResult, err := client.CreatePolicy(createPolicyRequest)
90+
if err != nil {
91+
return nil, err
92+
}
93+
return policyResult.Policy, nil
94+
}
95+
if err != nil {
96+
return nil, err
97+
}
98+
return policyResult.Policy, nil
99+
}
100+
101+
func createOrFindRole(roleName string, client iamClient.Client) (string, error) {
102+
roleResult, err := client.CreateOrFindRole(roleName, roleDescriptionString, assumeRolePolicyDocString)
103+
if err != nil {
104+
return "", err
105+
}
106+
107+
if roleResult != "" {
108+
log.Infof("Created new task execution role %s", roleResult)
109+
} else {
110+
log.Infof("Using existing role %s", roleName)
111+
}
112+
113+
return roleName, nil
114+
}
115+
116+
func attachRolePolicies(secretPolicyARN, roleName, region string, client iamClient.Client) error {
117+
managedPolicyARN := getExecutionRolePolicyARN(region)
118+
_, err := client.AttachRolePolicy(managedPolicyARN, roleName)
119+
if err != nil {
120+
return err
121+
}
122+
log.Infof("Attached AWS managed policy %s to role %s", managedPolicyARN, roleName)
123+
124+
_, err = client.AttachRolePolicy(secretPolicyARN, roleName)
125+
if err != nil {
126+
return err
127+
}
128+
log.Infof("Attached new policy %s to role %s", secretPolicyARN, roleName)
129+
130+
return nil
131+
}
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// Copyright 2015-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package regcreds
15+
16+
import (
17+
"testing"
18+
19+
"github.com/aws/aws-sdk-go/aws"
20+
"github.com/aws/aws-sdk-go/aws/awserr"
21+
"github.com/aws/aws-sdk-go/service/iam"
22+
"github.com/golang/mock/gomock"
23+
"github.com/pkg/errors"
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
func TestCreateTaskExecutionRole(t *testing.T) {
28+
testRegistry := "myreg.test.io"
29+
testRegCredARN := "arn:aws:secret/some-test-arn"
30+
testRegKMSKey := "arn:aws:kms:key/67yt-756yth"
31+
testCreds := make(map[string]CredsOutputEntry)
32+
testCreds[testRegistry] = CredsOutputEntry{
33+
CredentialARN: testRegCredARN,
34+
KMSKeyID: testRegKMSKey,
35+
}
36+
testRoleName := "myNginxProjectRole"
37+
38+
testPolicyArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy")
39+
testRoleArn := aws.String("arn:aws:iam::role/" + testRoleName)
40+
41+
mocks := setupTestController(t)
42+
gomock.InOrder(
43+
mocks.MockIAM.EXPECT().CreateOrFindRole(testRoleName, roleDescriptionString, assumeRolePolicyDocString).Return(*testRoleArn, nil),
44+
mocks.MockIAM.EXPECT().CreateRole(gomock.Any()).Return(&iam.CreateRoleOutput{Role: &iam.Role{Arn: testRoleArn}}, nil),
45+
)
46+
gomock.InOrder(
47+
// If KMSKeyID present, first thing to happen should be verifying its ARN
48+
mocks.MockKMS.EXPECT().GetValidKeyARN(testRegKMSKey).Return(testRegKMSKey, nil),
49+
mocks.MockIAM.EXPECT().CreatePolicy(gomock.Any()).Return(&iam.CreatePolicyOutput{Policy: &iam.Policy{Arn: testPolicyArn}}, nil),
50+
mocks.MockIAM.EXPECT().AttachRolePolicy(getExecutionRolePolicyARN("us-west-2"), testRoleName).Return(nil, nil),
51+
mocks.MockIAM.EXPECT().AttachRolePolicy(*testPolicyArn, testRoleName).Return(nil, nil),
52+
)
53+
54+
testParams := executionRoleParams{
55+
CredEntries: testCreds,
56+
RoleName: testRoleName,
57+
Region: "us-west-2",
58+
}
59+
60+
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
61+
assert.NoError(t, err, "Unexpected error when creating task execution role")
62+
}
63+
64+
func TestCreateTaskExecutionRole_NoKMSKey(t *testing.T) {
65+
testRegistry := "myreg.test.io"
66+
testRegCredARN := "arn:aws:secret/some-test-arn"
67+
testCreds := make(map[string]CredsOutputEntry)
68+
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
69+
testRoleName := "myNginxProjectRole"
70+
71+
testPolicyArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy")
72+
testRoleArn := aws.String("arn:aws:iam::role/" + testRoleName)
73+
74+
mocks := setupTestController(t)
75+
gomock.InOrder(
76+
mocks.MockIAM.EXPECT().CreateOrFindRole(testRoleName, roleDescriptionString, assumeRolePolicyDocString).Return(*testRoleArn, nil),
77+
mocks.MockIAM.EXPECT().CreateRole(gomock.Any()).Return(&iam.CreateRoleOutput{Role: &iam.Role{Arn: testRoleArn}}, nil),
78+
)
79+
gomock.InOrder(
80+
mocks.MockIAM.EXPECT().CreatePolicy(gomock.Any()).Return(&iam.CreatePolicyOutput{Policy: &iam.Policy{Arn: testPolicyArn}}, nil),
81+
mocks.MockIAM.EXPECT().AttachRolePolicy(getExecutionRolePolicyARN("us-west-2"), testRoleName).Return(nil, nil),
82+
mocks.MockIAM.EXPECT().AttachRolePolicy(*testPolicyArn, testRoleName).Return(nil, nil),
83+
)
84+
85+
testParams := executionRoleParams{
86+
CredEntries: testCreds,
87+
RoleName: testRoleName,
88+
Region: "us-west-2",
89+
}
90+
91+
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
92+
assert.NoError(t, err, "Unexpected error when creating task execution role")
93+
}
94+
95+
func TestCreateTaskExecutionRole_RoleExists(t *testing.T) {
96+
testRegistry := "myreg.test.io"
97+
testRegCredARN := "arn:aws:secret/some-test-arn"
98+
testCreds := make(map[string]CredsOutputEntry)
99+
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
100+
testRoleName := "myNginxProjectRole"
101+
102+
testPolicyArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy")
103+
roleExistsError := awserr.New("EntityAlreadyExists", "Didn't you see the error code? This role already exists.", errors.New("something went wrong"))
104+
105+
mocks := setupTestController(t)
106+
gomock.InOrder(
107+
// CreateOrFindRole should return nil if given role already exists
108+
mocks.MockIAM.EXPECT().CreateOrFindRole(testRoleName, roleDescriptionString, assumeRolePolicyDocString).Return("", nil),
109+
mocks.MockIAM.EXPECT().CreateRole(gomock.Any()).Return(nil, roleExistsError),
110+
)
111+
gomock.InOrder(
112+
mocks.MockIAM.EXPECT().CreatePolicy(gomock.Any()).Return(&iam.CreatePolicyOutput{Policy: &iam.Policy{Arn: testPolicyArn}}, nil),
113+
mocks.MockIAM.EXPECT().AttachRolePolicy(getExecutionRolePolicyARN("us-west-2"), testRoleName).Return(nil, nil),
114+
mocks.MockIAM.EXPECT().AttachRolePolicy(*testPolicyArn, testRoleName).Return(nil, nil),
115+
)
116+
117+
testParams := executionRoleParams{
118+
CredEntries: testCreds,
119+
RoleName: testRoleName,
120+
Region: "us-west-2",
121+
}
122+
123+
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
124+
assert.NoError(t, err, "Unexpected error when creating task execution role")
125+
}
126+
127+
func TestCreateTaskExecutionRole_RoleAndPolicyAlreadyExists(t *testing.T) {
128+
testRegistry := "myreg.test.io"
129+
testRegCredARN := "arn:aws:secret/some-test-arn"
130+
testCreds := make(map[string]CredsOutputEntry)
131+
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
132+
testRoleName := "myNginxProjectRole"
133+
134+
testPolicySecondArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy-20181010222222")
135+
entityExistsError := awserr.New("EntityAlreadyExists", "Didn't you see the error code? This resource already exists.", errors.New("something went wrong"))
136+
137+
mocks := setupTestController(t)
138+
gomock.InOrder(
139+
// CreateOrFindRole should return nil if given role already exists
140+
mocks.MockIAM.EXPECT().CreateOrFindRole(testRoleName, roleDescriptionString, assumeRolePolicyDocString).Return("", nil),
141+
mocks.MockIAM.EXPECT().CreateRole(gomock.Any()).Return(nil, entityExistsError),
142+
)
143+
gomock.InOrder(
144+
// CreatePolicy should be called again if the generated policy name already exists
145+
mocks.MockIAM.EXPECT().CreatePolicy(gomock.Any()).Return(nil, entityExistsError),
146+
mocks.MockIAM.EXPECT().CreatePolicy(gomock.Any()).Return(&iam.CreatePolicyOutput{Policy: &iam.Policy{Arn: testPolicySecondArn}}, nil),
147+
mocks.MockIAM.EXPECT().AttachRolePolicy(getExecutionRolePolicyARN("us-west-2"), testRoleName).Return(nil, nil),
148+
mocks.MockIAM.EXPECT().AttachRolePolicy(*testPolicySecondArn, testRoleName).Return(nil, nil),
149+
)
150+
151+
testParams := executionRoleParams{
152+
CredEntries: testCreds,
153+
RoleName: testRoleName,
154+
Region: "us-west-2",
155+
}
156+
157+
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
158+
assert.NoError(t, err, "Unexpected error when creating task execution role")
159+
}
160+
161+
func TestCreateTaskExecutionRole_ErrorOnCreateRoleFails(t *testing.T) {
162+
testRegistry := "myreg.test.io"
163+
testRegCredARN := "arn:aws:secret/some-test-arn"
164+
testCreds := make(map[string]CredsOutputEntry)
165+
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
166+
testRoleName := "myNginxProjectRole"
167+
168+
mocks := setupTestController(t)
169+
gomock.InOrder(
170+
mocks.MockIAM.EXPECT().CreateOrFindRole(testRoleName, roleDescriptionString, assumeRolePolicyDocString).Return("", errors.New("something went wrong")),
171+
mocks.MockIAM.EXPECT().CreateRole(gomock.Any()).Return(nil, errors.New("something went wrong")),
172+
)
173+
174+
testParams := executionRoleParams{
175+
CredEntries: testCreds,
176+
RoleName: testRoleName,
177+
Region: "us-west-2",
178+
}
179+
180+
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
181+
assert.Error(t, err, "Expected error when CreateRole fails")
182+
}
183+
184+
func TestCreateTaskExecutionRole_ErrorOnCreatePolicyFails(t *testing.T) {
185+
testRegistry := "myreg.test.io"
186+
testRegCredARN := "arn:aws:secret/some-test-arn"
187+
testCreds := make(map[string]CredsOutputEntry)
188+
testCreds[testRegistry] = CredsOutputEntry{CredentialARN: testRegCredARN}
189+
testRoleName := "myNginxProjectRole"
190+
191+
//testPolicyArn := aws.String("arn:aws:iam::policy/" + testRoleName + "-policy")
192+
testRoleArn := aws.String("arn:aws:iam::role/" + testRoleName)
193+
194+
mocks := setupTestController(t)
195+
gomock.InOrder(
196+
mocks.MockIAM.EXPECT().CreateOrFindRole(testRoleName, roleDescriptionString, assumeRolePolicyDocString).Return(*testRoleArn, nil),
197+
mocks.MockIAM.EXPECT().CreateRole(gomock.Any()).Return(&iam.CreateRoleOutput{Role: &iam.Role{Arn: testRoleArn}}, nil),
198+
)
199+
gomock.InOrder(
200+
mocks.MockIAM.EXPECT().CreatePolicy(gomock.Any()).Return(nil, errors.New("something went wrong")),
201+
)
202+
203+
testParams := executionRoleParams{
204+
CredEntries: testCreds,
205+
RoleName: testRoleName,
206+
Region: "us-west-2",
207+
}
208+
209+
err := createTaskExecutionRole(testParams, mocks.MockIAM, mocks.MockKMS)
210+
assert.Error(t, err, "Expected error when CreatePolicy fails")
211+
}

0 commit comments

Comments
 (0)