Skip to content

Commit

Permalink
Support storing the policy hash in database
Browse files Browse the repository at this point in the history
Signed-off-by: Kevin DeJong <[email protected]>
  • Loading branch information
kddejong committed Sep 5, 2019
1 parent 9d78187 commit 76bb011
Show file tree
Hide file tree
Showing 13 changed files with 250 additions and 70 deletions.
3 changes: 2 additions & 1 deletion cmd/lambda/accounts/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,13 @@ func (c createController) Call(ctx context.Context, req *events.APIGatewayProxyR
}

// Create an IAM Role for the Redbox principal (end-user) to login to
createRolRes, err := c.createPrincipalRole(account)
createRolRes, policyHash, err := c.createPrincipalRole(account)
if err != nil {
log.Printf("failed to create principal role for %s: %s", request.ID, err)
return response.ServerError(), nil
}
account.PrincipalRoleArn = createRolRes.RoleArn
account.PrincipalPolicyHash = policyHash

// Write the Account to the DB
err = c.Dao.PutAccount(account)
Expand Down
2 changes: 1 addition & 1 deletion cmd/lambda/accounts/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ func StoragerMock() common.Storager {
storagerMock := &commonMocks.Storager{}

storagerMock.On("GetTemplateObject", mock.Anything, mock.Anything, mock.Anything).
Return("", nil)
Return("", "", nil)

return storagerMock
}
Expand Down
12 changes: 7 additions & 5 deletions cmd/lambda/accounts/redboxprincipal.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/aws/aws-sdk-go/service/iam"
)

