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