From 7c215f2c3efee06e73b2dc9e971dfd8b57d399bf Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 4 Oct 2021 08:58:31 +0200 Subject: [PATCH] Polishing. Simplify file read. Add tests. See gh-609 Original pull request: gh-614 --- docs/src/main/asciidoc/authentication.adoc | 5 +- .../config/ClientAuthenticationFactory.java | 32 ++++++------- .../ClientAuthenticationFactoryUnitTests.java | 47 +++++++++++++++++++ 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/docs/src/main/asciidoc/authentication.adoc b/docs/src/main/asciidoc/authentication.adoc index 46947ee7..d5056ceb 100644 --- a/docs/src/main/asciidoc/authentication.adoc +++ b/docs/src/main/asciidoc/authentication.adoc @@ -9,9 +9,8 @@ Spring Cloud Vault supports token and AppId authentication. === Token authentication Tokens are the core method for authentication within Vault. -Token authentication requires a static token to be provided using the -https://github.com/spring-cloud/spring-cloud-commons/blob/master/docs/src/main/asciidoc/spring-cloud-commons.adoc#the-bootstrap-application-context[Bootstrap Application Context]. -As a fallback the token may also be retrieved from ~/.vault-token which is the default location used by the Vault CLI to cache a token. +Token authentication requires a static token to be provided using the configuration. +As a fallback, the token may also be retrieved from `~/.vault-token` which is the default location used by the Vault CLI to cache tokens. NOTE: Token authentication is the default authentication method. If a token is disclosed an unintended party gains access to Vault and can access secrets for the intended client. diff --git a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java index 2b95a6e1..71635e70 100644 --- a/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java +++ b/spring-cloud-vault-config/src/main/java/org/springframework/cloud/vault/config/ClientAuthenticationFactory.java @@ -17,7 +17,6 @@ package org.springframework.cloud.vault.config; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -28,12 +27,12 @@ import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import org.springframework.beans.BeanUtils; +import org.springframework.boot.system.SystemProperties; import org.springframework.cloud.vault.config.VaultProperties.AppRoleProperties; import org.springframework.cloud.vault.config.VaultProperties.AwsIamProperties; import org.springframework.cloud.vault.config.VaultProperties.AzureMsiProperties; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.util.StreamUtils; import org.springframework.util.StringUtils; import org.springframework.vault.authentication.AppIdAuthentication; import org.springframework.vault.authentication.AppIdAuthenticationOptions; @@ -406,24 +405,25 @@ class ClientAuthenticationFactory { } private ClientAuthentication tokenAuthentication(VaultProperties vaultProperties) { + if (StringUtils.hasText(vaultProperties.getToken())) { return new TokenAuthentication(vaultProperties.getToken()); } + + Path vaultTokenPath = Paths.get(SystemProperties.get("user.home"), ".vault-token"); + + if (Files.exists(vaultTokenPath)) { + try { + return new TokenAuthentication(new String(Files.readAllBytes(vaultTokenPath), UTF_8)); + } + catch (IOException ex) { + throw new IllegalStateException(String.format("Could not retrieve vault token from %s", vaultTokenPath), + ex); + } + } else { - Path vaultTokenPath = Paths.get(System.getProperty("user.home"), ".vault-token"); - if (Files.exists(vaultTokenPath)) { - try (InputStream inputStream = Files.newInputStream(vaultTokenPath)) { - String token = StreamUtils.copyToString(inputStream, UTF_8); - return new TokenAuthentication(token); - } - catch (IOException ex) { - throw new IllegalStateException("Could not retrieve vault token from ~/.vault-token", ex); - } - } - else { - throw new IllegalStateException( - "Cannot create authentication mechanism for TOKEN. This method requires either a Token (spring.cloud.vault.token) or File ~/.vault-token."); - } + throw new IllegalStateException( + "Cannot create authentication mechanism for TOKEN. This method requires either a Token (spring.cloud.vault.token) or a token file at ~/.vault-token."); } } diff --git a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ClientAuthenticationFactoryUnitTests.java b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ClientAuthenticationFactoryUnitTests.java index e1c84d36..9151fc29 100644 --- a/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ClientAuthenticationFactoryUnitTests.java +++ b/spring-cloud-vault-config/src/test/java/org/springframework/cloud/vault/config/ClientAuthenticationFactoryUnitTests.java @@ -16,8 +16,16 @@ package org.springframework.cloud.vault.config; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + import org.junit.Test; +import org.springframework.boot.system.SystemProperties; import org.springframework.core.io.ClassPathResource; import org.springframework.vault.authentication.AppRoleAuthenticationOptions; import org.springframework.vault.authentication.AppRoleAuthenticationOptions.RoleId; @@ -25,10 +33,12 @@ import org.springframework.vault.authentication.AppRoleAuthenticationOptions.Sec import org.springframework.vault.authentication.ClientAuthentication; import org.springframework.vault.authentication.ClientCertificateAuthentication; import org.springframework.vault.authentication.PcfAuthentication; +import org.springframework.vault.authentication.TokenAuthentication; import org.springframework.vault.support.VaultToken; import org.springframework.web.client.RestTemplate; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** @@ -178,4 +188,41 @@ public class ClientAuthenticationFactoryUnitTests { assertThat(clientAuthentication).isInstanceOf(ClientCertificateAuthentication.class); } + @Test + public void shouldSupportTokenFromFile() throws IOException { + + VaultProperties properties = new VaultProperties(); + properties.setAuthentication(VaultProperties.AuthenticationMethod.TOKEN); + + Path vaultTokenPath = Paths.get(SystemProperties.get("user.home"), ".vault-token"); + Files.write(vaultTokenPath, "hello".getBytes(StandardCharsets.UTF_8), StandardOpenOption.WRITE, + StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE); + try { + ClientAuthentication clientAuthentication = new ClientAuthenticationFactory(properties, new RestTemplate(), + new RestTemplate()).createClientAuthentication(); + + assertThat(clientAuthentication).isInstanceOf(TokenAuthentication.class); + VaultToken token = clientAuthentication.login(); + + assertThat(new String(token.toCharArray())).isEqualTo("hello"); + } + finally { + Files.deleteIfExists(vaultTokenPath); + } + } + + @Test + public void tokenAuthShouldFailIfTokenFileNotExistsAndTokenEmpty() throws IOException { + + VaultProperties properties = new VaultProperties(); + properties.setAuthentication(VaultProperties.AuthenticationMethod.TOKEN); + Path vaultTokenPath = Paths.get(SystemProperties.get("user.home"), ".vault-token"); + Files.deleteIfExists(vaultTokenPath); + + ClientAuthenticationFactory factory = new ClientAuthenticationFactory(properties, new RestTemplate(), + new RestTemplate()); + + assertThatIllegalStateException().isThrownBy(factory::createClientAuthentication); + } + }