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