Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helper for aliasmetadata and add to AWS auth #8783

Merged
merged 20 commits into from Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
70 changes: 63 additions & 7 deletions builtin/credential/aws/path_config_identity.go
Expand Up @@ -5,10 +5,51 @@ import (
"fmt"

"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/aliasmetadata"
"github.com/hashicorp/vault/sdk/helper/strutil"
"github.com/hashicorp/vault/sdk/logical"
)

var (
// iamAliasMetadataFields is a list of the default alias metadata
// added to tokens during login. The default alias type used
// by this back-end is the role ID. Subsequently, the default
// fields included are expected to have a low rate of change
// when the role ID is in use.
iamAliasMetadataFields = &aliasmetadata.Fields{
FieldName: "iam_metadata",
Default: []string{
"account_id",
"auth_type",
},
AvailableToAdd: []string{
"canonical_arn",
"client_arn",
"client_user_id",
"inferred_aws_region",
"inferred_entity_id",
"inferred_entity_type",
},
}

// ec2AliasMetadataFields is a list of the default alias metadata
// added to tokens during login. The default alias type used
// by this back-end is the role ID. Subsequently, the default
// fields included are expected to have a low rate of change
// when the role ID is in use.
ec2AliasMetadataFields = &aliasmetadata.Fields{
FieldName: "ec2_metadata",
Default: []string{
"account_id",
},
AvailableToAdd: []string{
"ami_id",
"instance_id",
"region",
},
}
)

func (b *backend) pathConfigIdentity() *framework.Path {
return &framework.Path{
Pattern: "config/identity$",
Expand All @@ -18,11 +59,13 @@ func (b *backend) pathConfigIdentity() *framework.Path {
Default: identityAliasIAMUniqueID,
Description: fmt.Sprintf("Configure how the AWS auth method generates entity aliases when using IAM auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasIAMUniqueID, identityAliasIAMFullArn, identityAliasRoleID),
},
iamAliasMetadataFields.FieldName: aliasmetadata.FieldSchema(iamAliasMetadataFields),
"ec2_alias": {
Type: framework.TypeString,
Default: identityAliasEC2InstanceID,
Description: fmt.Sprintf("Configure how the AWS auth method generates entity alias when using EC2 auth. Valid values are %q, %q, and %q. Defaults to %q.", identityAliasRoleID, identityAliasEC2InstanceID, identityAliasEC2ImageID, identityAliasRoleID),
},
ec2AliasMetadataFields.FieldName: aliasmetadata.FieldSchema(ec2AliasMetadataFields),
},

Operations: map[logical.Operation]framework.OperationHandler{
Expand All @@ -45,9 +88,12 @@ func identityConfigEntry(ctx context.Context, s logical.Storage) (*identityConfi
return nil, err
}

var entry identityConfig
entry := &identityConfig{
IAMAliasMetadataHandler: aliasmetadata.NewHandler(iamAliasMetadataFields),
EC2AliasMetadataHandler: aliasmetadata.NewHandler(ec2AliasMetadataFields),
}
if entryRaw != nil {
if err := entryRaw.DecodeJSON(&entry); err != nil {
if err := entryRaw.DecodeJSON(entry); err != nil {
return nil, err
}
}
Expand All @@ -60,7 +106,7 @@ func identityConfigEntry(ctx context.Context, s logical.Storage) (*identityConfi
entry.EC2Alias = identityAliasRoleID
}

return &entry, nil
return entry, nil
}

func pathConfigIdentityRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
Expand All @@ -71,8 +117,10 @@ func pathConfigIdentityRead(ctx context.Context, req *logical.Request, _ *framew

return &logical.Response{
Data: map[string]interface{}{
"iam_alias": config.IAMAlias,
"ec2_alias": config.EC2Alias,
"iam_alias": config.IAMAlias,
iamAliasMetadataFields.FieldName: config.IAMAliasMetadataHandler.GetAliasMetadata(),
"ec2_alias": config.EC2Alias,
ec2AliasMetadataFields.FieldName: config.EC2AliasMetadataHandler.GetAliasMetadata(),
},
}, nil
}
Expand Down Expand Up @@ -102,6 +150,12 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f
}
config.EC2Alias = ec2Alias
}
if err := config.IAMAliasMetadataHandler.ParseAliasMetadata(data); err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}
if err := config.EC2AliasMetadataHandler.ParseAliasMetadata(data); err != nil {
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
}

