// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1

package awsauth

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/sts"
	logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
	"github.com/hashicorp/vault/sdk/framework"
	"github.com/hashicorp/vault/sdk/helper/policyutil"
	"github.com/hashicorp/vault/sdk/logical"
)

const (
	testVaultHeaderValue = "VaultAcceptanceTesting"
	testValidRoleName    = "valid-role"
	testInvalidRoleName  = "invalid-role"
)

func TestBackend_CreateParseVerifyRoleTag(t *testing.T) {
	// create a backend
	config := logical.TestBackendConfig()
	storage := &logical.InmemStorage{}
	config.StorageView = storage
	config.System = &testSystemView{}

	b, err := Backend(config)
	if err != nil {
		t.Fatal(err)
	}

	err = b.Setup(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	// create a role entry
	data := map[string]interface{}{
		"auth_type":    "ec2",
		"policies":     "p,q,r,s",
		"bound_ami_id": "abcd-123",
	}
	resp, err := b.HandleRequest(context.Background(), &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "role/abcd-123",
		Storage:   storage,
		Data:      data,
	})
	if resp != nil && resp.IsError() {
		t.Fatalf("failed to create role")
	}
	if err != nil {
		t.Fatal(err)
	}

	// read the created role entry
	roleEntry, err := b.role(context.Background(), storage, "abcd-123")
	if err != nil {
		t.Fatal(err)
	}

	// create a nonce for the role tag
	nonce, err := createRoleTagNonce()
	if err != nil {
		t.Fatal(err)
	}
	rTag1 := &roleTag{
		Version:  "v1",
		Role:     "abcd-123",
		Nonce:    nonce,
		Policies: []string{"p", "q", "r"},
		MaxTTL:   200000000000, // 200s
	}

	// create a role tag against the role entry
	val, err := createRoleTagValue(rTag1, roleEntry)
	if err != nil {
		t.Fatal(err)
	}
	if val == "" {
		t.Fatalf("failed to create role tag")
	}

	// parse the created role tag
	rTag2, err := b.parseAndVerifyRoleTagValue(context.Background(), storage, val)
	if err != nil {
		t.Fatal(err)
	}

	// check the values in parsed role tag
	if rTag2.Version != "v1" ||
		rTag2.Nonce != nonce ||
		rTag2.Role != "abcd-123" ||
		rTag2.MaxTTL != 200000000000 || // 200s
		!policyutil.EquivalentPolicies(rTag2.Policies, []string{"p", "q", "r"}) ||
		len(rTag2.HMAC) == 0 {
		t.Fatalf("parsed role tag is invalid")
	}

	// verify the tag contents using role specific HMAC key
	verified, err := verifyRoleTagValue(rTag2, roleEntry)
	if err != nil {
		t.Fatal(err)
	}
	if !verified {
		t.Fatalf("failed to verify the role tag")
	}

	// register a different role
	resp, err = b.HandleRequest(context.Background(), &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "role/ami-6789",
		Storage:   storage,
		Data:      data,
	})
	if resp != nil && resp.IsError() {
		t.Fatalf("failed to create role")
	}
	if err != nil {
		t.Fatal(err)
	}

	// get the entry of the newly created role entry
	roleEntry2, err := b.role(context.Background(), storage, "ami-6789")
	if err != nil {
		t.Fatal(err)
	}

	// try to verify the tag created with previous role's HMAC key
	// with the newly registered entry's HMAC key
	verified, err = verifyRoleTagValue(rTag2, roleEntry2)
	if err != nil {
		t.Fatal(err)
	}
	if verified {
		t.Fatalf("verification of role tag should have failed")
	}

	// modify any value in role tag and try to verify it
	rTag2.Version = "v2"
	verified, err = verifyRoleTagValue(rTag2, roleEntry)
	if err != nil {
		t.Fatal(err)
	}
	if verified {
		t.Fatalf("verification of role tag should have failed: invalid Version")
	}
}

func TestBackend_prepareRoleTagPlaintextValue(t *testing.T) {
	// create a nonce for the role tag
	nonce, err := createRoleTagNonce()
	if err != nil {
		t.Fatal(err)
	}
	rTag := &roleTag{
		Version: "v1",
		Nonce:   nonce,
		Role:    "abcd-123",
	}

	rTag.Version = ""
	// try to create plaintext part of role tag
	// without specifying version
	val, err := prepareRoleTagPlaintextValue(rTag)
	if err == nil {
		t.Fatalf("expected error for missing version")
	}
	rTag.Version = "v1"

	rTag.Nonce = ""
	// try to create plaintext part of role tag
	// without specifying nonce
	val, err = prepareRoleTagPlaintextValue(rTag)
	if err == nil {
		t.Fatalf("expected error for missing nonce")
	}
	rTag.Nonce = nonce

	rTag.Role = ""
	// try to create plaintext part of role tag
	// without specifying role
	val, err = prepareRoleTagPlaintextValue(rTag)
	if err == nil {
		t.Fatalf("expected error for missing role")
	}
	rTag.Role = "abcd-123"

	// create the plaintext part of the tag
	val, err = prepareRoleTagPlaintextValue(rTag)
	if err != nil {
		t.Fatal(err)
	}

	// verify if it contains known fields
	if !strings.Contains(val, "r=") ||
		!strings.Contains(val, "d=") ||
		!strings.Contains(val, "m=") ||
		!strings.HasPrefix(val, "v1") {
		t.Fatalf("incorrect information in role tag plaintext value")
	}

	rTag.InstanceID = "instance-123"
	// create the role tag with instance_id specified
	val, err = prepareRoleTagPlaintextValue(rTag)
	if err != nil {
		t.Fatal(err)
	}
	// verify it
	if !strings.Contains(val, "i=") {
		t.Fatalf("missing instance ID in role tag plaintext value")
	}

	rTag.MaxTTL = 200000000000
	// create the role tag with max_ttl specified
	val, err = prepareRoleTagPlaintextValue(rTag)
	if err != nil {
		t.Fatal(err)
	}
	// verify it
	if !strings.Contains(val, "t=") {
		t.Fatalf("missing max_ttl field in role tag plaintext value")
	}
}

func TestBackend_CreateRoleTagNonce(t *testing.T) {
	// create a nonce for the role tag
	nonce, err := createRoleTagNonce()
	if err != nil {
		t.Fatal(err)
	}
	if nonce == "" {
		t.Fatalf("failed to create role tag nonce")
	}

	// verify that the value returned is base64 encoded
	nonceBytes, err := base64.StdEncoding.DecodeString(nonce)
	if err != nil {
		t.Fatal(err)
	}
	if len(nonceBytes) == 0 {
		t.Fatalf("length of role tag nonce is zero")
	}
}

