710 lines
25 KiB
Go
710 lines
25 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package awsauth
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
"github.com/aws/aws-sdk-go/service/sts"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
func TestBackend_pathLogin_getCallerIdentityResponse(t *testing.T) {
|
|
responseFromUser := `<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
|
<GetCallerIdentityResult>
|
|
<Arn>arn:aws:iam::123456789012:user/MyUserName</Arn>
|
|
<UserId>ASOMETHINGSOMETHINGSOMETHING</UserId>
|
|
<Account>123456789012</Account>
|
|
</GetCallerIdentityResult>
|
|
<ResponseMetadata>
|
|
<RequestId>7f4fc40c-853a-11e6-8848-8d035d01eb87</RequestId>
|
|
</ResponseMetadata>
|
|
</GetCallerIdentityResponse>`
|
|
expectedUserArn := "arn:aws:iam::123456789012:user/MyUserName"
|
|
|
|
responseFromAssumedRole := `<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
|
<GetCallerIdentityResult>
|
|
<Arn>arn:aws:sts::123456789012:assumed-role/RoleName/RoleSessionName</Arn>
|
|
<UserId>ASOMETHINGSOMETHINGELSE:RoleSessionName</UserId>
|
|
<Account>123456789012</Account>
|
|
</GetCallerIdentityResult>
|
|
<ResponseMetadata>
|
|
<RequestId>7f4fc40c-853a-11e6-8848-8d035d01eb87</RequestId>
|
|
</ResponseMetadata>
|
|
</GetCallerIdentityResponse>`
|
|
expectedRoleArn := "arn:aws:sts::123456789012:assumed-role/RoleName/RoleSessionName"
|
|
|
|
parsedUserResponse, err := parseGetCallerIdentityResponse(responseFromUser)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if parsedArn := parsedUserResponse.GetCallerIdentityResult[0].Arn; parsedArn != expectedUserArn {
|
|
t.Errorf("expected to parse arn %#v, got %#v", expectedUserArn, parsedArn)
|
|
}
|
|
|
|
parsedRoleResponse, err := parseGetCallerIdentityResponse(responseFromAssumedRole)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if parsedArn := parsedRoleResponse.GetCallerIdentityResult[0].Arn; parsedArn != expectedRoleArn {
|
|
t.Errorf("expected to parn arn %#v; got %#v", expectedRoleArn, parsedArn)
|
|
}
|
|
|
|
_, err = parseGetCallerIdentityResponse("SomeRandomGibberish")
|
|
if err == nil {
|
|
t.Errorf("expected to NOT parse random giberish, but didn't get an error")
|
|
}
|
|
}
|
|
|
|
func TestBackend_pathLogin_parseIamArn(t *testing.T) {
|
|
testParser := func(inputArn, expectedCanonicalArn string, expectedEntity iamEntity) {
|
|
entity, err := parseIamArn(inputArn)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if expectedCanonicalArn != "" && entity.canonicalArn() != expectedCanonicalArn {
|
|
t.Fatalf("expected to canonicalize ARN %q into %q but got %q instead", inputArn, expectedCanonicalArn, entity.canonicalArn())
|
|
}
|
|
if *entity != expectedEntity {
|
|
t.Fatalf("expected to get iamEntity %#v from input ARN %q but instead got %#v", expectedEntity, inputArn, *entity)
|
|
}
|
|
}
|
|
|
|
testParser("arn:aws:iam::123456789012:user/UserPath/MyUserName",
|
|
"arn:aws:iam::123456789012:user/MyUserName",
|
|
iamEntity{Partition: "aws", AccountNumber: "123456789012", Type: "user", Path: "UserPath", FriendlyName: "MyUserName"},
|
|
)
|
|
canonicalRoleArn := "arn:aws:iam::123456789012:role/RoleName"
|
|
testParser("arn:aws:sts::123456789012:assumed-role/RoleName/RoleSessionName",
|
|
canonicalRoleArn,
|
|
iamEntity{Partition: "aws", AccountNumber: "123456789012", Type: "assumed-role", FriendlyName: "RoleName", SessionInfo: "RoleSessionName"},
|
|
)
|
|
testParser("arn:aws:iam::123456789012:role/RolePath/RoleName",
|
|
canonicalRoleArn,
|
|
iamEntity{Partition: "aws", AccountNumber: "123456789012", Type: "role", Path: "RolePath", FriendlyName: "RoleName"},
|
|
)
|
|
testParser("arn:aws:iam::123456789012:instance-profile/profilePath/InstanceProfileName",
|
|
"",
|
|
iamEntity{Partition: "aws", AccountNumber: "123456789012", Type: "instance-profile", Path: "profilePath", FriendlyName: "InstanceProfileName"},
|
|
)
|
|
|
|
// Test that it properly handles pathological inputs...
|
|
_, err := parseIamArn("")
|
|
if err == nil {
|
|
t.Error("expected error from empty input string")
|
|
}
|
|
|
|
_, err = parseIamArn("arn:aws:iam::123456789012:role")
|
|
if err == nil {
|
|
t.Error("expected error from malformed ARN without a role name")
|
|
}
|
|
|
|
_, err = parseIamArn("arn:aws:iam")
|
|
if err == nil {
|
|
t.Error("expected error from incomplete ARN (arn:aws:iam)")
|
|
}
|
|
|
|
_, err = parseIamArn("arn:aws:iam::1234556789012:/")
|
|
if err == nil {
|
|
t.Error("expected error from empty principal type and no principal name (arn:aws:iam::1234556789012:/)")
|
|
}
|
|
_, err = parseIamArn("arn:aws:sts::1234556789012:assumed-role/role")
|
|
if err == nil {
|
|
t.Error("expected error from malformed assumed role ARN")
|
|
}
|
|
}
|
|
|
|
func TestBackend_validateVaultHeaderValue(t *testing.T) {
|
|
const canaryHeaderValue = "Vault-Server"
|
|
requestURL, err := url.Parse("https://sts.amazonaws.com/")
|
|
if err != nil {
|
|
t.Fatalf("error parsing test URL: %v", err)
|
|
}
|
|
postHeadersMissing := http.Header{
|
|
"Host": []string{"Foo"},
|
|
"Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"},
|
|
}
|
|
postHeadersInvalid := http.Header{
|
|
"Host": []string{"Foo"},
|
|
iamServerIdHeader: []string{"InvalidValue"},
|
|
"Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"},
|
|
}
|
|
postHeadersUnsigned := http.Header{
|
|
"Host": []string{"Foo"},
|
|
iamServerIdHeader: []string{canaryHeaderValue},
|
|
"Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"},
|
|
}
|
|
postHeadersValid := http.Header{
|
|
"Host": []string{"Foo"},
|
|
iamServerIdHeader: []string{canaryHeaderValue},
|
|
"Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"},
|
|
}
|
|
|
|
postHeadersSplit := http.Header{
|
|
"Host": []string{"Foo"},
|
|
iamServerIdHeader: []string{canaryHeaderValue},
|
|
"Authorization": []string{"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request", "SignedHeaders=content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"},
|
|
}
|
|
|
|
err = validateVaultHeaderValue(postHeadersMissing, requestURL, canaryHeaderValue)
|
|
if err == nil {
|
|
t.Error("validated POST request with missing Vault header")
|
|
}
|
|
|
|
err = validateVaultHeaderValue(postHeadersInvalid, requestURL, canaryHeaderValue)
|
|
if err == nil {
|
|
t.Error("validated POST request with invalid Vault header value")
|
|
}
|
|
|
|
err = validateVaultHeaderValue(postHeadersUnsigned, requestURL, canaryHeaderValue)
|
|
if err == nil {
|
|
t.Error("validated POST request with unsigned Vault header")
|
|
}
|
|
|
|
err = validateVaultHeaderValue(postHeadersValid, requestURL, canaryHeaderValue)
|
|
if err != nil {
|
|
t.Errorf("did NOT validate valid POST request: %v", err)
|
|
}
|
|
|
|
err = validateVaultHeaderValue(postHeadersSplit, requestURL, canaryHeaderValue)
|
|
if err != nil {
|
|
t.Errorf("did NOT validate valid POST request with split Authorization header: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestBackend_pathLogin_IAMHeaders tests login with iam_request_headers,
|
|
// supporting both base64 encoded string and JSON headers
|
|
func TestBackend_pathLogin_IAMHeaders(t *testing.T) {
|
|
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)
|
|
}
|
|
|
|
// sets up a test server to stand in for STS service
|
|
ts := setupIAMTestServer()
|
|
defer ts.Close()
|
|
|
|
clientConfigData := map[string]interface{}{
|
|
"iam_server_id_header_value": testVaultHeaderValue,
|
|
"sts_endpoint": ts.URL,
|
|
}
|
|
clientRequest := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/client",
|
|
Storage: storage,
|
|
Data: clientConfigData,
|
|
}
|
|
_, err = b.HandleRequest(context.Background(), clientRequest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Configure identity.
|
|
_, err = b.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/identity",
|
|
Storage: storage,
|
|
Data: map[string]interface{}{
|
|
"iam_alias": "role_id",
|
|
"iam_metadata": []string{
|
|
"account_id",
|
|
"auth_type",
|
|
"canonical_arn",
|
|
"client_arn",
|
|
"client_user_id",
|
|
"inferred_aws_region",
|
|
"inferred_entity_id",
|
|
"inferred_entity_type",
|
|
},
|
|
"ec2_alias": "role_id",
|
|
"ec2_metadata": []string{
|
|
"account_id",
|
|
"ami_id",
|
|
"instance_id",
|
|
"region",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a role entry
|
|
roleEntry := &awsRoleEntry{
|
|
RoleID: "foo",
|
|
Version: currentRoleStorageVersion,
|
|
AuthType: iamAuthType,
|
|
}
|
|
|
|
if err := b.setRole(context.Background(), storage, testValidRoleName, roleEntry); err != nil {
|
|
t.Fatalf("failed to set entry: %s", err)
|
|
}
|
|
|
|
// create a baseline loginData map structure, including iam_request_headers
|
|
// already base64encoded. This is the "Default" loginData used for all tests.
|
|
// Each sub test can override the map's iam_request_headers entry
|
|
loginData, err := defaultLoginData()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expectedAuthMetadata := map[string]string{
|
|
"account_id": "123456789012",
|
|
"auth_type": "iam",
|
|
"canonical_arn": "arn:aws:iam::123456789012:user/valid-role",
|
|
"client_arn": "arn:aws:iam::123456789012:user/valid-role",
|
|
"client_user_id": "ASOMETHINGSOMETHINGSOMETHING",
|
|
}
|
|
|
|
// expected errors for certain tests
|
|
missingHeaderErr := errors.New("error validating X-Vault-AWS-IAM-Server-ID header: missing header \"X-Vault-AWS-IAM-Server-ID\"")
|
|
parsingErr := errors.New("error making upstream request: error parsing STS response")
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Header interface{}
|
|
ExpectErr error
|
|
}{
|
|
{
|
|
Name: "Default",
|
|
},
|
|
{
|
|
Name: "Map-complete",
|
|
Header: map[string]interface{}{
|
|
"Content-Length": "43",
|
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
"User-Agent": "aws-sdk-go/1.14.24 (go1.11; darwin; amd64)",
|
|
"X-Amz-Date": "20180910T203328Z",
|
|
"X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting",
|
|
"Authorization": "AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4",
|
|
},
|
|
},
|
|
{
|
|
Name: "Map-incomplete",
|
|
Header: map[string]interface{}{
|
|
"Content-Length": "43",
|
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
"User-Agent": "aws-sdk-go/1.14.24 (go1.11; darwin; amd64)",
|
|
"X-Amz-Date": "20180910T203328Z",
|
|
"Authorization": "AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4",
|
|
},
|
|
ExpectErr: missingHeaderErr,
|
|
},
|
|
{
|
|
Name: "Map-illegal-header",
|
|
Header: map[string]interface{}{
|
|
"Content-Length": "43",
|
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
"User-Agent": "aws-sdk-go/1.14.24 (go1.11; darwin; amd64)",
|
|
"X-Amz-Date": "20180910T203328Z",
|
|
"Authorization": "AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4",
|
|
"X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting",
|
|
"X-Amz-Mallory-Header": "<?xml><h4ck0r/>",
|
|
},
|
|
ExpectErr: errors.New("invalid request header: X-Amz-Mallory-Header"),
|
|
},
|
|
{
|
|
Name: "JSON-complete",
|
|
Header: `{
|
|
"Content-Length":"43",
|
|
"Content-Type":"application/x-www-form-urlencoded; charset=utf-8",
|
|
"User-Agent":"aws-sdk-go/1.14.24 (go1.11; darwin; amd64)",
|
|
"X-Amz-Date":"20180910T203328Z",
|
|
"X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting",
|
|
"Authorization":"AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4"
|
|
}`,
|
|
},
|
|
{
|
|
Name: "JSON-incomplete",
|
|
Header: `{
|
|
"Content-Length":"43",
|
|
"Content-Type":"application/x-www-form-urlencoded; charset=utf-8",
|
|
"User-Agent":"aws-sdk-go/1.14.24 (go1.11; darwin; amd64)",
|
|
"X-Amz-Date":"20180910T203328Z",
|
|
"X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting",
|
|
"Authorization":"AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id"
|
|
}`,
|
|
ExpectErr: parsingErr,
|
|
},
|
|
{
|
|
Name: "Base64-complete",
|
|
Header: base64Complete(),
|
|
},
|
|
{
|
|
Name: "Base64-incomplete-missing-header",
|
|
Header: base64MissingVaultID(),
|
|
ExpectErr: missingHeaderErr,
|
|
},
|
|
{
|
|
Name: "Base64-incomplete-missing-auth-sig",
|
|
Header: base64MissingAuthField(),
|
|
ExpectErr: parsingErr,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
if tc.Header != nil {
|
|
loginData["iam_request_headers"] = tc.Header
|
|
}
|
|
|
|
loginRequest := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "login",
|
|
Storage: storage,
|
|
Data: loginData,
|
|
Connection: &logical.Connection{},
|
|
}
|
|
|
|
resp, err := b.HandleRequest(context.Background(), loginRequest)
|
|
if err != nil || resp == nil || resp.IsError() {
|
|
if tc.ExpectErr != nil && tc.ExpectErr.Error() == resp.Error().Error() {
|
|
return
|
|
}
|
|
t.Errorf("un expected failed login:\nresp: %#v\n\nerr: %v", resp, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedAuthMetadata, resp.Auth.Alias.Metadata) {
|
|
t.Errorf("expected metadata (%#v) to match (%#v)", expectedAuthMetadata, resp.Auth.Alias.Metadata)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestBackend_pathLogin_IAMRoleResolution tests role resolution for an Iam login
|
|
func TestBackend_pathLogin_IAMRoleResolution(t *testing.T) {
|
|
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)
|
|
}
|
|
|
|
// sets up a test server to stand in for STS service
|
|
ts := setupIAMTestServer()
|
|
defer ts.Close()
|
|
|
|
clientConfigData := map[string]interface{}{
|
|
"iam_server_id_header_value": testVaultHeaderValue,
|
|
"sts_endpoint": ts.URL,
|
|
}
|
|
clientRequest := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/client",
|
|
Storage: storage,
|
|
Data: clientConfigData,
|
|
}
|
|
_, err = b.HandleRequest(context.Background(), clientRequest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Configure identity.
|
|
_, err = b.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/identity",
|
|
Storage: storage,
|
|
Data: map[string]interface{}{
|
|
"iam_alias": "role_id",
|
|
"iam_metadata": []string{
|
|
"account_id",
|
|
"auth_type",
|
|
"canonical_arn",
|
|
"client_arn",
|
|
"client_user_id",
|
|
"inferred_aws_region",
|
|
"inferred_entity_id",
|
|
"inferred_entity_type",
|
|
},
|
|
"ec2_alias": "role_id",
|
|
"ec2_metadata": []string{
|
|
"account_id",
|
|
"ami_id",
|
|
"instance_id",
|
|
"region",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a role entry
|
|
roleEntry := &awsRoleEntry{
|
|
RoleID: "foo",
|
|
Version: currentRoleStorageVersion,
|
|
AuthType: iamAuthType,
|
|
}
|
|
|
|
if err := b.setRole(context.Background(), storage, testValidRoleName, roleEntry); err != nil {
|
|
t.Fatalf("failed to set entry: %s", err)
|
|
}
|
|
|
|
// create a baseline loginData map structure, including iam_request_headers
|
|
// already base64encoded. This is the "Default" loginData used for all tests.
|
|
// Each sub test can override the map's iam_request_headers entry
|
|
loginData, err := defaultLoginData()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
loginRequest := &logical.Request{
|
|
Operation: logical.ResolveRoleOperation,
|
|
Path: "login",
|
|
Storage: storage,
|
|
Data: loginData,
|
|
Connection: &logical.Connection{},
|
|
}
|
|
|
|
resp, err := b.HandleRequest(context.Background(), loginRequest)
|
|
if err != nil || resp == nil || resp.IsError() {
|
|
t.Errorf("unexpected failed role resolution:\nresp: %#v\n\nerr: %v", resp, err)
|
|
}
|
|
if resp.Data["role"] != testValidRoleName {
|
|
t.Fatalf("Role was not as expected. Expected %s, received %s", testValidRoleName, resp.Data["role"])
|
|
}
|
|
}
|
|
|
|
func TestBackend_defaultAliasMetadata(t *testing.T) {
|
|
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)
|
|
}
|
|
|
|
// sets up a test server to stand in for STS service
|
|
ts := setupIAMTestServer()
|
|
defer ts.Close()
|
|
|
|
clientConfigData := map[string]interface{}{
|
|
"iam_server_id_header_value": testVaultHeaderValue,
|
|
"sts_endpoint": ts.URL,
|
|
}
|
|
clientRequest := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/client",
|
|
Storage: storage,
|
|
Data: clientConfigData,
|
|
}
|
|
_, err = b.HandleRequest(context.Background(), clientRequest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Configure identity.
|
|
_, err = b.HandleRequest(context.Background(), &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "config/identity",
|
|
Storage: storage,
|
|
Data: map[string]interface{}{
|
|
"iam_alias": "role_id",
|
|
"ec2_alias": "role_id",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// create a role entry
|
|
roleEntry := &awsRoleEntry{
|
|
RoleID: "foo",
|
|
Version: currentRoleStorageVersion,
|
|
AuthType: iamAuthType,
|
|
}
|
|
|
|
if err := b.setRole(context.Background(), storage, testValidRoleName, roleEntry); err != nil {
|
|
t.Fatalf("failed to set entry: %s", err)
|
|
}
|
|
|
|
// create a baseline loginData map structure, including iam_request_headers
|
|
// already base64encoded. This is the "Default" loginData used for all tests.
|
|
// Each sub test can override the map's iam_request_headers entry
|
|
loginData, err := defaultLoginData()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expectedAliasMetadata := map[string]string{
|
|
"account_id": "123456789012",
|
|
"auth_type": "iam",
|
|
}
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Header interface{}
|
|
ExpectErr error
|
|
}{
|
|
{
|
|
Name: "Default",
|
|
},
|
|
{
|
|
Name: "Map-complete",
|
|
Header: map[string]interface{}{
|
|
"Content-Length": "43",
|
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
|
"User-Agent": "aws-sdk-go/1.14.24 (go1.11; darwin; amd64)",
|
|
"X-Amz-Date": "20180910T203328Z",
|
|
"X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting",
|
|
"Authorization": "AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4",
|
|
},
|
|
},
|
|
{
|
|
Name: "JSON-complete",
|
|
Header: `{
|
|
"Content-Length":"43",
|
|
"Content-Type":"application/x-www-form-urlencoded; charset=utf-8",
|
|
"User-Agent":"aws-sdk-go/1.14.24 (go1.11; darwin; amd64)",
|
|
"X-Amz-Date":"20180910T203328Z",
|
|
"X-Vault-Aws-Iam-Server-Id": "VaultAcceptanceTesting",
|
|
"Authorization":"AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180910/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=cdef5819b2e97f1ff0f3e898fd2621aa03af00a4ec3e019122c20e5482534bf4"
|
|
}`,
|
|
},
|
|
{
|
|
Name: "Base64-complete",
|
|
Header: base64Complete(),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
if tc.Header != nil {
|
|
loginData["iam_request_headers"] = tc.Header
|
|
}
|
|
|
|
loginRequest := &logical.Request{
|
|
Operation: logical.UpdateOperation,
|
|
Path: "login",
|
|
Storage: storage,
|
|
Data: loginData,
|
|
Connection: &logical.Connection{},
|
|
}
|
|
|
|
resp, err := b.HandleRequest(context.Background(), loginRequest)
|
|
if err != nil || resp == nil || resp.IsError() {
|
|
if tc.ExpectErr != nil && tc.ExpectErr.Error() == resp.Error().Error() {
|
|
return
|
|
}
|
|
t.Errorf("un expected failed login:\nresp: %#v\n\nerr: %v", resp, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(expectedAliasMetadata, resp.Auth.Alias.Metadata) {
|
|
t.Errorf("expected metadata (%#v) to match (%#v)", expectedAliasMetadata, resp.Auth.Alias.Metadata)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func defaultLoginData() (map[string]interface{}, error) {
|
|
awsSession, err := session.NewSession()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create session: %s", err)
|
|
}
|
|
|
|
stsService := sts.New(awsSession)
|
|
stsInputParams := &sts.GetCallerIdentityInput{}
|
|
stsRequestValid, _ := stsService.GetCallerIdentityRequest(stsInputParams)
|
|
stsRequestValid.HTTPRequest.Header.Add(iamServerIdHeader, testVaultHeaderValue)
|
|
stsRequestValid.HTTPRequest.Header.Add("Authorization", fmt.Sprintf("%s,%s,%s",
|
|
"AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/iam/aws4_request",
|
|
"SignedHeaders=content-type;host;x-amz-date;x-vault-aws-iam-server-id",
|
|
"Signature=5d672d79c15b13162d9279b0855cfba6789a8edb4c82c400e06b5924a6f2b5d7"))
|
|
stsRequestValid.Sign()
|
|
|
|
return buildCallerIdentityLoginData(stsRequestValid.HTTPRequest, testValidRoleName)
|
|
}
|
|
|
|
// setupIAMTestServer configures httptest server to intercept and respond to the
|
|
// IAM login path's invocation of submitCallerIdentityRequest (which does not
|
|
// use the AWS SDK), which receieves the mocked response responseFromUser
|
|
// containing user information matching the role.
|
|
func setupIAMTestServer() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
responseString := `<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
|
|
<GetCallerIdentityResult>
|
|
<Arn>arn:aws:iam::123456789012:user/valid-role</Arn>
|
|
<UserId>ASOMETHINGSOMETHINGSOMETHING</UserId>
|
|
<Account>123456789012</Account>
|
|
</GetCallerIdentityResult>
|
|
<ResponseMetadata>
|
|
<RequestId>7f4fc40c-853a-11e6-8848-8d035d01eb87</RequestId>
|
|
</ResponseMetadata>
|
|
</GetCallerIdentityResponse>
|
|
`
|
|
|
|
auth := r.Header.Get("Authorization")
|
|
parts := strings.Split(auth, ",")
|
|
for i, s := range parts {
|
|
s = strings.TrimSpace(s)
|
|
key := strings.Split(s, "=")
|
|
parts[i] = key[0]
|
|
}
|
|
|
|
// verify the "Authorization" header contains all the expected parts
|
|
expectedAuthParts := []string{"AWS4-HMAC-SHA256 Credential", "SignedHeaders", "Signature"}
|
|
var matchingCount int
|
|
for _, v := range parts {
|
|
for _, z := range expectedAuthParts {
|
|
if z == v {
|
|
matchingCount++
|
|
}
|
|
}
|
|
}
|
|
if matchingCount != len(expectedAuthParts) {
|
|
responseString = "missing auth parts"
|
|
}
|
|
w.Header().Add("Content-Type", "text/xml")
|
|
fmt.Fprintln(w, responseString)
|
|
}))
|
|
}
|
|
|
|
// base64Complete returns a base64 encoded auth header as expected
|
|
func base64Complete() string {
|
|
min := `{"Authorization":["AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180907/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=97086b0531854844099fc52733fa2c88a2bfb54b2689600c6e249358a8353b52"],"Content-Length":["43"],"Content-Type":["application/x-www-form-urlencoded; charset=utf-8"],"User-Agent":["aws-sdk-go/1.14.24 (go1.11; darwin; amd64)"],"X-Amz-Date":["20180907T222145Z"],"X-Vault-Aws-Iam-Server-Id":["VaultAcceptanceTesting"]}`
|
|
return min
|
|
}
|
|
|
|
// base64MissingVaultID returns a base64 encoded auth header, that omits the
|
|
// Vault ID header
|
|
func base64MissingVaultID() string {
|
|
min := `{"Authorization":["AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180907/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id, Signature=97086b0531854844099fc52733fa2c88a2bfb54b2689600c6e249358a8353b52"],"Content-Length":["43"],"Content-Type":["application/x-www-form-urlencoded; charset=utf-8"],"User-Agent":["aws-sdk-go/1.14.24 (go1.11; darwin; amd64)"],"X-Amz-Date":["20180907T222145Z"]}`
|
|
return min
|
|
}
|
|
|
|
// base64MissingAuthField returns a base64 encoded Auth header, that omits the
|
|
// "Signature" part
|
|
func base64MissingAuthField() string {
|
|
min := `{"Authorization":["AWS4-HMAC-SHA256 Credential=AKIAJPQ466AIIQW4LPSQ/20180907/us-east-1/sts/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-date;x-vault-aws-iam-server-id"],"Content-Length":["43"],"Content-Type":["application/x-www-form-urlencoded; charset=utf-8"],"User-Agent":["aws-sdk-go/1.14.24 (go1.11; darwin; amd64)"],"X-Amz-Date":["20180907T222145Z"],"X-Vault-Aws-Iam-Server-Id":["VaultAcceptanceTesting"]}`
|
|
return min
|
|
}
|