diff --git a/CHANGELOG.md b/CHANGELOG.md
index 63c231d..c5573dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,6 @@
## 0.6.0 [work in progress]
+* [feature] Initialization from environment variables using `fromEnv()` in factory (#8)
+* [feature] Automatic authentication with `buildAndAuth()`
* [feature] Custom timeout and number of retries (#9)
* [fix] `SecretResponse` does not throw NPE on `get(key)` and `getData()`
diff --git a/pom.xml b/pom.xml
index d8ce5e4..d96571e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -72,5 +72,11 @@
2.0.0.0
test
+
+ com.github.stefanbirkner
+ system-rules
+ 1.16.0
+ test
+
diff --git a/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java b/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java
index b35233e..6e6fb38 100644
--- a/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java
+++ b/src/main/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactory.java
@@ -17,6 +17,8 @@
package de.stklcode.jvault.connector.factory;
import de.stklcode.jvault.connector.HTTPVaultConnector;
+import de.stklcode.jvault.connector.VaultConnector;
+import de.stklcode.jvault.connector.exception.ConnectionException;
import de.stklcode.jvault.connector.exception.TlsException;
import de.stklcode.jvault.connector.exception.VaultConnectorException;
@@ -25,8 +27,11 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
@@ -39,6 +44,11 @@ import java.security.cert.X509Certificate;
* @since 0.1
*/
public class HTTPVaultConnectorFactory extends VaultConnectorFactory {
+ private static final String ENV_VAULT_ADDR = "VAULT_ADDR";
+ private static final String ENV_VAULT_CACERT = "VAULT_CACERT";
+ private static final String ENV_VAULT_TOKEN = "VAULT_TOKEN";
+ private static final String ENV_VAULT_MAX_RETRIES = "VAULT_MAX_RETRIES";
+
public static final String DEFAULT_HOST = "127.0.0.1";
public static final Integer DEFAULT_PORT = 8200;
public static final boolean DEFAULT_TLS = true;
@@ -52,6 +62,7 @@ public class HTTPVaultConnectorFactory extends VaultConnectorFactory {
private SSLContext sslContext;
private int numberOfRetries;
private Integer timeout;
+ private String token;
/**
* Default empty constructor.
@@ -154,6 +165,55 @@ public class HTTPVaultConnectorFactory extends VaultConnectorFactory {
return this;
}
+ /**
+ * Set token for automatic authentication, using {@link #buildAndAuth()}.
+ *
+ * @param token Vault token
+ * @return self
+ * @since 0.6.0
+ */
+ public HTTPVaultConnectorFactory withToken(String token) throws VaultConnectorException {
+ this.token = token;
+ return this;
+ }
+
+ /**
+ * Build connector based on the {@code }VAULT_ADDR} and {@code VAULT_CACERT} (optional) environment variables.
+ *
+ * @return self
+ * @since 0.6.0
+ */
+ public HTTPVaultConnectorFactory fromEnv() throws VaultConnectorException {
+ /* Parse URL from environment variable */
+ if (System.getenv(ENV_VAULT_ADDR) != null && !System.getenv(ENV_VAULT_ADDR).trim().isEmpty()) {
+ try {
+ URL url = new URL(System.getenv(ENV_VAULT_ADDR));
+ this.host = url.getHost();
+ this.port = url.getPort();
+ this.tls = url.getProtocol().equals("https");
+ } catch (MalformedURLException e) {
+ throw new ConnectionException("URL provided in environment variable malformed", e);
+ }
+ }
+
+ /* Read number of retries */
+ if (System.getenv(ENV_VAULT_MAX_RETRIES) != null) {
+ try {
+ numberOfRetries = Integer.parseInt(System.getenv(ENV_VAULT_MAX_RETRIES));
+ } catch (NumberFormatException ignored) {
+ }
+ }
+
+ /* Read token */
+ token = System.getenv(ENV_VAULT_TOKEN);
+
+ /* Parse certificate, if set */
+ if (System.getenv(ENV_VAULT_CACERT) != null && !System.getenv(ENV_VAULT_CACERT).trim().isEmpty()) {
+ return withTrustedCA(Paths.get(System.getenv(ENV_VAULT_CACERT)));
+ }
+ return this;
+ }
+
/**
* Define the number of retries to attempt on 5xx errors.
*
@@ -183,6 +243,15 @@ public class HTTPVaultConnectorFactory extends VaultConnectorFactory {
return new HTTPVaultConnector(host, tls, port, prefix, sslContext, numberOfRetries, timeout);
}
+ @Override
+ public HTTPVaultConnector buildAndAuth() throws VaultConnectorException {
+ if (token == null)
+ throw new ConnectionException("No vault token provided, unable to authenticate.");
+ HTTPVaultConnector con = new HTTPVaultConnector(host, tls, port, prefix, sslContext, numberOfRetries, timeout);
+ con.authToken(token);
+ return con;
+ }
+
/**
* Create SSL Context trusting only provided certificate.
*
diff --git a/src/main/java/de/stklcode/jvault/connector/factory/VaultConnectorFactory.java b/src/main/java/de/stklcode/jvault/connector/factory/VaultConnectorFactory.java
index efed35e..9d3ffd7 100644
--- a/src/main/java/de/stklcode/jvault/connector/factory/VaultConnectorFactory.java
+++ b/src/main/java/de/stklcode/jvault/connector/factory/VaultConnectorFactory.java
@@ -23,13 +23,14 @@ import de.stklcode.jvault.connector.exception.VaultConnectorException;
* Abstract Vault Connector Factory interface.
* Provides builder pattern style factory for Vault connectors.
*
- * @author Stefan Kalscheuer
- * @since 0.1
+ * @author Stefan Kalscheuer
+ * @since 0.1
*/
public abstract class VaultConnectorFactory {
/**
* Get Factory implementation for HTTP Vault Connector
- * @return HTTP Connector Factory
+ *
+ * @return HTTP Connector Factory
*/
public static HTTPVaultConnectorFactory httpFactory() {
return new HTTPVaultConnectorFactory();
@@ -37,7 +38,16 @@ public abstract class VaultConnectorFactory {
/**
* Build command, produces connector after initialization.
- * @return Vault Connector instance.
+ *
+ * @return Vault Connector instance.
*/
public abstract VaultConnector build();
+
+ /**
+ * Build connector and authenticate with token set in factory or from environment.
+ *
+ * @return Authenticated Vault connector instance.
+ * @since 0.6.0
+ */
+ public abstract VaultConnector buildAndAuth() throws VaultConnectorException;
}
diff --git a/src/test/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactoryTest.java b/src/test/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactoryTest.java
new file mode 100644
index 0000000..b1629e0
--- /dev/null
+++ b/src/test/java/de/stklcode/jvault/connector/factory/HTTPVaultConnectorFactoryTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2016-2017 Stefan Kalscheuer
+ *
+ * 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 de.stklcode.jvault.connector.factory;
+
+import de.stklcode.jvault.connector.HTTPVaultConnector;
+import de.stklcode.jvault.connector.exception.TlsException;
+import de.stklcode.jvault.connector.exception.VaultConnectorException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.contrib.java.lang.system.EnvironmentVariables;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.file.NoSuchFileException;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * JUnit test for HTTP Vault connector factory
+ *
+ * @author Stefan Kalscheuer
+ * @since 0.6.0
+ */
+public class HTTPVaultConnectorFactoryTest {
+ private static String VAULT_ADDR = "https://localhost:8201";
+ private static Integer VAULT_MAX_RETRIES = 13;
+ private static String VAULT_TOKEN = "00001111-2222-3333-4444-555566667777";
+
+ @Rule
+ public TemporaryFolder tmpDir = new TemporaryFolder();
+
+ @Rule
+ public final EnvironmentVariables environment = new EnvironmentVariables();
+
+ /**
+ * Test building from environment variables
+ */
+ @Test
+ public void testFromEnv() throws NoSuchFieldException, IllegalAccessException, IOException {
+ /* Provide address only should be enough */
+ setenv(VAULT_ADDR, null, null, null);
+
+ HTTPVaultConnectorFactory factory = null;
+ HTTPVaultConnector connector;
+ try {
+ factory = VaultConnectorFactory.httpFactory().fromEnv();
+ } catch (VaultConnectorException e) {
+ fail("Factory creation from minimal environment failed");
+ }
+ connector = factory.build();
+
+ assertThat("URL nor set correctly", getPrivate(connector, "baseURL"), is(equalTo(VAULT_ADDR + "/v1/")));
+ assertThat("SSL context set when no cert provided", getPrivate(connector, "sslContext"), is(nullValue()));
+ assertThat("Non-default number of retries, when none set", getPrivate(connector, "retries"), is(0));
+
+ /* Provide address and number of retries */
+ setenv(VAULT_ADDR, null, VAULT_MAX_RETRIES.toString(), null);
+
+ try {
+ factory = VaultConnectorFactory.httpFactory().fromEnv();
+ } catch (VaultConnectorException e) {
+ fail("Factory creation from environment failed");
+ }
+ connector = factory.build();
+
+ assertThat("URL nor set correctly", getPrivate(connector, "baseURL"), is(equalTo(VAULT_ADDR + "/v1/")));
+ assertThat("SSL context set when no cert provided", getPrivate(connector, "sslContext"), is(nullValue()));
+ assertThat("Number of retries not set correctly", getPrivate(connector, "retries"), is(VAULT_MAX_RETRIES));
+
+ /* Provide CA certificate */
+ String VAULT_CACERT = tmpDir.newFolder().toString() + "/doesnotexist";
+ setenv(VAULT_ADDR, VAULT_CACERT, VAULT_MAX_RETRIES.toString(), null);
+
+ try {
+ VaultConnectorFactory.httpFactory().fromEnv();
+ fail("Creation with unknown cert path failed.");
+ } catch (VaultConnectorException e) {
+ assertThat(e, is(instanceOf(TlsException.class)));
+ assertThat(e.getCause(), is(instanceOf(NoSuchFileException.class)));
+ assertThat(((NoSuchFileException)e.getCause()).getFile(), is(VAULT_CACERT));
+ }
+
+ /* Automatic authentication */
+ setenv(VAULT_ADDR, null, VAULT_MAX_RETRIES.toString(), VAULT_TOKEN);
+
+ try {
+ factory = VaultConnectorFactory.httpFactory().fromEnv();
+ } catch (VaultConnectorException e) {
+ fail("Factory creation from minimal environment failed");
+ }
+ assertThat("Token nor set correctly", getPrivate(factory, "token"), is(equalTo(VAULT_TOKEN)));
+ }
+
+ private void setenv(String vault_addr, String vault_cacert, String vault_max_retries, String vault_token) {
+ environment.set("VAULT_ADDR", vault_addr);
+ environment.set("VAULT_CACERT", vault_cacert);
+ environment.set("VAULT_MAX_RETRIES", vault_max_retries);
+ environment.set("VAULT_TOKEN", vault_token);
+ }
+
+ private Object getPrivate(Object target, String fieldName) throws NoSuchFieldException, IllegalAccessException {
+ Field field = target.getClass().getDeclaredField(fieldName);
+ if (field.isAccessible())
+ return field.get(target);
+ field.setAccessible(true);
+ Object value = field.get(target);
+ field.setAccessible(false);
+ return value;
+ }
+}