diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/ConfigurationAsCode.java b/plugin/src/main/java/io/jenkins/plugins/casc/ConfigurationAsCode.java index 29743cb56e..d897e1dd41 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/ConfigurationAsCode.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/ConfigurationAsCode.java @@ -649,6 +649,7 @@ private void configureWith(Mapping entries) throws ConfiguratorException { monitor.reset(); ConfigurationContext context = new ConfigurationContext(registry); context.addListener(monitor::record); + context.getSecretSources().forEach(SecretSource::init); try (ACLContext acl = ACL.as(ACL.SYSTEM)) { invokeWith(entries, (configurator, config) -> configurator.configure(config, context)); } diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/SecretSource.java b/plugin/src/main/java/io/jenkins/plugins/casc/SecretSource.java index 2c1bc6b26c..48729a6600 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/SecretSource.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/SecretSource.java @@ -17,6 +17,10 @@ public abstract class SecretSource implements ExtensionPoint { + public void init() { + // NOOP + } + public abstract Optional reveal(String secret) throws IOException; public static List all() { diff --git a/plugin/src/main/java/io/jenkins/plugins/casc/impl/secrets/VaultSecretSource.java b/plugin/src/main/java/io/jenkins/plugins/casc/impl/secrets/VaultSecretSource.java index 02cb98121c..14375f9dfd 100644 --- a/plugin/src/main/java/io/jenkins/plugins/casc/impl/secrets/VaultSecretSource.java +++ b/plugin/src/main/java/io/jenkins/plugins/casc/impl/secrets/VaultSecretSource.java @@ -52,7 +52,6 @@ public class VaultSecretSource extends SecretSource { private VaultAuthenticator vaultAuthenticator; private String[] vaultPaths; - public VaultSecretSource() { Optional vaultFile = Optional.ofNullable(System.getenv(CASC_VAULT_FILE)); Properties prop = new Properties(); @@ -64,7 +63,7 @@ public VaultSecretSource() { Optional vaultNamespace = getVariable(CASC_VAULT_NAMESPACE, prop); Optional vaultPaths = getCommaSeparatedVariables(CASC_VAULT_PATHS, prop) .map(Optional::of) - .orElse(getCommaSeparatedVariables(CASC_VAULT_PATH, prop)); // TODO: deprecate! + .orElseGet(() -> getCommaSeparatedVariables(CASC_VAULT_PATH, prop)); // TODO: deprecate! // Check mandatory variables are set if (!vaultUrl.isPresent() || !vaultPaths.isPresent()) return; @@ -168,19 +167,6 @@ private void readPropertiesFromVaultFile(String vaultFile, Properties prop) { @Override public Optional reveal(String secret) { if (StringUtils.isBlank(secret)) return Optional.empty(); - - // TODO: move this to SecretSource.init() function which gets called only once when CasC.configure() is run - // Ensure secrets are up-to-date - if (vaultAuthenticator != null) { - try { - vaultAuthenticator.authenticate(vault, vaultConfig); - } catch (VaultException e) { - LOGGER.log(Level.WARNING, "Could not authenticate with vault client", e); - } - - readSecretsFromVault(); - } - return Optional.ofNullable(secrets.get(secret)); } @@ -202,4 +188,17 @@ private Optional getCommaSeparatedVariables(String key, Properties pro "Please use CASC_VAULT_PATHS instead."); // TODO: deprecate! return getVariable(key, prop).map(str -> str.split(",")); } + + @Override + public void init() { + // Ensure secrets are up-to-date and Check vault authentication + if (vaultAuthenticator != null) { + try { + vaultAuthenticator.authenticate(vault, vaultConfig); + } catch (VaultException e) { + LOGGER.log(Level.WARNING, "Could not authenticate with vault client", e); + } + readSecretsFromVault(); + } + } } diff --git a/plugin/src/test/java/io/jenkins/plugins/casc/misc/Env.java b/plugin/src/test/java/io/jenkins/plugins/casc/misc/Env.java new file mode 100644 index 0000000000..4d43b8ccab --- /dev/null +++ b/plugin/src/test/java/io/jenkins/plugins/casc/misc/Env.java @@ -0,0 +1,16 @@ +package io.jenkins.plugins.casc.misc; + +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target(METHOD) +@Retention(RUNTIME) +@Repeatable( value = Envs.class ) +public @interface Env { + String name(); + String value(); +} diff --git a/plugin/src/test/java/io/jenkins/plugins/casc/misc/EnvVarsRule.java b/plugin/src/test/java/io/jenkins/plugins/casc/misc/EnvVarsRule.java new file mode 100644 index 0000000000..3627fbe059 --- /dev/null +++ b/plugin/src/test/java/io/jenkins/plugins/casc/misc/EnvVarsRule.java @@ -0,0 +1,45 @@ +package io.jenkins.plugins.casc.misc; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Properties; +import java.util.stream.Collectors; +import org.junit.contrib.java.lang.system.EnvironmentVariables; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public class EnvVarsRule extends EnvironmentVariables { + + @Override + public Statement apply(Statement base, Description description) { + EnvsFromFile configuredWithEnvsFromFile = description.getAnnotation(EnvsFromFile.class); + if (Objects.nonNull(configuredWithEnvsFromFile)) { + + final String[] resource = configuredWithEnvsFromFile.value(); + + final List envFiles = Arrays.stream(resource) + .map(s -> Paths.get(System.getProperty("java.io.tmpdir"), s).toString()) + .collect(Collectors.toList()); + + Properties properties = new Properties(); + for (String file : envFiles) { + try (FileInputStream inputStream = new FileInputStream(file)) { + properties.load(inputStream); + } catch (IOException e) { + e.printStackTrace(); + } + } + properties.forEach((key, value) -> set(String.valueOf(key), String.valueOf(value))); + } + Envs configuredWithEnvs = description.getAnnotation(Envs.class); + if (Objects.nonNull(configuredWithEnvs)) { + List envs = Arrays.asList(configuredWithEnvs.value()); + envs.forEach(env -> set(env.name(), env.value())); + } + return super.apply(base, description); + } +} diff --git a/plugin/src/test/java/io/jenkins/plugins/casc/misc/Envs.java b/plugin/src/test/java/io/jenkins/plugins/casc/misc/Envs.java new file mode 100644 index 0000000000..bfad327891 --- /dev/null +++ b/plugin/src/test/java/io/jenkins/plugins/casc/misc/Envs.java @@ -0,0 +1,19 @@ +package io.jenkins.plugins.casc.misc; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import org.junit.Test; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target(METHOD) +@Retention(RUNTIME) +public @interface Envs { + + Env[] value() default {}; + + Class expected() default Test.None.class; + + String message() default ""; +} diff --git a/plugin/src/test/java/io/jenkins/plugins/casc/misc/EnvsFromFile.java b/plugin/src/test/java/io/jenkins/plugins/casc/misc/EnvsFromFile.java new file mode 100644 index 0000000000..c62d7898ba --- /dev/null +++ b/plugin/src/test/java/io/jenkins/plugins/casc/misc/EnvsFromFile.java @@ -0,0 +1,19 @@ +package io.jenkins.plugins.casc.misc; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import org.junit.Test; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Target(METHOD) +@Retention(RUNTIME) +public @interface EnvsFromFile { + + String[] value() default {}; + + Class expected() default Test.None.class; + + String message() default ""; +} diff --git a/plugin/src/test/java/io/jenkins/plugins/casc/vault/VaultSecretSourceTest.java b/plugin/src/test/java/io/jenkins/plugins/casc/vault/VaultSecretSourceTest.java index 9e43b2a25c..8869819d14 100644 --- a/plugin/src/test/java/io/jenkins/plugins/casc/vault/VaultSecretSourceTest.java +++ b/plugin/src/test/java/io/jenkins/plugins/casc/vault/VaultSecretSourceTest.java @@ -2,19 +2,27 @@ import io.jenkins.plugins.casc.ConfigurationContext; +import io.jenkins.plugins.casc.ConfiguratorException; import io.jenkins.plugins.casc.ConfiguratorRegistry; +import io.jenkins.plugins.casc.SecretSource; import io.jenkins.plugins.casc.SecretSourceResolver; -import org.junit.*; -import org.junit.contrib.java.lang.system.EnvironmentVariables; -import org.jvnet.hudson.test.JenkinsRule; -import org.testcontainers.vault.VaultContainer; - +import io.jenkins.plugins.casc.misc.*; +import java.io.File; import java.io.IOException; +import java.nio.file.Paths; import java.util.logging.Level; import java.util.logging.Logger; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.testcontainers.vault.VaultContainer; -import static org.hamcrest.CoreMatchers.equalTo; import static io.jenkins.plugins.casc.vault.VaultTestUtil.*; +import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.junit.Assume.assumeTrue; @@ -27,11 +35,10 @@ public class VaultSecretSourceTest { public static VaultContainer vaultContainer = createVaultContainer(); @Rule - public JenkinsRule j = new JenkinsRule(); - - @Rule - public EnvironmentVariables envVars = new EnvironmentVariables() - .set("CASC_VAULT_FILE", getClass().getResource("vaultTest_cascFile").getPath()); + public RuleChain chain = RuleChain + .outerRule(new EnvVarsRule() + .set("CASC_VAULT_FILE", getClass().getResource("vaultTest_cascFile").getPath())) + .around(new JenkinsConfiguredWithCodeRule()); private ConfigurationContext context; @@ -44,6 +51,12 @@ public static void configureContainer() { configureVaultContainer(vaultContainer); } + @AfterClass + public static void removeAppRoleFile() { + File file = Paths.get(System.getProperty("java.io.tmpdir"), VAULT_APPROLE_FILE).toFile(); + assert file.delete() || !file.exists(); + } + @Before public void refreshConfigurationContext() { // Setup Jenkins @@ -52,109 +65,140 @@ public void refreshConfigurationContext() { } @Test + @ConfiguredWithCode("vault.yml") + @Envs({ + @Env(name = "CASC_VAULT_USER", value = VAULT_USER), + @Env(name = "CASC_VAULT_PW", value = VAULT_PW), + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV1_1 + "," + VAULT_PATH_KV1_2), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "1") + }) public void kv1WithUser() { - envVars.set("CASC_VAULT_USER", VAULT_USER); - envVars.set("CASC_VAULT_PW", VAULT_PW); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV1_1 + "," + VAULT_PATH_KV1_2); - envVars.set("CASC_VAULT_ENGINE_VERSION", "1"); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("123")); } @Test + @ConfiguredWithCode("vault.yml") + @Envs({ + @Env(name = "CASC_VAULT_USER", value = VAULT_USER), + @Env(name = "CASC_VAULT_PW", value = VAULT_PW), + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "2") + }) public void kv2WithUser() { - envVars.set("CASC_VAULT_USER", VAULT_USER); - envVars.set("CASC_VAULT_PW", VAULT_PW); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2); - envVars.set("CASC_VAULT_ENGINE_VERSION", "2"); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("123")); } @Test + @ConfiguredWithCode("vault.yml") + @Envs({ + @Env(name = "CASC_VAULT_USER", value = "1234"), + @Env(name = "CASC_VAULT_PW", value = VAULT_PW), + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV2_1), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "2") + }) public void kv2WithWrongUser() { - envVars.set("CASC_VAULT_USER", "1234"); - envVars.set("CASC_VAULT_PW", VAULT_PW); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV2_1); - envVars.set("CASC_VAULT_ENGINE_VERSION", "2"); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("")); } @Test + @ConfiguredWithCode("vault.yml") + @Envs({ + @Env(name = "CASC_VAULT_TOKEN", value = VAULT_ROOT_TOKEN), + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV1_1 + "," + VAULT_PATH_KV1_2), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "1") + }) public void kv1WithToken() { - envVars.set("CASC_VAULT_TOKEN", VAULT_ROOT_TOKEN); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV1_1 + "," + VAULT_PATH_KV1_2); - envVars.set("CASC_VAULT_ENGINE_VERSION", "1"); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("123")); } @Test + @ConfiguredWithCode("vault.yml") + @Envs({ + @Env(name = "CASC_VAULT_TOKEN", value = VAULT_ROOT_TOKEN), + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "2") + }) public void kv2WithToken() { - envVars.set("CASC_VAULT_TOKEN", VAULT_ROOT_TOKEN); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2); - envVars.set("CASC_VAULT_ENGINE_VERSION", "2"); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("123")); } @Test + @ConfiguredWithCode("vault.yml") + @Envs({ + @Env(name = "CASC_VAULT_TOKEN", value = "1234"), + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV1_1), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "1") + }) public void kv1WithWrongToken() { - envVars.set("CASC_VAULT_TOKEN", "1234"); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV1_1); - envVars.set("CASC_VAULT_ENGINE_VERSION", "1"); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("")); } @Test + @ConfiguredWithCode("vault.yml") + @EnvsFromFile(VAULT_APPROLE_FILE) + @Envs({ + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV1_1 + "," + VAULT_PATH_KV1_2), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "1") + }) public void kv1WithApprole() { - envVars.set("CASC_VAULT_APPROLE", VAULT_APPROLE_ID); - envVars.set("CASC_VAULT_APPROLE_SECRET", VAULT_APPROLE_SECRET); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV1_1 + "," + VAULT_PATH_KV1_2); - envVars.set("CASC_VAULT_ENGINE_VERSION", "1"); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("123")); } @Test - public void kv2WithApprole() { - envVars.set("CASC_VAULT_APPROLE", VAULT_APPROLE_ID); - envVars.set("CASC_VAULT_APPROLE_SECRET", VAULT_APPROLE_SECRET); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2); - envVars.set("CASC_VAULT_ENGINE_VERSION", "2"); + @ConfiguredWithCode("vault.yml") + @EnvsFromFile(VAULT_APPROLE_FILE) + @Envs({ + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "2") + }) + public void kv2WithApprole() throws ConfiguratorException { assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("123")); } @Test + @ConfiguredWithCode("vault.yml") + @EnvsFromFile(VAULT_APPROLE_FILE) + @Envs({ + @Env(name = "CASC_VAULT_APPROLE", value = "1234"), + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV2_1), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "2") + }) public void kv2WithWrongApprole() { - envVars.set("CASC_VAULT_APPROLE", "1234"); - envVars.set("CASC_VAULT_APPROLE_SECRET", VAULT_APPROLE_SECRET); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV2_1); - envVars.set("CASC_VAULT_ENGINE_VERSION", "2"); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("")); } @Test + @ConfiguredWithCode("vault.yml") + @EnvsFromFile(VAULT_APPROLE_FILE) + @Envs({ + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "2") + }) public void kv2WithApproleMultipleKeys() { - envVars.set("CASC_VAULT_APPROLE", VAULT_APPROLE_ID); - envVars.set("CASC_VAULT_APPROLE_SECRET", VAULT_APPROLE_SECRET); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2); - envVars.set("CASC_VAULT_ENGINE_VERSION", "2"); assertThat(SecretSourceResolver.resolve(context, "${key2}"), equalTo("456")); assertThat(SecretSourceResolver.resolve(context, "${key3}"), equalTo("789")); } @Test + @ConfiguredWithCode("vault.yml") + @EnvsFromFile(VAULT_APPROLE_FILE) + @Envs({ + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2 + "," + VAULT_PATH_KV2_3), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "2") + }) public void kv2WithApproleMultipleKeysOverriden() { - envVars.set("CASC_VAULT_APPROLE", VAULT_APPROLE_ID); - envVars.set("CASC_VAULT_APPROLE_SECRET", VAULT_APPROLE_SECRET); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV2_1 + "," + VAULT_PATH_KV2_2 + "," + VAULT_PATH_KV2_3); - envVars.set("CASC_VAULT_ENGINE_VERSION", "2"); assertThat(SecretSourceResolver.resolve(context, "${key2}"), equalTo("321")); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("123")); } @Test - public void kv2WithApproleWithReauth() { - envVars.set("CASC_VAULT_APPROLE", VAULT_APPROLE_ID); - envVars.set("CASC_VAULT_APPROLE_SECRET", VAULT_APPROLE_SECRET); - envVars.set("CASC_VAULT_PATHS", VAULT_PATH_KV2_AUTH_TEST); - envVars.set("CASC_VAULT_ENGINE_VERSION", "2"); + @ConfiguredWithCode("vault.yml") + @EnvsFromFile(VAULT_APPROLE_FILE) + @Envs({ + @Env(name = "CASC_VAULT_PATHS", value = VAULT_PATH_KV2_AUTH_TEST), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "2") + }) + public void kv2WithApproleWithReauth() throws Exception { assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("auth-test")); try { @@ -162,7 +206,8 @@ public void kv2WithApproleWithReauth() { Thread.sleep(2000); // Update secret - runCommand(vaultContainer, "vault", "kv", "put", VAULT_PATH_KV2_AUTH_TEST, "key1=re-auth-test"); + runCommand(vaultContainer, "vault", "kv", "put", VAULT_PATH_KV2_AUTH_TEST, + "key1=re-auth-test"); } catch (InterruptedException e) { LOGGER.log(Level.WARNING, "Test got interrupted", e); assert false; @@ -171,16 +216,20 @@ public void kv2WithApproleWithReauth() { assert false; } + // SecretSource.init is normally called on configure + context.getSecretSources().forEach(SecretSource::init); assertThat(SecretSourceResolver.resolve(context, "${key1}"), equalTo("re-auth-test")); } // TODO: used to check for backwards compatibility. Deprecate! @Test + @ConfiguredWithCode("vault.yml") + @EnvsFromFile(VAULT_APPROLE_FILE) + @Envs({ + @Env(name = "CASC_VAULT_PATH", value = VAULT_PATH_KV1_1 + "," + VAULT_PATH_KV1_2), + @Env(name = "CASC_VAULT_ENGINE_VERSION", value = "1") + }) public void kv2WithUserDeprecatedPath() { - envVars.set("CASC_VAULT_APPROLE", VAULT_APPROLE_ID); - envVars.set("CASC_VAULT_APPROLE_SECRET", VAULT_APPROLE_SECRET); - envVars.set("CASC_VAULT_PATH", VAULT_PATH_KV1_1 + "," + VAULT_PATH_KV1_2); - envVars.set("CASC_VAULT_ENGINE_VERSION", "1"); assertThat(SecretSourceResolver.resolve(context, "${key3}"), equalTo("789")); } } diff --git a/plugin/src/test/java/io/jenkins/plugins/casc/vault/VaultTestUtil.java b/plugin/src/test/java/io/jenkins/plugins/casc/vault/VaultTestUtil.java index b2515289c6..9e9b009cb9 100644 --- a/plugin/src/test/java/io/jenkins/plugins/casc/vault/VaultTestUtil.java +++ b/plugin/src/test/java/io/jenkins/plugins/casc/vault/VaultTestUtil.java @@ -2,6 +2,11 @@ import com.bettercloud.vault.Vault; import com.bettercloud.vault.VaultConfig; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.MountableFile; import org.testcontainers.utility.TestEnvironment; @@ -27,8 +32,8 @@ class VaultTestUtil { public static final String VAULT_PATH_KV2_2 = "kv-v2/dev"; public static final String VAULT_PATH_KV2_3 = "kv-v2/qa"; public static final String VAULT_PATH_KV2_AUTH_TEST = "kv-v2/auth-test"; - public static String VAULT_APPROLE_ID = ""; - public static String VAULT_APPROLE_SECRET = ""; + public static final String VAULT_APPROLE_FILE = "JCasC_temp_approle_secret.prop"; + private static boolean configured = false; public static void runCommand(VaultContainer container, final String... command) throws IOException, InterruptedException { @@ -56,17 +61,18 @@ public static VaultContainer createVaultContainer() { } public static void configureVaultContainer(VaultContainer container) { + if (configured) return; try { // Create Secret Backends runCommand(container, "vault", "secrets", "enable", "-path=kv-v2", - "-version=2", "kv"); + "-version=2", "kv"); runCommand(container, "vault", "secrets", "enable", "-path=kv-v1", - "-version=1", "kv"); + "-version=1", "kv"); // Create user/password credential runCommand(container, "vault", "auth", "enable", "userpass"); runCommand(container, "vault", "write", "auth/userpass/users/" + VAULT_USER, - "password=" + VAULT_PW, "policies=admin"); + "password=" + VAULT_PW, "policies=admin"); // Create policies runCommand(container, "vault", "policy", "write", "admin", "/admin.hcl"); @@ -74,26 +80,39 @@ public static void configureVaultContainer(VaultContainer container) { // Create AppRole runCommand(container, "vault", "auth", "enable", "approle"); runCommand(container, "vault", "write", "auth/approle/role/admin", - "secret_id_ttl=10m", "token_num_uses=0", "token_ttl=4s", "token_max_ttl=4s", - "secret_id_num_uses=1000", "policies=admin"); + "secret_id_ttl=10m", "token_num_uses=0", "token_ttl=4s", "token_max_ttl=4s", + "secret_id_num_uses=1000", "policies=admin"); // Retrieve AppRole credentials VaultConfig config = new VaultConfig().address("http://localhost:8200") - .token(VAULT_ROOT_TOKEN).engineVersion(1).build(); + .token(VAULT_ROOT_TOKEN).engineVersion(1).build(); Vault vaultClient = new Vault(config); - VAULT_APPROLE_ID = vaultClient.logical().read("auth/approle/role/admin/role-id") - .getData().get("role_id"); - VAULT_APPROLE_SECRET = vaultClient.logical().write("auth/approle/role/admin/secret-id", - new HashMap<>()).getData().get("secret_id"); + final String roleID = vaultClient.logical().read("auth/approle/role/admin/role-id") + .getData().get("role_id"); + final String secretID = vaultClient.logical().write("auth/approle/role/admin/secret-id", + new HashMap<>()).getData().get("secret_id"); + + Properties properties = new Properties(); + properties.put("CASC_VAULT_APPROLE", roleID); + properties.put("CASC_VAULT_APPROLE_SECRET", secretID); + + Path filePath = Paths.get(System.getProperty("java.io.tmpdir"), VAULT_APPROLE_FILE); + File file = filePath.toFile(); + FileOutputStream fos = new FileOutputStream(file); + properties.store(fos, null); // add secrets for v1 and v2 - runCommand(container, "vault", "kv", "put", VAULT_PATH_KV1_1, "key1=123", "key2=456"); + runCommand(container, "vault", "kv", "put", VAULT_PATH_KV1_1, "key1=123", + "key2=456"); runCommand(container, "vault", "kv", "put", VAULT_PATH_KV1_2, "key3=789"); - runCommand(container, "vault", "kv", "put", VAULT_PATH_KV2_1, "key1=123", "key2=456"); + runCommand(container, "vault", "kv", "put", VAULT_PATH_KV2_1, "key1=123", + "key2=456"); runCommand(container, "vault", "kv", "put", VAULT_PATH_KV2_2, "key3=789"); runCommand(container, "vault", "kv", "put", VAULT_PATH_KV2_3, "key2=321"); - runCommand(container, "vault", "kv", "put", VAULT_PATH_KV2_AUTH_TEST, "key1=auth-test"); - + runCommand(container, "vault", "kv", "put", VAULT_PATH_KV2_AUTH_TEST, + "key1=auth-test"); + LOGGER.log(Level.INFO, "Vault is configured"); + configured = true; } catch (Exception e) { LOGGER.log(Level.WARNING, e.getMessage()); } diff --git a/plugin/src/test/resources/io/jenkins/plugins/casc/vault/vault.yml b/plugin/src/test/resources/io/jenkins/plugins/casc/vault/vault.yml new file mode 100644 index 0000000000..ff193e9b77 --- /dev/null +++ b/plugin/src/test/resources/io/jenkins/plugins/casc/vault/vault.yml @@ -0,0 +1,2 @@ +jenkins: + systemMessage: "Test '{key1}', '{key2}', '{key3}'"