1package awsauth 2 3import ( 4 "context" 5 "fmt" 6 7 "github.com/hashicorp/vault/sdk/framework" 8 "github.com/hashicorp/vault/sdk/logical" 9) 10 11// awsStsEntry is used to store details of an STS role for assumption 12type awsStsEntry struct { 13 StsRole string `json:"sts_role"` 14} 15 16func (b *backend) pathListSts() *framework.Path { 17 return &framework.Path{ 18 Pattern: "config/sts/?", 19 20 Operations: map[logical.Operation]framework.OperationHandler{ 21 logical.ListOperation: &framework.PathOperation{ 22 Callback: b.pathStsList, 23 }, 24 }, 25 26 HelpSynopsis: pathListStsHelpSyn, 27 HelpDescription: pathListStsHelpDesc, 28 } 29} 30 31func (b *backend) pathConfigSts() *framework.Path { 32 return &framework.Path{ 33 Pattern: "config/sts/" + framework.GenericNameRegex("account_id"), 34 Fields: map[string]*framework.FieldSchema{ 35 "account_id": { 36 Type: framework.TypeString, 37 Description: `AWS account ID to be associated with STS role. If set, 38Vault will use assumed credentials to verify any login attempts from EC2 39instances in this account.`, 40 }, 41 "sts_role": { 42 Type: framework.TypeString, 43 Description: `AWS ARN for STS role to be assumed when interacting with the account specified. 44The Vault server must have permissions to assume this role.`, 45 }, 46 }, 47 48 ExistenceCheck: b.pathConfigStsExistenceCheck, 49 50 Operations: map[logical.Operation]framework.OperationHandler{ 51 logical.CreateOperation: &framework.PathOperation{ 52 Callback: b.pathConfigStsCreateUpdate, 53 }, 54 logical.UpdateOperation: &framework.PathOperation{ 55 Callback: b.pathConfigStsCreateUpdate, 56 }, 57 logical.ReadOperation: &framework.PathOperation{ 58 Callback: b.pathConfigStsRead, 59 }, 60 logical.DeleteOperation: &framework.PathOperation{ 61 Callback: b.pathConfigStsDelete, 62 }, 63 }, 64 65 HelpSynopsis: pathConfigStsSyn, 66 HelpDescription: pathConfigStsDesc, 67 } 68} 69 70// Establishes dichotomy of request operation between CreateOperation and UpdateOperation. 71// Returning 'true' forces an UpdateOperation, CreateOperation otherwise. 72func (b *backend) pathConfigStsExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) { 73 accountID := data.Get("account_id").(string) 74 if accountID == "" { 75 return false, fmt.Errorf("missing account_id") 76 } 77 78 entry, err := b.lockedAwsStsEntry(ctx, req.Storage, accountID) 79 if err != nil { 80 return false, err 81 } 82 83 return entry != nil, nil 84} 85 86// pathStsList is used to list all the AWS STS role configurations 87func (b *backend) pathStsList(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 88 b.configMutex.RLock() 89 defer b.configMutex.RUnlock() 90 sts, err := req.Storage.List(ctx, "config/sts/") 91 if err != nil { 92 return nil, err 93 } 94 return logical.ListResponse(sts), nil 95} 96 97// nonLockedSetAwsStsEntry creates or updates an STS role association with the given accountID 98// This method does not acquire the write lock before creating or updating. If locking is 99// desired, use lockedSetAwsStsEntry instead 100func (b *backend) nonLockedSetAwsStsEntry(ctx context.Context, s logical.Storage, accountID string, stsEntry *awsStsEntry) error { 101 if accountID == "" { 102 return fmt.Errorf("missing AWS account ID") 103 } 104 105 if stsEntry == nil { 106 return fmt.Errorf("missing AWS STS Role ARN") 107 } 108 109 entry, err := logical.StorageEntryJSON("config/sts/"+accountID, stsEntry) 110 if err != nil { 111 return err 112 } 113 114 if entry == nil { 115 return fmt.Errorf("failed to create storage entry for AWS STS configuration") 116 } 117 118 return s.Put(ctx, entry) 119} 120 121// lockedSetAwsStsEntry creates or updates an STS role association with the given accountID 122// This method acquires the write lock before creating or updating the STS entry. 123func (b *backend) lockedSetAwsStsEntry(ctx context.Context, s logical.Storage, accountID string, stsEntry *awsStsEntry) error { 124 if accountID == "" { 125 return fmt.Errorf("missing AWS account ID") 126 } 127 128 if stsEntry == nil { 129 return fmt.Errorf("missing sts entry") 130 } 131 132 b.configMutex.Lock() 133 defer b.configMutex.Unlock() 134 135 return b.nonLockedSetAwsStsEntry(ctx, s, accountID, stsEntry) 136} 137 138// nonLockedAwsStsEntry returns the STS role associated with the given accountID. 139// This method does not acquire the read lock before returning information. If locking is 140// desired, use lockedAwsStsEntry instead 141func (b *backend) nonLockedAwsStsEntry(ctx context.Context, s logical.Storage, accountID string) (*awsStsEntry, error) { 142 entry, err := s.Get(ctx, "config/sts/"+accountID) 143 if err != nil { 144 return nil, err 145 } 146 if entry == nil { 147 return nil, nil 148 } 149 var stsEntry awsStsEntry 150 if err := entry.DecodeJSON(&stsEntry); err != nil { 151 return nil, err 152 } 153 154 return &stsEntry, nil 155} 156 157// lockedAwsStsEntry returns the STS role associated with the given accountID. 158// This method acquires the read lock before returning the association. 159func (b *backend) lockedAwsStsEntry(ctx context.Context, s logical.Storage, accountID string) (*awsStsEntry, error) { 160 b.configMutex.RLock() 161 defer b.configMutex.RUnlock() 162 163 return b.nonLockedAwsStsEntry(ctx, s, accountID) 164} 165 166// pathConfigStsRead is used to return information about an STS role/AWS accountID association 167func (b *backend) pathConfigStsRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 168 accountID := data.Get("account_id").(string) 169 if accountID == "" { 170 return logical.ErrorResponse("missing account id"), nil 171 } 172 173 stsEntry, err := b.lockedAwsStsEntry(ctx, req.Storage, accountID) 174 if err != nil { 175 return nil, err 176 } 177 if stsEntry == nil { 178 return nil, nil 179 } 180 181 return &logical.Response{ 182 Data: map[string]interface{}{ 183 "sts_role": stsEntry.StsRole, 184 }, 185 }, nil 186} 187 188// pathConfigStsCreateUpdate is used to associate an STS role with a given AWS accountID 189func (b *backend) pathConfigStsCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 190 accountID := data.Get("account_id").(string) 191 if accountID == "" { 192 return logical.ErrorResponse("missing AWS account ID"), nil 193 } 194 195 b.configMutex.Lock() 196 defer b.configMutex.Unlock() 197 198 // Check if an STS role is already registered 199 stsEntry, err := b.nonLockedAwsStsEntry(ctx, req.Storage, accountID) 200 if err != nil { 201 return nil, err 202 } 203 if stsEntry == nil { 204 stsEntry = &awsStsEntry{} 205 } 206 207 // Check that an STS role has actually been provided 208 stsRole, ok := data.GetOk("sts_role") 209 if ok { 210 stsEntry.StsRole = stsRole.(string) 211 } else if req.Operation == logical.CreateOperation { 212 return logical.ErrorResponse("missing sts role"), nil 213 } 214 215 if stsEntry.StsRole == "" { 216 return logical.ErrorResponse("sts role cannot be empty"), nil 217 } 218 219 // save the provided STS role 220 if err := b.nonLockedSetAwsStsEntry(ctx, req.Storage, accountID, stsEntry); err != nil { 221 return nil, err 222 } 223 224 return nil, nil 225} 226 227// pathConfigStsDelete is used to delete a previously configured STS configuration 228func (b *backend) pathConfigStsDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 229 b.configMutex.Lock() 230 defer b.configMutex.Unlock() 231 232 accountID := data.Get("account_id").(string) 233 if accountID == "" { 234 return logical.ErrorResponse("missing account id"), nil 235 } 236 237 return nil, req.Storage.Delete(ctx, "config/sts/"+accountID) 238} 239 240const pathConfigStsSyn = ` 241Specify STS roles to be assumed for certain AWS accounts. 242` 243 244const pathConfigStsDesc = ` 245Allows the explicit association of STS roles to satellite AWS accounts (i.e. those 246which are not the account in which the Vault server is running.) Login attempts from 247EC2 instances running in these accounts will be verified using credentials obtained 248by assumption of these STS roles. 249 250The environment in which the Vault server resides must have access to assume the 251given STS roles. 252` 253const pathListStsHelpSyn = ` 254List all the AWS account/STS role relationships registered with Vault. 255` 256 257const pathListStsHelpDesc = ` 258AWS accounts will be listed by account ID, along with their respective role names. 259` 260