diff --git a/.travis.yml b/.travis.yml
index 44c5a261..2f6cb24c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ language: java
jdk:
- oraclejdk8
-sudo: false
+sudo: required
addons:
apt:
@@ -33,9 +33,10 @@ before_install:
- test ! -f ~/.m2/settings.xml || rm ~/.m2/settings.xml
install:
+ - src/test/bash/minikube_ci_initialize.sh
- src/test/bash/start.sh
-script: ./mvnw clean verify -P${PROFILE:-ci}
+script: ./mvnw clean verify -DMINIKUBE_IP=$(./minikube ip) -P${PROFILE:-ci}
after_script:
- pkill vault
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeAuthentication.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeAuthentication.java
new file mode 100644
index 00000000..b21859e1
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeAuthentication.java
@@ -0,0 +1,106 @@
+/*
+ * 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 java.util.HashMap;
+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;
+import org.springframework.vault.support.VaultResponse;
+import org.springframework.vault.support.VaultToken;
+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}.
+ *
+ * @author Michal Budzyn
+ * @see KubeAuthenticationOptions
+ * @see RestOperations
+ * @see Auth Backend:
+ * Kubernetes
+ */
+public class KubeAuthentication implements ClientAuthentication {
+
+ private static final Log logger = LogFactory.getLog(KubeAuthentication.class);
+
+ private final KubeAuthenticationOptions options;
+
+ private final RestOperations restOperations;
+
+ /**
+ * Create a {@link KubeAuthentication} using {@link KubeAuthenticationOptions} and
+ * {@link RestOperations}.
+ *
+ * @param options must not be {@literal null}.
+ * @param restOperations must not be {@literal null}.
+ */
+ public KubeAuthentication(KubeAuthenticationOptions options,
+ RestOperations restOperations) {
+
+ Assert.notNull(options, "KubeAuthenticationOptions must not be null");
+ Assert.notNull(restOperations, "RestOperations must not be null");
+
+ this.options = options;
+ this.restOperations = restOperations;
+ }
+
+ private static Map getKubeLogin(String role, String jwt) {
+
+ Assert.hasText(role, "role must not be empty");
+ Assert.hasText(role, "jwt must not be empty");
+
+ Map login = new HashMap<>();
+
+ login.put("jwt", jwt);
+ login.put("role", role);
+
+ return login;
+ }
+
+ @Override
+ public VaultToken login() throws VaultException {
+ return createTokenUsingKubernetes();
+ }
+
+ private VaultToken createTokenUsingKubernetes() {
+
+ Map login = getKubeLogin(options.getRole(),
+ options.getJwtSupplier().getKubeJwt());
+
+ try {
+ VaultResponse response = restOperations.postForObject("auth/{mount}/login",
+ login, VaultResponse.class, options.getPath());
+
+ Assert.state(response != null && response.getAuth() != null,
+ "Auth field must not be null");
+
+ logger.debug("Login successful using Kubernetes authentication");
+
+ return LoginTokenUtil.from(response.getAuth());
+ }
+ catch (HttpStatusCodeException e) {
+ throw new VaultException(String.format("Cannot login using kubernetes: %s",
+ VaultResponses.getError(e.getResponseBodyAsString())));
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeAuthenticationOptions.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeAuthenticationOptions.java
new file mode 100644
index 00000000..03954e0f
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeAuthenticationOptions.java
@@ -0,0 +1,118 @@
+/*
+ * 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}.
+ *
+ * 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeJwtSupplier.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeJwtSupplier.java
new file mode 100644
index 00000000..10fe8376
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeJwtSupplier.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Interface to obtain a Kubernetes Service Account Token for Kubernetes authentication.
+ * Implementations are used by {@link KubeAuthentication}.
+ *
+ * @author Michal Budzyn
+ * @see KubeAuthentication
+ */
+public interface KubeJwtSupplier {
+
+ /**
+ * Get a JWT for Kubernetes authentication.
+ *
+ * @return the Kubernetes Service Account JWT.
+ */
+ String getKubeJwt();
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeServiceAccountTokenFile.java b/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeServiceAccountTokenFile.java
new file mode 100644
index 00000000..00359feb
--- /dev/null
+++ b/spring-vault-core/src/main/java/org/springframework/vault/authentication/KubeServiceAccountTokenFile.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ *
+ * A file containing a token for a pod’s service account is automatically mounted at
+ * /var/run/secrets/kubernetes.io/serviceaccount/token
+ *
+ * @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);
+ }
+ }
+}
diff --git a/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java b/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java
index 6f475c34..6f49d65a 100644
--- a/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java
+++ b/spring-vault-core/src/main/java/org/springframework/vault/config/EnvironmentVaultConfiguration.java
@@ -32,19 +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.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.VaultToken;
import org.springframework.vault.support.SslConfiguration.KeyStoreConfiguration;
+import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestOperations;
/**
@@ -125,6 +129,7 @@ import org.springframework.web.client.RestOperations;
*
*
* @author Mark Paluch
+ * @author Michal Budzyn
* @see org.springframework.core.env.Environment
* @see org.springframework.core.env.PropertySource
* @see VaultEndpoint
@@ -133,6 +138,7 @@ import org.springframework.web.client.RestOperations;
* @see AwsEc2Authentication
* @see ClientCertificateAuthentication
* @see CubbyholeAuthentication
+ * @see KubeAuthentication
*/
@Configuration
public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration implements
@@ -224,7 +230,8 @@ public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration im
return new ClientCertificateAuthentication(restOperations());
case CUBBYHOLE:
return cubbyholeAuthentication();
-
+ case KUBERNETES:
+ return kubeAuthentication();
default:
throw new IllegalStateException(String.format(
"Vault authentication method %s is not supported with %s",
@@ -325,6 +332,23 @@ public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration im
return new CubbyholeAuthentication(options, restOperations());
}
+ protected ClientAuthentication kubeAuthentication() {
+
+ String role = getProperty("vault.kubernetes.role");
+ Assert.hasText(role, "Vault Kubernetes authentication: role must not be empty");
+
+ String tokenFile = getProperty("vault.kubernetes.service-account-token-file");
+ if (!StringUtils.hasText(tokenFile)) {
+ tokenFile = KubeServiceAccountTokenFile.DEFAULT_KUBERNETES_SERVICE_ACCOUNT_TOKEN_FILE;
+ }
+ KubeJwtSupplier jwtSupplier = new KubeServiceAccountTokenFile(tokenFile);
+
+ KubeAuthenticationOptions authenticationOptions = KubeAuthenticationOptions
+ .builder().role(role).jwtSupplier(jwtSupplier).build();
+
+ return new KubeAuthentication(authenticationOptions, restOperations());
+ }
+
@Nullable
private String getProperty(String key) {
return getEnvironment().getProperty(key);
@@ -342,6 +366,6 @@ public class EnvironmentVaultConfiguration extends AbstractVaultConfiguration im
}
enum AuthenticationMethod {
- TOKEN, APPID, APPROLE, AWS_EC2, CERT, CUBBYHOLE;
+ TOKEN, APPID, APPROLE, AWS_EC2, CERT, CUBBYHOLE, KUBERNETES;
}
}
diff --git a/spring-vault-core/src/test/java/org/springframework/vault/authentication/KubeAuthenticationIntegrationTestBase.java b/spring-vault-core/src/test/java/org/springframework/vault/authentication/KubeAuthenticationIntegrationTestBase.java
new file mode 100644
index 00000000..042695db
--- /dev/null
+++ b/spring-vault-core/src/test/java/org/springframework/vault/authentication/KubeAuthenticationIntegrationTestBase.java
@@ -0,0 +1,79 @@
+/*
+ * 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 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;
+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;
+
+/**
+ * Integration test base class for {@link KubeAuthentication} tests.
+ *
+ * @author Michal Budzyn
+ */
+public abstract class KubeAuthenticationIntegrationTestBase
+ extends IntegrationTestSupport {
+
+ @Before
+ public void before() {
+
+ String minikubeIp = System.getProperty("MINIKUBE_IP");
+ assumeTrue(StringUtils.hasText(minikubeIp)
+ && prepare().getVersion().isGreaterThanOrEqualTo(Version.parse("0.8.3")));
+
+ if (!prepare().hasAuth("kubernetes")) {
+ prepare().mountAuth("kubernetes");
+ }
+
+ prepare().getVaultOperations()
+ .doWithSession((RestOperationsCallback