func TestBackend_ConfigTidyIdentities(t *testing.T) {
	for _, path := range []string{"config/tidy/identity-whitelist", "config/tidy/identity-accesslist"} {
		// create a backend
		config := logical.TestBackendConfig()
		storage := &logical.InmemStorage{}
		config.StorageView = storage
		config.System = &testSystemView{}

		b, err := Backend(config)
		if err != nil {
			t.Fatal(err)
		}

		err = b.Setup(context.Background(), config)
		if err != nil {
			t.Fatal(err)
		}

		// test update operation
		tidyRequest := &logical.Request{
			Operation: logical.UpdateOperation,
			Path:      path,
			Storage:   storage,
		}
		data := map[string]interface{}{
			"safety_buffer":         "60",
			"disable_periodic_tidy": true,
		}
		tidyRequest.Data = data
		_, err = b.HandleRequest(context.Background(), tidyRequest)
		if err != nil {
			t.Fatal(err)
		}

		// test read operation
		tidyRequest.Operation = logical.ReadOperation
		resp, err := b.HandleRequest(context.Background(), tidyRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.IsError() {
			t.Fatalf("failed to read %q endpoint", path)
		}
		if resp.Data["safety_buffer"].(int) != 60 || !resp.Data["disable_periodic_tidy"].(bool) {
			t.Fatalf("bad: expected: safety_buffer:60 disable_periodic_tidy:true actual: safety_buffer:%d disable_periodic_tidy:%t\n", resp.Data["safety_buffer"].(int), resp.Data["disable_periodic_tidy"].(bool))
		}

		// test delete operation
		tidyRequest.Operation = logical.DeleteOperation
		resp, err = b.HandleRequest(context.Background(), tidyRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp != nil {
			t.Fatalf("failed to delete %q", path)
		}
	}
}

func TestBackend_ConfigTidyRoleTags(t *testing.T) {
	for _, path := range []string{"config/tidy/roletag-blacklist", "config/tidy/roletag-denylist"} {
		config := logical.TestBackendConfig()
		storage := &logical.InmemStorage{}
		config.StorageView = storage
		config.System = &testSystemView{}

		b, err := Backend(config)
		if err != nil {
			t.Fatal(err)
		}

		err = b.Setup(context.Background(), config)
		if err != nil {
			t.Fatal(err)
		}

		// test update operation
		tidyRequest := &logical.Request{
			Operation: logical.UpdateOperation,
			Path:      path,
			Storage:   storage,
		}
		data := map[string]interface{}{
			"safety_buffer":         "60",
			"disable_periodic_tidy": true,
		}
		tidyRequest.Data = data
		_, err = b.HandleRequest(context.Background(), tidyRequest)
		if err != nil {
			t.Fatal(err)
		}

		// test read operation
		tidyRequest.Operation = logical.ReadOperation
		resp, err := b.HandleRequest(context.Background(), tidyRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.IsError() {
			t.Fatalf("failed to read %s endpoint", path)
		}
		if resp.Data["safety_buffer"].(int) != 60 || !resp.Data["disable_periodic_tidy"].(bool) {
			t.Fatalf("bad: expected: safety_buffer:60 disable_periodic_tidy:true actual: safety_buffer:%d disable_periodic_tidy:%t\n", resp.Data["safety_buffer"].(int), resp.Data["disable_periodic_tidy"].(bool))
		}

		// test delete operation
		tidyRequest.Operation = logical.DeleteOperation
		resp, err = b.HandleRequest(context.Background(), tidyRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp != nil {
			t.Fatalf("failed to delete %s", path)
		}
	}
}

func TestBackend_TidyIdentities(t *testing.T) {
	for _, path := range []string{"tidy/identity-whitelist", "tidy/identity-accesslist"} {
		config := logical.TestBackendConfig()
		storage := &logical.InmemStorage{}
		config.StorageView = storage
		config.System = &testSystemView{}

		b, err := Backend(config)
		if err != nil {
			t.Fatal(err)
		}

		err = b.Setup(context.Background(), config)
		if err != nil {
			t.Fatal(err)
		}

		expiredIdentityWhitelist := &accessListIdentity{
			ExpirationTime: time.Now().Add(-1 * 24 * 365 * time.Hour),
		}
		entry, err := logical.StorageEntryJSON("whitelist/identity/id1", expiredIdentityWhitelist)
		if err != nil {
			t.Fatal(err)
		}
		if err := storage.Put(context.Background(), entry); err != nil {
			t.Fatal(err)
		}

		// test update operation
		_, err = b.HandleRequest(context.Background(), &logical.Request{
			Operation: logical.UpdateOperation,
			Path:      path,
			Storage:   storage,
		})
		if err != nil {
			t.Fatal(err)
		}

		// let tidy finish in the background
		time.Sleep(1 * time.Second)

		entry, err = storage.Get(context.Background(), "whitelist/identity/id1")
		if err != nil {
			t.Fatal(err)
		}
		if entry != nil {
			t.Fatal("wl tidy did not remove expired entry")
		}
	}
}

func TestBackend_TidyRoleTags(t *testing.T) {
	for _, path := range []string{"tidy/roletag-blacklist", "tidy/roletag-denylist"} {
		config := logical.TestBackendConfig()
		storage := &logical.InmemStorage{}
		config.StorageView = storage
		config.System = &testSystemView{}

		b, err := Backend(config)
		if err != nil {
			t.Fatal(err)
		}

		err = b.Setup(context.Background(), config)
		if err != nil {
			t.Fatal(err)
		}

		expiredIdentityWhitelist := &roleTagBlacklistEntry{
			ExpirationTime: time.Now().Add(-1 * 24 * 365 * time.Hour),
		}
		entry, err := logical.StorageEntryJSON("blacklist/roletag/id1", expiredIdentityWhitelist)
		if err != nil {
			t.Fatal(err)
		}
		if err := storage.Put(context.Background(), entry); err != nil {
			t.Fatal(err)
		}

		// test update operation
		_, err = b.HandleRequest(context.Background(), &logical.Request{
			Operation: logical.UpdateOperation,
			Path:      path,
			Storage:   storage,
		})
		if err != nil {
			t.Fatal(err)
		}

		// let tidy finish in the background
		time.Sleep(1 * time.Second)

		entry, err = storage.Get(context.Background(), "blacklist/roletag/id1")
		if err != nil {
			t.Fatal(err)
		}
		if entry != nil {
			t.Fatal("bl tidy did not remove expired entry")
		}
	}
}

func TestBackend_ConfigClient(t *testing.T) {
	config := logical.TestBackendConfig()
	storage := &logical.InmemStorage{}
	config.StorageView = storage
	config.System = &testSystemView{}

	b, err := Backend(config)
	if err != nil {
		t.Fatal(err)
	}

	err = b.Setup(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	data := map[string]interface{}{
		"access_key": "AKIAJBRHKV6EVTTNXDHA",
		"secret_key": "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",
	}

	stepCreate := logicaltest.TestStep{
		Operation: logical.CreateOperation,
		Path:      "config/client",
		Data:      data,
	}

	stepUpdate := logicaltest.TestStep{
		Operation: logical.UpdateOperation,
		Path:      "config/client",
		Data:      data,
	}

	data3 := map[string]interface{}{
		"access_key": "",
		"secret_key": "mCtSM8ZUEQ3mOFVZYPBQkf2sO6F/W7a5TVzrl3Oj",
	}
	stepInvalidAccessKey := logicaltest.TestStep{
		Operation: logical.UpdateOperation,
		Path:      "config/client",
		Data:      data3,
		ErrorOk:   true,
	}

	data4 := map[string]interface{}{
		"access_key": "accesskey",
		"secret_key": "",
	}
	stepInvalidSecretKey := logicaltest.TestStep{
		Operation: logical.UpdateOperation,
		Path:      "config/client",
		Data:      data4,
		ErrorOk:   true,
	}

	logicaltest.Test(t, logicaltest.TestCase{
		AcceptanceTest:    false,
		CredentialBackend: b,
		Steps: []logicaltest.TestStep{
			stepCreate,
			stepInvalidAccessKey,
			stepInvalidSecretKey,
			stepUpdate,
		},
	})

	// test existence check returning false
	checkFound, exists, err := b.HandleExistenceCheck(context.Background(), &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "config/client",
		Storage:   storage,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !checkFound {
		t.Fatal("existence check not found for path 'config/client'")
	}
	if exists {
		t.Fatal("existence check should have returned 'false' for 'config/client'")
	}

	// create an entry
	configClientCreateRequest := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "config/client",
		Data:      data,
		Storage:   storage,
	}
	_, err = b.HandleRequest(context.Background(), configClientCreateRequest)
	if err != nil {
		t.Fatal(err)
	}

	// test existence check returning true
	checkFound, exists, err = b.HandleExistenceCheck(context.Background(), &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "config/client",
		Storage:   storage,
	})
	if err != nil {
		t.Fatal(err)
	}
	if !checkFound {
		t.Fatal("existence check not found for path 'config/client'")
	}
	if !exists {
		t.Fatal("existence check should have returned 'true' for 'config/client'")
	}

	endpointData := map[string]interface{}{
		"secret_key": "secretkey",
		"access_key": "accesskey",
		"endpoint":   "endpointvalue",
	}

	endpointReq := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "config/client",
		Storage:   storage,
		Data:      endpointData,
	}
	_, err = b.HandleRequest(context.Background(), endpointReq)
	if err != nil {
		t.Fatal(err)
	}

	endpointReq.Operation = logical.ReadOperation
	resp, err := b.HandleRequest(context.Background(), endpointReq)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil ||
		resp.IsError() {
		t.Fatalf("")
	}
	actual := resp.Data["endpoint"].(string)
	if actual != "endpointvalue" {
		t.Fatalf("bad: endpoint: expected:endpointvalue actual:%s\n", actual)
	}
}

func TestBackend_pathConfigCertificate(t *testing.T) {
	config := logical.TestBackendConfig()
	storage := &logical.InmemStorage{}
	config.StorageView = storage
	config.System = &testSystemView{}

	b, err := Backend(config)
	if err != nil {
		t.Fatal(err)
	}

	err = b.Setup(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	certReq := &logical.Request{
		Operation: logical.CreateOperation,
		Storage:   storage,
		Path:      "config/certificate/cert1",
	}
	checkFound, exists, err := b.HandleExistenceCheck(context.Background(), certReq)
	if err != nil {
		t.Fatal(err)
	}
	if !checkFound {
		t.Fatal("existence check not found for path 'config/certificate/cert1'")
	}
	if exists {
		t.Fatal("existence check should have returned 'false' for 'config/certificate/cert1'")
	}

	data := map[string]interface{}{
		"type": "pkcs7",
		"aws_public_cert": `LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM3VENDQXEwQ0NRQ1d1a2paNVY0YVp6QUpC
Z2NxaGtqT09BUURNRnd4Q3pBSkJnTlZCQVlUQWxWVE1Sa3cKRndZRFZRUUlFeEJYWVhOb2FXNW5k
Rzl1SUZOMFlYUmxNUkF3RGdZRFZRUUhFd2RUWldGMGRHeGxNU0F3SGdZRApWUVFLRXhkQmJXRjZi
MjRnVjJWaUlGTmxjblpwWTJWeklFeE1RekFlRncweE1qQXhNRFV4TWpVMk1USmFGdzB6Ck9EQXhN
RFV4TWpVMk1USmFNRnd4Q3pBSkJnTlZCQVlUQWxWVE1Sa3dGd1lEVlFRSUV4QlhZWE5vYVc1bmRH
OXUKSUZOMFlYUmxNUkF3RGdZRFZRUUhFd2RUWldGMGRHeGxNU0F3SGdZRFZRUUtFeGRCYldGNmIy
NGdWMlZpSUZObApjblpwWTJWeklFeE1RekNDQWJjd2dnRXNCZ2NxaGtqT09BUUJNSUlCSHdLQmdR
Q2prdmNTMmJiMVZRNHl0LzVlCmloNU9PNmtLL24xTHpsbHI3RDhad3RRUDhmT0VwcDVFMm5nK0Q2
VWQxWjFnWWlwcjU4S2ozbnNzU05wSTZiWDMKVnlJUXpLN3dMY2xuZC9Zb3pxTk5tZ0l5WmVjTjdF
Z2xLOUlUSEpMUCt4OEZ0VXB0M1FieVlYSmRtVk1lZ042UApodmlZdDVKSC9uWWw0aGgzUGExSEpk
c2tnUUlWQUxWSjNFUjExK0tvNHRQNm53dkh3aDYrRVJZUkFvR0JBSTFqCmsrdGtxTVZIdUFGY3ZB
R0tvY1Rnc2pKZW02LzVxb216SnVLRG1iSk51OVF4dzNyQW90WGF1OFFlK01CY0psL1UKaGh5MUtI
VnBDR2w5ZnVlUTJzNklMMENhTy9idXljVTFDaVlRazQwS05IQ2NIZk5pWmJkbHgxRTlycFVwN2Ju
RgpsUmEydjFudE1YM2NhUlZEZGJ0UEVXbWR4U0NZc1lGRGs0bVpyT0xCQTRHRUFBS0JnRWJtZXZl
NWY4TElFL0dmCk1ObVA5Q001ZW92UU9HeDVobzhXcUQrYVRlYnMrazJ0bjkyQkJQcWVacXBXUmE1
UC8ranJkS21sMXF4NGxsSFcKTVhyczNJZ0liNitoVUlCK1M4ZHo4L21tTzBicHI3NlJvWlZDWFlh
YjJDWmVkRnV0N3FjM1dVSDkrRVVBSDVtdwp2U2VEQ09VTVlRUjdSOUxJTll3b3VISXppcVFZTUFr
R0J5cUdTTTQ0QkFNREx3QXdMQUlVV1hCbGs0MHhUd1N3CjdIWDMyTXhYWXJ1c2U5QUNGQk5HbWRY
MlpCclZOR3JOOU4yZjZST2swazlLCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
`,
	}

	certReq.Data = data
	// test create operation
	resp, err := b.HandleRequest(context.Background(), certReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("resp: %#v, err: %v", resp, err)
	}

	certReq.Data = nil
	// test existence check
	checkFound, exists, err = b.HandleExistenceCheck(context.Background(), certReq)
	if err != nil {
		t.Fatal(err)
	}
	if !checkFound {
		t.Fatal("existence check not found for path 'config/certificate/cert1'")
	}
	if !exists {
		t.Fatal("existence check should have returned 'true' for 'config/certificate/cert1'")
	}

	certReq.Operation = logical.ReadOperation
	// test read operation
	resp, err = b.HandleRequest(context.Background(), certReq)
	if err != nil {
		t.Fatal(err)
	}
	expectedCert := `-----BEGIN CERTIFICATE-----
MIIC7TCCAq0CCQCWukjZ5V4aZzAJBgcqhkjOOAQDMFwxCzAJBgNVBAYTAlVTMRkw
FwYDVQQIExBXYXNoaW5ndG9uIFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYD
VQQKExdBbWF6b24gV2ViIFNlcnZpY2VzIExMQzAeFw0xMjAxMDUxMjU2MTJaFw0z
ODAxMDUxMjU2MTJaMFwxCzAJBgNVBAYTAlVTMRkwFwYDVQQIExBXYXNoaW5ndG9u
IFN0YXRlMRAwDgYDVQQHEwdTZWF0dGxlMSAwHgYDVQQKExdBbWF6b24gV2ViIFNl
cnZpY2VzIExMQzCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCjkvcS2bb1VQ4yt/5e
ih5OO6kK/n1Lzllr7D8ZwtQP8fOEpp5E2ng+D6Ud1Z1gYipr58Kj3nssSNpI6bX3
VyIQzK7wLclnd/YozqNNmgIyZecN7EglK9ITHJLP+x8FtUpt3QbyYXJdmVMegN6P
hviYt5JH/nYl4hh3Pa1HJdskgQIVALVJ3ER11+Ko4tP6nwvHwh6+ERYRAoGBAI1j
k+tkqMVHuAFcvAGKocTgsjJem6/5qomzJuKDmbJNu9Qxw3rAotXau8Qe+MBcJl/U
hhy1KHVpCGl9fueQ2s6IL0CaO/buycU1CiYQk40KNHCcHfNiZbdlx1E9rpUp7bnF
lRa2v1ntMX3caRVDdbtPEWmdxSCYsYFDk4mZrOLBA4GEAAKBgEbmeve5f8LIE/Gf
MNmP9CM5eovQOGx5ho8WqD+aTebs+k2tn92BBPqeZqpWRa5P/+jrdKml1qx4llHW
MXrs3IgIb6+hUIB+S8dz8/mmO0bpr76RoZVCXYab2CZedFut7qc3WUH9+EUAH5mw
vSeDCOUMYQR7R9LINYwouHIziqQYMAkGByqGSM44BAMDLwAwLAIUWXBlk40xTwSw
7HX32MxXYruse9ACFBNGmdX2ZBrVNGrN9N2f6ROk0k9K
-----END CERTIFICATE-----
`
	if resp.Data["aws_public_cert"].(string) != expectedCert {
		t.Fatalf("bad: expected:%s\n got:%s\n", expectedCert, resp.Data["aws_public_cert"].(string))
	}

	certReq.Operation = logical.CreateOperation
	certReq.Path = "config/certificate/cert2"
	certReq.Data = data
	// create another entry to test the list operation
	_, err = b.HandleRequest(context.Background(), certReq)
	if err != nil {
		t.Fatal(err)
	}

	certReq.Operation = logical.ListOperation
	certReq.Path = "config/certificates"
	// test list operation
	resp, err = b.HandleRequest(context.Background(), certReq)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil || resp.IsError() {
		t.Fatalf("failed to list config/certificates")
	}
	keys := resp.Data["keys"].([]string)
	if len(keys) != 2 {
		t.Fatalf("invalid keys listed: %#v\n", keys)
	}

	certReq.Operation = logical.DeleteOperation
	certReq.Path = "config/certificate/cert1"
	_, err = b.HandleRequest(context.Background(), certReq)
	if err != nil {
		t.Fatal(err)
	}

	certReq.Path = "config/certificate/cert2"
	_, err = b.HandleRequest(context.Background(), certReq)
	if err != nil {
		t.Fatal(err)
	}

	certReq.Operation = logical.ListOperation
	certReq.Path = "config/certificates"
	// test list operation
	resp, err = b.HandleRequest(context.Background(), certReq)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil || resp.IsError() {
		t.Fatalf("failed to list config/certificates")
	}
	if resp.Data["keys"] != nil {
		t.Fatalf("no entries should be present")
	}
}

func TestBackend_parseAndVerifyRoleTagValue(t *testing.T) {
	// create a backend
	config := logical.TestBackendConfig()
	storage := &logical.InmemStorage{}
	config.StorageView = storage
	config.System = &testSystemView{}

	b, err := Backend(config)
	if err != nil {
		t.Fatal(err)
	}

	err = b.Setup(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	// create a role
	data := map[string]interface{}{
		"auth_type":    "ec2",
		"policies":     "p,q,r,s",
		"max_ttl":      "120s",
		"role_tag":     "VaultRole",
		"bound_ami_id": "abcd-123",
	}
	resp, err := b.HandleRequest(context.Background(), &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "role/abcd-123",
		Storage:   storage,
		Data:      data,
	})
	if resp != nil && resp.IsError() {
		t.Fatalf("failed to create role")
	}
	if err != nil {
		t.Fatal(err)
	}

	// verify that the entry is created
	resp, err = b.HandleRequest(context.Background(), &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "role/abcd-123",
		Storage:   storage,
	})
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatalf("expected an role entry for abcd-123")
	}

	// create a role tag
	data2 := map[string]interface{}{
		"policies": "p,q,r,s",
	}
	resp, err = b.HandleRequest(context.Background(), &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "role/abcd-123/tag",
		Storage:   storage,
		Data:      data2,
	})
	if err != nil {
		t.Fatal(err)
	}
	if resp.Data["tag_key"].(string) == "" ||
		resp.Data["tag_value"].(string) == "" {
		t.Fatalf("invalid tag response: %#v\n", resp)
	}
	tagValue := resp.Data["tag_value"].(string)

	// parse the value and check if the verifiable values match
	rTag, err := b.parseAndVerifyRoleTagValue(context.Background(), storage, tagValue)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if rTag == nil {
		t.Fatalf("failed to parse role tag")
	}
	if rTag.Version != "v1" ||
		!policyutil.EquivalentPolicies(rTag.Policies, []string{"p", "q", "r", "s"}) ||
		rTag.Role != "abcd-123" {
		t.Fatalf("bad: parsed role tag contains incorrect values. Got: %#v\n", rTag)
	}
}

func TestBackend_PathRoleTag(t *testing.T) {
	config := logical.TestBackendConfig()
	storage := &logical.InmemStorage{}
	config.StorageView = storage
	config.System = &testSystemView{}

	b, err := Backend(config)
	if err != nil {
		t.Fatal(err)
	}

	err = b.Setup(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	data := map[string]interface{}{
		"auth_type":    "ec2",
		"policies":     "p,q,r,s",
		"max_ttl":      "120s",
		"role_tag":     "VaultRole",
		"bound_ami_id": "abcd-123",
	}
	resp, err := b.HandleRequest(context.Background(), &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "role/abcd-123",
		Storage:   storage,
		Data:      data,
	})
	if resp != nil && resp.IsError() {
		t.Fatalf("failed to create role")
	}
	if err != nil {
		t.Fatal(err)
	}

	resp, err = b.HandleRequest(context.Background(), &logical.Request{
		Operation: logical.ReadOperation,
		Path:      "role/abcd-123",
		Storage:   storage,
	})
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatalf("failed to find a role entry for abcd-123")
	}

	resp, err = b.HandleRequest(context.Background(), &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "role/abcd-123/tag",
		Storage:   storage,
	})
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil || resp.Data == nil {
		t.Fatalf("failed to create a tag on role: abcd-123")
	}
	if resp.IsError() {
		t.Fatalf("failed to create a tag on role: abcd-123: %s\n", resp.Data["error"])
	}
	if resp.Data["tag_value"].(string) == "" {
		t.Fatalf("role tag not present in the response data: %#v\n", resp.Data)
	}
}

