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