aboutsummaryrefslogtreecommitdiff
path: root/app/controllers/aws.go
blob: 4f3294237db5f3a4eb12e7174c08ac308a5f41ec (plain)
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
}