func TestBackend_PathBlacklistRoleTag(t *testing.T) {
	for _, path := range []string{"roletag-blacklist/", "roletag-denylist/"} {
		// create the backend
		storage := &logical.InmemStorage{}
		config := logical.TestBackendConfig()
		config.StorageView = storage
		config.System = &testSystemView{}

		b, err := Backend(config)
		if err != nil {
			t.Fatal(err)
		}

		err = b.Setup(context.Background(), config)
		if err != nil {
			t.Fatal(err)
		}

		// create an role entry
		data := map[string]interface{}{
			"auth_type":    "ec2",
			"policies":     "p,q,r,s",
			"role_tag":     "VaultRole",
			"bound_ami_id": "abcd-123",
		}
		resp, err := b.HandleRequest(context.Background(), &logical.Request{
			Operation: logical.CreateOperation,
			Path:      "role/abcd-123",
			Storage:   storage,
			Data:      data,
		})
		if resp != nil && resp.IsError() {
			t.Fatalf("failed to create role")
		}
		if err != nil {
			t.Fatal(err)
		}

		// create a role tag against an role registered before
		data2 := map[string]interface{}{
			"policies": "p,q,r,s",
		}
		resp, err = b.HandleRequest(context.Background(), &logical.Request{
			Operation: logical.UpdateOperation,
			Path:      "role/abcd-123/tag",
			Storage:   storage,
			Data:      data2,
		})
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.Data == nil {
			t.Fatalf("failed to create a tag on role: abcd-123")
		}
		if resp.IsError() {
			t.Fatalf("failed to create a tag on role: abcd-123: %s\n", resp.Data["error"])
		}
		tag := resp.Data["tag_value"].(string)
		if tag == "" {
			t.Fatalf("role tag not present in the response data: %#v\n", resp.Data)
		}

		// deny list that role tag
		resp, err = b.HandleRequest(context.Background(), &logical.Request{
			Operation: logical.UpdateOperation,
			Path:      path + tag,
			Storage:   storage,
		})
		if err != nil {
			t.Fatal(err)
		}
		if resp != nil {
			t.Fatalf("failed to deny list the roletag: %s\n", tag)
		}

		// read the deny list entry
		resp, err = b.HandleRequest(context.Background(), &logical.Request{
			Operation: logical.ReadOperation,
			Path:      path + tag,
			Storage:   storage,
		})
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.Data == nil {
			t.Fatalf("failed to read the deny list role tag: %s\n", tag)
		}
		if resp.IsError() {
			t.Fatalf("failed to read the deny list role tag:%s. Err: %s\n", tag, resp.Data["error"])
		}

		// delete the deny listed entry
		_, err = b.HandleRequest(context.Background(), &logical.Request{
			Operation: logical.DeleteOperation,
			Path:      path + tag,
			Storage:   storage,
		})
		if err != nil {
			t.Fatal(err)
		}

		// try to read the deleted entry
		tagEntry, err := b.lockedDenyLististRoleTagEntry(context.Background(), storage, tag)
		if err != nil {
			t.Fatal(err)
		}
		if tagEntry != nil {
			t.Fatalf("role tag should not have been present: %s\n", tag)
		}
	}
}

