Skip to content

Commit

Permalink
Limit roles and support setting of access for groups (spaces) (#4)
Browse files Browse the repository at this point in the history
* limit roles and support setting of access for groups (spaces)
  • Loading branch information
fishnix authored Mar 4, 2021
1 parent 5d7919e commit 8563c90
Show file tree
Hide file tree
Showing 14 changed files with 1,549 additions and 153 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ POST `/v1/ecr/{account}/repositories/{group}`
```json
{
"RepositoryName": "myAwesomeRepository",
"Groups": ["spindev-000001", "spindev-000002"],
"ScanOnPush": "true",
"Tags": [
{
Expand All @@ -69,6 +70,7 @@ POST `/v1/ecr/{account}/repositories/{group}`
{
"CreatedAt": "2020-12-14T15:34:18Z",
"EncryptionType": "AES256",
"Groups": ["spindev-000001", "spindev-000002"],
"KmsKeyId": "",
"ScanOnPush": "true",
"ImageTagMutability": "MUTABLE",
Expand Down Expand Up @@ -162,6 +164,7 @@ GET `/v1/ecr/{account}/repositories/{group}/{id}`
{
"CreatedAt": "2020-12-14T15:34:18Z",
"EncryptionType": "AES256",
"Groups": ["spindev-000001", "spindev-000002"],
"KmsKeyId": "",
"ScanOnPush": "true",
"ImageTagMutability": "MUTABLE",
Expand Down Expand Up @@ -199,6 +202,7 @@ PUT `/1/ecr/{account}/repositories/{group}/{id}`
```json
{
"ScanOnPush": "false",
"Groups": ["spindev-000001", "spindev-000002", "spindev-000003"],
"Tags": [
{
"Key": "Application",
Expand All @@ -214,6 +218,7 @@ PUT `/1/ecr/{account}/repositories/{group}/{id}`
{
"CreatedAt": "2020-12-14T15:34:18Z",
"EncryptionType": "AES256",
"Groups": ["spindev-000001", "spindev-000002", "spindev-000003"],
"KmsKeyId": "",
"ScanOnPush": "false",
"ImageTagMutability": "MUTABLE",
Expand Down
18 changes: 6 additions & 12 deletions api/handlers_repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ func (s *server) RepositoriesShowHandler(w http.ResponseWriter, r *http.Request)
account := vars["account"]
name := vars["name"]
group := vars["group"]
repository := fmt.Sprintf("%s/%s", group, name)

role := fmt.Sprintf("arn:aws:iam::%s:role/%s", account, s.session.RoleName)

Expand All @@ -176,23 +175,18 @@ func (s *server) RepositoriesShowHandler(w http.ResponseWriter, r *http.Request)
return
}

service := ecr.New(
ecr.WithSession(session.Session),
orch := newEcrOrchestrator(
ecr.New(ecr.WithSession(session.Session)),
s.org,
)

repo, err := service.GetRepositories(r.Context(), repository)
if err != nil {
handleError(w, err)
return
}

tags, err := service.GetRepositoryTags(r.Context(), aws.StringValue(repo.RepositoryArn))
resp, err := orch.repositoryDetails(r.Context(), account, group, name)
if err != nil {
handleError(w, err)
handleError(w, errors.Wrap(err, "failed to get repository details"))
return
}

j, err := json.Marshal(repositoryResponseFromECR(repo, tags))
j, err := json.Marshal(resp)
if err != nil {
handleError(w, errors.Wrap(err, "unable to marshal response from the ecr service"))
return
Expand Down
23 changes: 20 additions & 3 deletions api/handlers_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,18 @@ func (s *server) UsersCreateHandler(w http.ResponseWriter, r *http.Request) {
}

role := fmt.Sprintf("arn:aws:iam::%s:role/%s", account, s.session.RoleName)
policy, err := s.repositoryUserCreatePolicy()
if err != nil {
handleError(w, apierror.New(apierror.ErrInternalError, "failed to generate policy", err))
return
}

// IAM doesn't support resource tags, so we can't pass the s.orgPolicy here
session, err := s.assumeRole(
r.Context(),
s.session.ExternalID,
role,
"",
policy,
)
if err != nil {
msg := fmt.Sprintf("failed to assume role in account: %s", account)
Expand Down Expand Up @@ -99,6 +104,7 @@ func (s *server) UsersListHandler(w http.ResponseWriter, r *http.Request) {
s.session.ExternalID,
role,
"",
"arn:aws:iam::aws:policy/IAMReadOnlyAccess",
)
if err != nil {
msg := fmt.Sprintf("failed to assume role in account: %s", account)
Expand Down Expand Up @@ -146,6 +152,7 @@ func (s *server) UsersShowHandler(w http.ResponseWriter, r *http.Request) {
s.session.ExternalID,
role,
"",
"arn:aws:iam::aws:policy/IAMReadOnlyAccess",
)
if err != nil {
msg := fmt.Sprintf("failed to assume role in account: %s", account)
Expand Down Expand Up @@ -193,13 +200,18 @@ func (s *server) UsersUpdateHandler(w http.ResponseWriter, r *http.Request) {
}

role := fmt.Sprintf("arn:aws:iam::%s:role/%s", account, s.session.RoleName)
policy, err := s.repositoryUserUpdatePolicy()
if err != nil {
handleError(w, apierror.New(apierror.ErrInternalError, "failed to generate policy", err))
return
}

// IAM doesn't support resource tags, so we can't pass the s.orgPolicy here
session, err := s.assumeRole(
r.Context(),
s.session.ExternalID,
role,
"",
policy,
)
if err != nil {
msg := fmt.Sprintf("failed to assume role in account: %s", account)
Expand Down Expand Up @@ -239,13 +251,18 @@ func (s *server) UsersDeleteHandler(w http.ResponseWriter, r *http.Request) {
userName := vars["user"]

role := fmt.Sprintf("arn:aws:iam::%s:role/%s", account, s.session.RoleName)
policy, err := s.repositoryUserDeletePolicy()
if err != nil {
handleError(w, apierror.New(apierror.ErrInternalError, "failed to generate policy", err))
return
}

// IAM doesn't support resource tags, so we can't pass the s.orgPolicy here
session, err := s.assumeRole(
r.Context(),
s.session.ExternalID,
role,
"",
policy,
)
if err != nil {
msg := fmt.Sprintf("failed to assume role in account: %s", account)
Expand Down
77 changes: 72 additions & 5 deletions api/orchestration_repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,35 @@ import (
log "github.com/sirupsen/logrus"
)

// repositoryDetails returns the details about a repository
func (o *ecrOrchestrator) repositoryDetails(ctx context.Context, account, group, name string) (*RepositoryResponse, error) {
repository := fmt.Sprintf("%s/%s", group, name)

log.Debugf("getting details about repository %s", repository)

repo, err := o.client.GetRepositories(ctx, repository)
if err != nil {
return nil, err
}

policy, err := o.client.GetRepositoryPolicy(ctx, repository)
if err != nil {
return nil, err
}

groups, err := repositoryGroupsFromPolicy(policy)
if err != nil {
return nil, err
}

tags, err := o.client.GetRepositoryTags(ctx, aws.StringValue(repo.RepositoryArn))
if err != nil {
return nil, err
}

return repositoryResponseFromECR(repo, groups, tags), nil
}

// repositoryCreate orchestrates the creation of a repository from the RepositoryCreateRequest
func (o *ecrOrchestrator) repositoryCreate(ctx context.Context, account, group string, req *RepositoryCreateRequest) (*RepositoryResponse, error) {
repository := fmt.Sprintf("%s/%s", group, req.RepositoryName)
Expand Down Expand Up @@ -54,14 +83,21 @@ func (o *ecrOrchestrator) repositoryCreate(ctx context.Context, account, group s
return nil, err
}

tags, err := o.client.GetRepositoryTags(ctx, aws.StringValue(out.RepositoryArn))
policy, err := repositoryPolicy(req.Groups)
if err != nil {
return nil, err
}

log.Debugf("got output %+v", out)
if err := o.client.UpdateRepositoryPolicy(ctx, repository, policy); err != nil {
return nil, err
}

tags, err := o.client.GetRepositoryTags(ctx, aws.StringValue(out.RepositoryArn))
if err != nil {
return nil, err
}

return repositoryResponseFromECR(out, tags), nil
return repositoryResponseFromECR(out, req.Groups, tags), nil
}

// repositoryDelete orchestrates the deletion of a repository
Expand All @@ -75,6 +111,16 @@ func (o *ecrOrchestrator) repositoryDelete(ctx context.Context, account, group,
return nil, err
}

policy, err := o.client.GetRepositoryPolicy(ctx, repository)
if err != nil {
return nil, err
}

groups, err := repositoryGroupsFromPolicy(policy)
if err != nil {
return nil, err
}

tags, err := o.client.GetRepositoryTags(ctx, aws.StringValue(repo.RepositoryArn))
if err != nil {
return nil, err
Expand All @@ -87,7 +133,7 @@ func (o *ecrOrchestrator) repositoryDelete(ctx context.Context, account, group,

log.Debugf("got output %+v", out)

return repositoryResponseFromECR(out, tags), nil
return repositoryResponseFromECR(out, groups, tags), nil
}

// repositoryUpdate orchestrates updating a repository
Expand All @@ -114,6 +160,17 @@ func (o *ecrOrchestrator) repositoryUpdate(ctx context.Context, account, group,
}
}

if req.Groups != nil {
policy, err := repositoryPolicy(req.Groups)
if err != nil {
return nil, err
}

if err := o.client.UpdateRepositoryPolicy(ctx, repository, policy); err != nil {
return nil, err
}
}

if req.Tags != nil {
if err := o.client.UpdateRepositoryTags(ctx, aws.StringValue(repo.RepositoryArn), toECRTags(req.Tags)); err != nil {
return nil, err
Expand All @@ -125,10 +182,20 @@ func (o *ecrOrchestrator) repositoryUpdate(ctx context.Context, account, group,
return nil, err
}

policy, err := o.client.GetRepositoryPolicy(ctx, repository)
if err != nil {
return nil, err
}

groups, err := repositoryGroupsFromPolicy(policy)
if err != nil {
return nil, err
}

tags, err := o.client.GetRepositoryTags(ctx, aws.StringValue(repo.RepositoryArn))
if err != nil {
return nil, err
}

return repositoryResponseFromECR(repo, tags), nil
return repositoryResponseFromECR(repo, groups, tags), nil
}
33 changes: 22 additions & 11 deletions api/orchestration_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,20 @@ var EcrAdminPolicy = iam.PolicyDocument{
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
},
Resource: "*",
Resource: []string{"*"},
Condition: iam.Condition{
"StringEquals": iam.ConditionStatement{
"aws:ResourceTag/spinup:org": "${aws:PrincipalTag/spinup:org}",
"aws:ResourceTag/spinup:spaceid": "${aws:PrincipalTag/spinup:spaceid}",
"aws:ResourceTag/Name": "${aws:PrincipalTag/ResourceName}",
"StringEqualsIgnoreCase": iam.ConditionStatement{
"aws:ResourceTag/spinup:org": []string{"${aws:PrincipalTag/spinup:org}"},
"aws:ResourceTag/spinup:spaceid": []string{"${aws:PrincipalTag/spinup:spaceid}"},
"aws:ResourceTag/Name": []string{"${aws:PrincipalTag/ResourceName}"},
},
},
},
{
Sid: "AllowDockerLogin",
Effect: "Allow",
Action: []string{"ecr:GetAuthorizationToken"},
Resource: "*",
Resource: []string{"*"},
},
},
}
Expand Down Expand Up @@ -123,6 +123,17 @@ func (o *iamOrchestrator) repositoryUserDelete(ctx context.Context, name, group,
}
}

keys, err := o.client.ListAccessKeys(ctx, userName)
if err != nil {
return err
}

for _, k := range keys {
if err := o.client.DeleteAccessKey(ctx, userName, aws.StringValue(k.AccessKeyId)); err != nil {
return err
}
}

if err := o.client.DeleteUser(ctx, userName); err != nil {
return err
}
Expand Down Expand Up @@ -190,12 +201,12 @@ func (o *iamOrchestrator) userCreatePolicyIfMissing(ctx context.Context, name, p
return "", err
}

// If we cannot unmarshal the document we received into an iam.PolicyDocument or if
// the document doesn't match, let's try to update it. If unmarshaling fails, we assume
// our struct has changed (for example going from Resource string to Resource []string)
doc := iam.PolicyDocument{}
if err := json.Unmarshal([]byte(d), &doc); err != nil {
return "", err
}

if !awsutil.DeepEqual(doc, EcrAdminPolicy) {
err = json.Unmarshal([]byte(d), &doc)
if err != nil || !awsutil.DeepEqual(doc, EcrAdminPolicy) {
log.Warn("policy document is not the same, updating")

if err := o.client.UpdatePolicy(ctx, aws.StringValue(policy.Arn), ecrAdminPolicyDoc); err != nil {
Expand Down
Loading

0 comments on commit 8563c90

Please sign in to comment.