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