Polishing.

Rename Kube* authentication classes to Kubernetes*. Refactor KubernetesJwtSupplier to extend Supplier<String>. Load token file content eagerly. Extend Javadoc. Reformat code. Remove superfluous exception declarations in tests. Remove commented code from local_run_k8s.sh. Copy CA file from pod.

Reinstantiate AuthenticationStepsFactory for KubernetesAuthentication.

Add Kubernetes authentication to the reference documentation.

Original pull request: gh-166.
Related ticket: gh-143.
Closes gh-166.
This commit is contained in:
Mark Paluch
2017-11-01 17:53:40 +01:00
parent 696d047760
commit e5f96dd352
17 changed files with 574 additions and 309 deletions

View File

@@ -1,118 +0,0 @@
/*
* Copyright 2016-2017 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
*
* http://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.vault.authentication;
import org.springframework.util.Assert;
/**
* Authentication options for {@link KubeAuthentication}.
* <p>
* Authentication options provide the path, role and jwt supplier.
* {@link KubeAuthentication} can be constructed using {@link #builder()}. Instances of
* this class are immutable once constructed.
*
* @author Michal Budzyn
* @see KubeAuthentication
* @see #builder()
*/
public class KubeAuthenticationOptions {
public static final String DEFAULT_KUBERNETES_AUTHENTICATION_PATH = "kubernetes";
/**
* Path of the kubernetes authentication backend mount.
*/
private final String path;
/**
* The Role.
*/
private final String role;
/**
* {@link KubeJwtSupplier} instance to obtain a service account JSON Web Tokens.
*/
private final KubeJwtSupplier jwtSupplier;
private KubeAuthenticationOptions(String path, String role,
KubeJwtSupplier jwtSupplier) {
this.path = path;
this.role = role;
this.jwtSupplier = jwtSupplier;
}
public static KubernetesAuthenticationOptionsBuilder builder() {
return new KubernetesAuthenticationOptionsBuilder();
}
public String getPath() {
return path;
}
public String getRole() {
return role;
}
public KubeJwtSupplier getJwtSupplier() {
return jwtSupplier;
}
/**
* Builder for {@link KubeAuthenticationOptions}.
*/
public static class KubernetesAuthenticationOptionsBuilder {
private String path = DEFAULT_KUBERNETES_AUTHENTICATION_PATH;
private String role;
private KubeJwtSupplier jwtSupplier;
public KubernetesAuthenticationOptionsBuilder path(String path) {
Assert.hasText(path, "Path must not be empty");
this.path = path;
return this;
}
public KubernetesAuthenticationOptionsBuilder role(String role) {
Assert.hasText(role, "Role must not be empty");
this.role = role;
return this;
}
public KubernetesAuthenticationOptionsBuilder jwtSupplier(
KubeJwtSupplier jwtSupplier) {
Assert.notNull(jwtSupplier, "JwtSupplier must not be null");
this.jwtSupplier = jwtSupplier;
return this;
}
public KubeAuthenticationOptions build() {
Assert.notNull(role, "Role must not be null");
Assert.notNull(path, "Path must not be null");
Assert.notNull(jwtSupplier, "JwtSupplier must not be null");
return new KubeAuthenticationOptions(path, role, jwtSupplier);
}
}
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright 2016-2017 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
*
* http://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.vault.authentication;
import static java.nio.charset.StandardCharsets.US_ASCII;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;
import org.springframework.vault.VaultException;
/**
* Mechanism to retrieve a Kubernetes service account token.
* <p>
* A file containing a token for a pods service account is automatically mounted at
* <b>/var/run/secrets/kubernetes.io/serviceaccount/token</b>
*
* @author Michal Budzyn
* @see KubeJwtSupplier
*/
public class KubeServiceAccountTokenFile implements KubeJwtSupplier {
public static final String DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/token";
private final Resource resource;
public KubeServiceAccountTokenFile() {
this(DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE);
}
public KubeServiceAccountTokenFile(String fileName) {
this(new FileSystemResource(fileName));
}
public KubeServiceAccountTokenFile(File file) {
this(new FileSystemResource(file));
}
public KubeServiceAccountTokenFile(Resource resource) {
this.resource = resource;
}
@Override
public String getKubeJwt() {
try (InputStream is = resource.getInputStream()) {
return StreamUtils.copyToString(is, US_ASCII);
}
catch (IOException e) {
throw new VaultException(
String.format("Kube JWT token retrieval from %s failed", resource),
e);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2017 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.
@@ -20,6 +20,7 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.Assert;
import org.springframework.vault.VaultException;
import org.springframework.vault.client.VaultResponses;
@@ -29,32 +30,36 @@ import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestOperations;
/**
* Kubernetes implementation of {@link ClientAuthentication}. {@link KubeAuthentication}
* uses a Kubernetes Service Account JSON Web Token to login into Vault. JWT and Role are
* sent in the login request to Vault to obtain a {@link VaultToken}.
* Kubernetes implementation of {@link ClientAuthentication}.
* {@link KubernetesAuthentication} uses a Kubernetes Service Account JSON Web Token to
* login into Vault. JWT and Role are sent in the login request to Vault to obtain a
* {@link VaultToken}.
*
* @author Michal Budzyn
* @see KubeAuthenticationOptions
* @author Mark Paluch
* @since 2.0
* @see KubernetesAuthenticationOptions
* @see RestOperations
* @see <a href="https://www.vaultproject.io/docs/auth/kubernetes.html">Auth Backend:
* Kubernetes</a>
*/
public class KubeAuthentication implements ClientAuthentication {
public class KubernetesAuthentication implements ClientAuthentication,
AuthenticationStepsFactory {
private static final Log logger = LogFactory.getLog(KubeAuthentication.class);
private static final Log logger = LogFactory.getLog(KubernetesAuthentication.class);
private final KubeAuthenticationOptions options;
private final KubernetesAuthenticationOptions options;
private final RestOperations restOperations;
/**
* Create a {@link KubeAuthentication} using {@link KubeAuthenticationOptions} and
* {@link RestOperations}.
* Create a {@link KubernetesAuthentication} using
* {@link KubernetesAuthenticationOptions} and {@link RestOperations}.
*
* @param options must not be {@literal null}.
* @param restOperations must not be {@literal null}.
*/
public KubeAuthentication(KubeAuthenticationOptions options,
public KubernetesAuthentication(KubernetesAuthenticationOptions options,
RestOperations restOperations) {
Assert.notNull(options, "KubeAuthenticationOptions must not be null");
@@ -64,28 +69,29 @@ public class KubeAuthentication implements ClientAuthentication {
this.restOperations = restOperations;
}
private static Map<String, String> getKubeLogin(String role, String jwt) {
/**
* Creates a {@link AuthenticationSteps} for kubernetes authentication given
* {@link KubernetesAuthenticationOptions}.
*
* @param options must not be {@literal null}.
* @return {@link AuthenticationSteps} for kubernetes authentication.
*/
public static AuthenticationSteps createAuthenticationSteps(
KubernetesAuthenticationOptions options) {
Assert.hasText(role, "role must not be empty");
Assert.hasText(role, "jwt must not be empty");
Assert.notNull(options, "CubbyholeAuthenticationOptions must not be null");
Map<String, String> login = new HashMap<>();
login.put("jwt", jwt);
login.put("role", role);
return login;
String token = options.getJwtSupplier().get();
return AuthenticationSteps.fromSupplier(
() -> getKubernetesLogin(options.getRole(), token)) //
.login("auth/{mount}/login", options.getPath());
}
@Override
public VaultToken login() throws VaultException {
return createTokenUsingKubernetes();
}
private VaultToken createTokenUsingKubernetes() {
Map<String, String> login = getKubeLogin(options.getRole(),
options.getJwtSupplier().getKubeJwt());
Map<String, String> login = getKubernetesLogin(options.getRole(), options
.getJwtSupplier().get());
try {
VaultResponse response = restOperations.postForObject("auth/{mount}/login",
@@ -103,4 +109,22 @@ public class KubeAuthentication implements ClientAuthentication {
VaultResponses.getError(e.getResponseBodyAsString())));
}
}
@Override
public AuthenticationSteps getAuthenticationSteps() {
return createAuthenticationSteps(this.options);
}
private static Map<String, String> getKubernetesLogin(String role, String jwt) {
Assert.hasText(role, "Role must not be empty");
Assert.hasText(role, "JWT must not be empty");
Map<String, String> login = new HashMap<>();
login.put("jwt", jwt);
login.put("role", role);
return login;
}
}

View File

@@ -0,0 +1,162 @@
/*
* Copyright 2017 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
*
* http://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.vault.authentication;
import java.util.function.Supplier;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
/**
* Authentication options for {@link KubernetesAuthentication}.
* <p>
* Authentication options provide the path, role and jwt supplier.
* {@link KubernetesAuthentication} can be constructed using {@link #builder()}. Instances
* of this class are immutable once constructed.
*
* @author Michal Budzyn
* @author Mark Paluch
* @since 2.0
* @see KubernetesAuthentication
* @see #builder()
*/
public class KubernetesAuthenticationOptions {
public static final String DEFAULT_KUBERNETES_AUTHENTICATION_PATH = "kubernetes";
/**
* Path of the kubernetes authentication backend mount.
*/
private final String path;
/**
* Name of the role against which the login is being attempted.
*/
private final String role;
/**
* Supplier instance to obtain a service account JSON Web Tokens.
*/
private final Supplier<String> jwtSupplier;
private KubernetesAuthenticationOptions(String path, String role,
Supplier<String> jwtSupplier) {
this.path = path;
this.role = role;
this.jwtSupplier = jwtSupplier;
}
/**
* @return a new {@link KubernetesAuthenticationOptionsBuilder}.
*/
public static KubernetesAuthenticationOptionsBuilder builder() {
return new KubernetesAuthenticationOptionsBuilder();
}
/**
* @return the path of the aws authentication backend mount.
*/
public String getPath() {
return path;
}
/**
* @return name of the role against which the login is being attempted.
*/
public String getRole() {
return role;
}
/**
* @return JSON Web Token supplier.
*/
public Supplier<String> getJwtSupplier() {
return jwtSupplier;
}
/**
* Builder for {@link KubernetesAuthenticationOptions}.
*/
public static class KubernetesAuthenticationOptionsBuilder {
private String path = DEFAULT_KUBERNETES_AUTHENTICATION_PATH;
@Nullable
private String role;
@Nullable
private Supplier<String> jwtSupplier;
/**
* Configure the mount path.
*
* @param path must not be {@literal null} or empty.
* @return {@code this} {@link KubernetesAuthenticationOptionsBuilder}.
*/
public KubernetesAuthenticationOptionsBuilder path(String path) {
Assert.hasText(path, "Path must not be empty");
this.path = path;
return this;
}
/**
* Configure the role.
*
* @param role name of the role against which the login is being attempted, must
* not be {@literal null} or empty.
* @return {@code this} {@link KubernetesAuthenticationOptionsBuilder}.
*/
public KubernetesAuthenticationOptionsBuilder role(String role) {
Assert.hasText(role, "Role must not be empty");
this.role = role;
return this;
}
/**
* Configure the {@link Supplier} to obtain a Kubernetes authentication token.
*
* @param jwtSupplier the supplier, must not be {@literal null}.
* @return {@code this} {@link KubernetesAuthenticationOptionsBuilder}.
*/
public KubernetesAuthenticationOptionsBuilder jwtSupplier(
Supplier<String> jwtSupplier) {
Assert.notNull(jwtSupplier, "JwtSupplier must not be null");
this.jwtSupplier = jwtSupplier;
return this;
}
/**
* Build a new {@link KubernetesAuthenticationOptions} instance.
*
* @return a new {@link KubernetesAuthenticationOptions}.
*/
public KubernetesAuthenticationOptions build() {
Assert.notNull(role, "Role must not be null");
return new KubernetesAuthenticationOptions(path, role,
jwtSupplier == null ? new KubernetesServiceAccountTokenFile()
: jwtSupplier);
}
}
}

View File

@@ -15,19 +15,25 @@
*/
package org.springframework.vault.authentication;
import java.util.function.Supplier;
/**
* Interface to obtain a Kubernetes Service Account Token for Kubernetes authentication.
* Implementations are used by {@link KubeAuthentication}.
* Implementations are used by {@link KubernetesAuthentication}.
*
* @author Michal Budzyn
* @see KubeAuthentication
* @author Mark Paluch
* @since 2.0
* @see KubernetesAuthentication
*/
public interface KubeJwtSupplier {
@FunctionalInterface
public interface KubernetesJwtSupplier extends Supplier<String> {
/**
* Get a JWT for Kubernetes authentication.
*
* @return the Kubernetes Service Account JWT.
*/
String getKubeJwt();
@Override
String get();
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2017 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
*
* http://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.vault.authentication;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.springframework.util.StreamUtils;
import org.springframework.vault.VaultException;
/**
* Mechanism to retrieve a Kubernetes service account token.
* <p>
* A file containing a token for a pod's service account is automatically mounted at
* {@code /var/run/secrets/kubernetes.io/serviceaccount/token}.
*
* @author Michal Budzyn
* @author Mark Paluch
* @since 2.0
* @see KubernetesJwtSupplier
*/
public class KubernetesServiceAccountTokenFile implements KubernetesJwtSupplier {
/**
* Default path to the service account token file.
*/
public static final String DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE = "/var/run/secrets/kubernetes.io/serviceaccount/token";
private byte[] token;
/**
* Create a new {@link KubernetesServiceAccountTokenFile} pointing to the
* {@link #DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE}. Construction fails with an
* exception if the file does not exist.
*
* @throws IllegalArgumentException if the
* {@link #DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE} does not exist.
*/
public KubernetesServiceAccountTokenFile() {
this(DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE);
}
/**
* Create a new {@link KubernetesServiceAccountTokenFile}
* {@link KubernetesServiceAccountTokenFile} from a {@code path}.
*
* @param path path to the service account token file.
* @throws IllegalArgumentException if the{@code path} does not exist.
*/
public KubernetesServiceAccountTokenFile(String path) {
this(new FileSystemResource(path));
}
/**
* Create a new {@link KubernetesServiceAccountTokenFile}
* {@link KubernetesServiceAccountTokenFile} from a {@link File} handle.
*
* @param file path to the service account token file.
* @throws IllegalArgumentException if the{@code path} does not exist.
*/
public KubernetesServiceAccountTokenFile(File file) {
this(new FileSystemResource(file));
}
/**
* Create a new {@link KubernetesServiceAccountTokenFile}
* {@link KubernetesServiceAccountTokenFile} from a {@link Resource} handle.
*
* @param resource resource pointing to the service account token file.
* @throws IllegalArgumentException if the{@code path} does not exist.
*/
public KubernetesServiceAccountTokenFile(Resource resource) {
Assert.isTrue(resource.exists(),
() -> String.format("Resource %s does not exist", resource));
try {
this.token = readToken(resource);
}
catch (IOException e) {
throw new VaultException(String.format(
"Kube JWT token retrieval from %s failed", resource), e);
}
}
@Override
public String get() {
return new String(token, StandardCharsets.US_ASCII);
}
/**
* Read the token from {@link Resource}.
*
* @param resource the resource to read from, must not be {@literal null}.
* @return the new byte array that has been copied to (possibly empty).
* @throws IOException in case of I/O errors.
*/
protected static byte[] readToken(Resource resource) throws IOException {
Assert.notNull(resource, "Resource must not be null");
try (InputStream is = resource.getInputStream()) {
return StreamUtils.copyToByteArray(is);
}
}
}

View File

@@ -32,23 +32,23 @@ import org.springframework.vault.authentication.AppRoleAuthentication;
import org.springframework.vault.authentication.AppRoleAuthenticationOptions;
import org.springframework.vault.authentication.AwsEc2Authentication;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.AwsEc2AuthenticationOptionsBuilder;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.ClientCertificateAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthentication;
import org.springframework.vault.authentication.CubbyholeAuthenticationOptions;
import org.springframework.vault.authentication.IpAddressUserId;
import org.springframework.vault.authentication.KubeAuthentication;
import org.springframework.vault.authentication.KubeAuthenticationOptions;
import org.springframework.vault.authentication.KubeJwtSupplier;
import org.springframework.vault.authentication.KubeServiceAccountTokenFile;
import org.springframework.vault.authentication.KubernetesAuthentication;
import org.springframework.vault.authentication.KubernetesAuthenticationOptions;
import org.springframework.vault.authentication.KubernetesJwtSupplier;
import org.springframework.vault.authentication.KubernetesServiceAccountTokenFile;
import org.springframework.vault.authentication.MacAddressUserId;
import org.springframework.vault.authentication.StaticUserId;
import org.springframework.vault.authentication.TokenAuthentication;
import org.springframework.vault.authentication.AwsEc2AuthenticationOptions.AwsEc2AuthenticationOptionsBuilder;
import org.springframework.vault.client.VaultEndpoint;
import org.springframework.vault.support.SslConfiguration;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
import org.springframework.web.client.RestOperations;
/**
@@ -138,7 +138,7 @@ import org.springframework.web.client.RestOperations;
* @see AwsEc2Authentication
* @see ClientCertificateAuthentication
* @see CubbyholeAuthentication
* @see KubeAuthentication
* @see KubernetesAuthentication
*/
@Configuration
public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration implements
@@ -339,14 +339,18 @@ public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration im
String tokenFile = getProperty("vault.kubernetes.service-account-token-file");
if (!StringUtils.hasText(tokenFile)) {
tokenFile = KubeServiceAccountTokenFile.DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE;
tokenFile = KubernetesServiceAccountTokenFile.DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE;
}
KubeJwtSupplier jwtSupplier = new KubeServiceAccountTokenFile(tokenFile);
KubernetesJwtSupplier jwtSupplier = new KubernetesServiceAccountTokenFile(
tokenFile);
KubeAuthenticationOptions authenticationOptions = KubeAuthenticationOptions
.builder().role(role).jwtSupplier(jwtSupplier).build();
KubernetesAuthenticationOptions authenticationOptions = KubernetesAuthenticationOptions
.builder() //
.role(role) //
.jwtSupplier(jwtSupplier) //
.build();
return new KubeAuthentication(authenticationOptions, restOperations());
return new KubernetesAuthentication(authenticationOptions, restOperations());
}
@Nullable

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2017 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.
@@ -15,34 +15,51 @@
*/
package org.springframework.vault.authentication;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link KubeServiceAccountTokenFile}.
* Unit tests for {@link KubernetesServiceAccountTokenFile}.
*
* @author Michal Budzyn
* @author Mark Paluch
*/
public class KubeServiceAccountTokenFileUnitTests {
private final static String TEST_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tNHcydmciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjllMjQzNWY0LTgxNDctMTFlNy05MGFiLTA4MDAyN2NlZTQwNyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.asFRZRZ1gRj9sF0lQqbbxrNhW_lOdj9WjqUpH_4TstxqZZ7B36a0xKKXg6XaFWJY1eMsytpwa7uMzvsvf2pYCcklinaSE_F-wc42IOWcpwSLl4PND92Tp7n_JYEAbbSQVfZPzQ2Y7b6cWu6NRzDs638LwVTqYeWMWbcWlOMaTxjMzGTcgDe5RWslkKUPkYsvPOAFtt5ZErwtVcvTUmplJfHzdWwatlpZRQhYkxGgRIJ6LabXfZOd2N_TchJ3tHjAVBzUDTQq3APQssGb9df2RxVTUiyzbhdRGt7129-LCZ8rZYE7E-Mr3SSpExGYcDk-v0It8hky0CKtCLs2UHiABA";
private final static String TEST_TOKEN = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9."
+ "eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy"
+ "5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJu"
+ "ZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG"
+ "9rZW4tNHcydmciLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZp"
+ "Y2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2Vydm"
+ "ljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjllMjQzNWY0LTgxNDctMT"
+ "FlNy05MGFiLTA4MDAyN2NlZTQwNyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3"
+ "VudDpkZWZhdWx0OmRlZmF1bHQifQ."
+ "asFRZRZ1gRj9sF0lQqbbxrNhW_lOdj9WjqUpH_4TstxqZZ7B36a0xKKXg6XaFW"
+ "JY1eMsytpwa7uMzvsvf2pYCcklinaSE_F-wc42IOWcpwSLl4PND92Tp7n_JYEAbb"
+ "SQVfZPzQ2Y7b6cWu6NRzDs638LwVTqYeWMWbcWlOMaTxjMzGTcgDe5RWslkKUPkY"
+ "svPOAFtt5ZErwtVcvTUmplJfHzdWwatlpZRQhYkxGgRIJ6LabXfZOd2N_TchJ3tH"
+ "jAVBzUDTQq3APQssGb9df2RxVTUiyzbhdRGt7129-LCZ8rZYE7E-Mr3SSpExGYcD"
+ "k-v0It8hky0CKtCLs2UHiABA";
@Test
public void shouldGetJwtTokenFromResource() throws Exception {
final String jwt = new KubeServiceAccountTokenFile(
new ClassPathResource("kube-jwt-token")).getKubeJwt();
public void shouldGetJwtTokenFromResource() {
String jwt = new KubernetesServiceAccountTokenFile(new ClassPathResource(
"kube-jwt-token")).get();
assertThat(jwt).isEqualTo(TEST_TOKEN);
}
@Test
public void shouldGetJwtTokenFromFile() throws Exception {
final String fileName = new ClassPathResource("kube-jwt-token").getFile()
String fileName = new ClassPathResource("kube-jwt-token").getFile()
.getAbsolutePath();
final String jwt = new KubeServiceAccountTokenFile(fileName).getKubeJwt();
String jwt = new KubernetesServiceAccountTokenFile(fileName).get();
assertThat(jwt).isEqualTo(TEST_TOKEN);
}
}
}

View File

@@ -15,9 +15,6 @@
*/
package org.springframework.vault.authentication;
import static org.junit.Assume.assumeTrue;
import static org.springframework.vault.util.Settings.findWorkDir;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
@@ -25,18 +22,22 @@ import java.util.Map;
import org.assertj.core.util.Files;
import org.junit.Before;
import org.springframework.util.StringUtils;
import org.springframework.vault.core.RestOperationsCallback;
import org.springframework.vault.util.IntegrationTestSupport;
import org.springframework.vault.util.Version;
import static org.junit.Assume.assumeTrue;
import static org.springframework.vault.util.Settings.findWorkDir;
/**
* Integration test base class for {@link KubeAuthentication} tests.
* Integration test base class for {@link KubernetesAuthentication} tests.
*
* @author Michal Budzyn
*/
public abstract class KubeAuthenticationIntegrationTestBase
extends IntegrationTestSupport {
public abstract class KubernetesAuthenticationIntegrationTestBase extends
IntegrationTestSupport {
@Before
public void before() {
@@ -49,13 +50,12 @@ public abstract class KubeAuthenticationIntegrationTestBase
prepare().mountAuth("kubernetes");
}
prepare().getVaultOperations()
.doWithSession((RestOperationsCallback<Object>) restOperations -> {
prepare().getVaultOperations().doWithSession(
(RestOperationsCallback<Object>) restOperations -> {
File workDir = findWorkDir();
String certificate = Files.contentOf(
new File(workDir, "minikube/ca.crt"),
StandardCharsets.US_ASCII);
String certificate = Files.contentOf(new File(workDir,
"minikube/ca.crt"), StandardCharsets.US_ASCII);
String host = String.format("https://%s:8443", minikubeIp);
@@ -73,7 +73,6 @@ public abstract class KubeAuthenticationIntegrationTestBase
return restOperations.postForEntity("auth/kubernetes/role/my-role",
roleData, Map.class);
});
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2017 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.
@@ -15,12 +15,10 @@
*/
package org.springframework.vault.authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.vault.util.Settings.findWorkDir;
import java.io.File;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.vault.VaultException;
import org.springframework.vault.support.VaultToken;
@@ -28,27 +26,32 @@ import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.vault.util.Settings.findWorkDir;
/**
* Integration tests for {@link KubeAuthentication}.
* Integration tests for {@link KubernetesAuthentication}.
*
* @author Michal Budzyn
*/
public class KubeAuthenticationIntegrationTests
extends KubeAuthenticationIntegrationTestBase {
public class KubernetesAuthenticationIntegrationTests extends
KubernetesAuthenticationIntegrationTestBase {
@Test
public void shouldLoginSuccessfully() {
File tokenFile = new File(findWorkDir(), "minikube/hello-minikube-token");
KubeAuthenticationOptions options = KubeAuthenticationOptions.builder()
.role("my-role").jwtSupplier(new KubeServiceAccountTokenFile(tokenFile))
KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions
.builder().role("my-role")
.jwtSupplier(new KubernetesServiceAccountTokenFile(tokenFile))
.build();
RestTemplate restTemplate = TestRestTemplateFactory
.create(Settings.createSslConfiguration());
KubeAuthentication authentication = new KubeAuthentication(options, restTemplate);
KubernetesAuthentication authentication = new KubernetesAuthentication(options,
restTemplate);
VaultToken login = authentication.login();
assertThat(login.getToken()).isNotEmpty();
@@ -59,14 +62,15 @@ public class KubeAuthenticationIntegrationTests
File tokenFile = new File(findWorkDir(), "minikube/hello-minikube-token");
KubeAuthenticationOptions options = KubeAuthenticationOptions.builder()
.role("wrong").jwtSupplier(new KubeServiceAccountTokenFile(tokenFile))
KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions
.builder().role("wrong")
.jwtSupplier(new KubernetesServiceAccountTokenFile(tokenFile))
.build();
RestTemplate restTemplate = TestRestTemplateFactory
.create(Settings.createSslConfiguration());
new KubeAuthentication(options, restTemplate).login();
new KubernetesAuthentication(options, restTemplate).login();
}
@@ -75,14 +79,15 @@ public class KubeAuthenticationIntegrationTests
ClassPathResource tokenResource = new ClassPathResource("kube-jwt-token");
KubeAuthenticationOptions options = KubeAuthenticationOptions.builder()
KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions
.builder()
.role("my-role")
.jwtSupplier(new KubeServiceAccountTokenFile(tokenResource)).build();
.jwtSupplier(new KubernetesServiceAccountTokenFile(tokenResource))
.build();
RestTemplate restTemplate = TestRestTemplateFactory
.create(Settings.createSslConfiguration());
new KubeAuthentication(options, restTemplate).login();
new KubernetesAuthentication(options, restTemplate).login();
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright 2017 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
*
* http://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.vault.authentication;
import java.io.File;
import org.junit.Test;
import org.springframework.vault.support.VaultToken;
import org.springframework.vault.util.Settings;
import org.springframework.vault.util.TestRestTemplateFactory;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.vault.util.Settings.findWorkDir;
/**
* Integration tests for {@link KubernetesAuthentication} using
* {@link AuthenticationStepsExecutor}.
*
* @author Mark Paluch
*/
public class KubernetesAuthenticationStepsIntegrationTests extends
KubernetesAuthenticationIntegrationTestBase {
@Test
public void shouldLoginSuccessfully() {
File tokenFile = new File(findWorkDir(), "minikube/hello-minikube-token");
KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions
.builder().role("my-role")
.jwtSupplier(new KubernetesServiceAccountTokenFile(tokenFile)).build();
RestTemplate restTemplate = TestRestTemplateFactory.create(Settings
.createSslConfiguration());
AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(
KubernetesAuthentication.createAuthenticationSteps(options), restTemplate);
VaultToken login = executor.login();
assertThat(login.getToken()).doesNotContain(Settings.token().getToken());
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2016-2017 the original author or authors.
* Copyright 2017 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.
@@ -15,15 +15,9 @@
*/
package org.springframework.vault.authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
@@ -33,40 +27,50 @@ import org.springframework.vault.client.VaultClients.PrefixAwareUriTemplateHandl
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.jsonPath;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.method;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;
/**
* Unit tests for {@link KubeAuthentication}.
* Unit tests for {@link KubernetesAuthentication}.
*
* @author Michal Budzyn
*/
public class KubeAuthenticationUnitTests {
public class KubernetesAuthenticationUnitTests {
private RestTemplate restTemplate;
private MockRestServiceServer mockRest;
@Before
public void before() throws Exception {
public void before() {
RestTemplate restTemplate = VaultClients.createRestTemplate();
restTemplate.setUriTemplateHandler(new PrefixAwareUriTemplateHandler());
this.mockRest = MockRestServiceServer.createServer(restTemplate);
this.restTemplate = restTemplate;
}
@Test
public void loginShouldObtainTokenWithStaticJwtSupplier() throws Exception {
public void loginShouldObtainTokenWithStaticJwtSupplier() {
KubeAuthenticationOptions options = KubeAuthenticationOptions.builder()
.role("hello") //
KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions
.builder().role("hello") //
.jwtSupplier(() -> "my-jwt-token").build();
mockRest.expect(requestTo("/auth/kubernetes/login"))
.andExpect(method(HttpMethod.POST))
.andExpect(jsonPath("$.role").value("hello"))
.andExpect(jsonPath("$.jwt").value("my-jwt-token"))
.andRespond(withSuccess().contentType(MediaType.APPLICATION_JSON)
.body("{" + "\"auth\":{\"client_token\":\"my-token\"}" + "}"));
.andRespond(
withSuccess().contentType(MediaType.APPLICATION_JSON).body(
"{" + "\"auth\":{\"client_token\":\"my-token\"}" + "}"));
KubeAuthentication authentication = new KubeAuthentication(options, restTemplate);
KubernetesAuthentication authentication = new KubernetesAuthentication(options,
restTemplate);
VaultToken login = authentication.login();
assertThat(login).isInstanceOf(LoginToken.class);
@@ -74,14 +78,14 @@ public class KubeAuthenticationUnitTests {
}
@Test(expected = VaultException.class)
public void loginShouldFail() throws Exception {
public void loginShouldFail() {
KubeAuthenticationOptions options = KubeAuthenticationOptions.builder()
.role("hello").jwtSupplier(() -> "my-jwt-token").build();
KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions
.builder().role("hello").jwtSupplier(() -> "my-jwt-token").build();
mockRest.expect(requestTo("/auth/kubernetes/login")) //
.andRespond(withServerError());
new KubeAuthentication(options, restTemplate).login();
new KubernetesAuthentication(options, restTemplate).login();
}
}

View File

@@ -15,33 +15,46 @@
*/
package org.springframework.vault.config;
import static org.assertj.core.api.Assertions.assertThat;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.authentication.KubeAuthentication;
import org.springframework.vault.authentication.KubernetesAuthentication;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link EnvironmentVaultConfiguration} with Kube authentication.
*
* @author Michal Budzyn
* @author Mark Paluch
*/
@RunWith(SpringRunner.class)
@TestPropertySource(properties = { "vault.uri=https://localhost:8123",
"vault.authentication=kubernetes", "vault.kubernetes.role=my-role"})
public class EnvironmentVaultConfigurationKubeAuthenticationUnitTests {
"vault.authentication=kubernetes", "vault.kubernetes.role=my-role",
"vault.kubernetes.service-account-token-file=target/token" })
public class EnvironmentVaultConfigurationKubernetesAuthenticationUnitTests {
@Configuration
@Import(EnvironmentVaultConfiguration.class)
static class ApplicationConfiguration {
}
@BeforeClass
public static void beforeClass() throws Exception {
Files.write(Paths.get("target", "token"), "token".getBytes());
}
@Autowired
private EnvironmentVaultConfiguration configuration;
@@ -50,6 +63,6 @@ public class EnvironmentVaultConfigurationKubeAuthenticationUnitTests {
ClientAuthentication clientAuthentication = configuration.clientAuthentication();
assertThat(clientAuthentication).isInstanceOf(KubeAuthentication.class);
assertThat(clientAuthentication).isInstanceOf(KubernetesAuthentication.class);
}
}

View File

@@ -10,6 +10,7 @@
* Transit batch encrypt and decrypt support.
* Policy management for policies stored as JSON.
* Support CSR signing, certificate revocation and CRL retrieval.
* <<vault.authentication.kubernetes,Kubernetes authentication>>.
* RoleId/SecretId unwrapping for <<vault.authentication.approle,AppRole authentication>>.
[[new-features.1-1-0]]

View File

@@ -534,6 +534,47 @@ See also:
* https://www.vaultproject.io/docs/secrets/cubbyhole/index.html[Vault Documentation: Cubbyhole Secret Backend]
* https://www.vaultproject.io/docs/concepts/response-wrapping.html[Vault Documentation: Response Wrapping]
[[vault.authentication.kubernetes]]
== Kubernetes authentication
Vault supports since 0.8.3 https://www.vaultproject.io/docs/auth/kubernetes.html[kubernetes]
-based authentication using Kubernetes tokens.
Using Kubernetes authentication requires a Kubernetes Service Account Token,
usually mounted at `/var/run/secrets/kubernetes.io/serviceaccount/token`. The file contains
the token which is read and sent to Vault. Vault verifies its validity using Kubernets' API
during login.
Configuring Kubernetes authentication requires at least the role name to be provided:
====
[source,java]
----
@Configuration
class AppConfig extends AbstractVaultConfiguration {
// …
@Override
public ClientAuthentication clientAuthentication() {
KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions.builder()
.role(…).build();
return new KubernetesAuthentication(options, restOperations());
}
// …
}
----
====
You can configure the authentication via `KubernetesAuthenticationOptions`.
See also:
* https://www.vaultproject.io/docs/auth/kubernetes.html[Vault Documentation: Using the Kubernetes auth backend]
* https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/[Kubernetes Documentation: Configure Service Accounts for Pods]
[[vault.authentication.steps]]
== Authentication Steps

View File

@@ -12,6 +12,7 @@ fi
mkdir -p work/minikube
SERVICE_ACCOUNT_TOKEN_FILE=work/minikube/hello-minikube-token
SERVICE_ACCOUNT_CA_CRT=work/minikube/ca.crt
function is_cluster_running() {
local _running=$(${CMD_MINIKUBE} status | grep "cluster: Running" || true)
@@ -40,8 +41,9 @@ while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${HELLO_MINIKUBE_URL})" !=
sleep 3
done
# Copy service account token
POD_NAME=$(${CMD_KUBECTL} get pod --selector=run=hello-minikube -o jsonpath='{.items..metadata.name}')
# Copy service account token
${CMD_KUBECTL} exec ${POD_NAME} -- cat /var/run/secrets/kubernetes.io/serviceaccount/token > ${SERVICE_ACCOUNT_TOKEN_FILE}
if [ $? != 0 ] ; then
echo "Error while retrieving service account token file"
@@ -50,12 +52,8 @@ fi
# Copy ca cert
cp $HOME/.minikube/ca.crt work/minikube
#BASEDIR=`dirname $0`/../../..
#sh <(
#cat <<-EOF
#cd ${BASEDIR} && ${BASEDIR}/src/test/bash/env.sh
#vault auth-enable kubernetes
#vault write auth/kubernetes/config kubernetes_host=https://$(minikube ip):8443 kubernetes_ca_cert=@$HOME/.minikube/ca.crt
#EOF
#)
${CMD_KUBECTL} exec ${POD_NAME} -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt > ${SERVICE_ACCOUNT_CA_CRT}
if [ $? != 0 ] ; then
echo "Error while retrieving service account ca.crt"
exit 1
fi

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env bash
# Taken from https://github.com/aaron-prindle/minikube-travis-example/blob/master/minikube-ci-initialize.sh
# Thanks to https://github.com/aaron-prindle
#