mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	Add support for cross account management of static roles in AWS Secrets (#29645)
* aws-secrets/add-cross-acc-mgmt-static-roles * refactor * add function pointer for tests * delete commented out code * update * update comment * update func name * add flag * remove docs
This commit is contained in:
		 Milena Zlaticanin
					Milena Zlaticanin
				
			
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			 GitHub
						GitHub
					
				
			
						parent
						
							64e92ba9fd
						
					
				
				
					commit
					6e0c771e57
				
			| @@ -12,6 +12,7 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/aws/aws-sdk-go/service/iam/iamiface" | 	"github.com/aws/aws-sdk-go/service/iam/iamiface" | ||||||
| 	"github.com/aws/aws-sdk-go/service/sts/stsiface" | 	"github.com/aws/aws-sdk-go/service/sts/stsiface" | ||||||
|  | 	"github.com/hashicorp/go-hclog" | ||||||
| 	"github.com/hashicorp/vault/sdk/framework" | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
| 	"github.com/hashicorp/vault/sdk/logical" | 	"github.com/hashicorp/vault/sdk/logical" | ||||||
| 	"github.com/hashicorp/vault/sdk/queue" | 	"github.com/hashicorp/vault/sdk/queue" | ||||||
| @@ -87,6 +88,10 @@ func Backend(_ *logical.BackendConfig) *backend { | |||||||
| type backend struct { | type backend struct { | ||||||
| 	*framework.Backend | 	*framework.Backend | ||||||
|  |  | ||||||
|  | 	// Function pointer used to override the IAM client creation for mocked testing | ||||||
|  | 	// If set, this function will be called instead of creating real IAM clients | ||||||
|  | 	nonCachedClientIAMFunc func(context.Context, logical.Storage, hclog.Logger, *staticRoleEntry) (iamiface.IAMAPI, error) | ||||||
|  |  | ||||||
| 	// Mutex to protect access to reading and writing policies | 	// Mutex to protect access to reading and writing policies | ||||||
| 	roleMutex sync.RWMutex | 	roleMutex sync.RWMutex | ||||||
|  |  | ||||||
| @@ -131,8 +136,9 @@ func (b *backend) clearClients() { | |||||||
| } | } | ||||||
|  |  | ||||||
| // clientIAM returns the configured IAM client. If nil, it constructs a new one | // clientIAM returns the configured IAM client. If nil, it constructs a new one | ||||||
| // and returns it, setting it the internal variable | // and returns it, setting it the internal variable. | ||||||
| func (b *backend) clientIAM(ctx context.Context, s logical.Storage) (iamiface.IAMAPI, error) { | // entry is only needed when configuring the client to use for role assumption. | ||||||
|  | func (b *backend) clientIAM(ctx context.Context, s logical.Storage, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
| 	b.clientMutex.RLock() | 	b.clientMutex.RLock() | ||||||
| 	if b.iamClient != nil { | 	if b.iamClient != nil { | ||||||
| 		b.clientMutex.RUnlock() | 		b.clientMutex.RUnlock() | ||||||
| @@ -150,10 +156,11 @@ func (b *backend) clientIAM(ctx context.Context, s logical.Storage) (iamiface.IA | |||||||
| 		return b.iamClient, nil | 		return b.iamClient, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	iamClient, err := b.nonCachedClientIAM(ctx, s, b.Logger()) | 	iamClient, err := b.nonCachedClientIAM(ctx, s, b.Logger(), entry) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	b.iamClient = iamClient | 	b.iamClient = iamClient | ||||||
|  |  | ||||||
| 	return b.iamClient, nil | 	return b.iamClient, nil | ||||||
| @@ -248,3 +255,13 @@ func (b *backend) initialize(ctx context.Context, request *logical.Initializatio | |||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // getNonCachedIAMClient returns an IAM client. In a test env, if a mocked client creation | ||||||
|  | // function is set (nonCachedClientIAMFunc), it will be used instead of the default client creation function. | ||||||
|  | // This allows us to mock AWS clients in tests. | ||||||
|  | func (b *backend) getNonCachedIAMClient(ctx context.Context, storage logical.Storage, cfg staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 	if b.nonCachedClientIAMFunc != nil { | ||||||
|  | 		return b.nonCachedClientIAMFunc(ctx, storage, b.Logger(), &cfg) | ||||||
|  | 	} | ||||||
|  | 	return b.nonCachedClientIAM(ctx, storage, b.Logger(), &cfg) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -148,21 +148,33 @@ func (b *backend) getRootConfigs(ctx context.Context, s logical.Storage, clientT | |||||||
| 	return configs, nil | 	return configs, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (b *backend) nonCachedClientIAM(ctx context.Context, s logical.Storage, logger hclog.Logger) (*iam.IAM, error) { | func (b *backend) nonCachedClientIAM(ctx context.Context, s logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (*iam.IAM, error) { | ||||||
| 	awsConfig, err := b.getRootConfigs(ctx, s, "iam", logger) | 	var awsConfig *aws.Config | ||||||
|  | 	var err error | ||||||
|  |  | ||||||
|  | 	if entry != nil && entry.AssumeRoleARN != "" { | ||||||
|  | 		awsConfig, err = b.assumeRoleStatic(ctx, s, entry) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return nil, fmt.Errorf("failed to assume role %q: %w", entry.AssumeRoleARN, err) | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		configs, err := b.getRootConfigs(ctx, s, "iam", logger) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| 	if len(awsConfig) != 1 { | 		if len(configs) != 1 { | ||||||
| 			return nil, errors.New("could not obtain aws config") | 			return nil, errors.New("could not obtain aws config") | ||||||
| 		} | 		} | ||||||
| 	sess, err := session.NewSession(awsConfig[0]) | 		awsConfig = configs[0] | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	sess, err := session.NewSession(awsConfig) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	client := iam.New(sess) | 	client := iam.New(sess) | ||||||
| 	if client == nil { | 	if client == nil { | ||||||
| 		return nil, fmt.Errorf("could not obtain iam client") | 		return nil, fmt.Errorf("could not obtain IAM client") | ||||||
| 	} | 	} | ||||||
| 	return client, nil | 	return client, nil | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								builtin/logical/aws/client_ce.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								builtin/logical/aws/client_ce.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | //go:build !enterprise | ||||||
|  |  | ||||||
|  | package aws | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/aws/aws-sdk-go/aws" | ||||||
|  | 	"github.com/hashicorp/vault/sdk/logical" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // assumeRoleStatic assumes an AWS role for cross-account static role management. | ||||||
|  | // It uses the role ARN and session name provided in the staticRoleEntry configuration | ||||||
|  | // to generate credentials for the assumed role. | ||||||
|  | func (b *backend) assumeRoleStatic(ctx context.Context, s logical.Storage, entry *staticRoleEntry) (*aws.Config, error) { | ||||||
|  | 	return nil, fmt.Errorf("cross-account static roles are only supported in Vault Enterprise") | ||||||
|  | } | ||||||
| @@ -66,7 +66,7 @@ func (b *backend) getGroupPolicies(ctx context.Context, s logical.Storage, iamGr | |||||||
| 		return nil, nil, nil | 		return nil, nil, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	iamClient, err = b.clientIAM(ctx, s) | 	iamClient, err = b.clientIAM(ctx, s, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, nil, err | 		return nil, nil, err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ func (b *backend) pathConfigRotateRootUpdate(ctx context.Context, req *logical.R | |||||||
|  |  | ||||||
| func (b *backend) rotateRoot(ctx context.Context, req *logical.Request) (*logical.Response, error) { | func (b *backend) rotateRoot(ctx context.Context, req *logical.Request) (*logical.Response, error) { | ||||||
| 	// have to get the client config first because that takes out a read lock | 	// have to get the client config first because that takes out a read lock | ||||||
| 	client, err := b.clientIAM(ctx, req.Storage) | 	client, err := b.clientIAM(ctx, req.Storage, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -24,6 +24,9 @@ const ( | |||||||
| 	paramRoleName        = "name" | 	paramRoleName        = "name" | ||||||
| 	paramUsername        = "username" | 	paramUsername        = "username" | ||||||
| 	paramRotationPeriod  = "rotation_period" | 	paramRotationPeriod  = "rotation_period" | ||||||
|  | 	paramAssumeRoleARN   = "assume_role_arn" | ||||||
|  | 	paramRoleSessionName = "assume_role_session_name" | ||||||
|  | 	paramExternalID      = "external_id" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type staticRoleEntry struct { | type staticRoleEntry struct { | ||||||
| @@ -31,6 +34,9 @@ type staticRoleEntry struct { | |||||||
| 	ID                    string        `json:"id" structs:"id" mapstructure:"id"` | 	ID                    string        `json:"id" structs:"id" mapstructure:"id"` | ||||||
| 	Username              string        `json:"username" structs:"username" mapstructure:"username"` | 	Username              string        `json:"username" structs:"username" mapstructure:"username"` | ||||||
| 	RotationPeriod        time.Duration `json:"rotation_period" structs:"rotation_period" mapstructure:"rotation_period"` | 	RotationPeriod        time.Duration `json:"rotation_period" structs:"rotation_period" mapstructure:"rotation_period"` | ||||||
|  | 	AssumeRoleARN         string        `json:"assume_role_arn" structs:"assume_role_arn" mapstructure:"assume_role_arn"` | ||||||
|  | 	AssumeRoleSessionName string        `json:"assume_role_session_name" structs:"assume_role_session_name" mapstructure:"assume_role_session_name"` | ||||||
|  | 	ExternalID            string        `json:"external_id" structs:"external_id" mapstructure:"external_id"` | ||||||
| } | } | ||||||
|  |  | ||||||
| func pathStaticRoles(b *backend) *framework.Path { | func pathStaticRoles(b *backend) *framework.Path { | ||||||
| @@ -53,23 +59,12 @@ func pathStaticRoles(b *backend) *framework.Path { | |||||||
| 			}, | 			}, | ||||||
| 		}}, | 		}}, | ||||||
| 	} | 	} | ||||||
|  | 	fields := roleResponse[http.StatusOK][0].Fields | ||||||
|  | 	AddStaticAssumeRoleFieldsEnt(fields) | ||||||
|  |  | ||||||
| 	return &framework.Path{ | 	return &framework.Path{ | ||||||
| 		Pattern: fmt.Sprintf("%s/%s", pathStaticRole, framework.GenericNameWithAtRegex(paramRoleName)), | 		Pattern: fmt.Sprintf("%s/%s", pathStaticRole, framework.GenericNameWithAtRegex(paramRoleName)), | ||||||
| 		Fields: map[string]*framework.FieldSchema{ | 		Fields:  fields, | ||||||
| 			paramRoleName: { |  | ||||||
| 				Type:        framework.TypeString, |  | ||||||
| 				Description: descRoleName, |  | ||||||
| 			}, |  | ||||||
| 			paramUsername: { |  | ||||||
| 				Type:        framework.TypeString, |  | ||||||
| 				Description: descUsername, |  | ||||||
| 			}, |  | ||||||
| 			paramRotationPeriod: { |  | ||||||
| 				Type:        framework.TypeDurationSecond, |  | ||||||
| 				Description: descRotationPeriod, |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
|  |  | ||||||
| 		Operations: map[logical.Operation]framework.OperationHandler{ | 		Operations: map[logical.Operation]framework.OperationHandler{ | ||||||
| 			logical.ReadOperation: &framework.PathOperation{ | 			logical.ReadOperation: &framework.PathOperation{ | ||||||
| @@ -159,6 +154,11 @@ func (b *backend) pathStaticRolesWrite(ctx context.Context, req *logical.Request | |||||||
|  |  | ||||||
| 	// other params are optional if we're not Creating | 	// other params are optional if we're not Creating | ||||||
|  |  | ||||||
|  | 	err = validateAssumeRoleFields(data, &config) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if rawUsername, ok := data.GetOk(paramUsername); ok { | 	if rawUsername, ok := data.GetOk(paramUsername); ok { | ||||||
| 		config.Username = rawUsername.(string) | 		config.Username = rawUsername.(string) | ||||||
|  |  | ||||||
| @@ -299,10 +299,11 @@ func (b *backend) validateRoleName(name string) error { | |||||||
| // validateIAMUser checks the user information we have for the role against the information on AWS. On a create, it uses the username | // validateIAMUser checks the user information we have for the role against the information on AWS. On a create, it uses the username | ||||||
| // to retrieve the user information and _sets_ the userID. On update, it validates the userID and username. | // to retrieve the user information and _sets_ the userID. On update, it validates the userID and username. | ||||||
| func (b *backend) validateIAMUserExists(ctx context.Context, storage logical.Storage, entry *staticRoleEntry, isCreate bool) error { | func (b *backend) validateIAMUserExists(ctx context.Context, storage logical.Storage, entry *staticRoleEntry, isCreate bool) error { | ||||||
| 	c, err := b.clientIAM(ctx, storage) | 	c, err := b.getNonCachedIAMClient(ctx, storage, *entry) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("unable to validate username %q: %w", entry.Username, err) | 		return fmt.Errorf("unable to get client to validate username %q: %w", entry.Username, err) | ||||||
| 	} | 	} | ||||||
|  | 	b.iamClient = c | ||||||
|  |  | ||||||
| 	// we don't really care about the content of the result, just that it's not an error | 	// we don't really care about the content of the result, just that it's not an error | ||||||
| 	out, err := c.GetUser(&iam.GetUserInput{ | 	out, err := c.GetUser(&iam.GetUserInput{ | ||||||
| @@ -364,4 +365,7 @@ const ( | |||||||
| 	descUsername       = "The IAM user to adopt as a static role." | 	descUsername       = "The IAM user to adopt as a static role." | ||||||
| 	descRotationPeriod = `Period by which to rotate the backing credential of the adopted user.  | 	descRotationPeriod = `Period by which to rotate the backing credential of the adopted user.  | ||||||
| This can be a Go duration (e.g, '1m', 24h'), or an integer number of seconds.` | This can be a Go duration (e.g, '1m', 24h'), or an integer number of seconds.` | ||||||
|  | 	descAssumeRoleARN   = `The AWS ARN for the role to be assumed when interacting with the account specified.` | ||||||
|  | 	descRoleSessionName = `An identifier for the assumed role session.` | ||||||
|  | 	descExternalID      = `An external ID to be passed to the assumed role session.` | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										29
									
								
								builtin/logical/aws/path_static_roles_ce.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								builtin/logical/aws/path_static_roles_ce.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | // Copyright (c) HashiCorp, Inc. | ||||||
|  | // SPDX-License-Identifier: BUSL-1.1 | ||||||
|  |  | ||||||
|  | //go:build !enterprise | ||||||
|  |  | ||||||
|  | package aws | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
|  | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // AddStaticAssumeRoleFieldsEnt is a no-op for community edition | ||||||
|  | func AddStaticAssumeRoleFieldsEnt(fields map[string]*framework.FieldSchema) { | ||||||
|  | 	// no-op | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func validateAssumeRoleFields(data *framework.FieldData, config *staticRoleEntry) error { | ||||||
|  | 	_, hasAssumeRoleARN := data.GetOk(paramAssumeRoleARN) | ||||||
|  | 	_, hasRoleSessionName := data.GetOk(paramRoleSessionName) | ||||||
|  | 	_, hasExternalID := data.GetOk(paramExternalID) | ||||||
|  |  | ||||||
|  | 	if hasAssumeRoleARN || hasRoleSessionName || hasExternalID { | ||||||
|  | 		return fmt.Errorf("cross-account static roles are only supported in Vault Enterprise") | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
| @@ -11,6 +11,8 @@ import ( | |||||||
|  |  | ||||||
| 	"github.com/aws/aws-sdk-go/aws" | 	"github.com/aws/aws-sdk-go/aws" | ||||||
| 	"github.com/aws/aws-sdk-go/service/iam" | 	"github.com/aws/aws-sdk-go/service/iam" | ||||||
|  | 	"github.com/aws/aws-sdk-go/service/iam/iamiface" | ||||||
|  | 	"github.com/hashicorp/go-hclog" | ||||||
| 	"github.com/hashicorp/go-secure-stdlib/awsutil" | 	"github.com/hashicorp/go-secure-stdlib/awsutil" | ||||||
| 	"github.com/hashicorp/vault/sdk/framework" | 	"github.com/hashicorp/vault/sdk/framework" | ||||||
| 	"github.com/hashicorp/vault/sdk/logical" | 	"github.com/hashicorp/vault/sdk/logical" | ||||||
| @@ -97,7 +99,10 @@ func TestStaticRolesValidation(t *testing.T) { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| 			} | 			} | ||||||
| 			b.iamClient = miam | 			// Used to override the real IAM client creation to return the mocked client | ||||||
|  | 			b.nonCachedClientIAMFunc = func(ctx context.Context, s logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 				return miam, nil | ||||||
|  | 			} | ||||||
| 			if err := b.Setup(bgCTX, config); err != nil { | 			if err := b.Setup(bgCTX, config); err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| 			} | 			} | ||||||
| @@ -241,7 +246,10 @@ func TestStaticRolesWrite(t *testing.T) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			b := Backend(config) | 			b := Backend(config) | ||||||
| 			b.iamClient = miam | 			// Used to override the real IAM client creation to return the mocked client | ||||||
|  | 			b.nonCachedClientIAMFunc = func(ctx context.Context, s logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 				return miam, nil | ||||||
|  | 			} | ||||||
| 			if err := b.Setup(bgCTX, config); err != nil { | 			if err := b.Setup(bgCTX, config); err != nil { | ||||||
| 				t.Fatal(err) | 				t.Fatal(err) | ||||||
| 			} | 			} | ||||||
| @@ -454,7 +462,10 @@ func TestStaticRoleDelete(t *testing.T) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			b := Backend(config) | 			b := Backend(config) | ||||||
| 			b.iamClient = miam | 			// Used to override the real IAM client creation to return the mocked client | ||||||
|  | 			b.nonCachedClientIAMFunc = func(ctx context.Context, s logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 				return miam, nil | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// put in storage | 			// put in storage | ||||||
| 			staticRole := staticRoleEntry{ | 			staticRole := staticRoleEntry{ | ||||||
|   | |||||||
| @@ -175,7 +175,7 @@ func (b *backend) pathUserRollback(ctx context.Context, req *logical.Request, _k | |||||||
| 	username := entry.UserName | 	username := entry.UserName | ||||||
|  |  | ||||||
| 	// Get the client | 	// Get the client | ||||||
| 	client, err := b.clientIAM(ctx, req.Storage) | 	client, err := b.clientIAM(ctx, req.Storage, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -59,6 +59,7 @@ func (b *backend) rotateCredential(ctx context.Context, storage logical.Storage) | |||||||
| 		return false, nil | 		return false, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	b.Logger().Debug("rotating credential", "role", item.Key) | ||||||
| 	cfg := item.Value.(staticRoleEntry) | 	cfg := item.Value.(staticRoleEntry) | ||||||
|  |  | ||||||
| 	creds, err := b.createCredential(ctx, storage, cfg, true) | 	creds, err := b.createCredential(ctx, storage, cfg, true) | ||||||
| @@ -86,9 +87,10 @@ func (b *backend) rotateCredential(ctx context.Context, storage logical.Storage) | |||||||
|  |  | ||||||
| // createCredential will create a new iam credential, deleting the oldest one if necessary. | // createCredential will create a new iam credential, deleting the oldest one if necessary. | ||||||
| func (b *backend) createCredential(ctx context.Context, storage logical.Storage, cfg staticRoleEntry, shouldLockStorage bool) (*awsCredentials, error) { | func (b *backend) createCredential(ctx context.Context, storage logical.Storage, cfg staticRoleEntry, shouldLockStorage bool) (*awsCredentials, error) { | ||||||
| 	iamClient, err := b.clientIAM(ctx, storage) | 	// Always create a fresh client | ||||||
|  | 	iamClient, err := b.getNonCachedIAMClient(ctx, storage, cfg) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("unable to get the AWS IAM client: %w", err) | 		return nil, fmt.Errorf("failed to get IAM client for role %q: %w", cfg.Name, err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// IAM users can have a most 2 sets of keys at a time. | 	// IAM users can have a most 2 sets of keys at a time. | ||||||
| @@ -190,8 +192,13 @@ func (b *backend) deleteCredential(ctx context.Context, storage logical.Storage, | |||||||
| 		return fmt.Errorf("couldn't delete from storage: %w", err) | 		return fmt.Errorf("couldn't delete from storage: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	iamClient, err := b.nonCachedClientIAM(ctx, storage, b.Logger(), &cfg) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return fmt.Errorf("failed to get IAM client for role %q while deleting: %w", cfg.Name, err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// because we have the information, this is the one we created, so it's safe for us to delete. | 	// because we have the information, this is the one we created, so it's safe for us to delete. | ||||||
| 	_, err = b.iamClient.DeleteAccessKey(&iam.DeleteAccessKeyInput{ | 	_, err = iamClient.DeleteAccessKey(&iam.DeleteAccessKeyInput{ | ||||||
| 		AccessKeyId: aws.String(creds.AccessKeyID), | 		AccessKeyId: aws.String(creds.AccessKeyID), | ||||||
| 		UserName:    aws.String(cfg.Username), | 		UserName:    aws.String(cfg.Username), | ||||||
| 	}) | 	}) | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ import ( | |||||||
| 	"github.com/aws/aws-sdk-go/aws" | 	"github.com/aws/aws-sdk-go/aws" | ||||||
| 	"github.com/aws/aws-sdk-go/service/iam" | 	"github.com/aws/aws-sdk-go/service/iam" | ||||||
| 	"github.com/aws/aws-sdk-go/service/iam/iamiface" | 	"github.com/aws/aws-sdk-go/service/iam/iamiface" | ||||||
|  | 	"github.com/hashicorp/go-hclog" | ||||||
| 	"github.com/hashicorp/go-secure-stdlib/awsutil" | 	"github.com/hashicorp/go-secure-stdlib/awsutil" | ||||||
| 	"github.com/hashicorp/vault/api" | 	"github.com/hashicorp/vault/api" | ||||||
| 	"github.com/hashicorp/vault/helper/testhelpers" | 	"github.com/hashicorp/vault/helper/testhelpers" | ||||||
| @@ -146,7 +147,14 @@ func TestRotation(t *testing.T) { | |||||||
| 				if err != nil { | 				if err != nil { | ||||||
| 					t.Fatalf("couldn't initialze mock IAM handler: %s", err) | 					t.Fatalf("couldn't initialze mock IAM handler: %s", err) | ||||||
| 				} | 				} | ||||||
| 				b.iamClient = miam |  | ||||||
|  | 				// Used to override the IAM client creation to return the mocked client | ||||||
|  | 				b.nonCachedClientIAMFunc = func(ctx context.Context, storage logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 					if entry.Username == cred.config.Username && entry.ID == cred.config.ID { | ||||||
|  | 						return miam, nil | ||||||
|  | 					} | ||||||
|  | 					return nil, fmt.Errorf("unexpected IAM client creation for user %q", entry.Username) | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				c, err := b.createCredential(bgCTX, config.StorageView, cred.config, true) | 				c, err := b.createCredential(bgCTX, config.StorageView, cred.config, true) | ||||||
| 				if err != nil { | 				if err != nil { | ||||||
| @@ -192,7 +200,11 @@ func TestRotation(t *testing.T) { | |||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				t.Fatalf("couldn't initialze mock IAM handler: %s", err) | 				t.Fatalf("couldn't initialze mock IAM handler: %s", err) | ||||||
| 			} | 			} | ||||||
| 			b.iamClient = miam |  | ||||||
|  | 			// Set the IAM mock client to be used in the rotation | ||||||
|  | 			b.nonCachedClientIAMFunc = func(ctx context.Context, storage logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 				return miam, nil | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			req := &logical.Request{ | 			req := &logical.Request{ | ||||||
| 				Storage: config.StorageView, | 				Storage: config.StorageView, | ||||||
| @@ -340,7 +352,10 @@ func TestCreateCredential(t *testing.T) { | |||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			b := Backend(config) | 			b := Backend(config) | ||||||
| 			b.iamClient = fiam |  | ||||||
|  | 			b.nonCachedClientIAMFunc = func(ctx context.Context, s logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 				return fiam, nil | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			_, err = b.createCredential(context.Background(), config.StorageView, staticRoleEntry{Username: c.username, ID: c.id}, true) | 			_, err = b.createCredential(context.Background(), config.StorageView, staticRoleEntry{Username: c.username, ID: c.id}, true) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @@ -403,7 +418,10 @@ func TestRequeueOnError(t *testing.T) { | |||||||
| 		t.Fail() | 		t.Fail() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	b.iamClient = miam | 	// Used to override the IAM real client creation to return the mocked client | ||||||
|  | 	b.nonCachedClientIAMFunc = func(ctx context.Context, s logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 		return miam, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	_, err = b.createCredential(bgCTX, config.StorageView, cred, true) | 	_, err = b.createCredential(bgCTX, config.StorageView, cred, true) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -428,7 +446,9 @@ func TestRequeueOnError(t *testing.T) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		t.Fatalf("couldn't initialize the mock iam: %s", err) | 		t.Fatalf("couldn't initialize the mock iam: %s", err) | ||||||
| 	} | 	} | ||||||
| 	b.iamClient = miam | 	b.nonCachedClientIAMFunc = func(ctx context.Context, s logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 		return miam, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// now rotate, but it will fail | 	// now rotate, but it will fail | ||||||
| 	r, e := b.rotateCredential(bgCTX, config.StorageView) | 	r, e := b.rotateCredential(bgCTX, config.StorageView) | ||||||
| @@ -501,6 +521,12 @@ func Test_RotationQueueInitialized(t *testing.T) { | |||||||
| 				b := Backend(config) | 				b := Backend(config) | ||||||
| 				b.iamClient = mockClient | 				b.iamClient = mockClient | ||||||
| 				b.minAllowableRotationPeriod = 1 * time.Second | 				b.minAllowableRotationPeriod = 1 * time.Second | ||||||
|  |  | ||||||
|  | 				// Used to override the IAM real client creation to return the mocked client | ||||||
|  | 				b.nonCachedClientIAMFunc = func(ctx context.Context, storage logical.Storage, logger hclog.Logger, entry *staticRoleEntry) (iamiface.IAMAPI, error) { | ||||||
|  | 					return mockClient, nil | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				err := b.Setup(ctx, config) | 				err := b.Setup(ctx, config) | ||||||
| 				return b, err | 				return b, err | ||||||
| 			}, | 			}, | ||||||
|   | |||||||
| @@ -361,7 +361,7 @@ func (b *backend) secretAccessKeysCreate( | |||||||
| 	displayName, policyName string, | 	displayName, policyName string, | ||||||
| 	role *awsRoleEntry, | 	role *awsRoleEntry, | ||||||
| ) (*logical.Response, error) { | ) (*logical.Response, error) { | ||||||
| 	iamClient, err := b.clientIAM(ctx, s) | 	iamClient, err := b.clientIAM(ctx, s, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return logical.ErrorResponse(err.Error()), nil | 		return logical.ErrorResponse(err.Error()), nil | ||||||
| 	} | 	} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user