2015-03-20 16:59:48 +00:00
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
2018-01-08 18:31:38 +00:00
|
|
|
"context"
|
2015-03-20 16:59:48 +00:00
|
|
|
"fmt"
|
2015-08-25 00:00:54 +00:00
|
|
|
"regexp"
|
2015-03-21 10:49:56 +00:00
|
|
|
"time"
|
2015-03-20 16:59:48 +00:00
|
|
|
|
2021-07-21 00:42:00 +00:00
|
|
|
"github.com/hashicorp/go-secure-stdlib/awsutil"
|
2021-02-26 00:03:24 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
2021-07-20 16:48:29 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/helper/template"
|
2021-02-26 00:03:24 +00:00
|
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
|
|
|
2015-08-06 16:26:41 +00:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/service/iam"
|
2015-12-08 04:32:49 +00:00
|
|
|
"github.com/aws/aws-sdk-go/service/sts"
|
2018-04-05 15:49:21 +00:00
|
|
|
"github.com/hashicorp/errwrap"
|
2015-03-20 16:59:48 +00:00
|
|
|
)
|
|
|
|
|
2021-07-20 16:48:29 +00:00
|
|
|
const (
|
|
|
|
secretAccessKeyType = "access_keys"
|
|
|
|
storageKey = "config/root"
|
|
|
|
)
|
2015-03-20 16:59:48 +00:00
|
|
|
|
2015-04-19 05:25:37 +00:00
|
|
|
func secretAccessKeys(b *backend) *framework.Secret {
|
2015-03-20 16:59:48 +00:00
|
|
|
return &framework.Secret{
|
2018-09-18 20:26:06 +00:00
|
|
|
Type: secretAccessKeyType,
|
2015-03-20 16:59:48 +00:00
|
|
|
Fields: map[string]*framework.FieldSchema{
|
2021-04-08 16:43:39 +00:00
|
|
|
"access_key": {
|
2015-03-20 16:59:48 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Access Key",
|
|
|
|
},
|
|
|
|
|
2021-04-08 16:43:39 +00:00
|
|
|
"secret_key": {
|
2015-03-20 16:59:48 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Secret Key",
|
|
|
|
},
|
2021-04-08 16:43:39 +00:00
|
|
|
"security_token": {
|
2015-12-08 04:32:49 +00:00
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Security Token",
|
|
|
|
},
|
2015-03-20 16:59:48 +00:00
|
|
|
},
|
|
|
|
|
2015-04-19 05:25:37 +00:00
|
|
|
Renew: b.secretAccessKeysRenew,
|
2018-09-18 20:26:06 +00:00
|
|
|
Revoke: b.secretAccessKeysRevoke,
|
2015-03-20 16:59:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-09 16:40:47 +00:00
|
|
|
func genUsername(displayName, policyName, userType, usernameTemplate string) (ret string, err error) {
|
2016-02-19 21:35:06 +00:00
|
|
|
switch userType {
|
2021-08-09 16:40:47 +00:00
|
|
|
case "iam_user", "assume_role":
|
|
|
|
// IAM users are capped at 64 chars
|
2021-07-20 16:48:29 +00:00
|
|
|
up, err := template.NewTemplate(template.Template(usernameTemplate))
|
|
|
|
if err != nil {
|
2021-08-09 16:40:47 +00:00
|
|
|
return "", fmt.Errorf("unable to initialize username template: %w", err)
|
2021-07-20 16:48:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
um := UsernameMetadata{
|
2021-08-09 16:40:47 +00:00
|
|
|
Type: "IAM",
|
2021-07-20 16:48:29 +00:00
|
|
|
DisplayName: normalizeDisplayName(displayName),
|
|
|
|
PolicyName: normalizeDisplayName(policyName),
|
|
|
|
}
|
|
|
|
|
|
|
|
ret, err = up.Generate(um)
|
|
|
|
if err != nil {
|
2021-08-09 16:40:47 +00:00
|
|
|
return "", fmt.Errorf("failed to generate username: %w", err)
|
2021-07-20 16:48:29 +00:00
|
|
|
}
|
2021-08-09 16:40:47 +00:00
|
|
|
// To prevent a custom template from exceeding IAM length limits
|
2021-07-20 16:48:29 +00:00
|
|
|
if len(ret) > 64 {
|
2021-08-09 16:40:47 +00:00
|
|
|
return "", fmt.Errorf("the username generated by the template exceeds the IAM username length limits of 64 chars")
|
2016-02-19 21:35:06 +00:00
|
|
|
}
|
|
|
|
case "sts":
|
2021-07-20 16:48:29 +00:00
|
|
|
up, err := template.NewTemplate(template.Template(usernameTemplate))
|
|
|
|
if err != nil {
|
2021-08-09 16:40:47 +00:00
|
|
|
return "", fmt.Errorf("unable to initialize username template: %w", err)
|
2021-07-20 16:48:29 +00:00
|
|
|
}
|
2016-02-19 21:35:06 +00:00
|
|
|
|
2021-08-09 16:40:47 +00:00
|
|
|
um := UsernameMetadata{
|
|
|
|
Type: "STS",
|
|
|
|
}
|
2021-07-20 16:48:29 +00:00
|
|
|
ret, err = up.Generate(um)
|
|
|
|
if err != nil {
|
2021-08-09 16:40:47 +00:00
|
|
|
return "", fmt.Errorf("failed to generate username: %w", err)
|
|
|
|
}
|
|
|
|
// To prevent a custom template from exceeding STS length limits
|
|
|
|
if len(ret) > 32 {
|
|
|
|
return "", fmt.Errorf("the username generated by the template exceeds the STS username length limits of 32 chars")
|
2021-07-20 16:48:29 +00:00
|
|
|
}
|
|
|
|
}
|
2016-02-19 21:35:06 +00:00
|
|
|
return
|
2016-01-21 20:04:16 +00:00
|
|
|
}
|
|
|
|
|
secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles (#6789)
* secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles
AWS now allows you to pass policy ARNs as well as, and in addition to,
policy documents for AssumeRole and GetFederationToken (see
https://aws.amazon.com/about-aws/whats-new/2019/05/session-permissions/).
Vault already collects policy ARNs for iam_user credential types; now it
will allow policy ARNs for assumed_role and federation_token credential
types and plumb them through to the appropriate AWS calls.
This brings along a minor breaking change. Vault roles of the
federation_token credential type are now required to have either a
policy_document or a policy_arns specified. This was implicit
previously; a missing policy_document would result in a validation error
from the AWS SDK when retrieving credentials. However, it would still
allow creating a role that didn't have a policy_document specified and
then later specifying it, after which retrieving the AWS credentials
would work. Similar workflows in which the Vault role didn't have a
policy_document specified for some period of time, such as deleting the
policy_document and then later adding it back, would also have worked
previously but will now be broken.
The reason for this breaking change is because a credential_type of
federation_token without either a policy_document or policy_arns
specified will return credentials that have equivalent permissions to
the credentials the Vault server itself is using. This is quite
dangerous (e.g., it could allow Vault clients access to retrieve
credentials that could modify Vault's underlying storage) and so should
be discouraged. This scenario is still possible when passing in an
appropriate policy_document or policy_arns parameter, but clients should
be explicitly aware of what they are doing and opt in to it by passing
in the appropriate role parameters.
* Error out on dangerous federation token retrieval
The AWS secrets role code now disallows creation of a dangerous role
configuration; however, pre-existing roles could have existed that would
trigger this now-dangerous code path, so also adding a check for this
configuration at credential retrieval time.
* Run makefmt
* Fix tests
* Fix comments/docs
2019-08-20 19:34:41 +00:00
|
|
|
func (b *backend) getFederationToken(ctx context.Context, s logical.Storage,
|
|
|
|
displayName, policyName, policy string, policyARNs []string,
|
2022-01-27 18:06:34 +00:00
|
|
|
iamGroups []string, lifeTimeInSeconds int64) (*logical.Response, error,
|
|
|
|
) {
|
2020-06-09 23:56:12 +00:00
|
|
|
groupPolicies, groupPolicyARNs, err := b.getGroupPolicies(ctx, s, iamGroups)
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
if groupPolicies != nil {
|
|
|
|
groupPolicies = append(groupPolicies, policy)
|
|
|
|
policy, err = combinePolicyDocuments(groupPolicies...)
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(groupPolicyARNs) > 0 {
|
|
|
|
policyARNs = append(policyARNs, groupPolicyARNs...)
|
|
|
|
}
|
|
|
|
|
2018-09-18 20:26:06 +00:00
|
|
|
stsClient, err := b.clientSTS(ctx, s)
|
2015-12-08 04:32:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
|
2021-08-09 16:40:47 +00:00
|
|
|
config, err := readConfig(ctx, s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read configuration: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set as defaultUsernameTemplate if not provided
|
|
|
|
usernameTemplate := config.UsernameTemplate
|
|
|
|
if usernameTemplate == "" {
|
|
|
|
usernameTemplate = defaultUserNameTemplate
|
|
|
|
}
|
|
|
|
|
|
|
|
username, usernameError := genUsername(displayName, policyName, "sts", usernameTemplate)
|
2021-07-20 16:48:29 +00:00
|
|
|
// Send a 400 to Framework.OperationFunc Handler
|
|
|
|
if usernameError != nil {
|
|
|
|
return nil, usernameError
|
|
|
|
}
|
2015-12-08 04:32:49 +00:00
|
|
|
|
secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles (#6789)
* secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles
AWS now allows you to pass policy ARNs as well as, and in addition to,
policy documents for AssumeRole and GetFederationToken (see
https://aws.amazon.com/about-aws/whats-new/2019/05/session-permissions/).
Vault already collects policy ARNs for iam_user credential types; now it
will allow policy ARNs for assumed_role and federation_token credential
types and plumb them through to the appropriate AWS calls.
This brings along a minor breaking change. Vault roles of the
federation_token credential type are now required to have either a
policy_document or a policy_arns specified. This was implicit
previously; a missing policy_document would result in a validation error
from the AWS SDK when retrieving credentials. However, it would still
allow creating a role that didn't have a policy_document specified and
then later specifying it, after which retrieving the AWS credentials
would work. Similar workflows in which the Vault role didn't have a
policy_document specified for some period of time, such as deleting the
policy_document and then later adding it back, would also have worked
previously but will now be broken.
The reason for this breaking change is because a credential_type of
federation_token without either a policy_document or policy_arns
specified will return credentials that have equivalent permissions to
the credentials the Vault server itself is using. This is quite
dangerous (e.g., it could allow Vault clients access to retrieve
credentials that could modify Vault's underlying storage) and so should
be discouraged. This scenario is still possible when passing in an
appropriate policy_document or policy_arns parameter, but clients should
be explicitly aware of what they are doing and opt in to it by passing
in the appropriate role parameters.
* Error out on dangerous federation token retrieval
The AWS secrets role code now disallows creation of a dangerous role
configuration; however, pre-existing roles could have existed that would
trigger this now-dangerous code path, so also adding a check for this
configuration at credential retrieval time.
* Run makefmt
* Fix tests
* Fix comments/docs
2019-08-20 19:34:41 +00:00
|
|
|
getTokenInput := &sts.GetFederationTokenInput{
|
|
|
|
Name: aws.String(username),
|
|
|
|
DurationSeconds: &lifeTimeInSeconds,
|
|
|
|
}
|
|
|
|
if len(policy) > 0 {
|
|
|
|
getTokenInput.Policy = aws.String(policy)
|
|
|
|
}
|
|
|
|
if len(policyARNs) > 0 {
|
|
|
|
getTokenInput.PolicyArns = convertPolicyARNs(policyARNs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If neither a policy document nor policy ARNs are specified, then GetFederationToken will
|
|
|
|
// return credentials equivalent to that of the Vault server itself. We probably don't want
|
|
|
|
// that by default; the behavior can be explicitly opted in to by associating the Vault role
|
|
|
|
// with a policy ARN or document that allows the appropriate permissions.
|
|
|
|
if policy == "" && len(policyARNs) == 0 {
|
2020-06-09 23:56:12 +00:00
|
|
|
return logical.ErrorResponse("must specify at least one of policy_arns or policy_document with %s credential_type", federationTokenCred), nil
|
secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles (#6789)
* secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles
AWS now allows you to pass policy ARNs as well as, and in addition to,
policy documents for AssumeRole and GetFederationToken (see
https://aws.amazon.com/about-aws/whats-new/2019/05/session-permissions/).
Vault already collects policy ARNs for iam_user credential types; now it
will allow policy ARNs for assumed_role and federation_token credential
types and plumb them through to the appropriate AWS calls.
This brings along a minor breaking change. Vault roles of the
federation_token credential type are now required to have either a
policy_document or a policy_arns specified. This was implicit
previously; a missing policy_document would result in a validation error
from the AWS SDK when retrieving credentials. However, it would still
allow creating a role that didn't have a policy_document specified and
then later specifying it, after which retrieving the AWS credentials
would work. Similar workflows in which the Vault role didn't have a
policy_document specified for some period of time, such as deleting the
policy_document and then later adding it back, would also have worked
previously but will now be broken.
The reason for this breaking change is because a credential_type of
federation_token without either a policy_document or policy_arns
specified will return credentials that have equivalent permissions to
the credentials the Vault server itself is using. This is quite
dangerous (e.g., it could allow Vault clients access to retrieve
credentials that could modify Vault's underlying storage) and so should
be discouraged. This scenario is still possible when passing in an
appropriate policy_document or policy_arns parameter, but clients should
be explicitly aware of what they are doing and opt in to it by passing
in the appropriate role parameters.
* Error out on dangerous federation token retrieval
The AWS secrets role code now disallows creation of a dangerous role
configuration; however, pre-existing roles could have existed that would
trigger this now-dangerous code path, so also adding a check for this
configuration at credential retrieval time.
* Run makefmt
* Fix tests
* Fix comments/docs
2019-08-20 19:34:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tokenResp, err := stsClient.GetFederationToken(getTokenInput)
|
2015-12-08 04:32:49 +00:00
|
|
|
if err != nil {
|
2020-06-09 23:56:12 +00:00
|
|
|
return logical.ErrorResponse("Error generating STS keys: %s", err), awsutil.CheckAWSError(err)
|
2015-12-08 04:32:49 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 21:28:25 +00:00
|
|
|
// STS credentials cannot be revoked so do not create a lease
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"access_key": *tokenResp.Credentials.AccessKeyId,
|
|
|
|
"secret_key": *tokenResp.Credentials.SecretAccessKey,
|
|
|
|
"security_token": *tokenResp.Credentials.SessionToken,
|
|
|
|
"ttl": uint64(tokenResp.Credentials.Expiration.Sub(time.Now()).Seconds()),
|
|
|
|
},
|
|
|
|
}, nil
|
2015-12-08 04:32:49 +00:00
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
func (b *backend) assumeRole(ctx context.Context, s logical.Storage,
|
secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles (#6789)
* secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles
AWS now allows you to pass policy ARNs as well as, and in addition to,
policy documents for AssumeRole and GetFederationToken (see
https://aws.amazon.com/about-aws/whats-new/2019/05/session-permissions/).
Vault already collects policy ARNs for iam_user credential types; now it
will allow policy ARNs for assumed_role and federation_token credential
types and plumb them through to the appropriate AWS calls.
This brings along a minor breaking change. Vault roles of the
federation_token credential type are now required to have either a
policy_document or a policy_arns specified. This was implicit
previously; a missing policy_document would result in a validation error
from the AWS SDK when retrieving credentials. However, it would still
allow creating a role that didn't have a policy_document specified and
then later specifying it, after which retrieving the AWS credentials
would work. Similar workflows in which the Vault role didn't have a
policy_document specified for some period of time, such as deleting the
policy_document and then later adding it back, would also have worked
previously but will now be broken.
The reason for this breaking change is because a credential_type of
federation_token without either a policy_document or policy_arns
specified will return credentials that have equivalent permissions to
the credentials the Vault server itself is using. This is quite
dangerous (e.g., it could allow Vault clients access to retrieve
credentials that could modify Vault's underlying storage) and so should
be discouraged. This scenario is still possible when passing in an
appropriate policy_document or policy_arns parameter, but clients should
be explicitly aware of what they are doing and opt in to it by passing
in the appropriate role parameters.
* Error out on dangerous federation token retrieval
The AWS secrets role code now disallows creation of a dangerous role
configuration; however, pre-existing roles could have existed that would
trigger this now-dangerous code path, so also adding a check for this
configuration at credential retrieval time.
* Run makefmt
* Fix tests
* Fix comments/docs
2019-08-20 19:34:41 +00:00
|
|
|
displayName, roleName, roleArn, policy string, policyARNs []string,
|
2022-01-27 18:06:34 +00:00
|
|
|
iamGroups []string, lifeTimeInSeconds int64, roleSessionName string) (*logical.Response, error,
|
|
|
|
) {
|
2020-06-09 23:56:12 +00:00
|
|
|
// grab any IAM group policies associated with the vault role, both inline
|
|
|
|
// and managed
|
|
|
|
groupPolicies, groupPolicyARNs, err := b.getGroupPolicies(ctx, s, iamGroups)
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
if len(groupPolicies) > 0 {
|
|
|
|
groupPolicies = append(groupPolicies, policy)
|
|
|
|
policy, err = combinePolicyDocuments(groupPolicies...)
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(groupPolicyARNs) > 0 {
|
|
|
|
policyARNs = append(policyARNs, groupPolicyARNs...)
|
|
|
|
}
|
|
|
|
|
2018-09-18 20:26:06 +00:00
|
|
|
stsClient, err := b.clientSTS(ctx, s)
|
2016-04-12 02:30:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
|
2021-07-20 16:48:29 +00:00
|
|
|
config, err := readConfig(ctx, s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read configuration: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set as defaultUsernameTemplate if not provided
|
|
|
|
usernameTemplate := config.UsernameTemplate
|
|
|
|
if usernameTemplate == "" {
|
|
|
|
usernameTemplate = defaultUserNameTemplate
|
|
|
|
}
|
|
|
|
|
|
|
|
var roleSessionNameError error
|
2021-05-17 18:03:09 +00:00
|
|
|
if roleSessionName == "" {
|
2021-08-09 16:40:47 +00:00
|
|
|
roleSessionName, roleSessionNameError = genUsername(displayName, roleName, "assume_role", usernameTemplate)
|
2021-07-20 16:48:29 +00:00
|
|
|
// Send a 400 to Framework.OperationFunc Handler
|
|
|
|
if roleSessionNameError != nil {
|
|
|
|
return nil, roleSessionNameError
|
|
|
|
}
|
2021-05-17 18:03:09 +00:00
|
|
|
} else {
|
|
|
|
roleSessionName = normalizeDisplayName(roleSessionName)
|
|
|
|
}
|
2016-04-12 02:30:30 +00:00
|
|
|
|
2018-08-16 10:38:13 +00:00
|
|
|
assumeRoleInput := &sts.AssumeRoleInput{
|
2021-05-17 18:03:09 +00:00
|
|
|
RoleSessionName: aws.String(roleSessionName),
|
2018-08-16 10:38:13 +00:00
|
|
|
RoleArn: aws.String(roleArn),
|
|
|
|
DurationSeconds: &lifeTimeInSeconds,
|
|
|
|
}
|
|
|
|
if policy != "" {
|
|
|
|
assumeRoleInput.SetPolicy(policy)
|
|
|
|
}
|
secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles (#6789)
* secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles
AWS now allows you to pass policy ARNs as well as, and in addition to,
policy documents for AssumeRole and GetFederationToken (see
https://aws.amazon.com/about-aws/whats-new/2019/05/session-permissions/).
Vault already collects policy ARNs for iam_user credential types; now it
will allow policy ARNs for assumed_role and federation_token credential
types and plumb them through to the appropriate AWS calls.
This brings along a minor breaking change. Vault roles of the
federation_token credential type are now required to have either a
policy_document or a policy_arns specified. This was implicit
previously; a missing policy_document would result in a validation error
from the AWS SDK when retrieving credentials. However, it would still
allow creating a role that didn't have a policy_document specified and
then later specifying it, after which retrieving the AWS credentials
would work. Similar workflows in which the Vault role didn't have a
policy_document specified for some period of time, such as deleting the
policy_document and then later adding it back, would also have worked
previously but will now be broken.
The reason for this breaking change is because a credential_type of
federation_token without either a policy_document or policy_arns
specified will return credentials that have equivalent permissions to
the credentials the Vault server itself is using. This is quite
dangerous (e.g., it could allow Vault clients access to retrieve
credentials that could modify Vault's underlying storage) and so should
be discouraged. This scenario is still possible when passing in an
appropriate policy_document or policy_arns parameter, but clients should
be explicitly aware of what they are doing and opt in to it by passing
in the appropriate role parameters.
* Error out on dangerous federation token retrieval
The AWS secrets role code now disallows creation of a dangerous role
configuration; however, pre-existing roles could have existed that would
trigger this now-dangerous code path, so also adding a check for this
configuration at credential retrieval time.
* Run makefmt
* Fix tests
* Fix comments/docs
2019-08-20 19:34:41 +00:00
|
|
|
if len(policyARNs) > 0 {
|
|
|
|
assumeRoleInput.SetPolicyArns(convertPolicyARNs(policyARNs))
|
|
|
|
}
|
2018-09-18 20:26:06 +00:00
|
|
|
tokenResp, err := stsClient.AssumeRole(assumeRoleInput)
|
2016-04-12 02:30:30 +00:00
|
|
|
if err != nil {
|
2020-06-09 23:56:12 +00:00
|
|
|
return logical.ErrorResponse("Error assuming role: %s", err), awsutil.CheckAWSError(err)
|
2016-04-12 02:30:30 +00:00
|
|
|
}
|
|
|
|
|
2022-10-28 21:28:25 +00:00
|
|
|
// STS credentials cannot be revoked so do not create a lease
|
|
|
|
return &logical.Response{
|
|
|
|
Data: map[string]interface{}{
|
|
|
|
"access_key": *tokenResp.Credentials.AccessKeyId,
|
|
|
|
"secret_key": *tokenResp.Credentials.SecretAccessKey,
|
|
|
|
"security_token": *tokenResp.Credentials.SessionToken,
|
|
|
|
"arn": *tokenResp.AssumedRoleUser.Arn,
|
|
|
|
"ttl": uint64(tokenResp.Credentials.Expiration.Sub(time.Now()).Seconds()),
|
|
|
|
},
|
|
|
|
}, nil
|
2016-04-12 02:30:30 +00:00
|
|
|
}
|
|
|
|
|
2021-07-20 16:48:29 +00:00
|
|
|
func readConfig(ctx context.Context, storage logical.Storage) (rootConfig, error) {
|
|
|
|
entry, err := storage.Get(ctx, storageKey)
|
|
|
|
if err != nil {
|
|
|
|
return rootConfig{}, err
|
|
|
|
}
|
|
|
|
if entry == nil {
|
|
|
|
return rootConfig{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var connConfig rootConfig
|
|
|
|
if err := entry.DecodeJSON(&connConfig); err != nil {
|
|
|
|
return rootConfig{}, err
|
|
|
|
}
|
|
|
|
return connConfig, nil
|
|
|
|
}
|
|
|
|
|
2015-03-21 10:49:56 +00:00
|
|
|
func (b *backend) secretAccessKeysCreate(
|
2018-01-19 06:44:44 +00:00
|
|
|
ctx context.Context,
|
2015-03-21 10:49:56 +00:00
|
|
|
s logical.Storage,
|
2021-02-26 00:03:24 +00:00
|
|
|
displayName, policyName string,
|
2022-09-08 00:31:20 +00:00
|
|
|
role *awsRoleEntry,
|
|
|
|
) (*logical.Response, error) {
|
2018-09-18 20:26:06 +00:00
|
|
|
iamClient, err := b.clientIAM(ctx, s)
|
2015-03-21 10:49:56 +00:00
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
|
2021-07-20 16:48:29 +00:00
|
|
|
config, err := readConfig(ctx, s)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read configuration: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set as defaultUsernameTemplate if not provided
|
|
|
|
usernameTemplate := config.UsernameTemplate
|
|
|
|
if usernameTemplate == "" {
|
|
|
|
usernameTemplate = defaultUserNameTemplate
|
|
|
|
}
|
|
|
|
|
2021-08-09 16:40:47 +00:00
|
|
|
username, usernameError := genUsername(displayName, policyName, "iam_user", usernameTemplate)
|
2021-07-20 16:48:29 +00:00
|
|
|
// Send a 400 to Framework.OperationFunc Handler
|
|
|
|
if usernameError != nil {
|
|
|
|
return nil, usernameError
|
|
|
|
}
|
2015-03-21 10:49:56 +00:00
|
|
|
|
|
|
|
// Write to the WAL that this user will be created. We do this before
|
|
|
|
// the user is created because if switch the order then the WAL put
|
|
|
|
// can fail, which would put us in an awkward position: we have a user
|
|
|
|
// we need to rollback but can't put the WAL entry to do the rollback.
|
2018-09-18 20:26:06 +00:00
|
|
|
walID, err := framework.PutWAL(ctx, s, "user", &walUser{
|
2015-03-21 10:49:56 +00:00
|
|
|
UserName: username,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2021-04-22 15:20:59 +00:00
|
|
|
return nil, fmt.Errorf("error writing WAL entry: %w", err)
|
2015-03-21 10:49:56 +00:00
|
|
|
}
|
|
|
|
|
2019-03-31 13:10:17 +00:00
|
|
|
userPath := role.UserPath
|
|
|
|
if userPath == "" {
|
|
|
|
userPath = "/"
|
|
|
|
}
|
|
|
|
|
2019-09-19 23:35:12 +00:00
|
|
|
createUserRequest := &iam.CreateUserInput{
|
2015-03-21 10:49:56 +00:00
|
|
|
UserName: aws.String(username),
|
2019-03-31 13:10:17 +00:00
|
|
|
Path: aws.String(userPath),
|
2019-09-19 23:35:12 +00:00
|
|
|
}
|
|
|
|
if role.PermissionsBoundaryARN != "" {
|
|
|
|
createUserRequest.PermissionsBoundary = aws.String(role.PermissionsBoundaryARN)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the user
|
|
|
|
_, err = iamClient.CreateUser(createUserRequest)
|
2015-03-21 10:49:56 +00:00
|
|
|
if err != nil {
|
2018-09-27 14:58:04 +00:00
|
|
|
if walErr := framework.DeleteWAL(ctx, s, walID); walErr != nil {
|
2021-04-22 15:20:59 +00:00
|
|
|
iamErr := fmt.Errorf("error creating IAM user: %w", err)
|
|
|
|
return nil, errwrap.Wrap(fmt.Errorf("failed to delete WAL entry: %w", walErr), iamErr)
|
logical/aws: Harden WAL entry creation (#5202)
* logical/aws: Harden WAL entry creation
If AWS IAM user creation failed in any way, the WAL corresponding to the
IAM user would get left around and Vault would try to roll it back.
However, because the user never existed, the rollback failed. Thus, the
WAL would essentially get "stuck" and Vault would continually attempt to
roll it back, failing every time. A similar situation could arise if the
IAM user that Vault created got deleted out of band, or if Vault deleted
it but was unable to write the lease revocation back to storage (e.g., a
storage failure).
This attempts to harden it in two ways. One is by deleting the WAL log
entry if the IAM user creation fails. However, the WAL deletion could
still fail, and this wouldn't help where the user is deleted out of
band, so second, consider the user rolled back if the user just doesn't
exist, under certain circumstances.
Fixes #5190
* Fix segfault in expiration unit tests
TestExpiration_Tidy was passing in a leaseEntry that had a nil Secret,
which then caused a segfault as the changes to revokeEntry didn't check
whether Secret was nil; this is probably unlikely to occur in real life,
but good to be extra cautious.
* Fix potential segfault
Missed the else...
* Respond to PR feedback
2018-09-27 14:54:59 +00:00
|
|
|
}
|
2020-06-09 23:56:12 +00:00
|
|
|
return logical.ErrorResponse("Error creating IAM user: %s", err), awsutil.CheckAWSError(err)
|
2015-03-21 10:49:56 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 10:38:13 +00:00
|
|
|
for _, arn := range role.PolicyArns {
|
2015-12-30 18:05:54 +00:00
|
|
|
// Attach existing policy against user
|
2018-09-18 20:26:06 +00:00
|
|
|
_, err = iamClient.AttachUserPolicy(&iam.AttachUserPolicyInput{
|
2015-12-30 18:05:54 +00:00
|
|
|
UserName: aws.String(username),
|
2018-08-16 10:38:13 +00:00
|
|
|
PolicyArn: aws.String(arn),
|
2015-12-30 18:05:54 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2020-06-09 23:56:12 +00:00
|
|
|
return logical.ErrorResponse("Error attaching user policy: %s", err), awsutil.CheckAWSError(err)
|
2015-12-30 18:05:54 +00:00
|
|
|
}
|
|
|
|
|
2018-08-16 10:38:13 +00:00
|
|
|
}
|
|
|
|
if role.PolicyDocument != "" {
|
2015-12-30 18:05:54 +00:00
|
|
|
// Add new inline user policy against user
|
2018-09-18 20:26:06 +00:00
|
|
|
_, err = iamClient.PutUserPolicy(&iam.PutUserPolicyInput{
|
2015-12-30 18:05:54 +00:00
|
|
|
UserName: aws.String(username),
|
|
|
|
PolicyName: aws.String(policyName),
|
2018-08-16 10:38:13 +00:00
|
|
|
PolicyDocument: aws.String(role.PolicyDocument),
|
2015-12-30 18:05:54 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2020-06-09 23:56:12 +00:00
|
|
|
return logical.ErrorResponse("Error putting user policy: %s", err), awsutil.CheckAWSError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, group := range role.IAMGroups {
|
|
|
|
// Add user to IAM groups
|
|
|
|
_, err = iamClient.AddUserToGroup(&iam.AddUserToGroupInput{
|
|
|
|
UserName: aws.String(username),
|
|
|
|
GroupName: aws.String(group),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse("Error adding user to group: %s", err), awsutil.CheckAWSError(err)
|
2015-12-30 18:05:54 +00:00
|
|
|
}
|
2015-03-21 10:49:56 +00:00
|
|
|
}
|
|
|
|
|
2021-02-26 00:03:24 +00:00
|
|
|
var tags []*iam.Tag
|
|
|
|
for key, value := range role.IAMTags {
|
|
|
|
// This assignment needs to be done in order to create unique addresses for
|
|
|
|
// these variables. Without doing so, all the tags will be copies of the last
|
|
|
|
// tag listed in the role.
|
|
|
|
k, v := key, value
|
|
|
|
tags = append(tags, &iam.Tag{Key: &k, Value: &v})
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(tags) > 0 {
|
|
|
|
_, err = iamClient.TagUser(&iam.TagUserInput{
|
|
|
|
Tags: tags,
|
|
|
|
UserName: &username,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse("Error adding tags to user: %s", err), awsutil.CheckAWSError(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-21 10:49:56 +00:00
|
|
|
// Create the keys
|
2018-09-18 20:26:06 +00:00
|
|
|
keyResp, err := iamClient.CreateAccessKey(&iam.CreateAccessKeyInput{
|
2015-03-21 10:49:56 +00:00
|
|
|
UserName: aws.String(username),
|
|
|
|
})
|
|
|
|
if err != nil {
|
2020-06-09 23:56:12 +00:00
|
|
|
return logical.ErrorResponse("Error creating access keys: %s", err), awsutil.CheckAWSError(err)
|
2015-03-21 10:49:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the WAL entry, we succeeded! If we fail, we don't return
|
|
|
|
// the secret because it'll get rolled back anyways, so we have to return
|
|
|
|
// an error here.
|
2018-09-18 20:26:06 +00:00
|
|
|
if err := framework.DeleteWAL(ctx, s, walID); err != nil {
|
2021-04-22 15:20:59 +00:00
|
|
|
return nil, fmt.Errorf("failed to commit WAL entry: %w", err)
|
2015-03-21 10:49:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return the info!
|
2018-09-18 20:26:06 +00:00
|
|
|
resp := b.Secret(secretAccessKeyType).Response(map[string]interface{}{
|
2015-12-08 04:32:49 +00:00
|
|
|
"access_key": *keyResp.AccessKey.AccessKeyId,
|
|
|
|
"secret_key": *keyResp.AccessKey.SecretAccessKey,
|
|
|
|
"security_token": nil,
|
2015-03-21 10:49:56 +00:00
|
|
|
}, map[string]interface{}{
|
|
|
|
"username": username,
|
2018-08-16 10:38:13 +00:00
|
|
|
"policy": role,
|
2016-01-29 22:44:09 +00:00
|
|
|
"is_sts": false,
|
|
|
|
})
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
lease, err := b.Lease(ctx, s)
|
2016-01-29 22:44:09 +00:00
|
|
|
if err != nil || lease == nil {
|
|
|
|
lease = &configLease{}
|
|
|
|
}
|
|
|
|
|
|
|
|
resp.Secret.TTL = lease.Lease
|
2018-04-03 16:20:20 +00:00
|
|
|
resp.Secret.MaxTTL = lease.LeaseMax
|
2016-01-29 22:44:09 +00:00
|
|
|
|
|
|
|
return resp, nil
|
2015-03-21 10:49:56 +00:00
|
|
|
}
|
|
|
|
|
2018-01-08 18:31:38 +00:00
|
|
|
func (b *backend) secretAccessKeysRenew(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
2016-01-29 22:44:09 +00:00
|
|
|
// STS already has a lifetime, and we don't support renewing it
|
|
|
|
isSTSRaw, ok := req.Secret.InternalData["is_sts"]
|
|
|
|
if ok {
|
|
|
|
isSTS, ok := isSTSRaw.(bool)
|
|
|
|
if ok {
|
|
|
|
if isSTS {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-19 06:44:44 +00:00
|
|
|
lease, err := b.Lease(ctx, req.Storage)
|
2015-04-19 05:25:37 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if lease == nil {
|
2016-01-29 22:44:09 +00:00
|
|
|
lease = &configLease{}
|
2015-04-19 05:25:37 +00:00
|
|
|
}
|
|
|
|
|
2018-04-03 16:20:20 +00:00
|
|
|
resp := &logical.Response{Secret: req.Secret}
|
|
|
|
resp.Secret.TTL = lease.Lease
|
|
|
|
resp.Secret.MaxTTL = lease.LeaseMax
|
|
|
|
return resp, nil
|
2015-04-19 05:25:37 +00:00
|
|
|
}
|
|
|
|
|
2018-09-18 20:26:06 +00:00
|
|
|
func (b *backend) secretAccessKeysRevoke(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
2016-01-21 20:04:16 +00:00
|
|
|
// STS cleans up after itself so we can skip this if is_sts internal data
|
|
|
|
// element set to true. If is_sts is not set, assumes old version
|
|
|
|
// and defaults to the IAM approach.
|
|
|
|
isSTSRaw, ok := req.Secret.InternalData["is_sts"]
|
|
|
|
if ok {
|
|
|
|
isSTS, ok := isSTSRaw.(bool)
|
|
|
|
if ok {
|
|
|
|
if isSTS {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return nil, fmt.Errorf("secret has is_sts but value could not be understood")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-20 16:59:48 +00:00
|
|
|
// Get the username from the internal data
|
|
|
|
usernameRaw, ok := req.Secret.InternalData["username"]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("secret is missing username internal data")
|
|
|
|
}
|
|
|
|
username, ok := usernameRaw.(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("secret is missing username internal data")
|
|
|
|
}
|
|
|
|
|
2015-03-21 10:18:46 +00:00
|
|
|
// Use the user rollback mechanism to delete this user
|
2018-09-26 14:10:00 +00:00
|
|
|
err := b.pathUserRollback(ctx, req, "user", map[string]interface{}{
|
2015-03-21 10:18:46 +00:00
|
|
|
"username": username,
|
2015-03-20 16:59:48 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2015-03-21 10:18:46 +00:00
|
|
|
return nil, err
|
2015-03-20 16:59:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
2015-08-24 23:12:45 +00:00
|
|
|
|
|
|
|
func normalizeDisplayName(displayName string) string {
|
2015-12-30 18:05:54 +00:00
|
|
|
re := regexp.MustCompile("[^a-zA-Z0-9+=,.@_-]")
|
2015-08-25 00:00:54 +00:00
|
|
|
return re.ReplaceAllString(displayName, "_")
|
2015-08-24 23:12:45 +00:00
|
|
|
}
|
secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles (#6789)
* secret/aws: Pass policy ARNs to AssumedRole and FederationToken roles
AWS now allows you to pass policy ARNs as well as, and in addition to,
policy documents for AssumeRole and GetFederationToken (see
https://aws.amazon.com/about-aws/whats-new/2019/05/session-permissions/).
Vault already collects policy ARNs for iam_user credential types; now it
will allow policy ARNs for assumed_role and federation_token credential
types and plumb them through to the appropriate AWS calls.
This brings along a minor breaking change. Vault roles of the
federation_token credential type are now required to have either a
policy_document or a policy_arns specified. This was implicit
previously; a missing policy_document would result in a validation error
from the AWS SDK when retrieving credentials. However, it would still
allow creating a role that didn't have a policy_document specified and
then later specifying it, after which retrieving the AWS credentials
would work. Similar workflows in which the Vault role didn't have a
policy_document specified for some period of time, such as deleting the
policy_document and then later adding it back, would also have worked
previously but will now be broken.
The reason for this breaking change is because a credential_type of
federation_token without either a policy_document or policy_arns
specified will return credentials that have equivalent permissions to
the credentials the Vault server itself is using. This is quite
dangerous (e.g., it could allow Vault clients access to retrieve
credentials that could modify Vault's underlying storage) and so should
be discouraged. This scenario is still possible when passing in an
appropriate policy_document or policy_arns parameter, but clients should
be explicitly aware of what they are doing and opt in to it by passing
in the appropriate role parameters.
* Error out on dangerous federation token retrieval
The AWS secrets role code now disallows creation of a dangerous role
configuration; however, pre-existing roles could have existed that would
trigger this now-dangerous code path, so also adding a check for this
configuration at credential retrieval time.
* Run makefmt
* Fix tests
* Fix comments/docs
2019-08-20 19:34:41 +00:00
|
|
|
|
|
|
|
func convertPolicyARNs(policyARNs []string) []*sts.PolicyDescriptorType {
|
|
|
|
size := len(policyARNs)
|
|
|
|
retval := make([]*sts.PolicyDescriptorType, size, size)
|
|
|
|
for i, arn := range policyARNs {
|
|
|
|
retval[i] = &sts.PolicyDescriptorType{
|
|
|
|
Arn: aws.String(arn),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return retval
|
|
|
|
}
|
2021-07-20 16:48:29 +00:00
|
|
|
|
|
|
|
type UsernameMetadata struct {
|
2021-08-09 16:40:47 +00:00
|
|
|
Type string
|
2021-07-20 16:48:29 +00:00
|
|
|
DisplayName string
|
|
|
|
PolicyName string
|
|
|
|
}
|