From 2e40128024023ab03f6cb34c82066da945317b4c Mon Sep 17 00:00:00 2001 From: krisiye Date: Tue, 9 Feb 2021 17:46:41 -0500 Subject: [PATCH] Add support for AWS STS credential types. We now support assumed role and federation tokens. Closes gh-572 Original pull request gh-575. --- docs/src/main/asciidoc/secret-backends.adoc | 37 +++++- .../vault/config/aws/AwsCredentialType.java | 28 +++++ .../vault/config/aws/VaultAwsProperties.java | 56 +++++++++ .../VaultConfigAwsBootstrapConfiguration.java | 115 ++++++++++++++---- 4 files changed, 213 insertions(+), 23 deletions(-) create mode 100644 spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/AwsCredentialType.java diff --git a/docs/src/main/asciidoc/secret-backends.adoc b/docs/src/main/asciidoc/secret-backends.adoc index c5f67426..19df2b6a 100644 --- a/docs/src/main/asciidoc/secret-backends.adoc +++ b/docs/src/main/asciidoc/secret-backends.adoc @@ -196,10 +196,21 @@ dependency. The integration can be enabled by setting `spring.cloud.vault.aws=true` (default `false`) and providing the role name with `spring.cloud.vault.aws.role=…`. +Supported AWS credential Types: + +* iam_user (Defaults) +* assumed_role (STS) +* federation_token (STS) + The access key and secret key are stored in `cloud.aws.credentials.accessKey` -and `cloud.aws.credentials.secretKey` so using Spring Cloud AWS will pick up the generated credentials without further configuration. +and `cloud.aws.credentials.secretKey`. So using Spring Cloud AWS will pick up the generated credentials without further configuration. + You can configure the property names by setting `spring.cloud.vault.aws.access-key-property` and -`spring.cloud.vault.aws.secret-key-property`. +`spring.cloud.vault.aws.secret-key-property`. + +For STS security token, you can configure the property name by setting `spring.cloud.vault.aws.session-token-key-property`. The security token is stored under `cloud.aws.credentials.sessionToken` (defaults). + +Example: iam_user ==== [source,yaml] @@ -214,11 +225,33 @@ spring.cloud.vault: ---- ==== +Example: assumed_role (STS) +==== +[source,yaml] +---- +spring.cloud.vault: + aws: + enabled: true + role: sts-vault-role + backend: aws + credential-type: assumed_role + access-key-property: cloud.aws.credentials.accessKey + secret-key-property: cloud.aws.credentials.secretKey + session-token-key-property: cloud.aws.credentials.sessionToken + ttl: 3600s + role-arn: arn:aws:iam::${AWS_ACCOUNT}:role/sts-app-role +---- +==== + * `enabled` setting this value to `true` enables the AWS backend config usage * `role` sets the role name of the AWS role definition * `backend` sets the path of the AWS mount to use * `access-key-property` sets the property name in which the AWS access key is stored * `secret-key-property` sets the property name in which the AWS secret key is stored +* `session-token-key-property` sets the property name in which the AWS STS security token is stored. +* `credential-type` sets the aws credential type to use for this backend. Defaults to `iam_user` +* `ttl` sets the ttl for the STS token when using `assumed_role` or `federation_token`. Defaults to the ttl specified by the vault role. Min/Max values are also limited to what AWS would support for STS. +* `role-arn` sets the IAM role to assume if more than one are configured for the vault role when using `assumed_role`. See also: https://www.vaultproject.io/docs/secrets/aws/index.html[Vault Documentation: Setting up AWS with Vault] diff --git a/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/AwsCredentialType.java b/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/AwsCredentialType.java new file mode 100644 index 00000000..cdd6a8cb --- /dev/null +++ b/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/AwsCredentialType.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @author Kris Iyer + * + * Supported Aws credential types. + */ +package org.springframework.cloud.vault.config.aws; + +public enum AwsCredentialType { + + IAM_USER, ASSUMED_ROLE, FEDERATION_TOKEN + +} diff --git a/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultAwsProperties.java b/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultAwsProperties.java index 2b02cee4..6d669f05 100644 --- a/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultAwsProperties.java +++ b/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultAwsProperties.java @@ -24,6 +24,7 @@ import org.springframework.lang.Nullable; * Configuration properties for Vault using the AWS integration. * * @author Mark Paluch + * @author Kris Iyer */ @ConfigurationProperties("spring.cloud.vault.aws") public class VaultAwsProperties implements VaultSecretBackendDescriptor { @@ -44,6 +45,11 @@ public class VaultAwsProperties implements VaultSecretBackendDescriptor { */ private String backend = "aws"; + /** + * aws credential type + */ + private AwsCredentialType credentialType = AwsCredentialType.IAM_USER; + /** * Target property for the obtained access key. */ @@ -54,6 +60,24 @@ public class VaultAwsProperties implements VaultSecretBackendDescriptor { */ private String secretKeyProperty = "cloud.aws.credentials.secretKey"; + /** + * Target property for the obtained secret key. + */ + private String sessionTokenKeyProperty = "cloud.aws.credentials.sessionToken"; + + /** + * + * Role arn for assumed_role in case we have multiple roles associated with the vault + * role + */ + private String roleArn; + + /** + * TTL for sts tokens. Defaults to whatever the vault Role may have for Max. Also + * limited to what AWS supports to be the max for STS. + */ + private String ttl; + @Override public boolean isEnabled() { return this.enabled; @@ -97,4 +121,36 @@ public class VaultAwsProperties implements VaultSecretBackendDescriptor { this.secretKeyProperty = secretKeyProperty; } + public AwsCredentialType getCredentialType() { + return credentialType; + } + + public void setCredentialType(AwsCredentialType credentialType) { + this.credentialType = credentialType; + } + + public String getSessionTokenKeyProperty() { + return sessionTokenKeyProperty; + } + + public void setSessionTokenKeyProperty(String sessionTokenKeyProperty) { + this.sessionTokenKeyProperty = sessionTokenKeyProperty; + } + + public String getRoleArn() { + return roleArn; + } + + public void setRoleArn(String roleArn) { + this.roleArn = roleArn; + } + + public String getTtl() { + return ttl; + } + + public void setTtl(String ttl) { + this.ttl = ttl; + } + } diff --git a/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultConfigAwsBootstrapConfiguration.java b/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultConfigAwsBootstrapConfiguration.java index babdc74c..ebfeb050 100644 --- a/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultConfigAwsBootstrapConfiguration.java +++ b/spring-cloud-vault-config-aws/src/main/java/org/springframework/cloud/vault/config/aws/VaultConfigAwsBootstrapConfiguration.java @@ -21,6 +21,7 @@ import java.util.Map; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.cloud.vault.config.LeasingSecretBackendMetadata; import org.springframework.cloud.vault.config.PropertyNameTransformer; import org.springframework.cloud.vault.config.SecretBackendMetadata; import org.springframework.cloud.vault.config.SecretBackendMetadataFactory; @@ -28,12 +29,17 @@ import org.springframework.cloud.vault.config.VaultSecretBackendDescriptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.util.Assert; +import org.springframework.util.StringUtils; +import org.springframework.vault.core.lease.domain.RequestedSecret; +import org.springframework.vault.core.lease.domain.RequestedSecret.Mode; import org.springframework.vault.core.util.PropertyTransformer; /** * Bootstrap configuration providing support for the AWS secret backend. * * @author Mark Paluch + * @author Kris Iyer + * */ @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(VaultAwsProperties.class) @@ -57,6 +63,7 @@ public class VaultConfigAwsBootstrapConfiguration { * property names to names provided with * {@link VaultAwsProperties#getAccessKeyProperty()} and * {@link VaultAwsProperties#getSecretKeyProperty()}. + * {@link VaultAwsProperties#getSessionTokenKeyProperty()}. * @param properties must not be {@literal null}. * @return the {@link SecretBackendMetadata} */ @@ -68,34 +75,100 @@ public class VaultConfigAwsBootstrapConfiguration { transformer.addKeyTransformation("access_key", properties.getAccessKeyProperty()); transformer.addKeyTransformation("secret_key", properties.getSecretKeyProperty()); - return new SecretBackendMetadata() { + if (properties.getCredentialType() == AwsCredentialType.ASSUMED_ROLE + || properties.getCredentialType() == AwsCredentialType.FEDERATION_TOKEN) { - @Override - public String getName() { - return String.format("%s with Role %s", properties.getBackend(), properties.getRole()); - } + // security token transformer for STS + transformer.addKeyTransformation("security_token", properties.getSessionTokenKeyProperty()); - @Override - public String getPath() { - return String.format("%s/creds/%s", properties.getBackend(), properties.getRole()); - } + return new LeasingSecretBackendMetadata() { - @Override - public PropertyTransformer getPropertyTransformer() { - return transformer; - } + @Override + public String getName() { + return String.format("%s with Role %s", properties.getBackend(), properties.getRole()); + } - @Override - public Map getVariables() { + @Override + public String getPath() { + String defaultPath = "%s/sts/%s"; - Map variables = new HashMap<>(); + // do we have any ttl or role parameters configured? + if (StringUtils.hasText(properties.getTtl()) + || (properties.getCredentialType() == AwsCredentialType.ASSUMED_ROLE + && StringUtils.hasText(properties.getRoleArn()))) { + defaultPath += "?"; + } + // ttl for assumed_role or federation_token + // pass through to let aws take care of min and max validations + // per + // the vault role + if (StringUtils.hasText(properties.getTtl())) { + defaultPath += "ttl=" + properties.getTtl() + "&"; + } - variables.put("backend", properties.getBackend()); - variables.put("key", String.format("creds/%s", properties.getRole())); + // role_arn for assumed_role for vault role that has multiple role + // associations. + if (properties.getCredentialType() == AwsCredentialType.ASSUMED_ROLE + && StringUtils.hasText(properties.getRoleArn())) { + defaultPath += "role_arn=" + properties.getRoleArn(); + } + return String.format(defaultPath, properties.getBackend(), properties.getRole()); + } - return variables; - } - }; + @Override + public PropertyTransformer getPropertyTransformer() { + return transformer; + } + + @Override + public Map getVariables() { + + Map variables = new HashMap<>(); + + variables.put("backend", properties.getBackend()); + variables.put("key", String.format("sts/%s", properties.getRole())); + + return variables; + } + + @Override + public Mode getLeaseMode() { + return RequestedSecret.Mode.ROTATE; + } + }; + + } + else { + + return new SecretBackendMetadata() { + + @Override + public String getName() { + return String.format("%s with Role %s", properties.getBackend(), properties.getRole()); + } + + @Override + public String getPath() { + return String.format("%s/creds/%s", properties.getBackend(), properties.getRole()); + } + + @Override + public PropertyTransformer getPropertyTransformer() { + return transformer; + } + + @Override + public Map getVariables() { + + Map variables = new HashMap<>(); + + variables.put("backend", properties.getBackend()); + variables.put("key", String.format("creds/%s", properties.getRole())); + + return variables; + } + }; + } } @Override