Polishing.

Tweak Javadoc. Switch TTL from String to Duration for type-safe conversion. Extract anonymous classes into inner classes. Add unit test.

See gh-572
Closes gh-575.
This commit is contained in:
Mark Paluch
2021-03-11 11:01:38 +01:00
parent a7565ef285
commit 60ed7b7ea3
4 changed files with 210 additions and 101 deletions

View File

@@ -14,13 +14,14 @@
* limitations under the License.
*/
/**
* @author Kris Iyer
*
* Supported Aws credential types.
*/
package org.springframework.cloud.vault.config.aws;
/**
* Supported AWS credential types.
*
* @author Kris Iyer
* @since 3.0.2
*/
public enum AwsCredentialType {
IAM_USER, ASSUMED_ROLE, FEDERATION_TOKEN

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.vault.config.aws;
import java.time.Duration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.vault.config.VaultSecretBackendDescriptor;
import org.springframework.lang.Nullable;
@@ -66,17 +68,19 @@ public class VaultAwsProperties implements VaultSecretBackendDescriptor {
private String sessionTokenKeyProperty = "cloud.aws.credentials.sessionToken";
/**
*
* Role arn for assumed_role in case we have multiple roles associated with the vault
* role
* role.
* @since 3.0.2
*/
@Nullable
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.
* @since 3.0.2
*/
private String ttl;
private Duration ttl = Duration.ZERO;
@Override
public boolean isEnabled() {
@@ -122,7 +126,7 @@ public class VaultAwsProperties implements VaultSecretBackendDescriptor {
}
public AwsCredentialType getCredentialType() {
return credentialType;
return this.credentialType;
}
public void setCredentialType(AwsCredentialType credentialType) {
@@ -130,26 +134,27 @@ public class VaultAwsProperties implements VaultSecretBackendDescriptor {
}
public String getSessionTokenKeyProperty() {
return sessionTokenKeyProperty;
return this.sessionTokenKeyProperty;
}
public void setSessionTokenKeyProperty(String sessionTokenKeyProperty) {
this.sessionTokenKeyProperty = sessionTokenKeyProperty;
}
@Nullable
public String getRoleArn() {
return roleArn;
return this.roleArn;
}
public void setRoleArn(String roleArn) {
this.roleArn = roleArn;
}
public String getTtl() {
return ttl;
public Duration getTtl() {
return this.ttl;
}
public void setTtl(String ttl) {
public void setTtl(Duration ttl) {
this.ttl = ttl;
}

View File

@@ -18,6 +18,7 @@ package org.springframework.cloud.vault.config.aws;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -30,7 +31,6 @@ 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;
@@ -39,7 +39,6 @@ import org.springframework.vault.core.util.PropertyTransformer;
*
* @author Mark Paluch
* @author Kris Iyer
*
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(VaultAwsProperties.class)
@@ -81,93 +80,10 @@ public class VaultConfigAwsBootstrapConfiguration {
// security token transformer for STS
transformer.addKeyTransformation("security_token", properties.getSessionTokenKeyProperty());
return new LeasingSecretBackendMetadata() {
@Override
public String getName() {
return String.format("%s with Role %s", properties.getBackend(), properties.getRole());
}
@Override
public String getPath() {
String defaultPath = "%s/sts/%s";
// 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() + "&";
}
// 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());
}
@Override
public PropertyTransformer getPropertyTransformer() {
return transformer;
}
@Override
public Map<String, String> getVariables() {
Map<String, String> 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;
}
};
return new AwsStsLeasingSecretBackendMetadata(properties, transformer);
}
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<String, String> getVariables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", properties.getBackend());
variables.put("key", String.format("creds/%s", properties.getRole()));
return variables;
}
};
return new AwsLeasingSecretBackendMetadata(properties, transformer);
}
}
@@ -181,6 +97,109 @@ public class VaultConfigAwsBootstrapConfiguration {
return backendDescriptor instanceof VaultAwsProperties;
}
private static class AwsStsLeasingSecretBackendMetadata implements LeasingSecretBackendMetadata {
private final VaultAwsProperties properties;
private final PropertyNameTransformer transformer;
AwsStsLeasingSecretBackendMetadata(VaultAwsProperties properties, PropertyNameTransformer transformer) {
this.properties = properties;
this.transformer = transformer;
}
@Override
public String getName() {
return String.format("%s with Role %s", this.properties.getBackend(), this.properties.getRole());
}
@Override
public String getPath() {
String defaultPath = "%s/sts/%s";
StringJoiner joiner = new StringJoiner("&");
// ttl for assumed_role or federation_token
// pass through to let aws take care of min and max validations
// per the vault role
if (!this.properties.getTtl().isZero()) {
joiner.add("ttl=" + this.properties.getTtl().toMillis() + "ms");
}
// role_arn for assumed_role for vault role that has multiple role
// associations.
if (this.properties.getCredentialType() == AwsCredentialType.ASSUMED_ROLE
&& StringUtils.hasText(this.properties.getRoleArn())) {
joiner.add("role_arn=" + this.properties.getRoleArn());
}
String pathToUse = joiner.length() == 0 ? defaultPath : defaultPath + "?" + joiner;
return String.format(pathToUse, this.properties.getBackend(), this.properties.getRole());
}
@Override
public PropertyTransformer getPropertyTransformer() {
return this.transformer;
}
@Override
public Map<String, String> getVariables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", this.properties.getBackend());
variables.put("key", String.format("sts/%s", this.properties.getRole()));
return variables;
}
@Override
public Mode getLeaseMode() {
return Mode.ROTATE;
}
}
private static class AwsLeasingSecretBackendMetadata implements SecretBackendMetadata {
private final VaultAwsProperties properties;
private final PropertyNameTransformer transformer;
AwsLeasingSecretBackendMetadata(VaultAwsProperties properties, PropertyNameTransformer transformer) {
this.properties = properties;
this.transformer = transformer;
}
@Override
public String getName() {
return String.format("%s with Role %s", this.properties.getBackend(), this.properties.getRole());
}
@Override
public String getPath() {
return String.format("%s/creds/%s", this.properties.getBackend(), this.properties.getRole());
}
@Override
public PropertyTransformer getPropertyTransformer() {
return this.transformer;
}
@Override
public Map<String, String> getVariables() {
Map<String, String> variables = new HashMap<>();
variables.put("backend", this.properties.getBackend());
variables.put("key", String.format("creds/%s", this.properties.getRole()));
return variables;
}
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2016-2021 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.
*/
package org.springframework.cloud.vault.config.aws;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.springframework.cloud.vault.config.SecretBackendMetadata;
import org.springframework.cloud.vault.config.SecretBackendMetadataFactory;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link VaultConfigAwsBootstrapConfiguration}.
*
* @author Mark Paluch
*/
public class VaultConfigAwsBootstrapConfigurationUnitTests {
@Test
void shouldCreateIamTokenSecretBackendMetadataFactory() {
VaultAwsProperties properties = new VaultAwsProperties();
properties.setRole("readonly");
SecretBackendMetadataFactory<VaultAwsProperties> factory = new VaultConfigAwsBootstrapConfiguration()
.awsSecretBackendMetadataFactory();
SecretBackendMetadata metadata = factory.createMetadata(properties);
assertThat(metadata.getPath()).isEqualTo("aws/creds/readonly");
assertThat(metadata.getVariables()).containsEntry("backend", "aws").containsEntry("key", "creds/readonly");
}
@Test
void shouldCreateStsTokenSecretBackendMetadataFactory() {
VaultAwsProperties properties = new VaultAwsProperties();
properties.setCredentialType(AwsCredentialType.FEDERATION_TOKEN);
properties.setRole("readonly");
SecretBackendMetadataFactory<VaultAwsProperties> factory = new VaultConfigAwsBootstrapConfiguration()
.awsSecretBackendMetadataFactory();
SecretBackendMetadata metadata = factory.createMetadata(properties);
assertThat(metadata.getPath()).isEqualTo("aws/sts/readonly");
assertThat(metadata.getVariables()).containsEntry("backend", "aws").containsEntry("key", "sts/readonly");
}
@Test
void shouldCreateStsTokenSecretBackendMetadataFactoryWithTtlAndRoleArn() {
VaultAwsProperties properties = new VaultAwsProperties();
properties.setCredentialType(AwsCredentialType.ASSUMED_ROLE);
properties.setRoleArn("1:2:3");
properties.setTtl(Duration.ofMinutes(1));
properties.setRole("readonly");
SecretBackendMetadataFactory<VaultAwsProperties> factory = new VaultConfigAwsBootstrapConfiguration()
.awsSecretBackendMetadataFactory();
SecretBackendMetadata metadata = factory.createMetadata(properties);
assertThat(metadata.getPath()).isEqualTo("aws/sts/readonly?ttl=60000ms&role_arn=1:2:3");
assertThat(metadata.getVariables()).containsEntry("backend", "aws").containsEntry("key", "sts/readonly");
}
}