1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
package controllers
import (
"context"
"sync"
"code.crute.us/mcrute/cloud-identity-broker/app/middleware"
"code.crute.us/mcrute/cloud-identity-broker/app/models"
"code.crute.us/mcrute/cloud-identity-broker/cloud/aws"
"code.crute.us/mcrute/golib/secrets"
"github.com/labstack/echo/v4"
)
type requestContext struct {
Account *models.Account
Principal *models.User
AWS aws.AWSClient
}
// AWSAPI is a capability that all handlers talking to the AWS APIs should use.
// This capability does common permission checks and populates a request
// context with user, account, and AWS API information.
type AWSAPI struct {
Store models.AccountStore
Secrets secrets.Client
cache sync.Map // of aws.AWSClient
}
func (h *AWSAPI) getClientFor(ctx context.Context, a *models.Account) (aws.AWSClient, error) {
var client aws.AWSClient
if cv, ok := h.cache.Load(a.ShortName); !ok {
client, err := aws.NewAWSClientFromAccount(ctx, a, h.Secrets)
if err != nil {
return nil, err
}
cv, _ = h.cache.LoadOrStore(a.ShortName, client)
client = cv.(aws.AWSClient)
} else {
client = cv.(aws.AWSClient)
}
return client, nil
}
// Preload enumerates all managed accounts and pre-loads all of the
// clients into the cache.
//
// This exists because there is replication delay of around 5-10 seconds
// for IAM users into other regions so these should be created as early
// in the process lifecycle as possible.
func (h *AWSAPI) Preload(ctx context.Context) []error {
accounts, err := h.Store.List(ctx)
if err != nil {
return []error{err}
}
errors := []error{}
for _, a := range accounts {
_, err = h.getClientFor(ctx, a)
if err != nil {
errors = append(errors, err)
}
}
return errors
}
// GetContext checks that the user is authenticated and is authorized to access
// the requested AWS account. This should be the very first call in any handler
// that will eventually call the AWS APIs. Errors returned from this method are
// echo responses and can be returned directly to the client.
func (h *AWSAPI) GetContext(c echo.Context) (*requestContext, error) {
principal, err := middleware.GetAuthorizedPrincipal(c)
if err != nil {
return nil, echo.ErrUnauthorized
}
account, err := h.Store.GetForUser(context.Background(), c.Param("account"), principal)
if err != nil {
return nil, echo.NotFoundHandler(c)
}
client, err := h.getClientFor(c.Request().Context(), account)
if err != nil {
c.Logger().Errorf("Error building AWS client: %w", err)
return nil, echo.ErrInternalServerError
}
return &requestContext{
Account: account,
Principal: principal,
AWS: client,
}, nil
}
|