You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2015/11/05 18:29:43 UTC
[4/9] incubator-brooklyn git commit: Add Vault external configuration
supplier
Add Vault external configuration supplier
Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/78339994
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/78339994
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/78339994
Branch: refs/heads/master
Commit: 783399941cdc508f696dbc70360be5c38c08a3c3
Parents: 9bb0d71
Author: Richard Downer <ri...@apache.org>
Authored: Thu Nov 5 16:07:54 2015 +0000
Committer: Richard Downer <ri...@apache.org>
Committed: Thu Nov 5 16:07:54 2015 +0000
----------------------------------------------------------------------
.../vault/VaultAppIdExternalConfigSupplier.java | 71 +++++++++
.../vault/VaultExternalConfigSupplier.java | 114 ++++++++++++++
.../vault/VaultTokenExternalConfigSupplier.java | 23 +++
.../VaultUserPassExternalConfigSupplier.java | 39 +++++
.../VaultExternalConfigSupplierLiveTest.java | 151 +++++++++++++++++++
5 files changed, 398 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78339994/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultAppIdExternalConfigSupplier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultAppIdExternalConfigSupplier.java b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultAppIdExternalConfigSupplier.java
new file mode 100644
index 0000000..6e9cd8b
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultAppIdExternalConfigSupplier.java
@@ -0,0 +1,71 @@
+package org.apache.brooklyn.core.config.external.vault;
+
+import com.google.api.client.util.Lists;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.JsonObject;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.util.List;
+import java.util.Map;
+
+public class VaultAppIdExternalConfigSupplier extends VaultExternalConfigSupplier {
+
+ private static final Logger LOG = LoggerFactory.getLogger(VaultAppIdExternalConfigSupplier.class);
+
+ protected VaultAppIdExternalConfigSupplier(ManagementContext managementContext, String name, Map<String, String> config) {
+ super(managementContext, name, config);
+ }
+
+ protected String initAndLogIn(Map<String, String> config) {
+ List<String> errors = Lists.newArrayListWithCapacity(1);
+ String appId = config.get("appId");
+ if (Strings.isBlank(appId)) errors.add("missing configuration 'appId'");
+ if (!errors.isEmpty()) {
+ String message = String.format("Problem configuration Vault external config supplier '%s': %s",
+ name, Joiner.on(System.lineSeparator()).join(errors));
+ throw new IllegalArgumentException(message);
+ }
+
+ String userId = getUserId(config);
+
+ LOG.info("Config supplier named {} using Vault at {} appID {} userID {} path {}", new Object[] {
+ name, endpoint, appId, userId, path });
+
+ String path = "v1/auth/app-id/login";
+ ImmutableMap<String, String> requestData = ImmutableMap.of("app_id", appId, "user_id", userId);
+ ImmutableMap<String, String> headers = MINIMAL_HEADERS;
+ JsonObject response = apiPost(path, headers, requestData);
+ return response.getAsJsonObject("auth").get("client_token").getAsString();
+ }
+
+ private String getUserId(Map<String, String> config) {
+ String userId = config.get("userId");
+ if (Strings.isBlank(userId))
+ userId = getUserIdFromMacAddress();
+ return userId;
+ }
+
+ private static String getUserIdFromMacAddress() {
+ byte[] mac;
+ try {
+ InetAddress ip = InetAddress.getLocalHost();
+ NetworkInterface network = NetworkInterface.getByInetAddress(ip);
+ mac = network.getHardwareAddress();
+ } catch (Throwable t) {
+ throw Exceptions.propagate(t);
+ }
+ StringBuilder sb = new StringBuilder();
+ for (byte aMac : mac) {
+ sb.append(String.format("%02x", aMac));
+ }
+ return sb.toString();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78339994/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplier.java b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplier.java
new file mode 100644
index 0000000..031ee8e
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplier.java
@@ -0,0 +1,114 @@
+package org.apache.brooklyn.core.config.external.vault;
+
+import com.google.api.client.util.Lists;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.config.external.AbstractExternalConfigSupplier;
+import org.apache.brooklyn.util.core.http.HttpTool;
+import org.apache.brooklyn.util.core.http.HttpToolResponse;
+import org.apache.brooklyn.util.exceptions.Exceptions;
+import org.apache.brooklyn.util.net.Urls;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.http.client.HttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+import java.util.Map;
+
+public abstract class VaultExternalConfigSupplier extends AbstractExternalConfigSupplier {
+ public static final String CHARSET_NAME = "UTF-8";
+ public static final ImmutableMap<String, String> MINIMAL_HEADERS = ImmutableMap.of(
+ "Content-Type", "application/json; charset=" + CHARSET_NAME,
+ "Accept", "application/json",
+ "Accept-Charset", CHARSET_NAME);
+ private static final Logger LOG = LoggerFactory.getLogger(VaultExternalConfigSupplier.class);
+ protected final Map<String, String> config;
+ protected final String name;
+ protected final HttpClient httpClient;
+ protected final Gson gson;
+ protected final String endpoint;
+ protected final String path;
+ protected final String token;
+ protected final ImmutableMap<String, String> headersWithToken;
+
+ public VaultExternalConfigSupplier(ManagementContext managementContext, String name, Map<String, String> config) {
+ super(managementContext, name);
+ this.config = config;
+ this.name = name;
+ httpClient = HttpTool.httpClientBuilder().build();
+ gson = new GsonBuilder().create();
+
+ List<String> errors = Lists.newArrayListWithCapacity(2);
+ endpoint = config.get("endpoint");
+ if (Strings.isBlank(endpoint)) errors.add("missing configuration 'endpoint'");
+ path = config.get("path");
+ if (Strings.isBlank(path)) errors.add("missing configuration 'path'");
+ if (!errors.isEmpty()) {
+ String message = String.format("Problem configuration Vault external config supplier '%s': %s",
+ name, Joiner.on(System.lineSeparator()).join(errors));
+ throw new IllegalArgumentException(message);
+ }
+
+ token = initAndLogIn(config);
+ headersWithToken = ImmutableMap.<String, String>builder()
+ .putAll(MINIMAL_HEADERS)
+ .put("X-Vault-Token", token)
+ .build();
+ }
+
+ protected abstract String initAndLogIn(Map<String, String> config);
+
+ @Override
+ public String get(String key) {
+ JsonObject response = apiGet(Urls.mergePaths("v1", path), headersWithToken);
+ return response.getAsJsonObject("data").get(key).getAsString();
+ }
+
+ protected JsonObject apiGet(String path, ImmutableMap<String, String> headers) {
+ try {
+ String uri = Urls.mergePaths(endpoint, path);
+ LOG.info("Vault request - GET: {}", uri);
+ LOG.info("Vault request - headers: {}", headers.toString());
+ HttpToolResponse response = HttpTool.httpGet(httpClient, Urls.toUri(uri), headers);
+ LOG.info("Vault response - code: {} {}", new Object[]{Integer.toString(response.getResponseCode()), response.getReasonPhrase()});
+ LOG.info("Vault response - headers: {}", response.getHeaderLists().toString());
+ String responseBody = new String(response.getContent(), CHARSET_NAME);
+ LOG.info("Vault response - body: {}", responseBody);
+ if (HttpTool.isStatusCodeHealthy(response.getResponseCode())) {
+ return gson.fromJson(responseBody, JsonObject.class);
+ } else {
+ throw new IllegalStateException("HTTP request returned error");
+ }
+ } catch (UnsupportedEncodingException e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+
+ protected JsonObject apiPost(String path, ImmutableMap<String, String> headers, ImmutableMap<String, String> requestData) {
+ try {
+ String body = gson.toJson(requestData);
+ String uri = Urls.mergePaths(endpoint, path);
+ LOG.info("Vault request - POST: {}", uri);
+ LOG.info("Vault request - headers: {}", headers.toString());
+ LOG.info("Vault request - body: {}", body);
+ HttpToolResponse response = HttpTool.httpPost(httpClient, Urls.toUri(uri), headers, body.getBytes(CHARSET_NAME));
+ LOG.info("Vault response - code: {} {}", new Object[]{Integer.toString(response.getResponseCode()), response.getReasonPhrase()});
+ LOG.info("Vault response - headers: {}", response.getHeaderLists().toString());
+ String responseBody = new String(response.getContent(), CHARSET_NAME);
+ LOG.info("Vault response - body: {}", responseBody);
+ if (HttpTool.isStatusCodeHealthy(response.getResponseCode())) {
+ return gson.fromJson(responseBody, JsonObject.class);
+ } else {
+ throw new IllegalStateException("HTTP request returned error");
+ }
+ } catch (UnsupportedEncodingException e) {
+ throw Exceptions.propagate(e);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78339994/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultTokenExternalConfigSupplier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultTokenExternalConfigSupplier.java b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultTokenExternalConfigSupplier.java
new file mode 100644
index 0000000..ed994b4
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultTokenExternalConfigSupplier.java
@@ -0,0 +1,23 @@
+package org.apache.brooklyn.core.config.external.vault;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.core.config.external.ExternalConfigSupplier;
+import org.apache.brooklyn.util.text.Strings;
+
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class VaultTokenExternalConfigSupplier extends VaultExternalConfigSupplier {
+ public VaultTokenExternalConfigSupplier(ManagementContext managementContext, String name, Map<String, String> config) {
+ super(managementContext, name, config);
+ }
+
+ @Override
+ protected String initAndLogIn(Map<String, String> config) {
+ String tokenProperty = config.get("token");
+ checkArgument(Strings.isNonBlank(tokenProperty), "property not set: token");
+ return tokenProperty;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78339994/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultUserPassExternalConfigSupplier.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultUserPassExternalConfigSupplier.java b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultUserPassExternalConfigSupplier.java
new file mode 100644
index 0000000..c43ab90
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/config/external/vault/VaultUserPassExternalConfigSupplier.java
@@ -0,0 +1,39 @@
+package org.apache.brooklyn.core.config.external.vault;
+
+import com.google.api.client.util.Lists;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.JsonObject;
+import org.apache.brooklyn.api.mgmt.ManagementContext;
+import org.apache.brooklyn.util.text.Strings;
+
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+public class VaultUserPassExternalConfigSupplier extends VaultExternalConfigSupplier {
+ public VaultUserPassExternalConfigSupplier(ManagementContext managementContext, String name, Map<String, String> config) {
+ super(managementContext, name, config);
+ }
+
+ @Override
+ protected String initAndLogIn(Map<String, String> config) {
+ List<String> errors = Lists.newArrayListWithCapacity(2);
+ String username = config.get("username");
+ if (Strings.isBlank(username)) errors.add("missing configuration 'username'");
+ String password = config.get("password");
+ if (Strings.isBlank(username)) errors.add("missing configuration 'password'");
+ if (!errors.isEmpty()) {
+ String message = String.format("Problem configuration Vault external config supplier '%s': %s",
+ name, Joiner.on(System.lineSeparator()).join(errors));
+ throw new IllegalArgumentException(message);
+ }
+
+ String path = "v1/auth/userpass/login/" + username;
+ ImmutableMap<String, String> requestData = ImmutableMap.of("password", password);
+ ImmutableMap<String, String> headers = MINIMAL_HEADERS;
+ JsonObject response = apiPost(path, headers, requestData);
+ return response.getAsJsonObject("auth").get("client_token").getAsString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/78339994/core/src/test/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplierLiveTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplierLiveTest.java b/core/src/test/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplierLiveTest.java
new file mode 100644
index 0000000..a3b7eae
--- /dev/null
+++ b/core/src/test/java/org/apache/brooklyn/core/config/external/vault/VaultExternalConfigSupplierLiveTest.java
@@ -0,0 +1,151 @@
+package org.apache.brooklyn.core.config.external.vault;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.brooklyn.core.config.external.ExternalConfigSupplier;
+import org.apache.brooklyn.util.text.Strings;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import static org.testng.Assert.assertEquals;
+
+/**
+ * Test the operation of the Vault external configuration supplier.
+ *
+ * <p>To run this test, you must have a working Vault server, and set a number of properties to allow the test to
+ * query your Vault server.</p>
+ *
+ * <p>You should start, initialise and unseal your vault according to the Vault documentation. Then you should insert
+ * a secret into Vault:</p>
+ *
+ * <p><tt>vault write secret/test password=foobar</tt></p>
+ *
+ * <p>Then set system properties so that the test can reach Vault and knows about the secret:</p>
+ *
+ * <p><tt>-Dtest.brooklyn.vault.endpoint=http://127.0.0.1:8200<br />
+ * -Dtest.brooklyn.vault.path=secret/test<br />
+ * -Dtest.brooklyn.vault.propertyName=password<br />
+ * -Dtest.brooklyn.vault.propertyExpectedValue=foobar</tt></p>
+ *
+ * <p>You will also need to configure authentication methods for the individual tests. Refer to the "see also" section
+ * to find each method that needs further configuration.</p>
+ *
+ * @see #testAppIdAuthenticationWithAutomaticUserId()
+ */
+public class VaultExternalConfigSupplierLiveTest {
+
+ private String endpoint;
+ private String path;
+ private String propertyName;
+ private String propertyExpectedValue;
+
+ @BeforeClass
+ public void setUp() throws Exception {
+ endpoint = getTestProperty("endpoint");
+ path = getTestProperty("path");
+ propertyName = getTestProperty("propertyName");
+ propertyExpectedValue = getTestProperty("propertyExpectedValue");
+ }
+
+ private String getTestProperty(String name) {
+ String propName = "test.brooklyn.vault." + name;
+ String propVal = System.getProperty(propName);
+ if (Strings.isBlank(propVal))
+ throw new IllegalArgumentException(propName + " is not set");
+ return propVal;
+ }
+
+ /**
+ * Test using a hard-coded authentication token.
+ *
+ * <p>This provider does not do authentication, but instead uses a known token for authentication. When Vault is
+ * initialised, it will give you an <em>Initial Root Token</em>, which can be used for this test. However,
+ * obviously, passing around a well-known root token is A Bad Idea for use in production, and would largely undo all
+ * the useful security that vault provides.</p>
+ *
+ * <p>Set these system properties:</p>
+ *
+ * <p><tt>-Dtest.brooklyn.vault.token=1091fc84-70c1-b266-b99f-781684dd0d2b</tt></p>
+ */
+ @Test(groups = "Live")
+ public void testHardCodedToken() {
+ String token = getTestProperty("token");
+ ExternalConfigSupplier ecs = new VaultTokenExternalConfigSupplier(null, "test",
+ ImmutableMap.of("endpoint", endpoint, "token", token, "path", path));
+ assertEquals(ecs.get(propertyName), propertyExpectedValue);
+ }
+
+ /**
+ * Test using the "userpass" authentication backend.
+ *
+ * <p>Configure Vault to enable userpass and add a new user ID with password:</p>
+ *
+ * <p><tt>vault auth-enable userpass<br>
+ * vault write auth/userpass/users/brooklynTest password=s3kr1t policies=root # the "root" policy allows unrestricted access, you will want to use a different policy for real use
+ * </tt></p>
+ *
+ * <p>Set these system properties:</p>
+ *
+ * <p><tt>-Dtest.brooklyn.vault.username=brooklynTest<br />
+ * -Dtest.brooklyn.vault.password=s3kr1t</tt></p>
+ */
+ @Test(groups = "Live")
+ public void testUserPassAuthentication() {
+ String username = getTestProperty("username");
+ String password = getTestProperty("password");
+ ExternalConfigSupplier ecs = new VaultUserPassExternalConfigSupplier(null, "test",
+ ImmutableMap.of("endpoint", endpoint, "username", username, "password", password, "path", path));
+ assertEquals(ecs.get(propertyName), propertyExpectedValue);
+ }
+
+ /**
+ * Test using the "App ID" authentication backend, with a MAC-address based user ID.
+ *
+ * <p>First, determine the MAC address of your system. This is system dependent, but a good guess will be to
+ * inspect the routing table to determine the default route, and take the MAC address of the interface that the
+ * default route would use. Express the MAC address as a series of 12 low-case hexadecimal digits, without any
+ * symbols.</p>
+ *
+ * <p>Configure Vault to enable App-ID, add a new app ID, and authorise the MAC address to the app ID:</p>
+ *
+ * <p><tt>/vault auth-enable app-id<br>
+ * vault write auth/app-id/map/app-id/brooklyn value=root display_name=Brooklyn # the app ID here is "brooklyn"; the "root" policy allows unrestricted access, you will want to use a different policy for real use
+ * vault write auth/app-id/map/user-id/0c4de9bca2db value=brooklyn
+ * </tt></p>
+ *
+ * <p>Set these system properties:</p>
+ *
+ * <p><tt>-Dtest.brooklyn.vault.appId=brooklyn</tt></p>
+ */
+ @Test(groups = "Live")
+ public void testAppIdAuthenticationWithAutomaticUserId() {
+ String appId = getTestProperty("appId");
+ ExternalConfigSupplier ecs = new VaultAppIdExternalConfigSupplier(null, "test",
+ ImmutableMap.of("endpoint", endpoint, "appId", appId, "path", path));
+ assertEquals(ecs.get(propertyName), propertyExpectedValue);
+ }
+
+ /**
+ * Test using the "App ID" authentication backend, with an explicitly-given user ID.
+ *
+ * <p>Configure Vault to enable App-ID, add a new app ID, and authorise your chosen user ID to the app ID:</p>
+ *
+ * <p><tt>/vault auth-enable app-id<br>
+ * vault write auth/app-id/map/app-id/brooklyn value=root display_name=Brooklyn # the app ID here is "brooklyn"; the "root" policy allows unrestricted access, you will want to use a different policy for real use
+ * vault write auth/app-id/map/user-id/testUser value=brooklyn
+ * </tt></p>
+ *
+ * <p>Set these system properties:</p>
+ *
+ * <p><tt>-Dtest.brooklyn.vault.appId=brooklyn<br />
+ * -Dtest.brooklyn.vault.userId=testUser</tt></p>
+ */
+ @Test(groups = "Live")
+ public void testAppIdAuthenticationWithExplicitUserId() {
+ String appId = getTestProperty("appId");
+ String userId = getTestProperty("userId");
+ ExternalConfigSupplier ecs = new VaultAppIdExternalConfigSupplier(null, "test",
+ ImmutableMap.of("endpoint", endpoint, "appId", appId, "path", path, "userId", userId));
+ assertEquals(ecs.get(propertyName), propertyExpectedValue);
+ }
+
+}
\ No newline at end of file