diff options
author | Mike Crute <mike@crute.us> | 2022-12-24 08:30:27 -0800 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2022-12-24 08:30:27 -0800 |
commit | 4aac2b6026d27d4eba660b674bdb1f34bcfbfc54 (patch) | |
tree | 62eb040afb6d7f56f9bfb0af69cfc17ec2060a8b /app | |
parent | ed1504c2826f6a5d406dd72e51f5a90b77ffea45 (diff) | |
download | cloud-identity-broker-4aac2b6026d27d4eba660b674bdb1f34bcfbfc54.tar.bz2 cloud-identity-broker-4aac2b6026d27d4eba660b674bdb1f34bcfbfc54.tar.xz cloud-identity-broker-4aac2b6026d27d4eba660b674bdb1f34bcfbfc54.zip |
Use Vault for IAM users
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/api_account.go | 8 | ||||
-rw-r--r-- | app/controllers/aws.go | 51 | ||||
-rw-r--r-- | app/models/account.go | 12 | ||||
-rw-r--r-- | app/models/session_key.go | 2 |
4 files changed, 59 insertions, 14 deletions
diff --git a/app/controllers/api_account.go b/app/controllers/api_account.go index 815daf4..cecd334 100644 --- a/app/controllers/api_account.go +++ b/app/controllers/api_account.go | |||
@@ -2,14 +2,12 @@ package controllers | |||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "context" | 4 | "context" |
5 | "fmt" | ||
6 | "net/http" | 5 | "net/http" |
7 | "time" | 6 | "time" |
8 | 7 | ||
9 | "code.crute.us/mcrute/cloud-identity-broker/app" | 8 | "code.crute.us/mcrute/cloud-identity-broker/app" |
10 | "code.crute.us/mcrute/cloud-identity-broker/app/middleware" | 9 | "code.crute.us/mcrute/cloud-identity-broker/app/middleware" |
11 | "code.crute.us/mcrute/cloud-identity-broker/app/models" | 10 | "code.crute.us/mcrute/cloud-identity-broker/app/models" |
12 | "code.crute.us/mcrute/cloud-identity-broker/cloud/aws" | ||
13 | 11 | ||
14 | glecho "code.crute.us/mcrute/golib/echo" | 12 | glecho "code.crute.us/mcrute/golib/echo" |
15 | "code.crute.us/mcrute/golib/echo/controller" | 13 | "code.crute.us/mcrute/golib/echo/controller" |
@@ -90,7 +88,7 @@ func (h *APIAccountHandler) HandleGet(c echo.Context) error { | |||
90 | // details about the account so they should only be visible to users who | 88 | // details about the account so they should only be visible to users who |
91 | // can administer the account. | 89 | // can administer the account. |
92 | if !a.CanBeModifiedBy(p) { | 90 | if !a.CanBeModifiedBy(p) { |
93 | a.VaultMaterial = "" | 91 | a.AdminVaultMaterial = "" |
94 | a.Users = nil | 92 | a.Users = nil |
95 | } | 93 | } |
96 | 94 | ||
@@ -136,7 +134,7 @@ func (h *APIAccountHandler) HandlePut(c echo.Context) error { | |||
136 | a.AccountNumber = in.AccountNumber | 134 | a.AccountNumber = in.AccountNumber |
137 | a.Name = in.Name | 135 | a.Name = in.Name |
138 | a.ConsoleSessionDuration = in.ConsoleSessionDuration | 136 | a.ConsoleSessionDuration = in.ConsoleSessionDuration |
139 | a.VaultMaterial = in.VaultMaterial | 137 | a.AdminVaultMaterial = in.AdminVaultMaterial |
140 | a.DefaultRegion = in.DefaultRegion | 138 | a.DefaultRegion = in.DefaultRegion |
141 | a.Users = in.Users | 139 | a.Users = in.Users |
142 | 140 | ||
@@ -181,12 +179,14 @@ func (h *APIAccountHandler) HandlePost(c echo.Context) error { | |||
181 | } | 179 | } |
182 | } | 180 | } |
183 | 181 | ||
182 | /* TODO: Validate that the vault material exists | ||
184 | if err := aws.ValidateVaultMaterial(in.VaultMaterial); err != nil { | 183 | if err := aws.ValidateVaultMaterial(in.VaultMaterial); err != nil { |
185 | return &echo.HTTPError{ | 184 | return &echo.HTTPError{ |
186 | Code: http.StatusBadRequest, | 185 | Code: http.StatusBadRequest, |
187 | Message: fmt.Sprintf("Unable to access Vault material: %s", err), | 186 | Message: fmt.Sprintf("Unable to access Vault material: %s", err), |
188 | } | 187 | } |
189 | } | 188 | } |
189 | */ | ||
190 | 190 | ||
191 | if err := h.Store.Put(context.Background(), &in); err != nil { | 191 | if err := h.Store.Put(context.Background(), &in); err != nil { |
192 | return echo.ErrInternalServerError | 192 | return echo.ErrInternalServerError |
diff --git a/app/controllers/aws.go b/app/controllers/aws.go index 5b1765d..4f32942 100644 --- a/app/controllers/aws.go +++ b/app/controllers/aws.go | |||
@@ -2,11 +2,14 @@ package controllers | |||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "context" | 4 | "context" |
5 | "sync" | ||
5 | 6 | ||
6 | "code.crute.us/mcrute/cloud-identity-broker/app/middleware" | 7 | "code.crute.us/mcrute/cloud-identity-broker/app/middleware" |
7 | "code.crute.us/mcrute/cloud-identity-broker/app/models" | 8 | "code.crute.us/mcrute/cloud-identity-broker/app/models" |
8 | "code.crute.us/mcrute/cloud-identity-broker/cloud/aws" | 9 | "code.crute.us/mcrute/cloud-identity-broker/cloud/aws" |
9 | 10 | ||
11 | "code.crute.us/mcrute/golib/secrets" | ||
12 | |||
10 | "github.com/labstack/echo/v4" | 13 | "github.com/labstack/echo/v4" |
11 | ) | 14 | ) |
12 | 15 | ||
@@ -20,7 +23,49 @@ type requestContext struct { | |||
20 | // This capability does common permission checks and populates a request | 23 | // This capability does common permission checks and populates a request |
21 | // context with user, account, and AWS API information. | 24 | // context with user, account, and AWS API information. |
22 | type AWSAPI struct { | 25 | type AWSAPI struct { |
23 | Store models.AccountStore | 26 | Store models.AccountStore |
27 | Secrets secrets.Client | ||
28 | cache sync.Map // of aws.AWSClient | ||
29 | } | ||
30 | |||
31 | func (h *AWSAPI) getClientFor(ctx context.Context, a *models.Account) (aws.AWSClient, error) { | ||
32 | var client aws.AWSClient | ||
33 | |||
34 | if cv, ok := h.cache.Load(a.ShortName); !ok { | ||
35 | client, err := aws.NewAWSClientFromAccount(ctx, a, h.Secrets) | ||
36 | if err != nil { | ||
37 | return nil, err | ||
38 | } | ||
39 | |||
40 | cv, _ = h.cache.LoadOrStore(a.ShortName, client) | ||
41 | client = cv.(aws.AWSClient) | ||
42 | } else { | ||
43 | client = cv.(aws.AWSClient) | ||
44 | } | ||
45 | |||
46 | return client, nil | ||
47 | } | ||
48 | |||
49 | // Preload enumerates all managed accounts and pre-loads all of the | ||
50 | // clients into the cache. | ||
51 | // | ||
52 | // This exists because there is replication delay of around 5-10 seconds | ||
53 | // for IAM users into other regions so these should be created as early | ||
54 | // in the process lifecycle as possible. | ||
55 | func (h *AWSAPI) Preload(ctx context.Context) []error { | ||
56 | accounts, err := h.Store.List(ctx) | ||
57 | if err != nil { | ||
58 | return []error{err} | ||
59 | } | ||
60 | |||
61 | errors := []error{} | ||
62 | for _, a := range accounts { | ||
63 | _, err = h.getClientFor(ctx, a) | ||
64 | if err != nil { | ||
65 | errors = append(errors, err) | ||
66 | } | ||
67 | } | ||
68 | return errors | ||
24 | } | 69 | } |
25 | 70 | ||
26 | // GetContext checks that the user is authenticated and is authorized to access | 71 | // GetContext checks that the user is authenticated and is authorized to access |
@@ -38,7 +83,7 @@ func (h *AWSAPI) GetContext(c echo.Context) (*requestContext, error) { | |||
38 | return nil, echo.NotFoundHandler(c) | 83 | return nil, echo.NotFoundHandler(c) |
39 | } | 84 | } |
40 | 85 | ||
41 | ac, err := aws.NewAWSClientFromAccount(account) | 86 | client, err := h.getClientFor(c.Request().Context(), account) |
42 | if err != nil { | 87 | if err != nil { |
43 | c.Logger().Errorf("Error building AWS client: %w", err) | 88 | c.Logger().Errorf("Error building AWS client: %w", err) |
44 | return nil, echo.ErrInternalServerError | 89 | return nil, echo.ErrInternalServerError |
@@ -47,6 +92,6 @@ func (h *AWSAPI) GetContext(c echo.Context) (*requestContext, error) { | |||
47 | return &requestContext{ | 92 | return &requestContext{ |
48 | Account: account, | 93 | Account: account, |
49 | Principal: principal, | 94 | Principal: principal, |
50 | AWS: ac, | 95 | AWS: client, |
51 | }, nil | 96 | }, nil |
52 | } | 97 | } |
diff --git a/app/models/account.go b/app/models/account.go index af29c3e..346354c 100644 --- a/app/models/account.go +++ b/app/models/account.go | |||
@@ -25,8 +25,9 @@ type Account struct { | |||
25 | AccountType string `json:"account_type"` | 25 | AccountType string `json:"account_type"` |
26 | AccountNumber int `json:"account_number"` | 26 | AccountNumber int `json:"account_number"` |
27 | Name string `json:"name"` | 27 | Name string `json:"name"` |
28 | ConsoleSessionDuration time.Duration `json:"console_session_duration, omitempty"` | 28 | ConsoleSessionDuration time.Duration `json:"console_session_duration,omitempty"` |
29 | VaultMaterial string `json:"vault_material,omitempty"` | 29 | AdminVaultMaterial string `json:"admin_vault_material,omitempty"` |
30 | AssumedRoleARN string `json:"assumed_role_arn"` | ||
30 | DefaultRegion string `json:"default_region"` | 31 | DefaultRegion string `json:"default_region"` |
31 | Users []string `json:"users,omitempty"` | 32 | Users []string `json:"users,omitempty"` |
32 | Deleted *time.Time `json:"deleted,omitempty" bson:"deleted,omitempty"` | 33 | Deleted *time.Time `json:"deleted,omitempty" bson:"deleted,omitempty"` |
@@ -43,10 +44,9 @@ func (a *Account) CanBeModifiedBy(u *User) bool { | |||
43 | type MongoDbAccountStore struct { | 44 | type MongoDbAccountStore struct { |
44 | Db *mongodb.Mongo | 45 | Db *mongodb.Mongo |
45 | 46 | ||
46 | // ReturnDeleted will allow all methods to return deleted items. By default | 47 | // ReturnDeleted will allow all methods to return deleted items. items |
47 | // items where the Deleted field is set will not be returned. This should | 48 | // where the Deleted field is set will not be returned. Non-admin |
48 | // be the common cast for most code using this store but in some Admin | 49 | // use-cases should leave this set to false. |
49 | // use-cases it would be useful to show deleted accounts. | ||
50 | ReturnDeleted bool | 50 | ReturnDeleted bool |
51 | } | 51 | } |
52 | 52 | ||
diff --git a/app/models/session_key.go b/app/models/session_key.go index c8b327e..b75d6c4 100644 --- a/app/models/session_key.go +++ b/app/models/session_key.go | |||
@@ -52,7 +52,7 @@ type SessionKey struct { | |||
52 | NotBefore *time.Time | 52 | NotBefore *time.Time |
53 | PublicKey crypto.PublicKey | 53 | PublicKey crypto.PublicKey |
54 | PrivateKey *ecdsa.PrivateKey | 54 | PrivateKey *ecdsa.PrivateKey |
55 | ExposePrivateKeysInJSON bool `"-" json:"-" bson:"-"` | 55 | ExposePrivateKeysInJSON bool `json:"-" bson:"-"` |
56 | } | 56 | } |
57 | 57 | ||
58 | func GenerateSessionKey(ttl time.Duration) (*SessionKey, error) { | 58 | func GenerateSessionKey(ttl time.Duration) (*SessionKey, error) { |