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