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