1package approle 2 3import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/hashicorp/vault/sdk/framework" 10 "github.com/hashicorp/vault/sdk/helper/cidrutil" 11 "github.com/hashicorp/vault/sdk/helper/parseutil" 12 "github.com/hashicorp/vault/sdk/logical" 13) 14 15func pathLogin(b *backend) *framework.Path { 16 return &framework.Path{ 17 Pattern: "login$", 18 Fields: map[string]*framework.FieldSchema{ 19 "role_id": { 20 Type: framework.TypeString, 21 Description: "Unique identifier of the Role. Required to be supplied when the 'bind_secret_id' constraint is set.", 22 }, 23 "secret_id": { 24 Type: framework.TypeString, 25 Default: "", 26 Description: "SecretID belong to the App role", 27 }, 28 }, 29 Operations: map[logical.Operation]framework.OperationHandler{ 30 logical.UpdateOperation: &framework.PathOperation{ 31 Callback: b.pathLoginUpdate, 32 }, 33 logical.AliasLookaheadOperation: &framework.PathOperation{ 34 Callback: b.pathLoginUpdateAliasLookahead, 35 }, 36 }, 37 HelpSynopsis: pathLoginHelpSys, 38 HelpDescription: pathLoginHelpDesc, 39 } 40} 41 42func (b *backend) pathLoginUpdateAliasLookahead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 43 roleID := strings.TrimSpace(data.Get("role_id").(string)) 44 if roleID == "" { 45 return nil, fmt.Errorf("missing role_id") 46 } 47 48 return &logical.Response{ 49 Auth: &logical.Auth{ 50 Alias: &logical.Alias{ 51 Name: roleID, 52 }, 53 }, 54 }, nil 55} 56 57// Returns the Auth object indicating the authentication and authorization information 58// if the credentials provided are validated by the backend. 59func (b *backend) pathLoginUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 60 // RoleID must be supplied during every login 61 roleID := strings.TrimSpace(data.Get("role_id").(string)) 62 if roleID == "" { 63 return logical.ErrorResponse("missing role_id"), nil 64 } 65 66 // Look for the storage entry that maps the roleID to role 67 roleIDIndex, err := b.roleIDEntry(ctx, req.Storage, roleID) 68 if err != nil { 69 return nil, err 70 } 71 if roleIDIndex == nil { 72 return logical.ErrorResponse("invalid role ID"), nil 73 } 74 75 roleName := roleIDIndex.Name 76 77 roleLock := b.roleLock(roleName) 78 roleLock.RLock() 79 80 role, err := b.roleEntry(ctx, req.Storage, roleName) 81 roleLock.RUnlock() 82 if err != nil { 83 return nil, err 84 } 85 if role == nil { 86 return logical.ErrorResponse("invalid role ID"), nil 87 } 88 89 metadata := make(map[string]string) 90 var entry *secretIDStorageEntry 91 if role.BindSecretID { 92 secretID := strings.TrimSpace(data.Get("secret_id").(string)) 93 if secretID == "" { 94 return logical.ErrorResponse("missing secret_id"), nil 95 } 96 97 secretIDHMAC, err := createHMAC(role.HMACKey, secretID) 98 if err != nil { 99 return nil, fmt.Errorf("failed to create HMAC of secret_id: %w", err) 100 } 101 102 roleNameHMAC, err := createHMAC(role.HMACKey, role.name) 103 if err != nil { 104 return nil, fmt.Errorf("failed to create HMAC of role_name: %w", err) 105 } 106 107 entryIndex := fmt.Sprintf("%s%s/%s", role.SecretIDPrefix, roleNameHMAC, secretIDHMAC) 108 109 secretIDLock := b.secretIDLock(secretIDHMAC) 110 secretIDLock.RLock() 111 112 unlockFunc := secretIDLock.RUnlock 113 defer func() { 114 unlockFunc() 115 }() 116 117 entry, err = b.nonLockedSecretIDStorageEntry(ctx, req.Storage, role.SecretIDPrefix, roleNameHMAC, secretIDHMAC) 118 if err != nil { 119 return nil, err 120 } 121 if entry == nil { 122 return logical.ErrorResponse("invalid secret id"), nil 123 } 124 125 // If a secret ID entry does not have a corresponding accessor 126 // entry, revoke the secret ID immediately 127 accessorEntry, err := b.secretIDAccessorEntry(ctx, req.Storage, entry.SecretIDAccessor, role.SecretIDPrefix) 128 if err != nil { 129 return nil, fmt.Errorf("failed to read secret ID accessor entry: %w", err) 130 } 131 if accessorEntry == nil { 132 // Switch the locks and recheck the conditions 133 secretIDLock.RUnlock() 134 secretIDLock.Lock() 135 unlockFunc = secretIDLock.Unlock 136 137 entry, err = b.nonLockedSecretIDStorageEntry(ctx, req.Storage, role.SecretIDPrefix, roleNameHMAC, secretIDHMAC) 138 if err != nil { 139 return nil, err 140 } 141 if entry == nil { 142 return logical.ErrorResponse("invalid secret id"), nil 143 } 144 145 accessorEntry, err := b.secretIDAccessorEntry(ctx, req.Storage, entry.SecretIDAccessor, role.SecretIDPrefix) 146 if err != nil { 147 return nil, fmt.Errorf("failed to read secret ID accessor entry: %w", err) 148 } 149 150 if accessorEntry == nil { 151 if err := req.Storage.Delete(ctx, entryIndex); err != nil { 152 return nil, fmt.Errorf("error deleting secret ID %q from storage: %w", secretIDHMAC, err) 153 } 154 } 155 return logical.ErrorResponse("invalid secret id"), nil 156 } 157 158 switch { 159 case entry.SecretIDNumUses == 0: 160 // 161 // SecretIDNumUses will be zero only if the usage limit was not set at all, 162 // in which case, the SecretID will remain to be valid as long as it is not 163 // expired. 164 // 165 166 // Ensure that the CIDRs on the secret ID are still a subset of that of 167 // role's 168 err = verifyCIDRRoleSecretIDSubset(entry.CIDRList, role.SecretIDBoundCIDRs) 169 if err != nil { 170 return nil, err 171 } 172 173 // If CIDR restrictions are present on the secret ID, check if the 174 // source IP complies to it 175 if len(entry.CIDRList) != 0 { 176 if req.Connection == nil || req.Connection.RemoteAddr == "" { 177 return nil, fmt.Errorf("failed to get connection information") 178 } 179 180 belongs, err := cidrutil.IPBelongsToCIDRBlocksSlice(req.Connection.RemoteAddr, entry.CIDRList) 181 if !belongs || err != nil { 182 return logical.ErrorResponse(fmt.Errorf( 183 "source address %q unauthorized through CIDR restrictions on the secret ID: %w", 184 req.Connection.RemoteAddr, 185 err, 186 ).Error()), nil 187 } 188 } 189 default: 190 // 191 // If the SecretIDNumUses is non-zero, it means that its use-count should be updated 192 // in the storage. Switch the lock from a `read` to a `write` and update 193 // the storage entry. 194 // 195 196 secretIDLock.RUnlock() 197 secretIDLock.Lock() 198 unlockFunc = secretIDLock.Unlock 199 200 // Lock switching may change the data. Refresh the contents. 201 entry, err = b.nonLockedSecretIDStorageEntry(ctx, req.Storage, role.SecretIDPrefix, roleNameHMAC, secretIDHMAC) 202 if err != nil { 203 return nil, err 204 } 205 if entry == nil { 206 return logical.ErrorResponse(fmt.Sprintf("invalid secret_id %q", secretID)), nil 207 } 208 209 // If there exists a single use left, delete the SecretID entry from 210 // the storage but do not fail the validation request. Subsequent 211 // requests to use the same SecretID will fail. 212 if entry.SecretIDNumUses == 1 { 213 // Delete the secret IDs accessor first 214 err = b.deleteSecretIDAccessorEntry(ctx, req.Storage, entry.SecretIDAccessor, role.SecretIDPrefix) 215 if err != nil { 216 return nil, err 217 } 218 err = req.Storage.Delete(ctx, entryIndex) 219 if err != nil { 220 return nil, fmt.Errorf("failed to delete secret ID: %w", err) 221 } 222 } else { 223 // If the use count is greater than one, decrement it and update the last updated time. 224 entry.SecretIDNumUses -= 1 225 entry.LastUpdatedTime = time.Now() 226 227 sEntry, err := logical.StorageEntryJSON(entryIndex, &entry) 228 if err != nil { 229 return nil, err 230 } 231 232 err = req.Storage.Put(ctx, sEntry) 233 if err != nil { 234 return nil, err 235 } 236 } 237 238 // Ensure that the CIDRs on the secret ID are still a subset of that of 239 // role's 240 err = verifyCIDRRoleSecretIDSubset(entry.CIDRList, role.SecretIDBoundCIDRs) 241 if err != nil { 242 return nil, err 243 } 244 245 // If CIDR restrictions are present on the secret ID, check if the 246 // source IP complies to it 247 if len(entry.CIDRList) != 0 { 248 if req.Connection == nil || req.Connection.RemoteAddr == "" { 249 return nil, fmt.Errorf("failed to get connection information") 250 } 251 252 belongs, err := cidrutil.IPBelongsToCIDRBlocksSlice(req.Connection.RemoteAddr, entry.CIDRList) 253 if err != nil || !belongs { 254 return logical.ErrorResponse( 255 fmt.Errorf( 256 "source address %q unauthorized by CIDR restrictions on the secret ID: %w", 257 req.Connection.RemoteAddr, 258 err, 259 ).Error()), nil 260 } 261 } 262 } 263 264 metadata = entry.Metadata 265 } 266 267 if len(role.SecretIDBoundCIDRs) != 0 { 268 if req.Connection == nil || req.Connection.RemoteAddr == "" { 269 return nil, fmt.Errorf("failed to get connection information") 270 } 271 belongs, err := cidrutil.IPBelongsToCIDRBlocksSlice(req.Connection.RemoteAddr, role.SecretIDBoundCIDRs) 272 if err != nil || !belongs { 273 return logical.ErrorResponse( 274 fmt.Errorf( 275 "source address %q unauthorized by CIDR restrictions on the role: %w", 276 req.Connection.RemoteAddr, 277 err, 278 ).Error()), nil 279 } 280 } 281 282 // Parse the CIDRs we should be binding the token to. 283 tokenBoundCIDRs := role.TokenBoundCIDRs 284 if entry != nil && len(entry.TokenBoundCIDRs) > 0 { 285 tokenBoundCIDRs, err = parseutil.ParseAddrs(entry.TokenBoundCIDRs) 286 if err != nil { 287 return logical.ErrorResponse(err.Error()), nil 288 } 289 } 290 291 // For some reason, if metadata was set to nil while processing secret ID 292 // binding, ensure that it is initialized again to avoid a panic. 293 if metadata == nil { 294 metadata = make(map[string]string) 295 } 296 297 // Always include the role name, for later filtering 298 metadata["role_name"] = role.name 299 300 auth := &logical.Auth{ 301 InternalData: map[string]interface{}{ 302 "role_name": role.name, 303 }, 304 Metadata: metadata, 305 Alias: &logical.Alias{ 306 Name: role.RoleID, 307 Metadata: metadata, 308 }, 309 } 310 role.PopulateTokenAuth(auth) 311 312 // Allow for overridden token bound CIDRs 313 auth.BoundCIDRs = tokenBoundCIDRs 314 315 return &logical.Response{ 316 Auth: auth, 317 }, nil 318} 319 320// Invoked when the token issued by this backend is attempting a renewal. 321func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 322 roleName := req.Auth.InternalData["role_name"].(string) 323 if roleName == "" { 324 return nil, fmt.Errorf("failed to fetch role_name during renewal") 325 } 326 327 lock := b.roleLock(roleName) 328 lock.RLock() 329 defer lock.RUnlock() 330 331 // Ensure that the Role still exists. 332 role, err := b.roleEntry(ctx, req.Storage, roleName) 333 if err != nil { 334 return nil, fmt.Errorf("failed to validate role %q during renewal: %w", roleName, err) 335 } 336 if role == nil { 337 return nil, fmt.Errorf("role %q does not exist during renewal", roleName) 338 } 339 340 resp := &logical.Response{Auth: req.Auth} 341 resp.Auth.TTL = role.TokenTTL 342 resp.Auth.MaxTTL = role.TokenMaxTTL 343 resp.Auth.Period = role.TokenPeriod 344 return resp, nil 345} 346 347const pathLoginHelpSys = "Issue a token based on the credentials supplied" 348 349const pathLoginHelpDesc = ` 350While the credential 'role_id' is required at all times, 351other credentials required depends on the properties App role 352to which the 'role_id' belongs to. The 'bind_secret_id' 353constraint (enabled by default) on the App role requires the 354'secret_id' credential to be presented. 355 356'role_id' is fetched using the 'role/<role_name>/role_id' 357endpoint and 'secret_id' is fetched using the 'role/<role_name>/secret_id' 358endpoint.` 359