1package awsauth 2 3import ( 4 "context" 5 "crypto/subtle" 6 "crypto/x509" 7 "encoding/base64" 8 "encoding/pem" 9 "encoding/xml" 10 "fmt" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 "regexp" 15 "strings" 16 "time" 17 18 "github.com/aws/aws-sdk-go/aws" 19 "github.com/aws/aws-sdk-go/service/ec2" 20 "github.com/aws/aws-sdk-go/service/iam" 21 "github.com/fullsailor/pkcs7" 22 "github.com/hashicorp/errwrap" 23 cleanhttp "github.com/hashicorp/go-cleanhttp" 24 uuid "github.com/hashicorp/go-uuid" 25 "github.com/hashicorp/vault/helper/awsutil" 26 "github.com/hashicorp/vault/helper/jsonutil" 27 "github.com/hashicorp/vault/helper/strutil" 28 "github.com/hashicorp/vault/logical" 29 "github.com/hashicorp/vault/logical/framework" 30) 31 32const ( 33 reauthenticationDisabledNonce = "reauthentication-disabled-nonce" 34 iamAuthType = "iam" 35 ec2AuthType = "ec2" 36 ec2EntityType = "ec2_instance" 37) 38 39func pathLogin(b *backend) *framework.Path { 40 return &framework.Path{ 41 Pattern: "login$", 42 Fields: map[string]*framework.FieldSchema{ 43 "role": { 44 Type: framework.TypeString, 45 Description: `Name of the role against which the login is being attempted. 46If 'role' is not specified, then the login endpoint looks for a role 47bearing the name of the AMI ID of the EC2 instance that is trying to login. 48If a matching role is not found, login fails.`, 49 }, 50 51 "pkcs7": { 52 Type: framework.TypeString, 53 Description: `PKCS7 signature of the identity document when using an auth_type 54of ec2.`, 55 }, 56 57 "nonce": { 58 Type: framework.TypeString, 59 Description: `The nonce to be used for subsequent login requests when 60auth_type is ec2. If this parameter is not specified at 61all and if reauthentication is allowed, then the backend will generate a random 62nonce, attaches it to the instance's identity-whitelist entry and returns the 63nonce back as part of auth metadata. This value should be used with further 64login requests, to establish client authenticity. Clients can choose to set a 65custom nonce if preferred, in which case, it is recommended that clients provide 66a strong nonce. If a nonce is provided but with an empty value, it indicates 67intent to disable reauthentication. Note that, when 'disallow_reauthentication' 68option is enabled on either the role or the role tag, the 'nonce' holds no 69significance.`, 70 }, 71 72 "iam_http_request_method": { 73 Type: framework.TypeString, 74 Description: `HTTP method to use for the AWS request when auth_type is 75iam. This must match what has been signed in the 76presigned request. Currently, POST is the only supported value`, 77 }, 78 79 "iam_request_url": { 80 Type: framework.TypeString, 81 Description: `Base64-encoded full URL against which to make the AWS request 82when using iam auth_type.`, 83 }, 84 85 "iam_request_body": { 86 Type: framework.TypeString, 87 Description: `Base64-encoded request body when auth_type is iam. 88This must match the request body included in the signature.`, 89 }, 90 "iam_request_headers": { 91 Type: framework.TypeHeader, 92 Description: `Key/value pairs of headers for use in the 93sts:GetCallerIdentity HTTP requests headers when auth_type is iam. Can be either 94a Base64-encoded, JSON-serialized string, or a JSON object of key/value pairs. 95This must at a minimum include the headers over which AWS has included a signature.`, 96 }, 97 "identity": { 98 Type: framework.TypeString, 99 Description: `Base64 encoded EC2 instance identity document. This needs to be supplied along 100with the 'signature' parameter. If using 'curl' for fetching the identity 101document, consider using the option '-w 0' while piping the output to 'base64' 102binary.`, 103 }, 104 "signature": { 105 Type: framework.TypeString, 106 Description: `Base64 encoded SHA256 RSA signature of the instance identity document. This 107needs to be supplied along with 'identity' parameter.`, 108 }, 109 }, 110 111 Callbacks: map[logical.Operation]framework.OperationFunc{ 112 logical.UpdateOperation: b.pathLoginUpdate, 113 logical.AliasLookaheadOperation: b.pathLoginUpdate, 114 }, 115 116 HelpSynopsis: pathLoginSyn, 117 HelpDescription: pathLoginDesc, 118 } 119} 120 121// instanceIamRoleARN fetches the IAM role ARN associated with the given 122// instance profile name 123func (b *backend) instanceIamRoleARN(iamClient *iam.IAM, instanceProfileName string) (string, error) { 124 if iamClient == nil { 125 return "", fmt.Errorf("nil iamClient") 126 } 127 if instanceProfileName == "" { 128 return "", fmt.Errorf("missing instance profile name") 129 } 130 131 profile, err := iamClient.GetInstanceProfile(&iam.GetInstanceProfileInput{ 132 InstanceProfileName: aws.String(instanceProfileName), 133 }) 134 if err != nil { 135 return "", awsutil.AppendLogicalError(err) 136 } 137 if profile == nil { 138 return "", fmt.Errorf("nil output while getting instance profile details") 139 } 140 141 if profile.InstanceProfile == nil { 142 return "", fmt.Errorf("nil instance profile in the output of instance profile details") 143 } 144 145 if profile.InstanceProfile.Roles == nil || len(profile.InstanceProfile.Roles) != 1 { 146 return "", fmt.Errorf("invalid roles in the output of instance profile details") 147 } 148 149 if profile.InstanceProfile.Roles[0].Arn == nil { 150 return "", fmt.Errorf("nil role ARN in the output of instance profile details") 151 } 152 153 return *profile.InstanceProfile.Roles[0].Arn, nil 154} 155 156// validateInstance queries the status of the EC2 instance using AWS EC2 API 157// and checks if the instance is running and is healthy 158func (b *backend) validateInstance(ctx context.Context, s logical.Storage, instanceID, region, accountID string) (*ec2.Instance, error) { 159 // Create an EC2 client to pull the instance information 160 ec2Client, err := b.clientEC2(ctx, s, region, accountID) 161 if err != nil { 162 return nil, err 163 } 164 165 status, err := ec2Client.DescribeInstances(&ec2.DescribeInstancesInput{ 166 InstanceIds: []*string{ 167 aws.String(instanceID), 168 }, 169 }) 170 if err != nil { 171 errW := errwrap.Wrapf(fmt.Sprintf("error fetching description for instance ID %q: {{err}}", instanceID), err) 172 return nil, errwrap.Wrap(errW, awsutil.CheckAWSError(err)) 173 } 174 if status == nil { 175 return nil, fmt.Errorf("nil output from describe instances") 176 } 177 if len(status.Reservations) == 0 { 178 return nil, fmt.Errorf("no reservations found in instance description") 179 180 } 181 if len(status.Reservations[0].Instances) == 0 { 182 return nil, fmt.Errorf("no instance details found in reservations") 183 } 184 if *status.Reservations[0].Instances[0].InstanceId != instanceID { 185 return nil, fmt.Errorf("expected instance ID not matching the instance ID in the instance description") 186 } 187 if status.Reservations[0].Instances[0].State == nil { 188 return nil, fmt.Errorf("instance state in instance description is nil") 189 } 190 if *status.Reservations[0].Instances[0].State.Name != "running" { 191 return nil, fmt.Errorf("instance is not in 'running' state") 192 } 193 return status.Reservations[0].Instances[0], nil 194} 195 196// validateMetadata matches the given client nonce and pending time with the 197// one cached in the identity whitelist during the previous login. But, if 198// reauthentication is disabled, login attempt is failed immediately. 199func validateMetadata(clientNonce, pendingTime string, storedIdentity *whitelistIdentity, roleEntry *awsRoleEntry) error { 200 // For sanity 201 if !storedIdentity.DisallowReauthentication && storedIdentity.ClientNonce == "" { 202 return fmt.Errorf("client nonce missing in stored identity") 203 } 204 205 // If reauthentication is disabled or if the nonce supplied matches a 206 // predefined nonce which indicates reauthentication to be disabled, 207 // authentication will not succeed. 208 if storedIdentity.DisallowReauthentication || 209 subtle.ConstantTimeCompare([]byte(reauthenticationDisabledNonce), []byte(clientNonce)) == 1 { 210 return fmt.Errorf("reauthentication is disabled") 211 } 212 213 givenPendingTime, err := time.Parse(time.RFC3339, pendingTime) 214 if err != nil { 215 return err 216 } 217 218 storedPendingTime, err := time.Parse(time.RFC3339, storedIdentity.PendingTime) 219 if err != nil { 220 return err 221 } 222 223 // When the presented client nonce does not match the cached entry, it 224 // is either that a rogue client is trying to login or that a valid 225 // client suffered a migration. The migration is detected via 226 // pendingTime in the instance metadata, which sadly is only updated 227 // when an instance is stopped and started but *not* when the instance 228 // is rebooted. If reboot survivability is needed, either 229 // instrumentation to delete the instance ID from the whitelist is 230 // necessary, or the client must durably store the nonce. 231 // 232 // If the `allow_instance_migration` property of the registered role is 233 // enabled, then the client nonce mismatch is ignored, as long as the 234 // pending time in the presented instance identity document is newer 235 // than the cached pending time. The new pendingTime is stored and used 236 // for future checks. 237 // 238 // This is a weak criterion and hence the `allow_instance_migration` 239 // option should be used with caution. 240 if subtle.ConstantTimeCompare([]byte(clientNonce), []byte(storedIdentity.ClientNonce)) != 1 { 241 if !roleEntry.AllowInstanceMigration { 242 return fmt.Errorf("client nonce mismatch") 243 } 244 if roleEntry.AllowInstanceMigration && !givenPendingTime.After(storedPendingTime) { 245 return fmt.Errorf("client nonce mismatch and instance meta-data incorrect") 246 } 247 } 248 249 // Ensure that the 'pendingTime' on the given identity document is not 250 // before the 'pendingTime' that was used for previous login. This 251 // disallows old metadata documents from being used to perform login. 252 if givenPendingTime.Before(storedPendingTime) { 253 return fmt.Errorf("instance meta-data is older than the one used for previous login") 254 } 255 return nil 256} 257 258// Verifies the integrity of the instance identity document using its SHA256 259// RSA signature. After verification, returns the unmarshaled instance identity 260// document. 261func (b *backend) verifyInstanceIdentitySignature(ctx context.Context, s logical.Storage, identityBytes, signatureBytes []byte) (*identityDocument, error) { 262 if len(identityBytes) == 0 { 263 return nil, fmt.Errorf("missing instance identity document") 264 } 265 266 if len(signatureBytes) == 0 { 267 return nil, fmt.Errorf("missing SHA256 RSA signature of the instance identity document") 268 } 269 270 // Get the public certificates that are used to verify the signature. 271 // This returns a slice of certificates containing the default 272 // certificate and all the registered certificates via 273 // 'config/certificate/<cert_name>' endpoint, for verifying the RSA 274 // digest. 275 publicCerts, err := b.awsPublicCertificates(ctx, s, false) 276 if err != nil { 277 return nil, err 278 } 279 if publicCerts == nil || len(publicCerts) == 0 { 280 return nil, fmt.Errorf("certificates to verify the signature are not found") 281 } 282 283 // Check if any of the certs registered at the backend can verify the 284 // signature 285 for _, cert := range publicCerts { 286 err := cert.CheckSignature(x509.SHA256WithRSA, identityBytes, signatureBytes) 287 if err == nil { 288 var identityDoc identityDocument 289 if decErr := jsonutil.DecodeJSON(identityBytes, &identityDoc); decErr != nil { 290 return nil, decErr 291 } 292 return &identityDoc, nil 293 } 294 } 295 296 return nil, fmt.Errorf("instance identity verification using SHA256 RSA signature is unsuccessful") 297} 298 299// Verifies the correctness of the authenticated attributes present in the PKCS#7 300// signature. After verification, extracts the instance identity document from the 301// signature, parses it and returns it. 302func (b *backend) parseIdentityDocument(ctx context.Context, s logical.Storage, pkcs7B64 string) (*identityDocument, error) { 303 // Insert the header and footer for the signature to be able to pem decode it 304 pkcs7B64 = fmt.Sprintf("-----BEGIN PKCS7-----\n%s\n-----END PKCS7-----", pkcs7B64) 305 306 // Decode the PEM encoded signature 307 pkcs7BER, pkcs7Rest := pem.Decode([]byte(pkcs7B64)) 308 if len(pkcs7Rest) != 0 { 309 return nil, fmt.Errorf("failed to decode the PEM encoded PKCS#7 signature") 310 } 311 312 // Parse the signature from asn1 format into a struct 313 pkcs7Data, err := pkcs7.Parse(pkcs7BER.Bytes) 314 if err != nil { 315 return nil, errwrap.Wrapf("failed to parse the BER encoded PKCS#7 signature: {{err}}", err) 316 } 317 318 // Get the public certificates that are used to verify the signature. 319 // This returns a slice of certificates containing the default certificate 320 // and all the registered certificates via 'config/certificate/<cert_name>' endpoint 321 publicCerts, err := b.awsPublicCertificates(ctx, s, true) 322 if err != nil { 323 return nil, err 324 } 325 if publicCerts == nil || len(publicCerts) == 0 { 326 return nil, fmt.Errorf("certificates to verify the signature are not found") 327 } 328 329 // Before calling Verify() on the PKCS#7 struct, set the certificates to be used 330 // to verify the contents in the signer information. 331 pkcs7Data.Certificates = publicCerts 332 333 // Verify extracts the authenticated attributes in the PKCS#7 signature, and verifies 334 // the authenticity of the content using 'dsa.PublicKey' embedded in the public certificate. 335 if pkcs7Data.Verify() != nil { 336 return nil, fmt.Errorf("failed to verify the signature") 337 } 338 339 // Check if the signature has content inside of it 340 if len(pkcs7Data.Content) == 0 { 341 return nil, fmt.Errorf("instance identity document could not be found in the signature") 342 } 343 344 var identityDoc identityDocument 345 if err := jsonutil.DecodeJSON(pkcs7Data.Content, &identityDoc); err != nil { 346 return nil, err 347 } 348 349 return &identityDoc, nil 350} 351 352func (b *backend) pathLoginUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 353 anyEc2, allEc2 := hasValuesForEc2Auth(data) 354 anyIam, allIam := hasValuesForIamAuth(data) 355 switch { 356 case anyEc2 && anyIam: 357 return logical.ErrorResponse("supplied auth values for both ec2 and iam auth types"), nil 358 case anyEc2 && !allEc2: 359 return logical.ErrorResponse("supplied some of the auth values for the ec2 auth type but not all"), nil 360 case anyEc2: 361 return b.pathLoginUpdateEc2(ctx, req, data) 362 case anyIam && !allIam: 363 return logical.ErrorResponse("supplied some of the auth values for the iam auth type but not all"), nil 364 case anyIam: 365 return b.pathLoginUpdateIam(ctx, req, data) 366 default: 367 return logical.ErrorResponse("didn't supply required authentication values"), nil 368 } 369} 370 371// Returns whether the EC2 instance meets the requirements of the particular 372// AWS role entry. 373// The first error return value is whether there's some sort of validation 374// error that means the instance doesn't meet the role requirements 375// The second error return value indicates whether there's an error in even 376// trying to validate those requirements 377func (b *backend) verifyInstanceMeetsRoleRequirements(ctx context.Context, 378 s logical.Storage, instance *ec2.Instance, roleEntry *awsRoleEntry, roleName string, identityDoc *identityDocument) (error, error) { 379 380 switch { 381 case instance == nil: 382 return nil, fmt.Errorf("nil instance") 383 case roleEntry == nil: 384 return nil, fmt.Errorf("nil roleEntry") 385 case identityDoc == nil: 386 return nil, fmt.Errorf("nil identityDoc") 387 } 388 389 // Verify that the instance ID matches one of the ones set by the role 390 if len(roleEntry.BoundEc2InstanceIDs) > 0 && !strutil.StrListContains(roleEntry.BoundEc2InstanceIDs, *instance.InstanceId) { 391 return fmt.Errorf("instance ID %q does not belong to the role %q", *instance.InstanceId, roleName), nil 392 } 393 394 // Verify that the AccountID of the instance trying to login matches the 395 // AccountID specified as a constraint on role 396 if len(roleEntry.BoundAccountIDs) > 0 && !strutil.StrListContains(roleEntry.BoundAccountIDs, identityDoc.AccountID) { 397 return fmt.Errorf("account ID %q does not belong to role %q", identityDoc.AccountID, roleName), nil 398 } 399 400 // Verify that the AMI ID of the instance trying to login matches the 401 // AMI ID specified as a constraint on the role. 402 // 403 // Here, we're making a tradeoff and pulling the AMI ID out of the EC2 404 // API rather than the signed instance identity doc. They *should* match. 405 // This means we require an EC2 API call to retrieve the AMI ID, but we're 406 // already calling the API to validate the Instance ID anyway, so it shouldn't 407 // matter. The benefit is that we have the exact same code whether auth_type 408 // is ec2 or iam. 409 if len(roleEntry.BoundAmiIDs) > 0 { 410 if instance.ImageId == nil { 411 return nil, fmt.Errorf("AMI ID in the instance description is nil") 412 } 413 if !strutil.StrListContains(roleEntry.BoundAmiIDs, *instance.ImageId) { 414 return fmt.Errorf("AMI ID %q does not belong to role %q", *instance.ImageId, roleName), nil 415 } 416 } 417 418 // Validate the SubnetID if corresponding bound was set on the role 419 if len(roleEntry.BoundSubnetIDs) > 0 { 420 if instance.SubnetId == nil { 421 return nil, fmt.Errorf("subnet ID in the instance description is nil") 422 } 423 if !strutil.StrListContains(roleEntry.BoundSubnetIDs, *instance.SubnetId) { 424 return fmt.Errorf("subnet ID %q does not satisfy the constraint on role %q", *instance.SubnetId, roleName), nil 425 } 426 } 427 428 // Validate the VpcID if corresponding bound was set on the role 429 if len(roleEntry.BoundVpcIDs) > 0 { 430 if instance.VpcId == nil { 431 return nil, fmt.Errorf("VPC ID in the instance description is nil") 432 } 433 if !strutil.StrListContains(roleEntry.BoundVpcIDs, *instance.VpcId) { 434 return fmt.Errorf("VPC ID %q does not satisfy the constraint on role %q", *instance.VpcId, roleName), nil 435 } 436 } 437 438 // Check if the IAM instance profile ARN of the instance trying to 439 // login, matches the IAM instance profile ARN specified as a constraint 440 // on the role 441 if len(roleEntry.BoundIamInstanceProfileARNs) > 0 { 442 if instance.IamInstanceProfile == nil { 443 return nil, fmt.Errorf("IAM instance profile in the instance description is nil") 444 } 445 if instance.IamInstanceProfile.Arn == nil { 446 return nil, fmt.Errorf("IAM instance profile ARN in the instance description is nil") 447 } 448 iamInstanceProfileARN := *instance.IamInstanceProfile.Arn 449 matchesInstanceProfile := false 450 // NOTE: Can't use strutil.StrListContainsGlob. A * is a perfectly valid character in the "path" component 451 // of an ARN. See, e.g., https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateInstanceProfile.html : 452 // The path allows strings "containing any ASCII character from the ! (\u0021) thru the DEL character 453 // (\u007F), including most punctuation characters, digits, and upper and lowercased letters." 454 // So, e.g., arn:aws:iam::123456789012:instance-profile/Some*Path/MyProfileName is a perfectly valid instance 455 // profile ARN, and it wouldn't be correct to expand the * in the middle as a wildcard. 456 // If a user wants to match an IAM instance profile arn beginning with arn:aws:iam::123456789012:instance-profile/foo* 457 // then bound_iam_instance_profile_arn would need to be arn:aws:iam::123456789012:instance-profile/foo** 458 // Wanting to exactly match an ARN that has a * at the end is not a valid use case. The * is only valid in the 459 // path; it's not valid in the name. That means no valid ARN can ever end with a *. For example, 460 // arn:aws:iam::123456789012:instance-profile/Foo* is NOT valid as an instance profile ARN, so no valid instance 461 // profile ARN could ever equal that value. 462 for _, boundInstanceProfileARN := range roleEntry.BoundIamInstanceProfileARNs { 463 switch { 464 case strings.HasSuffix(boundInstanceProfileARN, "*") && strings.HasPrefix(iamInstanceProfileARN, boundInstanceProfileARN[:len(boundInstanceProfileARN)-1]): 465 matchesInstanceProfile = true 466 break 467 case iamInstanceProfileARN == boundInstanceProfileARN: 468 matchesInstanceProfile = true 469 break 470 } 471 } 472 if !matchesInstanceProfile { 473 return fmt.Errorf("IAM instance profile ARN %q does not satisfy the constraint role %q", iamInstanceProfileARN, roleName), nil 474 } 475 } 476 477 // Check if the IAM role ARN of the instance trying to login, matches 478 // the IAM role ARN specified as a constraint on the role. 479 if len(roleEntry.BoundIamRoleARNs) > 0 { 480 if instance.IamInstanceProfile == nil { 481 return nil, fmt.Errorf("IAM instance profile in the instance description is nil") 482 } 483 if instance.IamInstanceProfile.Arn == nil { 484 return nil, fmt.Errorf("IAM instance profile ARN in the instance description is nil") 485 } 486 487 // Fetch the instance profile ARN from the instance description 488 iamInstanceProfileARN := *instance.IamInstanceProfile.Arn 489 490 if iamInstanceProfileARN == "" { 491 return nil, fmt.Errorf("IAM instance profile ARN in the instance description is empty") 492 } 493 494 // Extract out the instance profile name from the instance 495 // profile ARN 496 iamInstanceProfileEntity, err := parseIamArn(iamInstanceProfileARN) 497 498 if err != nil { 499 return nil, errwrap.Wrapf(fmt.Sprintf("failed to parse IAM instance profile ARN %q: {{err}}", iamInstanceProfileARN), err) 500 } 501 502 // Use instance profile ARN to fetch the associated role ARN 503 iamClient, err := b.clientIAM(ctx, s, identityDoc.Region, identityDoc.AccountID) 504 if err != nil { 505 return nil, errwrap.Wrapf("could not fetch IAM client: {{err}}", err) 506 } else if iamClient == nil { 507 return nil, fmt.Errorf("received a nil iamClient") 508 } 509 iamRoleARN, err := b.instanceIamRoleARN(iamClient, iamInstanceProfileEntity.FriendlyName) 510 if err != nil { 511 return nil, errwrap.Wrapf("IAM role ARN could not be fetched: {{err}}", err) 512 } 513 if iamRoleARN == "" { 514 return nil, fmt.Errorf("IAM role ARN could not be fetched") 515 } 516 517 matchesInstanceRoleARN := false 518 for _, boundIamRoleARN := range roleEntry.BoundIamRoleARNs { 519 switch { 520 // as with boundInstanceProfileARN, can't use strutil.StrListContainsGlob because * can validly exist in the middle of an ARN 521 case strings.HasSuffix(boundIamRoleARN, "*") && strings.HasPrefix(iamRoleARN, boundIamRoleARN[:len(boundIamRoleARN)-1]): 522 matchesInstanceRoleARN = true 523 break 524 case iamRoleARN == boundIamRoleARN: 525 matchesInstanceRoleARN = true 526 break 527 } 528 } 529 if !matchesInstanceRoleARN { 530 return fmt.Errorf("IAM role ARN %q does not satisfy the constraint role %q", iamRoleARN, roleName), nil 531 } 532 } 533 534 return nil, nil 535} 536 537// pathLoginUpdateEc2 is used to create a Vault token by the EC2 instances 538// by providing the pkcs7 signature of the instance identity document 539// and a client created nonce. Client nonce is optional if 'disallow_reauthentication' 540// option is enabled on the registered role. 541func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 542 identityDocB64 := data.Get("identity").(string) 543 var identityDocBytes []byte 544 var err error 545 if identityDocB64 != "" { 546 identityDocBytes, err = base64.StdEncoding.DecodeString(identityDocB64) 547 if err != nil || len(identityDocBytes) == 0 { 548 return logical.ErrorResponse("failed to base64 decode the instance identity document"), nil 549 } 550 } 551 552 signatureB64 := data.Get("signature").(string) 553 var signatureBytes []byte 554 if signatureB64 != "" { 555 signatureBytes, err = base64.StdEncoding.DecodeString(signatureB64) 556 if err != nil { 557 return logical.ErrorResponse("failed to base64 decode the SHA256 RSA signature of the instance identity document"), nil 558 } 559 } 560 561 pkcs7B64 := data.Get("pkcs7").(string) 562 563 // Either the pkcs7 signature of the instance identity document, or 564 // the identity document itself along with its SHA256 RSA signature 565 // needs to be provided. 566 if pkcs7B64 == "" && (len(identityDocBytes) == 0 && len(signatureBytes) == 0) { 567 return logical.ErrorResponse("either pkcs7 or a tuple containing the instance identity document and its SHA256 RSA signature needs to be provided"), nil 568 } else if pkcs7B64 != "" && (len(identityDocBytes) != 0 && len(signatureBytes) != 0) { 569 return logical.ErrorResponse("both pkcs7 and a tuple containing the instance identity document and its SHA256 RSA signature is supplied; provide only one"), nil 570 } 571 572 // Verify the signature of the identity document and unmarshal it 573 var identityDocParsed *identityDocument 574 if pkcs7B64 != "" { 575 identityDocParsed, err = b.parseIdentityDocument(ctx, req.Storage, pkcs7B64) 576 if err != nil { 577 return nil, err 578 } 579 if identityDocParsed == nil { 580 return logical.ErrorResponse("failed to verify the instance identity document using pkcs7"), nil 581 } 582 } else { 583 identityDocParsed, err = b.verifyInstanceIdentitySignature(ctx, req.Storage, identityDocBytes, signatureBytes) 584 if err != nil { 585 return nil, err 586 } 587 if identityDocParsed == nil { 588 return logical.ErrorResponse("failed to verify the instance identity document using the SHA256 RSA digest"), nil 589 } 590 } 591 592 roleName := data.Get("role").(string) 593 594 // If roleName is not supplied, a role in the name of the instance's AMI ID will be looked for 595 if roleName == "" { 596 roleName = identityDocParsed.AmiID 597 } 598 599 // Get the entry for the role used by the instance 600 roleEntry, err := b.lockedAWSRole(ctx, req.Storage, roleName) 601 if err != nil { 602 return nil, err 603 } 604 if roleEntry == nil { 605 return logical.ErrorResponse(fmt.Sprintf("entry for role %q not found", roleName)), nil 606 } 607 608 if roleEntry.AuthType != ec2AuthType { 609 return logical.ErrorResponse(fmt.Sprintf("auth method ec2 not allowed for role %s", roleName)), nil 610 } 611 612 identityConfigEntry, err := identityConfigEntry(ctx, req.Storage) 613 if err != nil { 614 return nil, err 615 } 616 617 identityAlias := "" 618 619 switch identityConfigEntry.EC2Alias { 620 case identityAliasRoleID: 621 identityAlias = roleEntry.RoleID 622 case identityAliasEC2InstanceID: 623 identityAlias = identityDocParsed.InstanceID 624 case identityAliasEC2ImageID: 625 identityAlias = identityDocParsed.AmiID 626 } 627 628 // If we're just looking up for MFA, return the Alias info 629 if req.Operation == logical.AliasLookaheadOperation { 630 return &logical.Response{ 631 Auth: &logical.Auth{ 632 Alias: &logical.Alias{ 633 Name: identityAlias, 634 }, 635 }, 636 }, nil 637 } 638 639 // Validate the instance ID by making a call to AWS EC2 DescribeInstances API 640 // and fetching the instance description. Validation succeeds only if the 641 // instance is in 'running' state. 642 instance, err := b.validateInstance(ctx, req.Storage, identityDocParsed.InstanceID, identityDocParsed.Region, identityDocParsed.AccountID) 643 if err != nil { 644 return logical.ErrorResponse(fmt.Sprintf("failed to verify instance ID: %v", err)), nil 645 } 646 647 // Verify that the `Region` of the instance trying to login matches the 648 // `Region` specified as a constraint on role 649 if len(roleEntry.BoundRegions) > 0 && !strutil.StrListContains(roleEntry.BoundRegions, identityDocParsed.Region) { 650 return logical.ErrorResponse(fmt.Sprintf("Region %q does not satisfy the constraint on role %q", identityDocParsed.Region, roleName)), nil 651 } 652 653 validationError, err := b.verifyInstanceMeetsRoleRequirements(ctx, req.Storage, instance, roleEntry, roleName, identityDocParsed) 654 if err != nil { 655 return nil, err 656 } 657 if validationError != nil { 658 return logical.ErrorResponse(fmt.Sprintf("Error validating instance: %v", validationError)), nil 659 } 660 661 // Get the entry from the identity whitelist, if there is one 662 storedIdentity, err := whitelistIdentityEntry(ctx, req.Storage, identityDocParsed.InstanceID) 663 if err != nil { 664 return nil, err 665 } 666 667 // disallowReauthentication value that gets cached at the stored 668 // identity-whitelist entry is determined not just by the role entry. 669 // If client explicitly sets nonce to be empty, it implies intent to 670 // disable reauthentication. Also, role tag can override the 'false' 671 // value with 'true' (the other way around is not allowed). 672 673 // Read the value from the role entry 674 disallowReauthentication := roleEntry.DisallowReauthentication 675 676 clientNonce := "" 677 678 // Check if the nonce is supplied by the client 679 clientNonceRaw, clientNonceSupplied := data.GetOk("nonce") 680 if clientNonceSupplied { 681 clientNonce = clientNonceRaw.(string) 682 683 // Nonce explicitly set to empty implies intent to disable 684 // reauthentication by the client. Set a predefined nonce which 685 // indicates reauthentication being disabled. 686 if clientNonce == "" { 687 clientNonce = reauthenticationDisabledNonce 688 689 // Ensure that the intent lands in the whitelist 690 disallowReauthentication = true 691 } 692 } 693 694 // This is NOT a first login attempt from the client 695 if storedIdentity != nil { 696 // Check if the client nonce match the cached nonce and if the pending time 697 // of the identity document is not before the pending time of the document 698 // with which previous login was made. If 'allow_instance_migration' is 699 // enabled on the registered role, client nonce requirement is relaxed. 700 if err = validateMetadata(clientNonce, identityDocParsed.PendingTime, storedIdentity, roleEntry); err != nil { 701 return logical.ErrorResponse(err.Error()), nil 702 } 703 704 // Don't let subsequent login attempts to bypass the initial 705 // intent of disabling reauthentication, despite the properties 706 // of role getting updated. For example: Role has the value set 707 // to 'false', a role-tag login sets the value to 'true', then 708 // role gets updated to not use a role-tag, and a login attempt 709 // is made with role's value set to 'false'. Removing the entry 710 // from the identity-whitelist should be the only way to be 711 // able to login from the instance again. 712 disallowReauthentication = disallowReauthentication || storedIdentity.DisallowReauthentication 713 } 714 715 // If we reach this point without erroring and if the client nonce was 716 // not supplied, a first time login is implied and that the client 717 // intends that the nonce be generated by the backend. Create a random 718 // nonce to be associated for the instance ID. 719 if !clientNonceSupplied { 720 if clientNonce, err = uuid.GenerateUUID(); err != nil { 721 return nil, fmt.Errorf("failed to generate random nonce") 722 } 723 } 724 725 // Load the current values for max TTL and policies from the role entry, 726 // before checking for overriding max TTL in the role tag. The shortest 727 // max TTL is used to cap the token TTL; the longest max TTL is used to 728 // make the whitelist entry as long as possible as it controls for replay 729 // attacks. 730 shortestMaxTTL := b.System().MaxLeaseTTL() 731 longestMaxTTL := b.System().MaxLeaseTTL() 732 if roleEntry.MaxTTL > time.Duration(0) && roleEntry.MaxTTL < shortestMaxTTL { 733 shortestMaxTTL = roleEntry.MaxTTL 734 } 735 if roleEntry.MaxTTL > longestMaxTTL { 736 longestMaxTTL = roleEntry.MaxTTL 737 } 738 739 policies := roleEntry.Policies 740 rTagMaxTTL := time.Duration(0) 741 var roleTagResp *roleTagLoginResponse 742 if roleEntry.RoleTag != "" { 743 roleTagResp, err = b.handleRoleTagLogin(ctx, req.Storage, roleName, roleEntry, instance) 744 if err != nil { 745 return nil, err 746 } 747 if roleTagResp == nil { 748 return logical.ErrorResponse("failed to fetch and verify the role tag"), nil 749 } 750 } 751 752 if roleTagResp != nil { 753 // Role tag is enabled on the role. 754 755 // Overwrite the policies with the ones returned from processing the role tag 756 // If there are no policies on the role tag, policies on the role are inherited. 757 // If policies on role tag are set, by this point, it is verified that it is a subset of the 758 // policies on the role. So, apply only those. 759 if len(roleTagResp.Policies) != 0 { 760 policies = roleTagResp.Policies 761 } 762 763 // If roleEntry had disallowReauthentication set to 'true', do not reset it 764 // to 'false' based on role tag having it not set. But, if role tag had it set, 765 // be sure to override the value. 766 if !disallowReauthentication { 767 disallowReauthentication = roleTagResp.DisallowReauthentication 768 } 769 770 // Cache the value of role tag's max_ttl value 771 rTagMaxTTL = roleTagResp.MaxTTL 772 773 // Scope the shortestMaxTTL to the value set on the role tag 774 if roleTagResp.MaxTTL > time.Duration(0) && roleTagResp.MaxTTL < shortestMaxTTL { 775 shortestMaxTTL = roleTagResp.MaxTTL 776 } 777 if roleTagResp.MaxTTL > longestMaxTTL { 778 longestMaxTTL = roleTagResp.MaxTTL 779 } 780 } 781 782 // Save the login attempt in the identity whitelist 783 currentTime := time.Now() 784 if storedIdentity == nil { 785 // Role, ClientNonce and CreationTime of the identity entry, 786 // once set, should never change. 787 storedIdentity = &whitelistIdentity{ 788 Role: roleName, 789 ClientNonce: clientNonce, 790 CreationTime: currentTime, 791 } 792 } 793 794 // DisallowReauthentication, PendingTime, LastUpdatedTime and 795 // ExpirationTime may change. 796 storedIdentity.LastUpdatedTime = currentTime 797 storedIdentity.ExpirationTime = currentTime.Add(longestMaxTTL) 798 storedIdentity.PendingTime = identityDocParsed.PendingTime 799 storedIdentity.DisallowReauthentication = disallowReauthentication 800 801 // Don't cache the nonce if DisallowReauthentication is set 802 if storedIdentity.DisallowReauthentication { 803 storedIdentity.ClientNonce = "" 804 } 805 806 // Sanitize the nonce to a reasonable length 807 if len(clientNonce) > 128 && !storedIdentity.DisallowReauthentication { 808 return logical.ErrorResponse("client nonce exceeding the limit of 128 characters"), nil 809 } 810 811 if err = setWhitelistIdentityEntry(ctx, req.Storage, identityDocParsed.InstanceID, storedIdentity); err != nil { 812 return nil, err 813 } 814 815 resp := &logical.Response{ 816 Auth: &logical.Auth{ 817 Period: roleEntry.Period, 818 Policies: policies, 819 Metadata: map[string]string{ 820 "instance_id": identityDocParsed.InstanceID, 821 "region": identityDocParsed.Region, 822 "account_id": identityDocParsed.AccountID, 823 "role_tag_max_ttl": rTagMaxTTL.String(), 824 "role": roleName, 825 "ami_id": identityDocParsed.AmiID, 826 }, 827 LeaseOptions: logical.LeaseOptions{ 828 Renewable: true, 829 TTL: roleEntry.TTL, 830 MaxTTL: shortestMaxTTL, 831 }, 832 Alias: &logical.Alias{ 833 Name: identityAlias, 834 }, 835 }, 836 } 837 838 // Return the nonce only if reauthentication is allowed and if the nonce 839 // was not supplied by the user. 840 if !disallowReauthentication && !clientNonceSupplied { 841 // Echo the client nonce back. If nonce param was not supplied 842 // to the endpoint at all (setting it to empty string does not 843 // qualify here), callers should extract out the nonce from 844 // this field for reauthentication requests. 845 resp.Auth.Metadata["nonce"] = clientNonce 846 } 847 848 return resp, nil 849} 850 851// handleRoleTagLogin is used to fetch the role tag of the instance and 852// verifies it to be correct. Then the policies for the login request will be 853// set off of the role tag, if certain criteria satisfies. 854func (b *backend) handleRoleTagLogin(ctx context.Context, s logical.Storage, roleName string, roleEntry *awsRoleEntry, instance *ec2.Instance) (*roleTagLoginResponse, error) { 855 if roleEntry == nil { 856 return nil, fmt.Errorf("nil role entry") 857 } 858 if instance == nil { 859 return nil, fmt.Errorf("nil instance") 860 } 861 862 // Input validation on instance is not performed here considering 863 // that it would have been done in validateInstance method. 864 tags := instance.Tags 865 if tags == nil || len(tags) == 0 { 866 return nil, fmt.Errorf("missing tag with key %q on the instance", roleEntry.RoleTag) 867 } 868 869 // Iterate through the tags attached on the instance and look for 870 // a tag with its 'key' matching the expected role tag value. 871 rTagValue := "" 872 for _, tagItem := range tags { 873 if tagItem.Key != nil && *tagItem.Key == roleEntry.RoleTag { 874 rTagValue = *tagItem.Value 875 break 876 } 877 } 878 879 // If 'role_tag' is enabled on the role, and if a corresponding tag is not found 880 // to be attached to the instance, fail. 881 if rTagValue == "" { 882 return nil, fmt.Errorf("missing tag with key %q on the instance", roleEntry.RoleTag) 883 } 884 885 // Parse the role tag into a struct, extract the plaintext part of it and verify its HMAC 886 rTag, err := b.parseAndVerifyRoleTagValue(ctx, s, rTagValue) 887 if err != nil { 888 return nil, err 889 } 890 891 // Check if the role name with which this login is being made is same 892 // as the role name embedded in the tag. 893 if rTag.Role != roleName { 894 return nil, fmt.Errorf("role on the tag is not matching the role supplied") 895 } 896 897 // If instance_id was set on the role tag, check if the same instance is attempting to login 898 if rTag.InstanceID != "" && rTag.InstanceID != *instance.InstanceId { 899 return nil, fmt.Errorf("role tag is being used by an unauthorized instance") 900 } 901 902 // Check if the role tag is blacklisted 903 blacklistEntry, err := b.lockedBlacklistRoleTagEntry(ctx, s, rTagValue) 904 if err != nil { 905 return nil, err 906 } 907 if blacklistEntry != nil { 908 return nil, fmt.Errorf("role tag is blacklisted") 909 } 910 911 // Ensure that the policies on the RoleTag is a subset of policies on the role 912 if !strutil.StrListSubset(roleEntry.Policies, rTag.Policies) { 913 return nil, fmt.Errorf("policies on the role tag must be subset of policies on the role") 914 } 915 916 return &roleTagLoginResponse{ 917 Policies: rTag.Policies, 918 MaxTTL: rTag.MaxTTL, 919 DisallowReauthentication: rTag.DisallowReauthentication, 920 }, nil 921} 922 923// pathLoginRenew is used to renew an authenticated token 924func (b *backend) pathLoginRenew(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 925 authType, ok := req.Auth.Metadata["auth_type"] 926 if !ok { 927 // backwards compatibility for clients that have leases from before we added auth_type 928 authType = ec2AuthType 929 } 930 931 if authType == ec2AuthType { 932 return b.pathLoginRenewEc2(ctx, req, data) 933 } else if authType == iamAuthType { 934 return b.pathLoginRenewIam(ctx, req, data) 935 } else { 936 return nil, fmt.Errorf("unrecognized auth_type: %q", authType) 937 } 938} 939 940func (b *backend) pathLoginRenewIam(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 941 canonicalArn := req.Auth.Metadata["canonical_arn"] 942 if canonicalArn == "" { 943 return nil, fmt.Errorf("unable to retrieve canonical ARN from metadata during renewal") 944 } 945 946 roleName := "" 947 roleNameIfc, ok := req.Auth.InternalData["role_name"] 948 if ok { 949 roleName = roleNameIfc.(string) 950 } 951 if roleName == "" { 952 return nil, fmt.Errorf("error retrieving role_name during renewal") 953 } 954 roleEntry, err := b.lockedAWSRole(ctx, req.Storage, roleName) 955 if err != nil { 956 return nil, err 957 } 958 if roleEntry == nil { 959 return nil, fmt.Errorf("role entry not found") 960 } 961 962 // we don't really care what the inferred entity type was when the role was initially created. We 963 // care about what the role currently requires. However, the metadata's inferred_entity_id is only 964 // set when inferencing is turned on at initial login time. So, if inferencing is turned on, any 965 // existing roles will NOT be able to renew tokens. 966 // This might change later, but authenticating the actual inferred entity ID is NOT done if there 967 // is no inferencing requested in the role. The reason is that authenticating the inferred entity 968 // ID requires additional AWS IAM permissions that might not be present (e.g., 969 // ec2:DescribeInstances) as well as additional inferencing configuration (the inferred region). 970 // So, for now, if you want to turn on inferencing, all clients must re-authenticate and cannot 971 // renew existing tokens. 972 if roleEntry.InferredEntityType != "" { 973 if roleEntry.InferredEntityType == ec2EntityType { 974 instanceID, ok := req.Auth.Metadata["inferred_entity_id"] 975 if !ok { 976 return nil, fmt.Errorf("no inferred entity ID in auth metadata") 977 } 978 instanceRegion, ok := req.Auth.Metadata["inferred_aws_region"] 979 if !ok { 980 return nil, fmt.Errorf("no inferred AWS region in auth metadata") 981 } 982 _, err := b.validateInstance(ctx, req.Storage, instanceID, instanceRegion, req.Auth.Metadata["account_id"]) 983 if err != nil { 984 return nil, errwrap.Wrapf(fmt.Sprintf("failed to verify instance ID %q: {{err}}", instanceID), err) 985 } 986 } else { 987 return nil, fmt.Errorf("unrecognized entity_type in metadata: %q", roleEntry.InferredEntityType) 988 } 989 } 990 991 // Note that the error messages below can leak a little bit of information about the role information 992 // For example, if on renew, the client gets the "error parsing ARN..." error message, the client 993 // will know that it's a wildcard bind (but not the actual bind), even if the client can't actually 994 // read the role directly to know what the bind is. It's a relatively small amount of leakage, in 995 // some fairly corner cases, and in the most likely error case (role has been changed to a new ARN), 996 // the error message is identical. 997 if len(roleEntry.BoundIamPrincipalARNs) > 0 { 998 // We might not get here if all bindings were on the inferred entity, which we've already validated 999 // above 1000 // As with logins, there are three ways to pass this check: 1001 // 1: clientUserId is in roleEntry.BoundIamPrincipalIDs (entries in roleEntry.BoundIamPrincipalIDs 1002 // implies that roleEntry.ResolveAWSUniqueIDs is true) 1003 // 2: roleEntry.ResolveAWSUniqueIDs is false and canonical_arn is in roleEntry.BoundIamPrincipalARNs 1004 // 3: Full ARN matches one of the wildcard globs in roleEntry.BoundIamPrincipalARNs 1005 clientUserId, ok := req.Auth.Metadata["client_user_id"] 1006 switch { 1007 case ok && strutil.StrListContains(roleEntry.BoundIamPrincipalIDs, clientUserId): // check 1 passed 1008 case !roleEntry.ResolveAWSUniqueIDs && strutil.StrListContains(roleEntry.BoundIamPrincipalARNs, canonicalArn): // check 2 passed 1009 default: 1010 // check 3 is a bit more complex, so we do it last 1011 fullArn := b.getCachedUserId(clientUserId) 1012 if fullArn == "" { 1013 entity, err := parseIamArn(canonicalArn) 1014 if err != nil { 1015 return nil, errwrap.Wrapf(fmt.Sprintf("error parsing ARN %q: {{err}}", canonicalArn), err) 1016 } 1017 fullArn, err = b.fullArn(ctx, entity, req.Storage) 1018 if err != nil { 1019 return nil, errwrap.Wrapf(fmt.Sprintf("error looking up full ARN of entity %v: {{err}}", entity), err) 1020 } 1021 if fullArn == "" { 1022 return nil, fmt.Errorf("got empty string back when looking up full ARN of entity %v", entity) 1023 } 1024 if clientUserId != "" { 1025 b.setCachedUserId(clientUserId, fullArn) 1026 } 1027 } 1028 matchedWildcardBind := false 1029 for _, principalARN := range roleEntry.BoundIamPrincipalARNs { 1030 if strings.HasSuffix(principalARN, "*") && strutil.GlobbedStringsMatch(principalARN, fullArn) { 1031 matchedWildcardBind = true 1032 break 1033 } 1034 } 1035 if !matchedWildcardBind { 1036 return nil, fmt.Errorf("role no longer bound to ARN %q", canonicalArn) 1037 } 1038 } 1039 } 1040 1041 resp := &logical.Response{Auth: req.Auth} 1042 resp.Auth.TTL = roleEntry.TTL 1043 resp.Auth.MaxTTL = roleEntry.MaxTTL 1044 resp.Auth.Period = roleEntry.Period 1045 return resp, nil 1046} 1047 1048func (b *backend) pathLoginRenewEc2(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 1049 instanceID := req.Auth.Metadata["instance_id"] 1050 if instanceID == "" { 1051 return nil, fmt.Errorf("unable to fetch instance ID from metadata during renewal") 1052 } 1053 1054 region := req.Auth.Metadata["region"] 1055 if region == "" { 1056 return nil, fmt.Errorf("unable to fetch region from metadata during renewal") 1057 } 1058 1059 // Ensure backwards compatibility for older clients without account_id saved in metadata 1060 accountID, ok := req.Auth.Metadata["account_id"] 1061 if ok { 1062 if accountID == "" { 1063 return nil, fmt.Errorf("unable to fetch account_id from metadata during renewal") 1064 } 1065 } 1066 1067 // Cross check that the instance is still in 'running' state 1068 _, err := b.validateInstance(ctx, req.Storage, instanceID, region, accountID) 1069 if err != nil { 1070 return nil, errwrap.Wrapf(fmt.Sprintf("failed to verify instance ID %q: {{err}}", instanceID), err) 1071 } 1072 1073 storedIdentity, err := whitelistIdentityEntry(ctx, req.Storage, instanceID) 1074 if err != nil { 1075 return nil, err 1076 } 1077 if storedIdentity == nil { 1078 return nil, fmt.Errorf("failed to verify the whitelist identity entry for instance ID: %q", instanceID) 1079 } 1080 1081 // Ensure that role entry is not deleted 1082 roleEntry, err := b.lockedAWSRole(ctx, req.Storage, storedIdentity.Role) 1083 if err != nil { 1084 return nil, err 1085 } 1086 if roleEntry == nil { 1087 return nil, fmt.Errorf("role entry not found") 1088 } 1089 1090 // If the login was made using the role tag, then max_ttl from tag 1091 // is cached in internal data during login and used here to cap the 1092 // max_ttl of renewal. 1093 rTagMaxTTL, err := time.ParseDuration(req.Auth.Metadata["role_tag_max_ttl"]) 1094 if err != nil { 1095 return nil, err 1096 } 1097 1098 // Re-evaluate the maxTTL bounds 1099 shortestMaxTTL := b.System().MaxLeaseTTL() 1100 longestMaxTTL := b.System().MaxLeaseTTL() 1101 if roleEntry.MaxTTL > time.Duration(0) && roleEntry.MaxTTL < shortestMaxTTL { 1102 shortestMaxTTL = roleEntry.MaxTTL 1103 } 1104 if roleEntry.MaxTTL > longestMaxTTL { 1105 longestMaxTTL = roleEntry.MaxTTL 1106 } 1107 if rTagMaxTTL > time.Duration(0) && rTagMaxTTL < shortestMaxTTL { 1108 shortestMaxTTL = rTagMaxTTL 1109 } 1110 if rTagMaxTTL > longestMaxTTL { 1111 longestMaxTTL = rTagMaxTTL 1112 } 1113 1114 // Only LastUpdatedTime and ExpirationTime change and all other fields remain the same 1115 currentTime := time.Now() 1116 storedIdentity.LastUpdatedTime = currentTime 1117 storedIdentity.ExpirationTime = currentTime.Add(longestMaxTTL) 1118 1119 // Updating the expiration time is required for the tidy operation on the 1120 // whitelist identity storage items 1121 if err = setWhitelistIdentityEntry(ctx, req.Storage, instanceID, storedIdentity); err != nil { 1122 return nil, err 1123 } 1124 1125 resp := &logical.Response{Auth: req.Auth} 1126 resp.Auth.TTL = roleEntry.TTL 1127 resp.Auth.MaxTTL = shortestMaxTTL 1128 resp.Auth.Period = roleEntry.Period 1129 return resp, nil 1130} 1131 1132func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 1133 method := data.Get("iam_http_request_method").(string) 1134 if method == "" { 1135 return logical.ErrorResponse("missing iam_http_request_method"), nil 1136 } 1137 1138 // In the future, might consider supporting GET 1139 if method != "POST" { 1140 return logical.ErrorResponse("invalid iam_http_request_method; currently only 'POST' is supported"), nil 1141 } 1142 1143 rawUrlB64 := data.Get("iam_request_url").(string) 1144 if rawUrlB64 == "" { 1145 return logical.ErrorResponse("missing iam_request_url"), nil 1146 } 1147 rawUrl, err := base64.StdEncoding.DecodeString(rawUrlB64) 1148 if err != nil { 1149 return logical.ErrorResponse("failed to base64 decode iam_request_url"), nil 1150 } 1151 parsedUrl, err := url.Parse(string(rawUrl)) 1152 if err != nil { 1153 return logical.ErrorResponse("error parsing iam_request_url"), nil 1154 } 1155 1156 // TODO: There are two potentially valid cases we're not yet supporting that would 1157 // necessitate this check being changed. First, if we support GET requests. 1158 // Second if we support presigned POST requests 1159 bodyB64 := data.Get("iam_request_body").(string) 1160 if bodyB64 == "" { 1161 return logical.ErrorResponse("missing iam_request_body"), nil 1162 } 1163 bodyRaw, err := base64.StdEncoding.DecodeString(bodyB64) 1164 if err != nil { 1165 return logical.ErrorResponse("failed to base64 decode iam_request_body"), nil 1166 } 1167 body := string(bodyRaw) 1168 1169 headers := data.Get("iam_request_headers").(http.Header) 1170 if len(headers) == 0 { 1171 return logical.ErrorResponse("missing iam_request_headers"), nil 1172 } 1173 1174 config, err := b.lockedClientConfigEntry(ctx, req.Storage) 1175 if err != nil { 1176 return logical.ErrorResponse("error getting configuration"), nil 1177 } 1178 1179 endpoint := "https://sts.amazonaws.com" 1180 1181 if config != nil { 1182 if config.IAMServerIdHeaderValue != "" { 1183 err = validateVaultHeaderValue(headers, parsedUrl, config.IAMServerIdHeaderValue) 1184 if err != nil { 1185 return logical.ErrorResponse(fmt.Sprintf("error validating %s header: %v", iamServerIdHeader, err)), nil 1186 } 1187 } 1188 if config.STSEndpoint != "" { 1189 endpoint = config.STSEndpoint 1190 } 1191 } 1192 1193 callerID, err := submitCallerIdentityRequest(method, endpoint, parsedUrl, body, headers) 1194 if err != nil { 1195 return logical.ErrorResponse(fmt.Sprintf("error making upstream request: %v", err)), nil 1196 } 1197 1198 entity, err := parseIamArn(callerID.Arn) 1199 if err != nil { 1200 return logical.ErrorResponse(fmt.Sprintf("error parsing arn %q: %v", callerID.Arn, err)), nil 1201 } 1202 1203 roleName := data.Get("role").(string) 1204 if roleName == "" { 1205 roleName = entity.FriendlyName 1206 } 1207 1208 roleEntry, err := b.lockedAWSRole(ctx, req.Storage, roleName) 1209 if err != nil { 1210 return nil, err 1211 } 1212 if roleEntry == nil { 1213 return logical.ErrorResponse(fmt.Sprintf("entry for role %s not found", roleName)), nil 1214 } 1215 1216 if roleEntry.AuthType != iamAuthType { 1217 return logical.ErrorResponse(fmt.Sprintf("auth method iam not allowed for role %s", roleName)), nil 1218 } 1219 1220 identityConfigEntry, err := identityConfigEntry(ctx, req.Storage) 1221 if err != nil { 1222 return nil, err 1223 } 1224 1225 // This could either be a "userID:SessionID" (in the case of an assumed role) or just a "userID" 1226 // (in the case of an IAM user). 1227 callerUniqueId := strings.Split(callerID.UserId, ":")[0] 1228 identityAlias := "" 1229 switch identityConfigEntry.IAMAlias { 1230 case identityAliasRoleID: 1231 identityAlias = roleEntry.RoleID 1232 case identityAliasIAMUniqueID: 1233 identityAlias = callerUniqueId 1234 case identityAliasIAMFullArn: 1235 identityAlias = callerID.Arn 1236 } 1237 1238 // If we're just looking up for MFA, return the Alias info 1239 if req.Operation == logical.AliasLookaheadOperation { 1240 return &logical.Response{ 1241 Auth: &logical.Auth{ 1242 Alias: &logical.Alias{ 1243 Name: identityAlias, 1244 }, 1245 }, 1246 }, nil 1247 } 1248 1249 // The role creation should ensure that either we're inferring this is an EC2 instance 1250 // or that we're binding an ARN 1251 if len(roleEntry.BoundIamPrincipalARNs) > 0 { 1252 // As with renews, there are three ways to pass this check: 1253 // 1: callerUniqueId is in roleEntry.BoundIamPrincipalIDs (entries in roleEntry.BoundIamPrincipalIDs 1254 // implies that roleEntry.ResolveAWSUniqueIDs is true) 1255 // 2: roleEntry.ResolveAWSUniqueIDs is false and entity.canonicalArn() is in roleEntry.BoundIamPrincipalARNs 1256 // 3: Full ARN matches one of the wildcard globs in roleEntry.BoundIamPrincipalARNs 1257 // Need to be able to handle pathological configurations such as roleEntry.BoundIamPrincipalARNs looking something like: 1258 // arn:aw:iam::123456789012:{user/UserName,user/path/*,role/RoleName,role/path/*} 1259 switch { 1260 case strutil.StrListContains(roleEntry.BoundIamPrincipalIDs, callerUniqueId): // check 1 passed 1261 case !roleEntry.ResolveAWSUniqueIDs && strutil.StrListContains(roleEntry.BoundIamPrincipalARNs, entity.canonicalArn()): // check 2 passed 1262 default: 1263 // evaluate check 3 1264 fullArn := b.getCachedUserId(callerUniqueId) 1265 if fullArn == "" { 1266 fullArn, err = b.fullArn(ctx, entity, req.Storage) 1267 if err != nil { 1268 return logical.ErrorResponse(fmt.Sprintf("error looking up full ARN of entity %v: %v", entity, err)), nil 1269 } 1270 if fullArn == "" { 1271 return logical.ErrorResponse(fmt.Sprintf("got empty string back when looking up full ARN of entity %v", entity)), nil 1272 } 1273 b.setCachedUserId(callerUniqueId, fullArn) 1274 } 1275 matchedWildcardBind := false 1276 for _, principalARN := range roleEntry.BoundIamPrincipalARNs { 1277 if strings.HasSuffix(principalARN, "*") && strutil.GlobbedStringsMatch(principalARN, fullArn) { 1278 matchedWildcardBind = true 1279 break 1280 } 1281 } 1282 if !matchedWildcardBind { 1283 return logical.ErrorResponse(fmt.Sprintf("IAM Principal %q does not belong to the role %q", callerID.Arn, roleName)), nil 1284 } 1285 } 1286 } 1287 1288 policies := roleEntry.Policies 1289 1290 inferredEntityType := "" 1291 inferredEntityID := "" 1292 if roleEntry.InferredEntityType == ec2EntityType { 1293 instance, err := b.validateInstance(ctx, req.Storage, entity.SessionInfo, roleEntry.InferredAWSRegion, callerID.Account) 1294 if err != nil { 1295 return logical.ErrorResponse(fmt.Sprintf("failed to verify %s as a valid EC2 instance in region %s", entity.SessionInfo, roleEntry.InferredAWSRegion)), nil 1296 } 1297 1298 // build a fake identity doc to pass on metadata about the instance to verifyInstanceMeetsRoleRequirements 1299 identityDoc := &identityDocument{ 1300 Tags: nil, // Don't really need the tags, so not doing the work of converting them from Instance.Tags to identityDocument.Tags 1301 InstanceID: *instance.InstanceId, 1302 AmiID: *instance.ImageId, 1303 AccountID: callerID.Account, 1304 Region: roleEntry.InferredAWSRegion, 1305 PendingTime: instance.LaunchTime.Format(time.RFC3339), 1306 } 1307 1308 validationError, err := b.verifyInstanceMeetsRoleRequirements(ctx, req.Storage, instance, roleEntry, roleName, identityDoc) 1309 if err != nil { 1310 return nil, err 1311 } 1312 if validationError != nil { 1313 return logical.ErrorResponse(fmt.Sprintf("error validating instance: %s", validationError)), nil 1314 } 1315 1316 inferredEntityType = ec2EntityType 1317 inferredEntityID = entity.SessionInfo 1318 } 1319 1320 resp := &logical.Response{ 1321 Auth: &logical.Auth{ 1322 Period: roleEntry.Period, 1323 Policies: policies, 1324 Metadata: map[string]string{ 1325 "client_arn": callerID.Arn, 1326 "canonical_arn": entity.canonicalArn(), 1327 "client_user_id": callerUniqueId, 1328 "auth_type": iamAuthType, 1329 "inferred_entity_type": inferredEntityType, 1330 "inferred_entity_id": inferredEntityID, 1331 "inferred_aws_region": roleEntry.InferredAWSRegion, 1332 "account_id": entity.AccountNumber, 1333 "role_id": roleEntry.RoleID, 1334 }, 1335 InternalData: map[string]interface{}{ 1336 "role_name": roleName, 1337 "role_id": roleEntry.RoleID, 1338 }, 1339 DisplayName: entity.FriendlyName, 1340 LeaseOptions: logical.LeaseOptions{ 1341 Renewable: true, 1342 TTL: roleEntry.TTL, 1343 MaxTTL: roleEntry.MaxTTL, 1344 }, 1345 Alias: &logical.Alias{ 1346 Name: identityAlias, 1347 }, 1348 }, 1349 } 1350 1351 return resp, nil 1352} 1353 1354// These two methods (hasValuesFor*) return two bools 1355// The first is a hasAll, that is, does the request have all the values 1356// necessary for this auth method 1357// The second is a hasAny, that is, does the request have any of the fields 1358// exclusive to this auth method 1359func hasValuesForEc2Auth(data *framework.FieldData) (bool, bool) { 1360 _, hasPkcs7 := data.GetOk("pkcs7") 1361 _, hasIdentity := data.GetOk("identity") 1362 _, hasSignature := data.GetOk("signature") 1363 return (hasPkcs7 || (hasIdentity && hasSignature)), (hasPkcs7 || hasIdentity || hasSignature) 1364} 1365 1366func hasValuesForIamAuth(data *framework.FieldData) (bool, bool) { 1367 _, hasRequestMethod := data.GetOk("iam_http_request_method") 1368 _, hasRequestURL := data.GetOk("iam_request_url") 1369 _, hasRequestBody := data.GetOk("iam_request_body") 1370 _, hasRequestHeaders := data.GetOk("iam_request_headers") 1371 return (hasRequestMethod && hasRequestURL && hasRequestBody && hasRequestHeaders), 1372 (hasRequestMethod || hasRequestURL || hasRequestBody || hasRequestHeaders) 1373} 1374 1375func parseIamArn(iamArn string) (*iamEntity, error) { 1376 // iamArn should look like one of the following: 1377 // 1. arn:aws:iam::<account_id>:<entity_type>/<UserName> 1378 // 2. arn:aws:sts::<account_id>:assumed-role/<RoleName>/<RoleSessionName> 1379 // if we get something like 2, then we want to transform that back to what 1380 // most people would expect, which is arn:aws:iam::<account_id>:role/<RoleName> 1381 var entity iamEntity 1382 fullParts := strings.Split(iamArn, ":") 1383 if len(fullParts) != 6 { 1384 return nil, fmt.Errorf("unrecognized arn: contains %d colon-separated parts, expected 6", len(fullParts)) 1385 } 1386 if fullParts[0] != "arn" { 1387 return nil, fmt.Errorf("unrecognized arn: does not begin with \"arn:\"") 1388 } 1389 // normally aws, but could be aws-cn or aws-us-gov 1390 entity.Partition = fullParts[1] 1391 if fullParts[2] != "iam" && fullParts[2] != "sts" { 1392 return nil, fmt.Errorf("unrecognized service: %v, not one of iam or sts", fullParts[2]) 1393 } 1394 // fullParts[3] is the region, which doesn't matter for AWS IAM entities 1395 entity.AccountNumber = fullParts[4] 1396 // fullParts[5] would now be something like user/<UserName> or assumed-role/<RoleName>/<RoleSessionName> 1397 parts := strings.Split(fullParts[5], "/") 1398 if len(parts) < 2 { 1399 return nil, fmt.Errorf("unrecognized arn: %q contains fewer than 2 slash-separated parts", fullParts[5]) 1400 } 1401 entity.Type = parts[0] 1402 entity.Path = strings.Join(parts[1:len(parts)-1], "/") 1403 entity.FriendlyName = parts[len(parts)-1] 1404 // now, entity.FriendlyName should either be <UserName> or <RoleName> 1405 switch entity.Type { 1406 case "assumed-role": 1407 // Assumed roles don't have paths and have a slightly different format 1408 // parts[2] is <RoleSessionName> 1409 entity.Path = "" 1410 entity.FriendlyName = parts[1] 1411 entity.SessionInfo = parts[2] 1412 case "user": 1413 case "role": 1414 case "instance-profile": 1415 default: 1416 return &iamEntity{}, fmt.Errorf("unrecognized principal type: %q", entity.Type) 1417 } 1418 return &entity, nil 1419} 1420 1421func validateVaultHeaderValue(headers http.Header, requestUrl *url.URL, requiredHeaderValue string) error { 1422 providedValue := "" 1423 for k, v := range headers { 1424 if strings.EqualFold(iamServerIdHeader, k) { 1425 providedValue = strings.Join(v, ",") 1426 break 1427 } 1428 } 1429 if providedValue == "" { 1430 return fmt.Errorf("missing header %q", iamServerIdHeader) 1431 } 1432 1433 // NOT doing a constant time compare here since the value is NOT intended to be secret 1434 if providedValue != requiredHeaderValue { 1435 return fmt.Errorf("expected %q but got %q", requiredHeaderValue, providedValue) 1436 } 1437 1438 if authzHeaders, ok := headers["Authorization"]; ok { 1439 // authzHeader looks like AWS4-HMAC-SHA256 Credential=AKI..., SignedHeaders=host;x-amz-date;x-vault-awsiam-id, Signature=... 1440 // We need to extract out the SignedHeaders 1441 re := regexp.MustCompile(".*SignedHeaders=([^,]+)") 1442 authzHeader := strings.Join(authzHeaders, ",") 1443 matches := re.FindSubmatch([]byte(authzHeader)) 1444 if len(matches) < 1 { 1445 return fmt.Errorf("vault header wasn't signed") 1446 } 1447 if len(matches) > 2 { 1448 return fmt.Errorf("found multiple SignedHeaders components") 1449 } 1450 signedHeaders := string(matches[1]) 1451 return ensureHeaderIsSigned(signedHeaders, iamServerIdHeader) 1452 } 1453 // TODO: If we support GET requests, then we need to parse the X-Amz-SignedHeaders 1454 // argument out of the query string and search in there for the header value 1455 return fmt.Errorf("missing Authorization header") 1456} 1457 1458func buildHttpRequest(method, endpoint string, parsedUrl *url.URL, body string, headers http.Header) *http.Request { 1459 // This is all a bit complicated because the AWS signature algorithm requires that 1460 // the Host header be included in the signed headers. See 1461 // http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html 1462 // The use cases we want to support, in order of increasing complexity, are: 1463 // 1. All defaults (client assumes sts.amazonaws.com and server has no override) 1464 // 2. Alternate STS regions: client wants to go to a specific region, in which case 1465 // Vault must be configured with that endpoint as well. The client's signed request 1466 // will include a signature over what the client expects the Host header to be, 1467 // so we cannot change that and must match. 1468 // 3. Alternate STS regions with a proxy that is transparent to Vault's clients. 1469 // In this case, Vault is aware of the proxy, as the proxy is configured as the 1470 // endpoint, but the clients should NOT be aware of the proxy (because STS will 1471 // not be aware of the proxy) 1472 // It's also annoying because: 1473 // 1. The AWS Sigv4 algorithm requires the Host header to be defined 1474 // 2. Some of the official SDKs (at least botocore and aws-sdk-go) don't actually 1475 // include an explicit Host header in the HTTP requests they generate, relying on 1476 // the underlying HTTP library to do that for them. 1477 // 3. To get a validly signed request, the SDKs check if a Host header has been set 1478 // and, if not, add an inferred host header (based on the URI) to the internal 1479 // data structure used for calculating the signature, but never actually expose 1480 // that to clients. So then they just "hope" that the underlying library actually 1481 // adds the right Host header which was included in the signature calculation. 1482 // We could either explicitly require all Vault clients to explicitly add the Host header 1483 // in the encoded request, or we could also implicitly infer it from the URI. 1484 // We choose to support both -- allow you to explicitly set a Host header, but if not, 1485 // infer one from the URI. 1486 // HOWEVER, we have to preserve the request URI portion of the client's 1487 // URL because the GetCallerIdentity Action can be encoded in either the body 1488 // or the URL. So, we need to rebuild the URL sent to the http library to have the 1489 // custom, Vault-specified endpoint with the client-side request parameters. 1490 targetUrl := fmt.Sprintf("%s/%s", endpoint, parsedUrl.RequestURI()) 1491 request, err := http.NewRequest(method, targetUrl, strings.NewReader(body)) 1492 if err != nil { 1493 return nil 1494 } 1495 request.Host = parsedUrl.Host 1496 for k, vals := range headers { 1497 for _, val := range vals { 1498 request.Header.Add(k, val) 1499 } 1500 } 1501 return request 1502} 1503 1504func ensureHeaderIsSigned(signedHeaders, headerToSign string) error { 1505 // Not doing a constant time compare here, the values aren't secret 1506 for _, header := range strings.Split(signedHeaders, ";") { 1507 if header == strings.ToLower(headerToSign) { 1508 return nil 1509 } 1510 } 1511 return fmt.Errorf("vault header wasn't signed") 1512} 1513 1514func parseGetCallerIdentityResponse(response string) (GetCallerIdentityResponse, error) { 1515 decoder := xml.NewDecoder(strings.NewReader(response)) 1516 result := GetCallerIdentityResponse{} 1517 err := decoder.Decode(&result) 1518 return result, err 1519} 1520 1521func submitCallerIdentityRequest(method, endpoint string, parsedUrl *url.URL, body string, headers http.Header) (*GetCallerIdentityResult, error) { 1522 // NOTE: We need to ensure we're calling STS, instead of acting as an unintended network proxy 1523 // The protection against this is that this method will only call the endpoint specified in the 1524 // client config (defaulting to sts.amazonaws.com), so it would require a Vault admin to override 1525 // the endpoint to talk to alternate web addresses 1526 request := buildHttpRequest(method, endpoint, parsedUrl, body, headers) 1527 client := cleanhttp.DefaultClient() 1528 client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 1529 return http.ErrUseLastResponse 1530 } 1531 1532 response, err := client.Do(request) 1533 if err != nil { 1534 return nil, errwrap.Wrapf("error making request: {{err}}", err) 1535 } 1536 if response != nil { 1537 defer response.Body.Close() 1538 } 1539 // we check for status code afterwards to also print out response body 1540 responseBody, err := ioutil.ReadAll(response.Body) 1541 if err != nil { 1542 return nil, err 1543 } 1544 if response.StatusCode != 200 { 1545 return nil, fmt.Errorf("received error code %d from STS: %s", response.StatusCode, string(responseBody)) 1546 } 1547 callerIdentityResponse, err := parseGetCallerIdentityResponse(string(responseBody)) 1548 if err != nil { 1549 return nil, fmt.Errorf("error parsing STS response") 1550 } 1551 return &callerIdentityResponse.GetCallerIdentityResult[0], nil 1552} 1553 1554type GetCallerIdentityResponse struct { 1555 XMLName xml.Name `xml:"GetCallerIdentityResponse"` 1556 GetCallerIdentityResult []GetCallerIdentityResult `xml:"GetCallerIdentityResult"` 1557 ResponseMetadata []ResponseMetadata `xml:"ResponseMetadata"` 1558} 1559 1560type GetCallerIdentityResult struct { 1561 Arn string `xml:"Arn"` 1562 UserId string `xml:"UserId"` 1563 Account string `xml:"Account"` 1564} 1565 1566type ResponseMetadata struct { 1567 RequestId string `xml:"RequestId"` 1568} 1569 1570// identityDocument represents the items of interest from the EC2 instance 1571// identity document 1572type identityDocument struct { 1573 Tags map[string]interface{} `json:"tags,omitempty"` 1574 InstanceID string `json:"instanceId,omitempty"` 1575 AmiID string `json:"imageId,omitempty"` 1576 AccountID string `json:"accountId,omitempty"` 1577 Region string `json:"region,omitempty"` 1578 PendingTime string `json:"pendingTime,omitempty"` 1579} 1580 1581// roleTagLoginResponse represents the return values required after the process 1582// of verifying a role tag login 1583type roleTagLoginResponse struct { 1584 Policies []string `json:"policies"` 1585 MaxTTL time.Duration `json:"max_ttl"` 1586 DisallowReauthentication bool `json:"disallow_reauthentication"` 1587} 1588 1589type iamEntity struct { 1590 Partition string 1591 AccountNumber string 1592 Type string 1593 Path string 1594 FriendlyName string 1595 SessionInfo string 1596} 1597 1598// Returns a Vault-internal canonical ARN for referring to an IAM entity 1599func (e *iamEntity) canonicalArn() string { 1600 entityType := e.Type 1601 // canonicalize "assumed-role" into "role" 1602 if entityType == "assumed-role" { 1603 entityType = "role" 1604 } 1605 // Annoyingly, the assumed-role entity type doesn't have the Path of the role which was assumed 1606 // So, we "canonicalize" it by just completely dropping the path. The other option would be to 1607 // make an AWS API call to look up the role by FriendlyName, which introduces more complexity to 1608 // code and test, and it also breaks backwards compatibility in an area where we would really want 1609 // it 1610 return fmt.Sprintf("arn:%s:iam::%s:%s/%s", e.Partition, e.AccountNumber, entityType, e.FriendlyName) 1611} 1612 1613// This returns the "full" ARN of an iamEntity, how it would be referred to in AWS proper 1614func (b *backend) fullArn(ctx context.Context, e *iamEntity, s logical.Storage) (string, error) { 1615 // Not assuming path is reliable for any entity types 1616 client, err := b.clientIAM(ctx, s, getAnyRegionForAwsPartition(e.Partition).ID(), e.AccountNumber) 1617 if err != nil { 1618 return "", errwrap.Wrapf("error creating IAM client: {{err}}", err) 1619 } 1620 1621 switch e.Type { 1622 case "user": 1623 input := iam.GetUserInput{ 1624 UserName: aws.String(e.FriendlyName), 1625 } 1626 resp, err := client.GetUser(&input) 1627 if err != nil { 1628 return "", errwrap.Wrapf(fmt.Sprintf("error fetching user %q: {{err}}", e.FriendlyName), err) 1629 } 1630 if resp == nil { 1631 return "", fmt.Errorf("nil response from GetUser") 1632 } 1633 return *(resp.User.Arn), nil 1634 case "assumed-role": 1635 fallthrough 1636 case "role": 1637 input := iam.GetRoleInput{ 1638 RoleName: aws.String(e.FriendlyName), 1639 } 1640 resp, err := client.GetRole(&input) 1641 if err != nil { 1642 return "", errwrap.Wrapf(fmt.Sprintf("error fetching role %q: {{err}}", e.FriendlyName), err) 1643 } 1644 if resp == nil { 1645 return "", fmt.Errorf("nil response form GetRole") 1646 } 1647 return *(resp.Role.Arn), nil 1648 default: 1649 return "", fmt.Errorf("unrecognized entity type: %s", e.Type) 1650 } 1651} 1652 1653const iamServerIdHeader = "X-Vault-AWS-IAM-Server-ID" 1654 1655const pathLoginSyn = ` 1656Authenticates an EC2 instance with Vault. 1657` 1658 1659const pathLoginDesc = ` 1660Authenticate AWS entities, either an arbitrary IAM principal or EC2 instances. 1661 1662IAM principals are authenticated by processing a signed sts:GetCallerIdentity 1663request and then parsing the response to see who signed the request. Optionally, 1664the caller can be inferred to be another AWS entity type, with EC2 instances 1665the only currently supported entity type, and additional filtering can be 1666implemented based on that inferred type. 1667 1668An EC2 instance is authenticated using the PKCS#7 signature of the instance identity 1669document and a client created nonce. This nonce should be unique and should be used by 1670the instance for all future logins, unless 'disallow_reauthentication' option on the 1671registered role is enabled, in which case client nonce is optional. 1672 1673First login attempt, creates a whitelist entry in Vault associating the instance to the nonce 1674provided. All future logins will succeed only if the client nonce matches the nonce in the 1675whitelisted entry. 1676 1677By default, a cron task will periodically look for expired entries in the whitelist 1678and deletes them. The duration to periodically run this, is one hour by default. 1679However, this can be configured using the 'config/tidy/identities' endpoint. This tidy 1680action can be triggered via the API as well, using the 'tidy/identities' endpoint. 1681` 1682