func (c createController) createPrincipalRole(account db.RedboxAccount) (*rolemanager.CreateRoleWithPolicyOutput, error) {
func (c createController) createPrincipalRole(account db.RedboxAccount) (*rolemanager.CreateRoleWithPolicyOutput, string, error) {
// Create an assume role policy,
// to let principals from the same account assume the role.
//
Expand All @@ -35,27 +35,28 @@ func (c createController) createPrincipalRole(account db.RedboxAccount) (*rolema

// Render the default policy for the Redbox principal
policyName := c.PrincipalPolicyName
policy, err := c.StoragerService.GetTemplateObject(c.ArtifactsBucket, c.PrincipalPolicyS3Key,
policy, policyHash, err := c.StoragerService.GetTemplateObject(c.ArtifactsBucket, c.PrincipalPolicyS3Key,
redboxPrincipalPolicyInput{
PrincipalPolicyArn: fmt.Sprintf("arn:aws:iam::%s:policy/%s", account.ID, policyName),
PrincipalRoleArn: fmt.Sprintf("arn:aws:iam::%s:role/%s", account.ID, c.PrincipalRoleName),
PrincipalIAMDenyTags: c.PrincipalIAMDenyTags,
AdminRoleArn: account.AdminRoleArn,
})
if err != nil {
return nil, err
return nil, "", err
}

// Assume role into the new Redbox account
accountSession, err := c.TokenService.NewSession(&c.AWSSession, account.AdminRoleArn)
if err != nil {
return nil, err
return nil, "", err
}
iamClient := iam.New(accountSession)

// Create the Role + Policy
c.RoleManager.SetIAMClient(iamClient)
return c.RoleManager.CreateRoleWithPolicy(&rolemanager.CreateRoleWithPolicyInput{
createRoleOutput := &rolemanager.CreateRoleWithPolicyOutput{}
createRoleOutput, err = c.RoleManager.CreateRoleWithPolicy(&rolemanager.CreateRoleWithPolicyInput{
RoleName: c.PrincipalRoleName,
RoleDescription: "Role to be assumed by principal users of Redbox",
AssumeRolePolicyDocument: assumeRolePolicy,
Expand All @@ -68,6 +69,7 @@ func (c createController) createPrincipalRole(account db.RedboxAccount) (*rolema
),
IgnoreAlreadyExistsErrors: true,
})
return createRoleOutput, policyHash, err
}

type redboxPrincipalPolicyInput struct {
Expand Down
46 changes: 32 additions & 14 deletions cmd/lambda/update_redbox_principal_policy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,18 @@ func handler(ctx context.Context, snsEvent events.SNSEvent) error {
}

type processRecordInput struct {
AccountID string
DbSvc db.DBer
StoragerSvc common.Storager
TokenSvc common.TokenService
AwsSession awsiface.AwsSession
RoleManager rolemanager.PolicyManager
PrincipalRoleName string
PrincipalPolicyName string
PrincipalIAMDenyTags []string
PolicyBucket string
PolicyBucketKey string
AccountID string
DbSvc db.DBer
StoragerSvc common.Storager
TokenSvc common.TokenService
AwsSession awsiface.AwsSession
RoleManager rolemanager.PolicyManager
PrincipalRoleName string
PrincipalPolicyName string
PrincipalIAMDenyTags []string
PrevPrincipalPolicyHash string
PolicyBucket string
PolicyBucketKey string
}

func processRecord(input processRecordInput) error {
Expand All @@ -99,13 +100,17 @@ func processRecord(input processRecordInput) error {
return err
}

policy, err := input.StoragerSvc.GetTemplateObject(input.PolicyBucket, input.PolicyBucketKey, getPolicyInput{
policy, policyHash, err := input.StoragerSvc.GetTemplateObject(input.PolicyBucket, input.PolicyBucketKey, getPolicyInput{
PrincipalPolicyArn: principalPolicyArn.String(),
PrincipalRoleArn: fmt.Sprintf("arn:aws:iam::%s:role/%s", input.AccountID, input.PrincipalRoleName),
PrincipalIAMDenyTags: input.PrincipalIAMDenyTags,
AdminRoleArn: accountRes.AdminRoleArn,
})

if policyHash == accountRes.PrincipalPolicyHash {
log.Printf("Policy already matches. Not updating '%s'", principalPolicyArn.String())
return nil
}
// Assume role into the new Redbox account
accountSession, err := input.TokenSvc.NewSession(input.AwsSession, accountRes.AdminRoleArn)
if err != nil {
Expand All @@ -114,13 +119,26 @@ func processRecord(input processRecordInput) error {
}
iamSvc := iam.New(accountSession)

// Create the Role + Policy
// Update the Policy
input.RoleManager.SetIAMClient(iamSvc)
return input.RoleManager.MergePolicy(&rolemanager.MergePolicyInput{
log.Printf("Update policy '%s' to hash '%s' from '%s'.", principalPolicyArn.String(), accountRes.PrincipalPolicyHash, policyHash)
err = input.RoleManager.MergePolicy(&rolemanager.MergePolicyInput{
PolicyName: input.PrincipalPolicyName,
PolicyArn: principalPolicyArn,
PolicyDocument: policy,
})
if err != nil {
log.Printf("Failed updating the policy '%s': %s", principalPolicyArn.String(), err)
return err
}

log.Printf("Update account '%s' resource record. Policy Hash from '%s' to '%s'", input.AccountID, accountRes.PrincipalPolicyHash, policyHash)
_, err = input.DbSvc.UpdateAccountPrincipalPolicyHash(input.AccountID, input.PrevPrincipalPolicyHash, policyHash)
if err != nil {
log.Printf("Failed to update account '%s' resource record. Policy Hash from '%s' to '%s': %s",
input.AccountID, accountRes.PrincipalPolicyHash, policyHash, err)
}
return err
}

type getPolicyInput struct {
Expand Down
55 changes: 40 additions & 15 deletions cmd/lambda/update_redbox_principal_policy/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type testUpdateRedboxPrincipalPolicy struct {
TransitionLeaseStatusError error
PrincipalPolicyName string
PrincipalRoleName string
PrincipalPolicyHash string
PrincipalIAMDenyTags []string
StoragerPolicy string
StoragerError error
Expand All @@ -35,14 +36,28 @@ type testUpdateRedboxPrincipalPolicy struct {
func TestUpdateRedboxPrincipalPolicy(t *testing.T) {

tests := []testUpdateRedboxPrincipalPolicy{
// Happy Path FinanceLock
// Happy Path Update Principal Policy
{
GetAccountResult: &db.RedboxAccount{
ID: "123456789012",
AdminRoleArn: "arn:aws:iam::123456789012:role/RedBoxAdminRole",
},
PrincipalPolicyName: "RedboxPrincipalPolicy",
PrincipalRoleName: "RedboxPrincipalRole",
PrincipalPolicyHash: "aHash",
PrincipalIAMDenyTags: []string{"Redbox"},
StoragerPolicy: "{\"Test\" : \"Policy\"}",
},
// Same hash exists don't update.
{
GetAccountResult: &db.RedboxAccount{
ID: "123456789012",
AdminRoleArn: "arn:aws:iam::123456789012:role/RedBoxAdminRole",
PrincipalPolicyHash: "aHash",
},
PrincipalPolicyName: "RedboxPrincipalPolicy",
PrincipalRoleName: "RedboxPrincipalRole",
PrincipalPolicyHash: "aHash",
PrincipalIAMDenyTags: []string{"Redbox"},
StoragerPolicy: "{\"Test\" : \"Policy\"}",
},
Expand All @@ -55,6 +70,11 @@ func TestUpdateRedboxPrincipalPolicy(t *testing.T) {
mockDB.On("GetAccount", mock.Anything).Return(
test.GetAccountResult,
test.GetAccountError)
mockDB.On("UpdateAccountPrincipalPolicyHash",
test.GetAccountResult.ID,
test.GetAccountResult.PrincipalPolicyHash,
test.PrincipalPolicyHash,
).Return(nil, nil)
mockS3 := &commonmock.Storager{}
mockS3.On("GetTemplateObject", mock.Anything, mock.Anything, getPolicyInput{
PrincipalPolicyArn: fmt.Sprintf("arn:aws:iam::%s:policy/%s", test.GetAccountResult.ID, test.PrincipalPolicyName),
Expand All @@ -63,26 +83,31 @@ func TestUpdateRedboxPrincipalPolicy(t *testing.T) {
AdminRoleArn: test.GetAccountResult.AdminRoleArn,
}).Return(
test.StoragerPolicy,
test.PrincipalPolicyHash,
test.StoragerError,
)

mockAdminRoleSession := &awsMocks.AwsSession{}
mockAdminRoleSession.On("ClientConfig", mock.Anything).Return(client.Config{
Config: &aws.Config{},
})
mockToken := &commonmock.TokenService{}
mockToken.On("NewSession", mock.Anything, test.GetAccountResult.AdminRoleArn).
Return(mockAdminRoleSession, nil)
mockToken.On("AssumeRole", mock.Anything).Return(nil, nil)
mockRoleManager := &roleMock.PolicyManager{}
mockSession := &awsMocks.AwsSession{}
if test.PrincipalPolicyHash != test.GetAccountResult.PrincipalPolicyHash {
mockAdminRoleSession.On("ClientConfig", mock.Anything).Return(client.Config{
Config: &aws.Config{},
})
mockToken.On("NewSession", mock.Anything, test.GetAccountResult.AdminRoleArn).
Return(mockAdminRoleSession, nil)
mockToken.On("AssumeRole", mock.Anything).Return(nil, nil)

mockRoleManager := &roleMock.PolicyManager{}
mockRoleManager.On("SetIAMClient", mock.Anything).Return()
policyArn, _ := arn.Parse(fmt.Sprintf("arn:aws:iam::%s:policy/%s", test.GetAccountResult.ID, test.PrincipalPolicyName))
mockRoleManager.On("MergePolicy", &rolemanager.MergePolicyInput{
PolicyArn: policyArn,
PolicyName: test.PrincipalPolicyName,
PolicyDocument: test.StoragerPolicy,
}).Return(nil)
mockRoleManager.On("SetIAMClient", mock.Anything).Return()
policyArn, _ := arn.Parse(fmt.Sprintf("arn:aws:iam::%s:policy/%s", test.GetAccountResult.ID, test.PrincipalPolicyName))
mockRoleManager.On("MergePolicy", &rolemanager.MergePolicyInput{
PolicyArn: policyArn,
PolicyName: test.PrincipalPolicyName,
PolicyDocument: test.StoragerPolicy,
}).Return(nil)

}

// Call transitionFinanceLock
err := processRecord(processRecordInput{
Expand Down
15 changes: 8 additions & 7 deletions pkg/api/response/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import (
// dbAccount := db.RedboxAccount{...}
// accountRes := response.AccountResponse(dbAccount)
type AccountResponse struct {
ID string `json:"id"`
AccountStatus db.AccountStatus `json:"accountStatus"`
LastModifiedOn int64 `json:"lastModifiedOn"`
CreatedOn int64 `json:"createdOn"`
AdminRoleArn string `json:"adminRoleArn"` // Assumed by the Redbox master account, to manage this user account
PrincipalRoleArn string `json:"principalRoleArn"` // Assumed by principal users of Redbox
Metadata map[string]interface{} `json:"metadata"`
ID string `json:"id"`
AccountStatus db.AccountStatus `json:"accountStatus"`
LastModifiedOn int64 `json:"lastModifiedOn"`
CreatedOn int64 `json:"createdOn"`
AdminRoleArn string `json:"adminRoleArn"` // Assumed by the Redbox master account, to manage this user account
PrincipalRoleArn string `json:"principalRoleArn"` // Assumed by principal users of Redbox
PrincipalPolicyHash string `json:"principalPolicyHash"` // The policy used by the PrincipalRoleArn
Metadata map[string]interface{} `json:"metadata"`
}
17 changes: 12 additions & 5 deletions pkg/common/mocks/Storager.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 29 additions & 5 deletions pkg/common/storager.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// based on the provided S3 Object Input
type Storager interface {
GetObject(bucket string, key string) (string, error)
GetTemplateObject(bucket string, key string, input interface{}) (string, error)
GetTemplateObject(bucket string, key string, input interface{}) (string, string, error)
Upload(bucket string, key string, filepath string) error
Download(bukcet string, key string, filepath string) error
}
Expand Down Expand Up @@ -51,11 +51,35 @@ func (stor S3) GetObject(bucket string, key string) (string, error) {
return object, nil
}

// GetObjectWithETag returns a string output based on the results of the retrieval
// of an existing object from S3
func (stor S3) GetObjectWithETag(bucket string, key string) (string, string, error) {
// Retrieve the S3 Object
getInput := s3.GetObjectInput{
Bucket: &bucket,
Key: &key,
}
getOutput, err := stor.Client.GetObject(&getInput)
if err != nil {
return "", "", err
}

// Convert to string
buf := new(bytes.Buffer)
_, err = buf.ReadFrom(getOutput.Body)
if err != nil {
return "", "", err
}
object := buf.String()

return object, *getOutput.ETag, nil
}

// GetTemplateObject returns a string output based on the results of the retrieval
// of an existing object from S3
func (stor S3) GetTemplateObject(bucket string, key string, input interface{}) (string, error) {
func (stor S3) GetTemplateObject(bucket string, key string, input interface{}) (string, string, error) {
// Retrieve the S3 Object
templateString, err := stor.GetObject(bucket, key)
templateString, templateETag, err := stor.GetObjectWithETag(bucket, key)

tmpl := template.New(key)

Expand All @@ -65,14 +89,14 @@ func (stor S3) GetTemplateObject(bucket string, key string, input interface{}) (

templParsed, err := tmpl.Parse(templateString)
if err != nil {
return "", err
return "", "", err
}

// Render template
buf := &bytes.Buffer{}
err1 := templParsed.Execute(buf, input)

return strings.TrimSpace(buf.String()), err1
return strings.TrimSpace(buf.String()), templateETag, err1
}

// Upload puts an object to the provided S3 bucket based on the body provided
Expand Down
Loading

0 comments on commit 76bb011

Please sign in to comment.