/*
This is an acceptance test.

	Requires the following env vars:
	TEST_AWS_EC2_RSA2048
	TEST_AWS_EC2_PKCS7
	TEST_AWS_EC2_IDENTITY_DOCUMENT
	TEST_AWS_EC2_IDENTITY_DOCUMENT_SIG
	TEST_AWS_EC2_AMI_ID
	TEST_AWS_EC2_ACCOUNT_ID
	TEST_AWS_EC2_IAM_ROLE_ARN

	If this is being run on an EC2 instance, you can set the environment vars using this bash snippet:

	export TEST_AWS_EC2_RSA2048=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/rsa2048)
	export TEST_AWS_EC2_PKCS7=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/pkcs7)
	export TEST_AWS_EC2_IDENTITY_DOCUMENT=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | base64 -w 0)
	export TEST_AWS_EC2_IDENTITY_DOCUMENT_SIG=$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/signature | tr -d '\n')
	export TEST_AWS_EC2_AMI_ID=$(curl -s http://169.254.169.254/latest/meta-data/ami-id)
	export TEST_AWS_EC2_IAM_ROLE_ARN=$(aws iam get-role --role-name $(curl -q http://169.254.169.254/latest/meta-data/iam/security-credentials/ -S -s) --query Role.Arn --output text)
	export TEST_AWS_EC2_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)


	If the test is not being run on an EC2 instance that has access to
	credentials using EC2RoleProvider, on top of the above vars, following
	needs to be set:
	TEST_AWS_SECRET_KEY
	TEST_AWS_ACCESS_KEY
*/
func TestBackendAcc_LoginWithInstanceIdentityDocAndAccessListIdentity(t *testing.T) {
	for _, path := range []string{"identity-whitelist/", "identity-accesslist/"} {
		// This test case should be run only when certain env vars are set and
		// executed as an acceptance test.
		if os.Getenv(logicaltest.TestEnvVar) == "" {
			t.Skip(fmt.Sprintf("Acceptance tests skipped unless env %q set", logicaltest.TestEnvVar))
			return
		}

		rsa2048 := os.Getenv("TEST_AWS_EC2_RSA2048")
		if rsa2048 == "" {
			t.Skipf("env var TEST_AWS_EC2_RSA2048 not set, skipping test")
		}

		pkcs7 := os.Getenv("TEST_AWS_EC2_PKCS7")
		if pkcs7 == "" {
			t.Skipf("env var TEST_AWS_EC2_PKCS7 not set, skipping test")
		}

		identityDoc := os.Getenv("TEST_AWS_EC2_IDENTITY_DOCUMENT")
		if identityDoc == "" {
			t.Skipf("env var TEST_AWS_EC2_IDENTITY_DOCUMENT not set, skipping test")
		}

		identityDocSig := os.Getenv("TEST_AWS_EC2_IDENTITY_DOCUMENT_SIG")
		if identityDocSig == "" {
			t.Skipf("env var TEST_AWS_EC2_IDENTITY_DOCUMENT_SIG not set, skipping test")
		}

		amiID := os.Getenv("TEST_AWS_EC2_AMI_ID")
		if amiID == "" {
			t.Skipf("env var TEST_AWS_EC2_AMI_ID not set, skipping test")
		}

		iamARN := os.Getenv("TEST_AWS_EC2_IAM_ROLE_ARN")
		if iamARN == "" {
			t.Skipf("env var TEST_AWS_EC2_IAM_ROLE_ARN not set, skipping test")
		}

		accountID := os.Getenv("TEST_AWS_EC2_ACCOUNT_ID")
		if accountID == "" {
			t.Skipf("env var TEST_AWS_EC2_ACCOUNT_ID not set, skipping test")
		}

		roleName := amiID

		// create the backend
		storage := &logical.InmemStorage{}
		config := logical.TestBackendConfig()
		config.StorageView = storage
		b, err := Backend(config)
		if err != nil {
			t.Fatal(err)
		}

		err = b.Setup(context.Background(), config)
		if err != nil {
			t.Fatal(err)
		}

		accessKey := os.Getenv("TEST_AWS_ACCESS_KEY")
		secretKey := os.Getenv("TEST_AWS_SECRET_KEY")

		// In case of problems with making API calls using the credentials (2FA enabled,
		// for instance), the keys need not be set if the test is running on an EC2
		// instance with permissions to get the credentials using EC2RoleProvider.
		if accessKey != "" && secretKey != "" {
			// get the API credentials from env vars
			clientConfig := map[string]interface{}{
				"access_key": accessKey,
				"secret_key": secretKey,
			}
			if clientConfig["access_key"] == "" ||
				clientConfig["secret_key"] == "" {
				t.Fatalf("credentials not configured")
			}

			// store the credentials
			_, err = b.HandleRequest(context.Background(), &logical.Request{
				Operation: logical.UpdateOperation,
				Storage:   storage,
				Path:      "config/client",
				Data:      clientConfig,
			})
			if err != nil {
				t.Fatal(err)
			}
		}

		// Configure additional metadata to be returned for ec2 logins.
		identity := map[string]interface{}{
			"ec2_metadata": []string{"instance_id", "region", "ami_id"},
		}

		// store the identity
		_, err = b.HandleRequest(context.Background(), &logical.Request{
			Operation: logical.UpdateOperation,
			Storage:   storage,
			Path:      "config/identity",
			Data:      identity,
		})
		if err != nil {
			t.Fatal(err)
		}

		loginInput := map[string]interface{}{
			"pkcs7": pkcs7,
			"nonce": "vault-client-nonce",
		}

		parsedIdentityDoc, err := b.parseIdentityDocument(context.Background(), storage, pkcs7)
		if err != nil {
			t.Fatal(err)
		}

		// Perform the login operation with a AMI ID that is not matching
		// the bound on the role.
		loginRequest := &logical.Request{
			Operation: logical.UpdateOperation,
			Path:      "login",
			Storage:   storage,
			Data:      loginInput,
		}

		// Baseline role data that should succeed permit login
		data := map[string]interface{}{
			"auth_type":             "ec2",
			"policies":              "root",
			"max_ttl":               "120s",
			"bound_ami_id":          []string{"wrong_ami_id", amiID, "wrong_ami_id2"},
			"bound_account_id":      accountID,
			"bound_iam_role_arn":    iamARN,
			"bound_ec2_instance_id": []string{parsedIdentityDoc.InstanceID, "i-1234567"},
		}

		roleReq := &logical.Request{
			Operation: logical.CreateOperation,
			Path:      "role/" + roleName,
			Storage:   storage,
			Data:      data,
		}

		updateRoleExpectLoginFail := func(roleRequest, loginRequest *logical.Request) error {
			resp, err := b.HandleRequest(context.Background(), roleRequest)
			if err != nil || (resp != nil && resp.IsError()) {
				return fmt.Errorf("bad: failed to create role: resp:%#v\nerr:%v", resp, err)
			}
			resp, err = b.HandleRequest(context.Background(), loginRequest)
			if err != nil || resp == nil || (resp != nil && !resp.IsError()) {
				return fmt.Errorf("bad: expected login failure: resp:%#v\nerr:%v", resp, err)
			}
			return nil
		}

		// Test a role with the wrong AMI ID
		data["bound_ami_id"] = []string{"ami-1234567", "ami-7654321"}
		if err := updateRoleExpectLoginFail(roleReq, loginRequest); err != nil {
			t.Fatal(err)
		}

		roleReq.Operation = logical.UpdateOperation
		// Place the correct AMI ID in one of the values, but make the AccountID wrong
		data["bound_ami_id"] = []string{"wrong_ami_id_1", amiID, "wrong_ami_id_2"}
		data["bound_account_id"] = []string{"wrong-account-id", "wrong-account-id-2"}
		if err := updateRoleExpectLoginFail(roleReq, loginRequest); err != nil {
			t.Fatal(err)
		}

		// Place the correct AccountID in one of the values, but make the wrong IAMRoleARN
		data["bound_account_id"] = []string{"wrong-account-id-1", accountID, "wrong-account-id-2"}
		data["bound_iam_role_arn"] = []string{"wrong_iam_role_arn", "wrong_iam_role_arn_2"}
		if err := updateRoleExpectLoginFail(roleReq, loginRequest); err != nil {
			t.Fatal(err)
		}

		// Place correct IAM role ARN, but incorrect instance ID
		data["bound_iam_role_arn"] = []string{"wrong_iam_role_arn_1", iamARN, "wrong_iam_role_arn_2"}
		data["bound_ec2_instance_id"] = "i-1234567"
		if err := updateRoleExpectLoginFail(roleReq, loginRequest); err != nil {
			t.Fatal(err)
		}

		// Place correct instance ID, but substring of the IAM role ARN
		data["bound_ec2_instance_id"] = []string{parsedIdentityDoc.InstanceID, "i-1234567"}
		data["bound_iam_role_arn"] = []string{"wrong_iam_role_arn", iamARN[:len(iamARN)-2], "wrong_iam_role_arn_2"}
		if err := updateRoleExpectLoginFail(roleReq, loginRequest); err != nil {
			t.Fatal(err)
		}

		// place a wildcard in the middle of the role ARN
		// The :31 gets arn:aws:iam::123456789012:role/
		// This test relies on the role name having at least two characters
		data["bound_iam_role_arn"] = []string{"wrong_iam_role_arn", fmt.Sprintf("%s*%s", iamARN[:31], iamARN[32:])}
		if err := updateRoleExpectLoginFail(roleReq, loginRequest); err != nil {
			t.Fatal(err)
		}

		// globbed IAM role ARN
		data["bound_iam_role_arn"] = []string{"wrong_iam_role_arn_1", fmt.Sprintf("%s*", iamARN[:len(iamARN)-2]), "wrong_iam_role_arn_2"}
		resp, err := b.HandleRequest(context.Background(), roleReq)
		if err != nil || (resp != nil && resp.IsError()) {
			t.Fatalf("bad: failed to create role: resp:%#v\nerr:%v", resp, err)
		}

		// Now, the login attempt should succeed
		resp, err = b.HandleRequest(context.Background(), loginRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.Auth == nil || resp.IsError() {
			t.Fatalf("bad: failed to login: resp:%#v\nerr:%v", resp, err)
		}

		// Attempt to re-login with the identity signature
		delete(loginInput, "pkcs7")
		loginInput["identity"] = identityDoc
		loginInput["signature"] = identityDocSig

		resp, err = b.HandleRequest(context.Background(), loginRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.Auth == nil || resp.IsError() {
			t.Fatalf("bad: failed to login: resp:%#v\nerr:%v", resp, err)
		}

		// verify the presence of instance_id in the response object.
		instanceID := resp.Auth.Metadata["instance_id"]
		if instanceID == "" {
			t.Fatalf("instance ID not present in the response object")
		}
		if instanceID != parsedIdentityDoc.InstanceID {
			t.Fatalf("instance ID in response (%q) did not match instance ID from identity document (%q)", instanceID, parsedIdentityDoc.InstanceID)
		}

		_, ok := resp.Auth.Metadata["nonce"]
		if ok {
			t.Fatalf("client nonce should not have been returned")
		}

		loginInput["nonce"] = "changed-vault-client-nonce"
		// try to login again with changed nonce
		resp, err = b.HandleRequest(context.Background(), loginRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || !resp.IsError() {
			t.Fatalf("login attempt should have failed due to client nonce mismatch")
		}

		// Check if an access list identity entry is created after the login.
		wlRequest := &logical.Request{
			Operation: logical.ReadOperation,
			Path:      path + instanceID,
			Storage:   storage,
		}
		resp, err = b.HandleRequest(context.Background(), wlRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.Data == nil || resp.Data["role"] != roleName {
			t.Fatalf("failed to read access list identity")
		}

		// Delete the access list identity entry.
		wlRequest.Operation = logical.DeleteOperation
		resp, err = b.HandleRequest(context.Background(), wlRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp.IsError() {
			t.Fatalf("failed to delete access list identity")
		}

		// Allow a fresh login without supplying the nonce
		delete(loginInput, "nonce")

		resp, err = b.HandleRequest(context.Background(), loginRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.Auth == nil || resp.IsError() {
			t.Fatalf("login attempt failed")
		}

		_, ok = resp.Auth.Metadata["nonce"]
		if !ok {
			t.Fatalf("expected nonce to be returned")
		}

		// Attempt to re-login with the rsa2048 signature as a pkcs7 signature
		wlRequest.Operation = logical.DeleteOperation
		resp, err = b.HandleRequest(context.Background(), wlRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp.IsError() {
			t.Fatalf("failed to delete access list identity")
		}
		delete(loginInput, "identity")
		delete(loginInput, "signature")
		loginInput["pkcs7"] = rsa2048

		resp, err = b.HandleRequest(context.Background(), loginRequest)
		if err != nil {
			t.Fatal(err)
		}
		if resp == nil || resp.Auth == nil || resp.IsError() {
			t.Fatalf("bad: failed to login: resp:%#v\nerr:%v", resp, err)
		}

		// verify the presence of instance_id in the response object.
		instanceID = resp.Auth.Metadata["instance_id"]
		if instanceID == "" {
			t.Fatalf("instance ID not present in the response object")
		}
		if instanceID != parsedIdentityDoc.InstanceID {
			t.Fatalf("instance ID in response (%q) did not match instance ID from identity document (%q)", instanceID, parsedIdentityDoc.InstanceID)
		}
	}
}

func TestBackend_pathStsConfig(t *testing.T) {
	config := logical.TestBackendConfig()
	storage := &logical.InmemStorage{}
	config.StorageView = storage
	config.System = &testSystemView{}

	b, err := Backend(config)
	if err != nil {
		t.Fatal(err)
	}

	err = b.Setup(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}
	stsReq := &logical.Request{
		Operation: logical.CreateOperation,
		Storage:   storage,
		Path:      "config/sts/account1",
	}
	checkFound, exists, err := b.HandleExistenceCheck(context.Background(), stsReq)
	if err != nil {
		t.Fatal(err)
	}
	if !checkFound {
		t.Fatal("existence check not found for path 'config/sts/account1'")
	}
	if exists {
		t.Fatal("existence check should have returned 'false' for 'config/sts/account1'")
	}

	data := map[string]interface{}{
		"sts_role": "arn:aws:iam:account1:role/myRole",
	}

	data2 := map[string]interface{}{
		"sts_role":    "arn:aws:iam:account2:role/myRole2",
		"external_id": "fake_id",
	}

	stsReq.Data = data
	// test create operation
	resp, err := b.HandleRequest(context.Background(), stsReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("resp: %#v, err: %v", resp, err)
	}

	stsReq.Data = nil
	// test existence check
	checkFound, exists, err = b.HandleExistenceCheck(context.Background(), stsReq)
	if err != nil {
		t.Fatal(err)
	}
	if !checkFound {
		t.Fatal("existence check not found for path 'config/sts/account1'")
	}
	if !exists {
		t.Fatal("existence check should have returned 'true' for 'config/sts/account1'")
	}

	stsReq.Operation = logical.ReadOperation
	// test read operation
	resp, err = b.HandleRequest(context.Background(), stsReq)
	if err != nil {
		t.Fatal(err)
	}
	expectedStsRole := "arn:aws:iam:account1:role/myRole"
	if resp.Data["sts_role"].(string) != expectedStsRole {
		t.Fatalf("bad: expected:%s\n got:%s\n", expectedStsRole, resp.Data["sts_role"].(string))
	}

	stsReq.Operation = logical.CreateOperation
	stsReq.Path = "config/sts/account2"
	stsReq.Data = data2
	// create another entry with alternate data to test ExternalID and LIST
	resp, err = b.HandleRequest(context.Background(), stsReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatal(err)
	}

	// test second read
	stsReq.Operation = logical.ReadOperation
	resp, err = b.HandleRequest(context.Background(), stsReq)
	if err != nil {
		t.Fatal(err)
	}
	expectedStsRole = "arn:aws:iam:account2:role/myRole2"
	expectedExternalID := "fake_id"
	if resp.Data["sts_role"].(string) != expectedStsRole {
		t.Fatalf("bad: expected:%s\n got:%s\n", expectedStsRole, resp.Data["sts_role"].(string))
	}
	if resp.Data["external_id"].(string) != expectedExternalID {
		t.Fatalf("bad: expected:%s\n got:%s\n", expectedExternalID, resp.Data["external_id"].(string))
	}

	stsReq.Operation = logical.ListOperation
	stsReq.Path = "config/sts"
	// test list operation
	resp, err = b.HandleRequest(context.Background(), stsReq)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil || resp.IsError() {
		t.Fatalf("failed to list config/sts")
	}
	keys := resp.Data["keys"].([]string)
	if len(keys) != 2 {
		t.Fatalf("invalid keys listed: %#v\n", keys)
	}

	stsReq.Operation = logical.DeleteOperation
	stsReq.Path = "config/sts/account1"
	resp, err = b.HandleRequest(context.Background(), stsReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatal(err)
	}

	stsReq.Path = "config/sts/account2"
	resp, err = b.HandleRequest(context.Background(), stsReq)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatal(err)
	}

	stsReq.Operation = logical.ListOperation
	stsReq.Path = "config/sts"
	// test list operation
	resp, err = b.HandleRequest(context.Background(), stsReq)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil || resp.IsError() {
		t.Fatalf("failed to list config/sts")
	}
	if resp.Data["keys"] != nil {
		t.Fatalf("no entries should be present")
	}
}

func buildCallerIdentityLoginData(request *http.Request, roleName string) (map[string]interface{}, error) {
	headersJson, err := json.Marshal(request.Header)
	if err != nil {
		return nil, err
	}
	requestBody, err := io.ReadAll(request.Body)
	if err != nil {
		return nil, err
	}
	return map[string]interface{}{
		"iam_http_request_method": request.Method,
		"iam_request_url":         base64.StdEncoding.EncodeToString([]byte(request.URL.String())),
		"iam_request_headers":     base64.StdEncoding.EncodeToString(headersJson),
		"iam_request_body":        base64.StdEncoding.EncodeToString(requestBody),
		"role":                    roleName,
	}, nil
}

// This is an acceptance test.
// If the test is NOT being run on an AWS EC2 instance in an instance profile,
// it requires the following environment variables to be set:
// TEST_AWS_ACCESS_KEY_ID
// TEST_AWS_SECRET_ACCESS_KEY
// TEST_AWS_SECURITY_TOKEN or TEST_AWS_SESSION_TOKEN (optional, if you are using short-lived creds)
// These are intentionally NOT the "standard" variables to prevent accidentally
// using prod creds in acceptance tests
func TestBackendAcc_LoginWithCallerIdentity(t *testing.T) {
	// This test case should be run only when certain env vars are set and
	// executed as an acceptance test.
	if os.Getenv(logicaltest.TestEnvVar) == "" {
		t.Skip(fmt.Sprintf("Acceptance tests skipped unless env %q set", logicaltest.TestEnvVar))
		return
	}

	ctx := context.Background()
	storage := &logical.InmemStorage{}
	config := logical.TestBackendConfig()
	config.StorageView = storage
	b, err := Backend(config)
	if err != nil {
		t.Fatal(err)
	}

	err = b.Setup(context.Background(), config)
	if err != nil {
		t.Fatal(err)
	}

	// Override the default AWS env vars (if set) with our test creds
	// so that the credential provider chain will pick them up
	// NOTE that I'm not bothing to override the shared config file location,
	// so if creds are specified there, they will be used before IAM
	// instance profile creds
	// This doesn't provide perfect leakage protection (e.g., it will still
	// potentially pick up credentials from the ~/.config files), but probably
	// good enough rather than having to muck around in the low-level details
	for _, envvar := range []string{
		"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN",
	} {
		// Skip test if any of the required env vars are missing
		testEnvVar := os.Getenv("TEST_" + envvar)
		if testEnvVar == "" {
			t.Skipf("env var %s not set, skipping test", "TEST_"+envvar)
		}

		// restore existing environment variables (in case future tests need them)
		defer os.Setenv(envvar, os.Getenv(envvar))

		os.Setenv(envvar, testEnvVar)
	}
	awsSession, err := session.NewSession()
	if err != nil {
		fmt.Println("failed to create session,", err)
		return
	}

	stsService := sts.New(awsSession)
	stsInputParams := &sts.GetCallerIdentityInput{}

	testIdentity, err := stsService.GetCallerIdentity(stsInputParams)
	if err != nil {
		t.Fatalf("Received error retrieving identity: %s", err)
	}
	entity, err := parseIamArn(*testIdentity.Arn)
	if err != nil {
		t.Fatal(err)
	}

	// Test setup largely done
	// At this point, we're going to:
	// 1. Configure the client to require our test header value
	// 2. Configure identity to use the ARN for the alias
	// 3. Configure two different roles:
	//    a. One bound to our test user
	//    b. One bound to a garbage ARN
	// 4. Pass in a request that doesn't have the signed header, ensure
	//    we're not allowed to login
	// 5. Passin a request that has a validly signed header, but the wrong
	//    value, ensure it doesn't allow login
	// 6. Pass in a request that has a validly signed request, ensure
	//    it allows us to login to our role
	// 7. Pass in a request that has a validly signed request, asking for
	//    the other role, ensure it fails

	clientConfigData := map[string]interface{}{
		"iam_server_id_header_value": testVaultHeaderValue,
	}
	clientRequest := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "config/client",
		Storage:   storage,
		Data:      clientConfigData,
	}
	_, err = b.HandleRequest(ctx, clientRequest)
	if err != nil {
		t.Fatal(err)
	}

	configIdentityData := map[string]interface{}{
		"iam_alias": identityAliasIAMFullArn,
	}
	configIdentityRequest := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "config/identity",
		Storage:   storage,
		Data:      configIdentityData,
	}
	resp, err := b.HandleRequest(ctx, configIdentityRequest)
	if err != nil {
		t.Fatal(err)
	}
	if resp != nil && resp.IsError() {
		t.Fatalf("received error response when configuring identity: %#v", resp)
	}

	// configuring the valid role we'll be able to login to
	roleData := map[string]interface{}{
		"bound_iam_principal_arn": []string{entity.canonicalArn(), "arn:aws:iam::123456789012:role/FakeRoleArn1*"}, // Fake ARN MUST be wildcard terminated because we're resolving unique IDs, and the wildcard termination prevents unique ID resolution
		"policies":                "root",
		"auth_type":               iamAuthType,
	}
	roleRequest := &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "role/" + testValidRoleName,
		Storage:   storage,
		Data:      roleData,
	}
	resp, err = b.HandleRequest(ctx, roleRequest)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: failed to create role: resp:%#v\nerr:%v", resp, err)
	}

	// configuring a valid role we won't be able to login to
	roleDataEc2 := map[string]interface{}{
		"auth_type":    "ec2",
		"policies":     "root",
		"bound_ami_id": "ami-1234567",
	}
	roleRequestEc2 := &logical.Request{
		Operation: logical.CreateOperation,
		Path:      "role/ec2only",
		Storage:   storage,
		Data:      roleDataEc2,
	}
	resp, err = b.HandleRequest(ctx, roleRequestEc2)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: failed to create role; resp:%#v\nerr:%v", resp, err)
	}

	fakeArn := "arn:aws:iam::123456789012:role/somePath/FakeRole"
	fakeArn2 := "arn:aws:iam::123456789012:role/somePath/FakeRole2"
	fakeArnResolverCount := 0
	fakeArnResolver := func(ctx context.Context, s logical.Storage, arn string) (string, error) {
		if strings.HasPrefix(arn, fakeArn) {
			fakeArnResolverCount++
			return fmt.Sprintf("FakeUniqueIdFor%s%d", arn, fakeArnResolverCount), nil
		}
		return b.resolveArnToRealUniqueId(context.Background(), s, arn)
	}
	b.resolveArnToUniqueIDFunc = fakeArnResolver

	// now we're creating the invalid role we won't be able to login to
	roleData["bound_iam_principal_arn"] = []string{fakeArn, fakeArn2}
	roleRequest.Path = "role/" + testInvalidRoleName
	resp, err = b.HandleRequest(context.Background(), roleRequest)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: didn't fail to create role: resp:%#v\nerr:%v", resp, err)
	}

	// now, create the request without the signed header
	stsRequestNoHeader, _ := stsService.GetCallerIdentityRequest(stsInputParams)
	stsRequestNoHeader.Sign()
	loginData, err := buildCallerIdentityLoginData(stsRequestNoHeader.HTTPRequest, testValidRoleName)
	if err != nil {
		t.Fatal(err)
	}
	loginRequest := &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "login",
		Storage:   storage,
		Data:      loginData,
	}
	resp, err = b.HandleRequest(ctx, loginRequest)
	if err != nil || resp == nil || !resp.IsError() {
		t.Errorf("bad: expected failed login due to missing header: resp:%#v\nerr:%v", resp, err)
	}

	// create the request with the invalid header value

	// Not reusing stsRequestNoHeader because the process of signing the request
	// and reading the body modifies the underlying request, so it's just cleaner
	// to get new requests.
	stsRequestInvalidHeader, _ := stsService.GetCallerIdentityRequest(stsInputParams)
	stsRequestInvalidHeader.HTTPRequest.Header.Add(iamServerIdHeader, "InvalidValue")
	stsRequestInvalidHeader.Sign()
	loginData, err = buildCallerIdentityLoginData(stsRequestInvalidHeader.HTTPRequest, testValidRoleName)
	if err != nil {
		t.Fatal(err)
	}
	loginRequest = &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "login",
		Storage:   storage,
		Data:      loginData,
	}
	resp, err = b.HandleRequest(ctx, loginRequest)
	if err != nil || resp == nil || !resp.IsError() {
		t.Errorf("bad: expected failed login due to invalid header: resp:%#v\nerr:%v", resp, err)
	}

	// Now, valid request against invalid role
	stsRequestValid, _ := stsService.GetCallerIdentityRequest(stsInputParams)
	stsRequestValid.HTTPRequest.Header.Add(iamServerIdHeader, testVaultHeaderValue)
	stsRequestValid.Sign()
	loginData, err = buildCallerIdentityLoginData(stsRequestValid.HTTPRequest, testInvalidRoleName)
	if err != nil {
		t.Fatal(err)
	}
	loginRequest = &logical.Request{
		Operation: logical.UpdateOperation,
		Path:      "login",
		Storage:   storage,
		Data:      loginData,
	}
	resp, err = b.HandleRequest(ctx, loginRequest)
	if err != nil || resp == nil || !resp.IsError() {
		t.Errorf("bad: expected failed login due to invalid role: resp:%#v\nerr:%v", resp, err)
	}

	loginData["role"] = "ec2only"
	resp, err = b.HandleRequest(ctx, loginRequest)
	if err != nil || resp == nil || !resp.IsError() {
		t.Errorf("bad: expected failed login due to bad auth type: resp:%#v\nerr:%v", resp, err)
	}

	// finally, the happy path test :)

	loginData["role"] = testValidRoleName
	resp, err = b.HandleRequest(ctx, loginRequest)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil || resp.Auth == nil || resp.IsError() {
		t.Fatalf("bad: expected valid login: resp:%#v", resp)
	}
	if resp.Auth.Alias == nil {
		t.Fatalf("bad: nil auth Alias")
	}
	if resp.Auth.Alias.Name != *testIdentity.Arn {
		t.Fatalf("bad: expected identity alias of %q, got %q instead", *testIdentity.Arn, resp.Auth.Alias.Name)
	}

	renewReq := generateRenewRequest(storage, resp.Auth)
	// dump a fake ARN into the metadata to ensure that we ONLY look
	// at the unique ID that has been generated
	renewReq.Auth.Metadata["canonical_arn"] = "fake_arn"
	emptyLoginFd := &framework.FieldData{
		Raw:    map[string]interface{}{},
		Schema: b.pathLogin().Fields,
	}
	// ensure we can renew
	resp, err = b.pathLoginRenew(ctx, renewReq, emptyLoginFd)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("got nil response from renew")
	}
	if resp.IsError() {
		t.Fatalf("got error when renewing: %#v", *resp)
	}

	// Now, fake out the unique ID resolver to ensure we fail login if the unique ID
	// changes from under us
	b.resolveArnToUniqueIDFunc = resolveArnToFakeUniqueId
	// First, we need to update the role to force Vault to use our fake resolver to
	// pick up the fake user ID
	roleData["bound_iam_principal_arn"] = entity.canonicalArn()
	roleRequest.Path = "role/" + testValidRoleName
	resp, err = b.HandleRequest(ctx, roleRequest)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: failed to recreate role: resp:%#v\nerr:%v", resp, err)
	}
	resp, err = b.HandleRequest(ctx, loginRequest)
	if err != nil || resp == nil || !resp.IsError() {
		t.Errorf("bad: expected failed login due to changed AWS role ID: resp: %#v\nerr:%v", resp, err)
	}

	// and ensure a renew no longer works
	resp, err = b.pathLoginRenew(ctx, renewReq, emptyLoginFd)
	if err == nil || (resp != nil && !resp.IsError()) {
		t.Errorf("bad: expected failed renew due to changed AWS role ID: resp: %#v", resp)
	}
	// Undo the fake resolver...
	b.resolveArnToUniqueIDFunc = b.resolveArnToRealUniqueId

	// Now test that wildcard matching works
	wildcardRoleName := "valid_wildcard"
	wildcardEntity := *entity
	wildcardEntity.FriendlyName = "*"
	roleData["bound_iam_principal_arn"] = []string{wildcardEntity.canonicalArn(), "arn:aws:iam::123456789012:role/DoesNotExist/Vault_Fake_Role*"}
	roleRequest.Path = "role/" + wildcardRoleName
	resp, err = b.HandleRequest(ctx, roleRequest)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: failed to create wildcard roles: resp:%#v\nerr:%v", resp, err)
	}

	loginData["role"] = wildcardRoleName
	resp, err = b.HandleRequest(ctx, loginRequest)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil || resp.Auth == nil || resp.IsError() {
		t.Fatalf("bad: expected valid login: resp:%#v", resp)
	}
	// and ensure we can renew
	renewReq = generateRenewRequest(storage, resp.Auth)
	resp, err = b.pathLoginRenew(ctx, renewReq, emptyLoginFd)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("got nil response from renew")
	}
	if resp.IsError() {
		t.Fatalf("got error when renewing: %#v", *resp)
	}
	// ensure the cache is populated

	clientUserIDRaw, ok := resp.Auth.InternalData["client_user_id"]
	if !ok {
		t.Errorf("client_user_id not found in response")
	}
	clientUserID, ok := clientUserIDRaw.(string)
	if !ok {
		t.Errorf("client_user_id is not a string: %#v", clientUserIDRaw)
	}

	cachedArn := b.getCachedUserId(clientUserID)
	if cachedArn == "" {
		t.Errorf("got empty ARN back from user ID cache; expected full arn")
	}

	// Test for renewal with period
	period := 600 * time.Second
	roleData["period"] = period.String()
	roleRequest.Path = "role/" + testValidRoleName
	resp, err = b.HandleRequest(ctx, roleRequest)
	if err != nil || (resp != nil && resp.IsError()) {
		t.Fatalf("bad: failed to create wildcard role: resp:%#v\nerr:%v", resp, err)
	}

	loginData["role"] = testValidRoleName
	resp, err = b.HandleRequest(ctx, loginRequest)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil || resp.Auth == nil || resp.IsError() {
		t.Fatalf("bad: expected valid login: resp:%#v", resp)
	}

	renewReq = generateRenewRequest(storage, resp.Auth)
	resp, err = b.pathLoginRenew(context.Background(), renewReq, emptyLoginFd)
	if err != nil {
		t.Fatal(err)
	}
	if resp == nil {
		t.Fatal("got nil response from renew")
	}
	if resp.IsError() {
		t.Fatalf("got error when renewing: %#v", *resp)
	}

	if resp.Auth.Period != period {
		t.Fatalf("expected a period value of %s in the response, got: %s", period, resp.Auth.Period)
	}
}

func generateRenewRequest(s logical.Storage, auth *logical.Auth) *logical.Request {
	renewReq := &logical.Request{
		Storage: s,
		Auth:    &logical.Auth{},
	}
	renewReq.Auth.InternalData = auth.InternalData
	renewReq.Auth.Metadata = auth.Metadata
	renewReq.Auth.LeaseOptions = auth.LeaseOptions
	renewReq.Auth.Policies = auth.Policies
	renewReq.Auth.Period = auth.Period

	return renewReq
}

func TestGeneratePartitionToRegionMap(t *testing.T) {
	m := generatePartitionToRegionMap()
	if m["aws"].ID() != "us-east-1" {
		t.Fatal("expected us-east-1 but received " + m["aws"].ID())
	}
	if m["aws-us-gov"].ID() != "us-gov-west-1" {
		t.Fatal("expected us-gov-west-1 but received " + m["aws-us-gov"].ID())
	}
}