entry, err := logical.StorageEntryJSON("config/identity", config)
if err != nil {
Expand All @@ -117,8 +171,10 @@ func pathConfigIdentityUpdate(ctx context.Context, req *logical.Request, data *f
}

type identityConfig struct {
IAMAlias string `json:"iam_alias"`
EC2Alias string `json:"ec2_alias"`
IAMAlias string `json:"iam_alias"`
IAMAliasMetadataHandler aliasmetadata.Handler `json:"iam_alias_metadata_handler"`
EC2Alias string `json:"ec2_alias"`
EC2AliasMetadataHandler aliasmetadata.Handler `json:"ec2_alias_metadata_handler"`
}

const identityAliasIAMUniqueID = "unique_id"
Expand Down
40 changes: 24 additions & 16 deletions builtin/credential/aws/path_login.go
Expand Up @@ -845,15 +845,19 @@ func (b *backend) pathLoginUpdateEc2(ctx context.Context, req *logical.Request,
},
Alias: &logical.Alias{
Name: identityAlias,
Metadata: map[string]string{
"instance_id": identityDocParsed.InstanceID,
"region": identityDocParsed.Region,
"account_id": identityDocParsed.AccountID,
"ami_id": identityDocParsed.AmiID,
},
},
}
roleEntry.PopulateTokenAuth(auth)
if err := identityConfigEntry.EC2AliasMetadataHandler.PopulateDesiredAliasMetadata(auth, map[string]string{
"instance_id": identityDocParsed.InstanceID,
"region": identityDocParsed.Region,
"account_id": identityDocParsed.AccountID,
"ami_id": identityDocParsed.AmiID,
}); err != nil {
if b.Logger().IsWarn() {
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
b.Logger().Warn(fmt.Sprintf("unable to set alias metadata due to %s", err))
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
}
}

resp := &logical.Response{
Auth: auth,
Expand Down Expand Up @@ -1375,19 +1379,23 @@ func (b *backend) pathLoginUpdateIam(ctx context.Context, req *logical.Request,
DisplayName: entity.FriendlyName,
Alias: &logical.Alias{
Name: identityAlias,
Metadata: map[string]string{
"client_arn": callerID.Arn,
"canonical_arn": entity.canonicalArn(),
"client_user_id": callerUniqueId,
"auth_type": iamAuthType,
"inferred_entity_type": inferredEntityType,
"inferred_entity_id": inferredEntityID,
"inferred_aws_region": roleEntry.InferredAWSRegion,
"account_id": entity.AccountNumber,
},
},
}
roleEntry.PopulateTokenAuth(auth)
if err := identityConfigEntry.IAMAliasMetadataHandler.PopulateDesiredAliasMetadata(auth, map[string]string{
"client_arn": callerID.Arn,
"canonical_arn": entity.canonicalArn(),
"client_user_id": callerUniqueId,
"auth_type": iamAuthType,
"inferred_entity_type": inferredEntityType,
"inferred_entity_id": inferredEntityID,
"inferred_aws_region": roleEntry.InferredAWSRegion,
"account_id": entity.AccountNumber,
}); err != nil {
if b.Logger().IsWarn() {
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved
b.Logger().Warn(fmt.Sprintf("unable to set alias metadata due to %s", err))
}
}

return &logical.Response{
Auth: auth,
Expand Down
166 changes: 166 additions & 0 deletions builtin/credential/aws/path_login_test.go
Expand Up @@ -214,8 +214,38 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) {
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{
"default",
"canonical_arn",
"client_arn",
"client_user_id",
"inferred_aws_region",
"inferred_entity_id",
"inferred_entity_type",
},
"ec2_alias": "role_id",
"ec2_metadata": []string{
"default",
"ami_id",
"instance_id",
"region",
},
},
})
if err != nil {
t.Fatal(err)
}
tyrannosaurus-becks marked this conversation as resolved.
Show resolved Hide resolved

// create a role entry
roleEntry := &awsRoleEntry{
RoleID: "foo",
Version: currentRoleStorageVersion,
AuthType: iamAuthType,
}
Expand Down Expand Up @@ -346,6 +376,142 @@ func TestBackend_pathLogin_IAMHeaders(t *testing.T) {
}
}

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 {
Expand Down