1package approle 2 3import ( 4 "context" 5 "crypto/hmac" 6 "crypto/sha256" 7 "encoding/hex" 8 "fmt" 9 "time" 10 11 "github.com/hashicorp/errwrap" 12 uuid "github.com/hashicorp/go-uuid" 13 "github.com/hashicorp/vault/sdk/helper/cidrutil" 14 "github.com/hashicorp/vault/sdk/helper/locksutil" 15 "github.com/hashicorp/vault/sdk/logical" 16) 17 18// secretIDStorageEntry represents the information stored in storage 19// when a SecretID is created. The structure of the SecretID storage 20// entry is the same for all the types of SecretIDs generated. 21type secretIDStorageEntry struct { 22 // Accessor for the SecretID. It is a random UUID serving as 23 // a secondary index for the SecretID. This uniquely identifies 24 // the SecretID it belongs to, and hence can be used for listing 25 // and deleting SecretIDs. Accessors cannot be used as valid 26 // SecretIDs during login. 27 SecretIDAccessor string `json:"secret_id_accessor" mapstructure:"secret_id_accessor"` 28 29 // Number of times this SecretID can be used to perform the login 30 // operation 31 SecretIDNumUses int `json:"secret_id_num_uses" mapstructure:"secret_id_num_uses"` 32 33 // Duration after which this SecretID should expire. This is capped by 34 // the backend mount's max TTL value. 35 SecretIDTTL time.Duration `json:"secret_id_ttl" mapstructure:"secret_id_ttl"` 36 37 // The time when the SecretID was created 38 CreationTime time.Time `json:"creation_time" mapstructure:"creation_time"` 39 40 // The time when the SecretID becomes eligible for tidy operation. 41 // Tidying is performed by the PeriodicFunc of the backend which is 1 42 // minute apart. 43 ExpirationTime time.Time `json:"expiration_time" mapstructure:"expiration_time"` 44 45 // The time representing the last time this storage entry was modified 46 LastUpdatedTime time.Time `json:"last_updated_time" mapstructure:"last_updated_time"` 47 48 // Metadata that belongs to the SecretID 49 Metadata map[string]string `json:"metadata" mapstructure:"metadata"` 50 51 // CIDRList is a set of CIDR blocks that impose source address 52 // restrictions on the usage of SecretID 53 CIDRList []string `json:"cidr_list" mapstructure:"cidr_list"` 54 55 // TokenBoundCIDRs is a set of CIDR blocks that impose source address 56 // restrictions on the usage of the token generated by this SecretID 57 TokenBoundCIDRs []string `json:"token_cidr_list" mapstructure:"token_bound_cidrs"` 58 59 // This is a deprecated field 60 SecretIDNumUsesDeprecated int `json:"SecretIDNumUses" mapstructure:"SecretIDNumUses"` 61} 62 63// Represents the payload of the storage entry of the accessor that maps to a 64// unique SecretID. Note that SecretIDs should never be stored in plaintext 65// anywhere in the backend. SecretIDHMAC will be used as an index to fetch the 66// properties of the SecretID and to delete the SecretID. 67type secretIDAccessorStorageEntry struct { 68 // Hash of the SecretID which can be used to find the storage index at which 69 // properties of SecretID is stored. 70 SecretIDHMAC string `json:"secret_id_hmac" mapstructure:"secret_id_hmac"` 71} 72 73// verifyCIDRRoleSecretIDSubset checks if the CIDR blocks set on the secret ID 74// are a subset of CIDR blocks set on the role 75func verifyCIDRRoleSecretIDSubset(secretIDCIDRs []string, roleBoundCIDRList []string) error { 76 if len(secretIDCIDRs) != 0 { 77 // If there are no CIDR blocks on the role, then the subset 78 // requirement would be satisfied 79 if len(roleBoundCIDRList) != 0 { 80 subset, err := cidrutil.SubsetBlocks(roleBoundCIDRList, secretIDCIDRs) 81 if !subset || err != nil { 82 return errwrap.Wrapf(fmt.Sprintf("failed to verify subset relationship between CIDR blocks on the role %q and CIDR blocks on the secret ID %q: {{err}}", roleBoundCIDRList, secretIDCIDRs), err) 83 } 84 } 85 } 86 87 return nil 88} 89 90// Creates a SHA256 HMAC of the given 'value' using the given 'key' and returns 91// a hex encoded string. 92func createHMAC(key, value string) (string, error) { 93 if key == "" { 94 return "", fmt.Errorf("invalid HMAC key") 95 } 96 hm := hmac.New(sha256.New, []byte(key)) 97 hm.Write([]byte(value)) 98 return hex.EncodeToString(hm.Sum(nil)), nil 99} 100 101func (b *backend) secretIDLock(secretIDHMAC string) *locksutil.LockEntry { 102 return locksutil.LockForKey(b.secretIDLocks, secretIDHMAC) 103} 104 105func (b *backend) secretIDAccessorLock(secretIDAccessor string) *locksutil.LockEntry { 106 return locksutil.LockForKey(b.secretIDAccessorLocks, secretIDAccessor) 107} 108 109// nonLockedSecretIDStorageEntry fetches the secret ID properties from physical 110// storage. The entry will be indexed based on the given HMACs of both role 111// name and the secret ID. This method will not acquire secret ID lock to fetch 112// the storage entry. Locks need to be acquired before calling this method. 113func (b *backend) nonLockedSecretIDStorageEntry(ctx context.Context, s logical.Storage, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC string) (*secretIDStorageEntry, error) { 114 if secretIDHMAC == "" { 115 return nil, fmt.Errorf("missing secret ID HMAC") 116 } 117 118 if roleNameHMAC == "" { 119 return nil, fmt.Errorf("missing role name HMAC") 120 } 121 122 // Prepare the storage index at which the secret ID will be stored 123 entryIndex := fmt.Sprintf("%s%s/%s", roleSecretIDPrefix, roleNameHMAC, secretIDHMAC) 124 125 entry, err := s.Get(ctx, entryIndex) 126 if err != nil { 127 return nil, err 128 } 129 if entry == nil { 130 return nil, nil 131 } 132 133 result := secretIDStorageEntry{} 134 if err := entry.DecodeJSON(&result); err != nil { 135 return nil, err 136 } 137 138 // TODO: Remove this upgrade bit in future releases 139 persistNeeded := false 140 if result.SecretIDNumUsesDeprecated != 0 { 141 if result.SecretIDNumUses == 0 || 142 result.SecretIDNumUsesDeprecated < result.SecretIDNumUses { 143 result.SecretIDNumUses = result.SecretIDNumUsesDeprecated 144 persistNeeded = true 145 } 146 if result.SecretIDNumUses < result.SecretIDNumUsesDeprecated { 147 result.SecretIDNumUsesDeprecated = result.SecretIDNumUses 148 persistNeeded = true 149 } 150 } 151 152 if persistNeeded { 153 if err := b.nonLockedSetSecretIDStorageEntry(ctx, s, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC, &result); err != nil { 154 return nil, errwrap.Wrapf("failed to upgrade role storage entry {{err}}", err) 155 } 156 } 157 158 return &result, nil 159} 160 161// nonLockedSetSecretIDStorageEntry creates or updates a secret ID entry at the 162// physical storage. The entry will be indexed based on the given HMACs of both 163// role name and the secret ID. This method will not acquire secret ID lock to 164// create/update the storage entry. Locks need to be acquired before calling 165// this method. 166func (b *backend) nonLockedSetSecretIDStorageEntry(ctx context.Context, s logical.Storage, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC string, secretEntry *secretIDStorageEntry) error { 167 if roleSecretIDPrefix == "" { 168 return fmt.Errorf("missing secret ID prefix") 169 } 170 if secretIDHMAC == "" { 171 return fmt.Errorf("missing secret ID HMAC") 172 } 173 174 if roleNameHMAC == "" { 175 return fmt.Errorf("missing role name HMAC") 176 } 177 178 if secretEntry == nil { 179 return fmt.Errorf("nil secret entry") 180 } 181 182 entryIndex := fmt.Sprintf("%s%s/%s", roleSecretIDPrefix, roleNameHMAC, secretIDHMAC) 183 184 if entry, err := logical.StorageEntryJSON(entryIndex, secretEntry); err != nil { 185 return err 186 } else if err = s.Put(ctx, entry); err != nil { 187 return err 188 } 189 190 return nil 191} 192 193// registerSecretIDEntry creates a new storage entry for the given SecretID. 194func (b *backend) registerSecretIDEntry(ctx context.Context, s logical.Storage, roleName, secretID, hmacKey, roleSecretIDPrefix string, secretEntry *secretIDStorageEntry) (*secretIDStorageEntry, error) { 195 secretIDHMAC, err := createHMAC(hmacKey, secretID) 196 if err != nil { 197 return nil, errwrap.Wrapf("failed to create HMAC of secret ID: {{err}}", err) 198 } 199 roleNameHMAC, err := createHMAC(hmacKey, roleName) 200 if err != nil { 201 return nil, errwrap.Wrapf("failed to create HMAC of role_name: {{err}}", err) 202 } 203 204 lock := b.secretIDLock(secretIDHMAC) 205 lock.RLock() 206 207 entry, err := b.nonLockedSecretIDStorageEntry(ctx, s, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC) 208 if err != nil { 209 lock.RUnlock() 210 return nil, err 211 } 212 if entry != nil { 213 lock.RUnlock() 214 return nil, fmt.Errorf("SecretID is already registered") 215 } 216 217 // If there isn't an entry for the secretID already, switch the read lock 218 // with a write lock and create an entry. 219 lock.RUnlock() 220 lock.Lock() 221 defer lock.Unlock() 222 223 // But before saving a new entry, check if the secretID entry was created during the lock switch. 224 entry, err = b.nonLockedSecretIDStorageEntry(ctx, s, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC) 225 if err != nil { 226 return nil, err 227 } 228 if entry != nil { 229 return nil, fmt.Errorf("SecretID is already registered") 230 } 231 232 // 233 // Create a new entry for the SecretID 234 // 235 236 // Set the creation time for the SecretID 237 currentTime := time.Now() 238 secretEntry.CreationTime = currentTime 239 secretEntry.LastUpdatedTime = currentTime 240 241 // If SecretIDTTL is not specified or if it crosses the backend mount's limit, 242 // cap the expiration to backend's max. Otherwise, use it to determine the 243 // expiration time. 244 if secretEntry.SecretIDTTL < time.Duration(0) || secretEntry.SecretIDTTL > b.System().MaxLeaseTTL() { 245 secretEntry.ExpirationTime = currentTime.Add(b.System().MaxLeaseTTL()) 246 } else if secretEntry.SecretIDTTL != time.Duration(0) { 247 // Set the ExpirationTime only if SecretIDTTL was set. SecretIDs should not 248 // expire by default. 249 secretEntry.ExpirationTime = currentTime.Add(secretEntry.SecretIDTTL) 250 } 251 252 // Before storing the SecretID, store its accessor. 253 if err := b.createSecretIDAccessorEntry(ctx, s, secretEntry, secretIDHMAC, roleSecretIDPrefix); err != nil { 254 return nil, err 255 } 256 257 if err := b.nonLockedSetSecretIDStorageEntry(ctx, s, roleSecretIDPrefix, roleNameHMAC, secretIDHMAC, secretEntry); err != nil { 258 return nil, err 259 } 260 261 return secretEntry, nil 262} 263 264// secretIDAccessorEntry is used to read the storage entry that maps an 265// accessor to a secret_id. 266func (b *backend) secretIDAccessorEntry(ctx context.Context, s logical.Storage, secretIDAccessor, roleSecretIDPrefix string) (*secretIDAccessorStorageEntry, error) { 267 if secretIDAccessor == "" { 268 return nil, fmt.Errorf("missing secretIDAccessor") 269 } 270 271 var result secretIDAccessorStorageEntry 272 273 // Create index entry, mapping the accessor to the token ID 274 salt, err := b.Salt(ctx) 275 if err != nil { 276 return nil, err 277 } 278 accessorPrefix := secretIDAccessorPrefix 279 if roleSecretIDPrefix == secretIDLocalPrefix { 280 accessorPrefix = secretIDAccessorLocalPrefix 281 } 282 entryIndex := accessorPrefix + salt.SaltID(secretIDAccessor) 283 284 accessorLock := b.secretIDAccessorLock(secretIDAccessor) 285 accessorLock.RLock() 286 defer accessorLock.RUnlock() 287 288 if entry, err := s.Get(ctx, entryIndex); err != nil { 289 return nil, err 290 } else if entry == nil { 291 return nil, nil 292 } else if err := entry.DecodeJSON(&result); err != nil { 293 return nil, err 294 } 295 296 return &result, nil 297} 298 299// createSecretIDAccessorEntry creates an identifier for the SecretID. A storage index, 300// mapping the accessor to the SecretID is also created. This method should 301// be called when the lock for the corresponding SecretID is held. 302func (b *backend) createSecretIDAccessorEntry(ctx context.Context, s logical.Storage, entry *secretIDStorageEntry, secretIDHMAC, roleSecretIDPrefix string) error { 303 // Create a random accessor 304 accessorUUID, err := uuid.GenerateUUID() 305 if err != nil { 306 return err 307 } 308 entry.SecretIDAccessor = accessorUUID 309 310 // Create index entry, mapping the accessor to the token ID 311 salt, err := b.Salt(ctx) 312 if err != nil { 313 return err 314 } 315 316 accessorPrefix := secretIDAccessorPrefix 317 if roleSecretIDPrefix == secretIDLocalPrefix { 318 accessorPrefix = secretIDAccessorLocalPrefix 319 } 320 entryIndex := accessorPrefix + salt.SaltID(entry.SecretIDAccessor) 321 322 accessorLock := b.secretIDAccessorLock(accessorUUID) 323 accessorLock.Lock() 324 defer accessorLock.Unlock() 325 326 if entry, err := logical.StorageEntryJSON(entryIndex, &secretIDAccessorStorageEntry{ 327 SecretIDHMAC: secretIDHMAC, 328 }); err != nil { 329 return err 330 } else if err = s.Put(ctx, entry); err != nil { 331 return errwrap.Wrapf("failed to persist accessor index entry: {{err}}", err) 332 } 333 334 return nil 335} 336 337// deleteSecretIDAccessorEntry deletes the storage index mapping the accessor to a SecretID. 338func (b *backend) deleteSecretIDAccessorEntry(ctx context.Context, s logical.Storage, secretIDAccessor, roleSecretIDPrefix string) error { 339 salt, err := b.Salt(ctx) 340 if err != nil { 341 return err 342 } 343 344 accessorPrefix := secretIDAccessorPrefix 345 if roleSecretIDPrefix == secretIDLocalPrefix { 346 accessorPrefix = secretIDAccessorLocalPrefix 347 } 348 entryIndex := accessorPrefix + salt.SaltID(secretIDAccessor) 349 350 accessorLock := b.secretIDAccessorLock(secretIDAccessor) 351 accessorLock.Lock() 352 defer accessorLock.Unlock() 353 354 // Delete the accessor of the SecretID first 355 if err := s.Delete(ctx, entryIndex); err != nil { 356 return errwrap.Wrapf("failed to delete accessor storage entry: {{err}}", err) 357 } 358 359 return nil 360} 361 362// flushRoleSecrets deletes all the SecretIDs that belong to the given 363// RoleID. 364func (b *backend) flushRoleSecrets(ctx context.Context, s logical.Storage, roleName, hmacKey, roleSecretIDPrefix string) error { 365 roleNameHMAC, err := createHMAC(hmacKey, roleName) 366 if err != nil { 367 return errwrap.Wrapf("failed to create HMAC of role_name: {{err}}", err) 368 } 369 370 // Acquire the custom lock to perform listing of SecretIDs 371 b.secretIDListingLock.RLock() 372 defer b.secretIDListingLock.RUnlock() 373 374 secretIDHMACs, err := s.List(ctx, fmt.Sprintf("%s%s/", roleSecretIDPrefix, roleNameHMAC)) 375 if err != nil { 376 return err 377 } 378 for _, secretIDHMAC := range secretIDHMACs { 379 // Acquire the lock belonging to the SecretID 380 lock := b.secretIDLock(secretIDHMAC) 381 lock.Lock() 382 entryIndex := fmt.Sprintf("%s%s/%s", roleSecretIDPrefix, roleNameHMAC, secretIDHMAC) 383 if err := s.Delete(ctx, entryIndex); err != nil { 384 lock.Unlock() 385 return errwrap.Wrapf(fmt.Sprintf("error deleting SecretID %q from storage: {{err}}", secretIDHMAC), err) 386 } 387 lock.Unlock() 388 } 389 return nil 390} 391