e06a78a474
* Rename builtin/credential/aws-ec2 to aws The aws-ec2 authentication backend is being expanded and will become the generic aws backend. This is a small rename commit to keep the commit history clean. * Expand aws-ec2 backend to more generic aws This adds the ability to authenticate arbitrary AWS IAM principals using AWS's sts:GetCallerIdentity method. The AWS-EC2 auth backend is being to just AWS with the expansion. * Add missing aws auth handler to CLI This was omitted from the previous commit * aws auth backend general variable name cleanup Also fixed a bug where allowed auth types weren't being checked upon login, and added tests for it. * Update docs for the aws auth backend * Refactor aws bind validation * Fix env var override in aws backend test Intent is to override the AWS environment variables with the TEST_* versions if they are set, but the reverse was happening. * Update docs on use of IAM authentication profile AWS now allows you to change the instance profile of a running instance, so the use case of "a long-lived instance that's not in an instance profile" no longer means you have to use the the EC2 auth method. You can now just change the instance profile on the fly. * Fix typo in aws auth cli help * Respond to PR feedback * More PR feedback * Respond to additional PR feedback * Address more feedback on aws auth PR * Make aws auth_type immutable per role * Address more aws auth PR feedback * Address more iam auth PR feedback * Rename aws-ec2.html.md to aws.html.md Per PR feedback, to go along with new backend name. * Add MountType to logical.Request * Make default aws auth_type dependent upon MountType When MountType is aws-ec2, default to ec2 auth_type for backwards compatibility with legacy roles. Otherwise, default to iam. * Pass MountPoint and MountType back up to the core Previously the request router reset the MountPoint and MountType back to the empty string before returning to the core. This ensures they get set back to the correct values.
1505 lines
41 KiB
Go
1505 lines
41 KiB
Go
package awsauth
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"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"
|
|
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": "mCtSM8ZUEQ3mOFVZYPBQkf2sO6F/W7a5TVzrl3Oj",
|
|
}
|
|
|
|
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)
|
|
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 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)
|
|
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)
|
|
}
|
|
testIdentityArn, _, _, 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": testIdentityArn,
|
|
"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)
|
|
}
|
|
|
|
// now we're creating the invalid role we won't be able to login to
|
|
roleData["bound_iam_principal_arn"] = "arn:aws:iam::123456789012:role/FakeRole"
|
|
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 tests :)
|
|
|
|
loginData["role"] = testValidRoleName
|
|
resp, err = b.HandleRequest(loginRequest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp == nil || resp.Auth == nil || resp.IsError() {
|
|
t.Errorf("bad: expected valid login: resp:%#v", resp)
|
|
}
|
|
}
|