package awsauth import ( "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/http" "os" "strings" "testing" "time" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/sts" "github.com/hashicorp/vault/helper/policyutil" "github.com/hashicorp/vault/logical" "github.com/hashicorp/vault/logical/framework" logicaltest "github.com/hashicorp/vault/logical/testing" ) func TestBackend_CreateParseVerifyRoleTag(t *testing.T) { // create a backend config := logical.TestBackendConfig() storage := &logical.InmemStorage{} config.StorageView = storage b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(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(&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.lockedAWSRole(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(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(&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.lockedAWSRole(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) { // create a backend config := logical.TestBackendConfig() storage := &logical.InmemStorage{} config.StorageView = storage b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(config) if err != nil { t.Fatal(err) } // test update operation tidyRequest := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/tidy/identity-whitelist", Storage: storage, } data := map[string]interface{}{ "safety_buffer": "60", "disable_periodic_tidy": true, } tidyRequest.Data = data _, err = b.HandleRequest(tidyRequest) if err != nil { t.Fatal(err) } // test read operation tidyRequest.Operation = logical.ReadOperation resp, err := b.HandleRequest(tidyRequest) if err != nil { t.Fatal(err) } if resp == nil || resp.IsError() { t.Fatalf("failed to read config/tidy/identity-whitelist endpoint") } 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:%s 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(tidyRequest) if err != nil { t.Fatal(err) } if resp != nil { t.Fatalf("failed to delete config/tidy/identity-whitelist") } } func TestBackend_ConfigTidyRoleTags(t *testing.T) { config := logical.TestBackendConfig() storage := &logical.InmemStorage{} config.StorageView = storage b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(config) if err != nil { t.Fatal(err) } // test update operation tidyRequest := &logical.Request{ Operation: logical.UpdateOperation, Path: "config/tidy/roletag-blacklist", Storage: storage, } data := map[string]interface{}{ "safety_buffer": "60", "disable_periodic_tidy": true, } tidyRequest.Data = data _, err = b.HandleRequest(tidyRequest) if err != nil { t.Fatal(err) } // test read operation tidyRequest.Operation = logical.ReadOperation resp, err := b.HandleRequest(tidyRequest) if err != nil { t.Fatal(err) } if resp == nil || resp.IsError() { t.Fatalf("failed to read config/tidy/roletag-blacklist endpoint") } 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:%s 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(tidyRequest) if err != nil { t.Fatal(err) } if resp != nil { t.Fatalf("failed to delete config/tidy/roletag-blacklist") } } func TestBackend_TidyIdentities(t *testing.T) { config := logical.TestBackendConfig() storage := &logical.InmemStorage{} config.StorageView = storage b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(config) if err != nil { t.Fatal(err) } // test update operation _, err = b.HandleRequest(&logical.Request{ Operation: logical.UpdateOperation, Path: "tidy/identity-whitelist", Storage: storage, }) if err != nil { t.Fatal(err) } } func TestBackend_TidyRoleTags(t *testing.T) { config := logical.TestBackendConfig() storage := &logical.InmemStorage{} config.StorageView = storage b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(config) if err != nil { t.Fatal(err) } // test update operation _, err = b.HandleRequest(&logical.Request{ Operation: logical.UpdateOperation, Path: "tidy/roletag-blacklist", Storage: storage, }) if err != nil { t.Fatal(err) } } func TestBackend_ConfigClient(t *testing.T) { config := logical.TestBackendConfig() storage := &logical.InmemStorage{} config.StorageView = storage b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(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, Backend: b, Steps: []logicaltest.TestStep{ stepCreate, stepInvalidAccessKey, stepInvalidSecretKey, stepUpdate, }, }) // test existence check returning false checkFound, exists, err := b.HandleExistenceCheck(&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(configClientCreateRequest) if err != nil { t.Fatal(err) } //test existence check returning true checkFound, exists, err = b.HandleExistenceCheck(&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(endpointReq) if err != nil { t.Fatal(err) } endpointReq.Operation = logical.ReadOperation resp, err := b.HandleRequest(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 b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(config) if err != nil { t.Fatal(err) } certReq := &logical.Request{ Operation: logical.CreateOperation, Storage: storage, Path: "config/certificate/cert1", } checkFound, exists, err := b.HandleExistenceCheck(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(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(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(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(certReq) if err != nil { t.Fatal(err) } certReq.Operation = logical.ListOperation certReq.Path = "config/certificates" // test list operation resp, err = b.HandleRequest(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(certReq) if err != nil { t.Fatal(err) } certReq.Path = "config/certificate/cert2" _, err = b.HandleRequest(certReq) if err != nil { t.Fatal(err) } certReq.Operation = logical.ListOperation certReq.Path = "config/certificates" // test list operation resp, err = b.HandleRequest(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 b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(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(&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(&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(&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(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 b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(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(&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(&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(&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) { // 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(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(&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(&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) } // blacklist that role tag resp, err = b.HandleRequest(&logical.Request{ Operation: logical.UpdateOperation, Path: "roletag-blacklist/" + tag, Storage: storage, }) if err != nil { t.Fatal(err) } if resp != nil { t.Fatalf("failed to blacklist the roletag: %s\n", tag) } // read the blacklist entry resp, err = b.HandleRequest(&logical.Request{ Operation: logical.ReadOperation, Path: "roletag-blacklist/" + tag, Storage: storage, }) if err != nil { t.Fatal(err) } if resp == nil || resp.Data == nil { t.Fatalf("failed to read the blacklisted role tag: %s\n", tag) } if resp.IsError() { t.Fatalf("failed to read the blacklisted role tag:%s. Err: %s\n", tag, resp.Data["error"]) } // delete the blacklisted entry _, err = b.HandleRequest(&logical.Request{ Operation: logical.DeleteOperation, Path: "roletag-blacklist/" + tag, Storage: storage, }) if err != nil { t.Fatal(err) } // try to read the deleted entry tagEntry, err := b.lockedBlacklistRoleTagEntry(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_PKCS7 // TEST_AWS_EC2_AMI_ID // TEST_AWS_EC2_ACCOUNT_ID // TEST_AWS_EC2_IAM_ROLE_ARN // // 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_LoginWithInstanceIdentityDocAndWhitelistIdentity(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 '%s' set", logicaltest.TestEnvVar)) return } pkcs7 := os.Getenv("TEST_AWS_EC2_PKCS7") if pkcs7 == "" { t.Fatalf("env var TEST_AWS_EC2_PKCS7 not set") } amiID := os.Getenv("TEST_AWS_EC2_AMI_ID") if amiID == "" { t.Fatalf("env var TEST_AWS_EC2_AMI_ID not set") } iamARN := os.Getenv("TEST_AWS_EC2_IAM_ROLE_ARN") if iamARN == "" { t.Fatalf("env var TEST_AWS_EC2_IAM_ROLE_ARN not set") } accountID := os.Getenv("TEST_AWS_EC2_ACCOUNT_ID") if accountID == "" { t.Fatalf("env var TEST_AWS_EC2_ACCOUNT_ID not set") } 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(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(&logical.Request{ Operation: logical.UpdateOperation, Storage: storage, Path: "config/client", Data: clientConfig, }) if err != nil { t.Fatal(err) } } loginInput := map[string]interface{}{ "pkcs7": pkcs7, "nonce": "vault-client-nonce", } // 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, } // Place the wrong AMI ID in the role data. data := map[string]interface{}{ "auth_type": "ec2", "policies": "root", "max_ttl": "120s", "bound_ami_id": "wrong_ami_id", "bound_account_id": accountID, "bound_iam_role_arn": iamARN, } roleReq := &logical.Request{ Operation: logical.CreateOperation, Path: "role/" + roleName, Storage: storage, Data: data, } // Save the role with wrong AMI ID resp, err := b.HandleRequest(roleReq) if err != nil && (resp != nil && resp.IsError()) { t.Fatalf("bad: resp: %#v\nerr:%v", resp, err) } // Expect failure when tried to login with wrong AMI ID resp, err = b.HandleRequest(loginRequest) if err != nil || resp == nil || (resp != nil && !resp.IsError()) { t.Fatalf("bad: expected error response: resp:%#v\nerr:%v", resp, err) } // Place the correct AMI ID, but make the AccountID wrong roleReq.Operation = logical.UpdateOperation data["bound_ami_id"] = amiID data["bound_account_id"] = "wrong-account-id" resp, err = b.HandleRequest(roleReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("bad: failed to create role: resp:%#v\nerr:%v", resp, err) } // Expect failure when tried to login with incorrect AccountID resp, err = b.HandleRequest(loginRequest) if err != nil || resp == nil || (resp != nil && !resp.IsError()) { t.Fatalf("bad: expected error response: resp:%#v\nerr:%v", resp, err) } // Place the correct AccountID, but make the wrong IAMRoleARN data["bound_account_id"] = accountID data["bound_iam_role_arn"] = "wrong_iam_role_arn" resp, err = b.HandleRequest(roleReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("bad: failed to create role: resp:%#v\nerr:%v", resp, err) } // Attempt to login and expect a fail because IAM Role ARN is wrong resp, err = b.HandleRequest(loginRequest) if err != nil || resp == nil || (resp != nil && !resp.IsError()) { t.Fatalf("bad: expected error response: resp:%#v\nerr:%v", resp, err) } // place the correct IAM role ARN data["bound_iam_role_arn"] = iamARN resp, err = b.HandleRequest(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(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") } loginInput["nonce"] = "changed-vault-client-nonce" // try to login again with changed nonce resp, err = b.HandleRequest(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 a whitelist identity entry is created after the login. wlRequest := &logical.Request{ Operation: logical.ReadOperation, Path: "identity-whitelist/" + instanceID, Storage: storage, } resp, err = b.HandleRequest(wlRequest) if err != nil { t.Fatal(err) } if resp == nil || resp.Data == nil || resp.Data["role"] != roleName { t.Fatalf("failed to read whitelist identity") } // Delete the whitelist identity entry. wlRequest.Operation = logical.DeleteOperation resp, err = b.HandleRequest(wlRequest) if err != nil { t.Fatal(err) } if resp.IsError() { t.Fatalf("failed to delete whitelist identity") } // Allow a fresh login. resp, err = b.HandleRequest(loginRequest) if err != nil { t.Fatal(err) } if resp == nil || resp.Auth == nil || resp.IsError() { t.Fatalf("login attempt failed") } } func TestBackend_pathStsConfig(t *testing.T) { config := logical.TestBackendConfig() storage := &logical.InmemStorage{} config.StorageView = storage b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(config) if err != nil { t.Fatal(err) } stsReq := &logical.Request{ Operation: logical.CreateOperation, Storage: storage, Path: "config/sts/account1", } checkFound, exists, err := b.HandleExistenceCheck(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", } stsReq.Data = data // test create operation resp, err := b.HandleRequest(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(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(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 = data // create another entry to test the list operation resp, err = b.HandleRequest(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(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(stsReq) if err != nil || (resp != nil && resp.IsError()) { t.Fatal(err) } stsReq.Path = "config/sts/account2" resp, err = b.HandleRequest(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(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 := ioutil.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), "request_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 '%s' set", logicaltest.TestEnvVar)) return } storage := &logical.InmemStorage{} config := logical.TestBackendConfig() config.StorageView = storage b, err := Backend(config) if err != nil { t.Fatal(err) } err = b.Setup(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"} { // restore existing environment variables (in case future tests need them) defer os.Setenv(envvar, os.Getenv(envvar)) os.Setenv(envvar, os.Getenv("TEST_"+envvar)) } 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 two different roles: // a. One bound to our test user // b. One bound to a garbage ARN // 3. Pass in a request that doesn't have the signed header, ensure // we're not allowed to login // 4. Passin a request that has a validly signed header, but the wrong // value, ensure it doesn't allow login // 5. Pass in a request that has a validly signed request, ensure // it allows us to login to our role // 6. Pass in a request that has a validly signed request, asking for // the other role, ensure it fails const testVaultHeaderValue = "VaultAcceptanceTesting" const testValidRoleName = "valid-role" const testInvalidRoleName = "invalid-role" 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(clientRequest) if err != nil { t.Fatal(err) } // configuring the valid role we'll be able to login to roleData := map[string]interface{}{ "bound_iam_principal_arn": entity.canonicalArn(), "policies": "root", "auth_type": iamAuthType, } roleRequest := &logical.Request{ Operation: logical.CreateOperation, Path: "role/" + testValidRoleName, Storage: storage, Data: roleData, } resp, err := b.HandleRequest(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(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" fakeArnResolver := func(s logical.Storage, arn string) (string, error) { if arn == fakeArn { return fmt.Sprintf("FakeUniqueIdFor%s", fakeArn), nil } return b.resolveArnToRealUniqueId(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"] = fakeArn roleRequest.Path = "role/" + testInvalidRoleName resp, err = b.HandleRequest(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(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(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(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(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(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) // 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" empty_login_fd := &framework.FieldData{ Raw: map[string]interface{}{}, Schema: pathLogin(b).Fields, } // ensure we can renew resp, err = b.pathLoginRenew(renewReq, empty_login_fd) 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(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(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(renewReq, empty_login_fd) if err == nil || (resp != nil && !resp.IsError()) { t.Errorf("bad: expected failed renew due to changed AWS role ID: resp: %#v", resp, err) } // 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"] = wildcardEntity.canonicalArn() roleRequest.Path = "role/" + wildcardRoleName resp, err = b.HandleRequest(roleRequest) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("bad: failed to create wildcard role: resp:%#v\nerr:%v", resp, err) } loginData["role"] = wildcardRoleName resp, err = b.HandleRequest(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(renewReq, empty_login_fd) 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 cachedArn := b.getCachedUserId(resp.Auth.Metadata["client_user_id"]) if cachedArn == "" { t.Errorf("got empty ARN back from user ID cache; expected full arn") } } 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.IssueTime = time.Now() return renewReq }