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 }