Skip to content

Commit

Permalink
Add ability to reset access keys and update tags for a user (#3)
Browse files Browse the repository at this point in the history
* add ability to reset access keys and update tags for a user
  • Loading branch information
fishnix authored Feb 14, 2021
1 parent 68cd4a1 commit 2b88d11
Show file tree
Hide file tree
Showing 22 changed files with 1,591 additions and 150 deletions.
80 changes: 77 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ GET /v1/ecr/{account}/repositories/{group}/{name}/images
GET /v1/ecr/{account}/repositories/{group}/{name}/users
POST /v1/ecr/{account}/repositories/{group}/{name}/users
GET /v1/ecr/{account}/repositories/{group}/{name}/users/{user}
PUT /v1/ecr/{account}/repositories/{group}/{name}/users/{user}
DELETE /v1/ecr/{account}/repositories/{group}/{name}/users/{user}
```

Expand Down Expand Up @@ -417,17 +418,90 @@ GET /v1/ecr/{account}/repositories/{group}/{name}/users/{user}
"Key": "application",
"Value": "myapps"
},
{
"Key": "ResourceName",
"Value": "spindev-00001-myAwesomeRepository-user1"
},
{
"Key": "Name",
"Value": "spindev-00001/myAwesomeRepository"
},
{
"Key": "spinup:org",
"Value": "spindev"
},
{
"Key": "spinup:spaceid",
"Value": "spindev-00001"
}
]
}
```

#### Update a user

A user's tags and/or its access key can be updated. The operations are independent,
and can occur in the same request, or individually. If the access key is reset,
a new access key will be returned with the response.

PUT /v1/ecr/{account}/repositories/{group}/{name}/users/{user}

| Response Code | Definition |
| ----------------------------- | --------------------------------|
| **200 OK** | updated the user |
| **400 Bad Request** | badly formed request |
| **404 Not Found** | account not found |
| **500 Internal Server Error** | a server error occurred |

##### Example update user request body

```json
{
"resetkey": true,
"tags": [
{
"key": "application",
"value": "myapp123"
}
],
}
```

##### Example update user response

```json
{
"UserName": "user1",
"AccessKey": {
"AccessKeyId": "AAAAABBBBBCCCCCDDDDDEEEEEFFFFF",
"CreateDate": "2021-02-03T22:37:30Z",
"SecretAccessKey": "gxyz1234567890abcdefghijklmnop",
"Status": "Active",
"UserName": "spincool-00001-testrepo1-user1"
},
"DeletedAccessKeys": [
"QQQQQRRRRRSSSSSTTTTTUUUUVVVV"
],
"Tags": [
{
"Key": "application",
"Value": "myapp123"
},
{
"Key": "ResourceName",
"Value": "spindev-00001-myAwesomeRepository-user1"
},
{
"Key": "Name",
"Value": "spincool-00002/camdenstestrepo01"
"Value": "spindev-00001/myAwesomeRepository"
},
{
"Key": "spinup:org",
"Value": "spincool"
"Value": "spindev"
},
{
"Key": "spinup:spaceid",
"Value": "spincool-00002"
"Value": "spindev-00001"
}
]
}
Expand Down
34 changes: 12 additions & 22 deletions api/handlers_repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ import (
log "github.com/sirupsen/logrus"
)

type ecrOrchestrator struct {
client ecr.ECR
org string
}

// RepositoriesCreateHandler is the http handler for creating a repository
func (s *server) RepositoriesCreateHandler(w http.ResponseWriter, r *http.Request) {
w = LogWriter{w}
Expand Down Expand Up @@ -51,12 +46,10 @@ func (s *server) RepositoriesCreateHandler(w http.ResponseWriter, r *http.Reques
return
}

orch := &ecrOrchestrator{
client: ecr.New(
ecr.WithSession(session.Session),
),
org: s.org,
}
orch := newEcrOrchestrator(
ecr.New(ecr.WithSession(session.Session)),
s.org,
)

resp, err := orch.repositoryCreate(r.Context(), account, group, &req)
if err != nil {
Expand Down Expand Up @@ -239,12 +232,10 @@ func (s *server) RepositoriesUpdateHandler(w http.ResponseWriter, r *http.Reques
handleError(w, apierror.New(apierror.ErrForbidden, msg, nil))
}

orch := &ecrOrchestrator{
client: ecr.New(
ecr.WithSession(session.Session),
),
org: s.org,
}
orch := newEcrOrchestrator(
ecr.New(ecr.WithSession(session.Session)),
s.org,
)

resp, err := orch.repositoryUpdate(r.Context(), account, group, name, &req)
if err != nil {
Expand Down Expand Up @@ -285,11 +276,10 @@ func (s *server) RepositoriesDeleteHandler(w http.ResponseWriter, r *http.Reques
handleError(w, apierror.New(apierror.ErrForbidden, msg, nil))
}

orch := &ecrOrchestrator{
client: ecr.New(
ecr.WithSession(session.Session),
),
}
orch := newEcrOrchestrator(
ecr.New(ecr.WithSession(session.Session)),
s.org,
)

resp, err := orch.repositoryDelete(r.Context(), account, group, name)
if err != nil {
Expand Down
92 changes: 66 additions & 26 deletions api/handlers_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/YaleSpinup/apierror"
"github.com/YaleSpinup/ecr-api/iam"
"github.com/gorilla/mux"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)

Expand Down Expand Up @@ -52,20 +53,18 @@ func (s *server) UsersCreateHandler(w http.ResponseWriter, r *http.Request) {
return
}

orch := &iamOrchestrator{
client: iam.New(
iam.WithSession(session.Session),
),
org: s.org,
}
orch := newIamOrchestrator(
iam.New(iam.WithSession(session.Session)),
s.org,
)

groupName, err := orch.prepareAccount(r.Context())
if err != nil {
handleError(w, err)
return
}

out, err := orch.repositoryUserCreate(r.Context(), name, group, groupName, req)
out, err := orch.repositoryUserCreate(r.Context(), name, group, groupName, &req)
if err != nil {
handleError(w, err)
return
Expand Down Expand Up @@ -107,12 +106,10 @@ func (s *server) UsersListHandler(w http.ResponseWriter, r *http.Request) {
return
}

orch := &iamOrchestrator{
client: iam.New(
iam.WithSession(session.Session),
),
org: s.org,
}
orch := newIamOrchestrator(
iam.New(iam.WithSession(session.Session)),
s.org,
)

output, err := orch.listRepositoryUsers(r.Context(), group, name)
if err != nil {
Expand Down Expand Up @@ -156,12 +153,10 @@ func (s *server) UsersShowHandler(w http.ResponseWriter, r *http.Request) {
return
}

orch := &iamOrchestrator{
client: iam.New(
iam.WithSession(session.Session),
),
org: s.org,
}
orch := newIamOrchestrator(
iam.New(iam.WithSession(session.Session)),
s.org,
)

output, err := orch.getRepositoryUser(r.Context(), group, name, user)
if err != nil {
Expand All @@ -183,8 +178,55 @@ func (s *server) UsersShowHandler(w http.ResponseWriter, r *http.Request) {

// UsersUpdateHandler updates a repository user
func (s *server) UsersUpdateHandler(w http.ResponseWriter, r *http.Request) {
w = LogWriter{w}
vars := mux.Vars(r)
account := vars["account"]
group := vars["group"]
name := vars["name"]
userName := vars["user"]

req := RepositoryUserUpdateRequest{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
msg := fmt.Sprintf("cannot decode body into update repository user input: %s", err)
handleError(w, apierror.New(apierror.ErrBadRequest, msg, err))
return
}

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

// 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,
"",
)
if err != nil {
msg := fmt.Sprintf("failed to assume role in account: %s", account)
handleError(w, apierror.New(apierror.ErrForbidden, msg, nil))
return
}

orch := newIamOrchestrator(
iam.New(iam.WithSession(session.Session)),
s.org,
)

resp, err := orch.repositoryUserUpdate(r.Context(), name, group, userName, &req)
if err != nil {
handleError(w, errors.Wrap(err, "failed to update repository user"))
return
}

j, err := json.Marshal(resp)
if err != nil {
handleError(w, errors.Wrap(err, "unable to marshal response"))
return
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotImplemented)
w.WriteHeader(http.StatusOK)
w.Write(j)
}

// UsersDeleteHandler deletes a repository user
Expand All @@ -211,12 +253,10 @@ func (s *server) UsersDeleteHandler(w http.ResponseWriter, r *http.Request) {
return
}

orch := &iamOrchestrator{
client: iam.New(
iam.WithSession(session.Session),
),
org: s.org,
}
orch := newIamOrchestrator(
iam.New(iam.WithSession(session.Session)),
s.org,
)

if err := orch.repositoryUserDelete(r.Context(), name, group, userName); err != nil {
handleError(w, err)
Expand Down
54 changes: 48 additions & 6 deletions api/orchestration_users.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,6 @@ var EcrAdminPolicy = iam.PolicyDocument{
},
}

type iamOrchestrator struct {
client iam.IAM
org string
}

// listRepositoryUsers lists users in a repository
func (o *iamOrchestrator) listRepositoryUsers(ctx context.Context, group, name string) ([]string, error) {
path := fmt.Sprintf("/spinup/%s/%s/%s", o.org, group, name)
Expand Down Expand Up @@ -250,7 +245,7 @@ func (o *iamOrchestrator) userCreateGroupIfMissing(ctx context.Context, name, pa
return nil
}

func (o *iamOrchestrator) repositoryUserCreate(ctx context.Context, name, group, groupName string, req RepositoryUserCreateRequest) (*RepositoryUserResponse, error) {
func (o *iamOrchestrator) repositoryUserCreate(ctx context.Context, name, group, groupName string, req *RepositoryUserCreateRequest) (*RepositoryUserResponse, error) {
log.Infof("creating repository %s user %s in group %s in iam group %s", name, req.UserName, group, groupName)

path := fmt.Sprintf("/spinup/%s/%s/%s/", o.org, group, name)
Expand Down Expand Up @@ -280,3 +275,50 @@ func (o *iamOrchestrator) repositoryUserCreate(ctx context.Context, name, group,

return repositoryUserResponseFromIAM(o.org, user, nil, []string{groupName}), nil
}

func (o *iamOrchestrator) repositoryUserUpdate(ctx context.Context, name, group, userName string, req *RepositoryUserUpdateRequest) (*RepositoryUserResponse, error) {
log.Infof("updating repository %s user %s in group %s", name, userName, group)

uname := fmt.Sprintf("%s-%s-%s", group, name, userName)
repository := fmt.Sprintf("%s/%s", group, name)

response := &RepositoryUserResponse{
UserName: userName,
}

if req.Tags != nil {
req.Tags = normalizeUserTags(o.org, group, repository, uname, req.Tags)
if err := o.client.TagUser(ctx, uname, toIAMTags(req.Tags)); err != nil {
return nil, err
}
response.Tags = req.Tags
}

if req.ResetKey {
// get a list of users access keys
keys, err := o.client.ListAccessKeys(ctx, uname)
if err != nil {
return nil, err
}

newKeyOut, err := o.client.CreateAccessKey(ctx, uname)
if err != nil {
return nil, err
}
response.AccessKey = newKeyOut

deletedKeyIds := make([]string, 0, len(keys))
// delete the old access keys
for _, k := range keys {
err = o.client.DeleteAccessKey(ctx, uname, aws.StringValue(k.AccessKeyId))
if err != nil {
return response, err
}
deletedKeyIds = append(deletedKeyIds, aws.StringValue(k.AccessKeyId))
}

response.DeletedAccessKeys = deletedKeyIds
}

return response, nil
}
Loading

0 comments on commit 2b88d11

Please sign in to comment.