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