Skip to content

Commit

Permalink
add integration tests for build wrapper against vault container (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
jetersen committed Oct 9, 2019
1 parent ff6c2e8 commit 67d37cc
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 56 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/maven.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI

on: [push, pull_request]

jobs:
build:
name: Build on Jenkins ${{ matrix.jenkins-version }}, JDK ${{ matrix.java }} and ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
java: [1.8, 11]
jenkins-version: [2.138.4, 2.190.1]
os: [ubuntu-latest, windows-latest]
include:
- jenkins-version: '2.190.1'
flags: '-Djenkins.version=2.190.1 -Dslf4jVersion=1.7.26'
exclude:
- java: '11'
jenkins-version: '2.138.4'

steps:
- uses: actions/checkout@v1
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
- name: Build with Maven
run: |
mvn install -B -V --no-transfer-progress ${{ matrix.flags }}
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,3 @@ work/
.settings/
*.sublime*
tmp/
ssl/
24 changes: 5 additions & 19 deletions src/main/java/com/datapipe/jenkins/vault/VaultBuildWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
*/
package com.datapipe.jenkins.vault;

import com.bettercloud.vault.SslConfig;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.json.Json;
Expand Down Expand Up @@ -128,35 +127,22 @@ private List<String> retrieveLeaseIds(List<LogicalResponse> logicalResponses) {
}

protected void provideEnvironmentVariablesFromVault(Context context, Run build, EnvVars envVars) {
String url = getConfiguration().getVaultUrl();
VaultConfiguration config = getConfiguration();
String url = config.getVaultUrl();

if (StringUtils.isBlank(url)) {
throw new VaultPluginException(
"The vault url was not configured - please specify the vault url to use.");
}

VaultConfig vaultConfig;
try {
vaultConfig = new VaultConfig().address(configuration.getVaultUrl());

if (configuration.isSkipSslVerification()) {
vaultConfig.sslConfig(new SslConfig().verify(false).build());
}

if (StringUtils.isNotEmpty(configuration.getVaultNamespace())) {
vaultConfig.nameSpace(configuration.getVaultNamespace());
}
vaultConfig.engineVersion(configuration.getEngineVersion());
} catch (VaultException e) {
throw new VaultPluginException("Could not set up VaultConfig.", e);
}
VaultConfig vaultConfig = config.getVaultConfig();

VaultCredential credential = retrieveVaultCredentials(build);

vaultAccessor.setConfig(vaultConfig);
vaultAccessor.setCredential(credential);
vaultAccessor.setMaxRetries(configuration.getMaxRetries());
vaultAccessor.setRetryIntervalMilliseconds(configuration.getRetryIntervalMilliseconds());
vaultAccessor.setMaxRetries(config.getMaxRetries());
vaultAccessor.setRetryIntervalMilliseconds(config.getRetryIntervalMilliseconds());
vaultAccessor.init();

for (VaultSecret vaultSecret : vaultSecrets) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.datapipe.jenkins.vault.configuration;

import com.bettercloud.vault.SslConfig;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.domains.DomainRequirement;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import com.datapipe.jenkins.vault.credentials.VaultCredential;
import com.datapipe.jenkins.vault.exception.VaultPluginException;
import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
Expand Down Expand Up @@ -204,6 +208,25 @@ public ListBoxModel doFillEngineVersionItems(@AncestorInPath Item context) {
}
}

@NonNull
public VaultConfig getVaultConfig() {
VaultConfig vaultConfig = new VaultConfig();
vaultConfig.address(this.getVaultUrl());
vaultConfig.engineVersion(this.getEngineVersion());
try {
if (this.isSkipSslVerification()) {
vaultConfig.sslConfig(new SslConfig().verify(false).build());
}

if (StringUtils.isNotEmpty(this.getVaultNamespace())) {
vaultConfig.nameSpace(this.getVaultNamespace());
}
} catch (VaultException e) {
throw new VaultPluginException("Could not set up VaultConfig.", e);
}
return vaultConfig;
}

@Restricted(NoExternalUse.class)
public static ListBoxModel engineVersions(Item context) {
ListBoxModel options = new ListBoxModel(
Expand Down
146 changes: 146 additions & 0 deletions src/test/java/com/datapipe/jenkins/vault/it/buildwrapper/SSLTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.datapipe.jenkins.vault.it.buildwrapper;

import com.bettercloud.vault.SslConfig;
import com.bettercloud.vault.VaultConfig;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.datapipe.jenkins.vault.configuration.GlobalVaultConfiguration;
import com.datapipe.jenkins.vault.configuration.VaultConfiguration;
import com.datapipe.jenkins.vault.credentials.VaultTokenCredential;
import com.datapipe.jenkins.vault.util.TestConstants;
import com.datapipe.jenkins.vault.util.VaultContainer;
import hudson.model.Result;
import hudson.util.Secret;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;
import org.mockito.Mockito;

import static com.datapipe.jenkins.vault.util.VaultTestUtil.hasDockerDaemon;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.when;

public class SSLTest implements TestConstants {

@ClassRule
public static VaultContainer container = VaultContainer.createVaultContainer();

@ClassRule
public static JenkinsRule j = new JenkinsRule();

@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();

@Rule
public TemporaryFolder testFolder = new TemporaryFolder();

private static WorkflowJob pipeline;
private static final String credentialsId = "vaultToken";

@BeforeClass
public static void setupClass() throws IOException, InterruptedException {
assumeTrue(hasDockerDaemon());
container.initAndUnsealVault();
container.setBasicSecrets();

pipeline = j.createProject(WorkflowJob.class, "Pipeline");
String pipelineText = IOUtils.toString(TestConstants.class.getResourceAsStream("pipeline.groovy"));
pipeline.setDefinition(new CpsFlowDefinition(pipelineText, true));

VaultTokenCredential c = new VaultTokenCredential(CredentialsScope.GLOBAL,
credentialsId, "fake description", Secret.fromString(container.getRootToken()));
CredentialsProvider.lookupStores(j.jenkins).iterator().next()
.addCredentials(Domain.global(), c);
}

@Test
public void SSLError() throws Exception {
GlobalVaultConfiguration globalVaultConfiguration = GlobalVaultConfiguration.get();
VaultConfiguration vaultConfiguration = new VaultConfiguration();
vaultConfiguration.setVaultUrl(container.getAddress());
vaultConfiguration.setVaultCredentialId(credentialsId);
vaultConfiguration.setTimeout(1);
globalVaultConfiguration.setConfiguration(vaultConfiguration);

WorkflowRun build = pipeline.scheduleBuild2(0).get();

j.assertBuildStatus(Result.FAILURE, build);
j.assertLogContains("javax.net.ssl.SSLHandshakeException", build);
}

@Test
public void SSLOk() throws Exception {
File store = testFolder.newFile("cacerts.keystore");
File certificate = new File(CERT_PEMFILE);
createKeyStore(store, certificate);

GlobalVaultConfiguration globalVaultConfiguration = GlobalVaultConfiguration.get();
VaultConfiguration vaultConfiguration = Mockito.mock(VaultConfiguration.class);
when(vaultConfiguration.getVaultUrl()).thenReturn(container.getAddress());
when(vaultConfiguration.getVaultCredentialId()).thenReturn(credentialsId);
when(vaultConfiguration.getEngineVersion()).thenReturn(1);
when(vaultConfiguration.getTimeout()).thenReturn(5);
globalVaultConfiguration.setConfiguration(vaultConfiguration);

VaultConfig config = new VaultConfig()
.address(vaultConfiguration.getVaultUrl())
.engineVersion(vaultConfiguration.getEngineVersion())
.sslConfig(new SslConfig()
.trustStoreFile(store)
.verify(true)
.build()
);
when(vaultConfiguration.getVaultConfig()).thenReturn(config);

WorkflowRun build = pipeline.scheduleBuild2(0).get();

j.assertBuildStatus(Result.SUCCESS, build);
j.assertLogContains("****", build);
}

@Test
public void SSLSkipVerify() throws Exception {
GlobalVaultConfiguration globalVaultConfiguration = GlobalVaultConfiguration.get();
VaultConfiguration vaultConfiguration = new VaultConfiguration();
vaultConfiguration.setVaultUrl(container.getAddress());
vaultConfiguration.setVaultCredentialId(credentialsId);
vaultConfiguration.setEngineVersion(1);
vaultConfiguration.setTimeout(5);
vaultConfiguration.setSkipSslVerification(true);
globalVaultConfiguration.setConfiguration(vaultConfiguration);

WorkflowRun build = pipeline.scheduleBuild2(0).get();

j.assertBuildStatus(Result.SUCCESS, build);
j.assertLogContains("****", build);
}

private void createKeyStore(File store, File certificate) throws Exception {
KeyStore keyStore = KeyStore.getInstance("JKS");
CertificateFactory fact = CertificateFactory.getInstance("X.509");
FileInputStream is = new FileInputStream(certificate);
X509Certificate cer = (X509Certificate) fact.generateCertificate(is);
is.close();
keyStore.load(null, null);
keyStore.setCertificateEntry("dockerCert", cer);
try (FileOutputStream o = new FileOutputStream(store)) {
keyStore.store(o, "changeit".toCharArray());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.io.File;
import org.testcontainers.containers.Network;
import org.testcontainers.utility.TestEnvironment;

public interface TestConstants {

Expand All @@ -12,12 +11,10 @@ public interface TestConstants {
int MAX_RETRIES = 5;
int RETRY_MILLIS = 1000;

String CURRENT_WORKING_DIRECTORY = System.getProperty("user.dir");
String CURRENT_WORKING_DIRECTORY = System.getProperty("user.dir") + "/src/test/resources/com/datapipe/jenkins/vault/util";
String SSL_DIRECTORY = CURRENT_WORKING_DIRECTORY + File.separator + "ssl";
String CERT_PEMFILE = SSL_DIRECTORY + File.separator + "root-cert.pem";

String CLIENT_CERT_PEMFILE = SSL_DIRECTORY + File.separator + "client-cert.pem";

String CONTAINER_STARTUP_SCRIPT = "/vault/config/startup.sh";
String CONTAINER_CONFIG_FILE = "/vault/config/config.hcl";
String CONTAINER_OPENSSL_CONFIG_FILE = "/vault/config/libressl.conf";
Expand All @@ -29,7 +26,6 @@ public interface TestConstants {
String APPROLE_POLICY_FILE = "/home/vault/approlePolicy.hcl";

Network CONTAINER_NETWORK = Network.newNetwork();
boolean DOCKER_AVAILABLE = TestEnvironment.dockerApiAtLeast("1.10");

String VAULT_DOCKER_IMAGE = "vault:1.0.3";
String VAULT_ROOT_TOKEN = "root-token";
Expand Down
31 changes: 12 additions & 19 deletions src/test/java/com/datapipe/jenkins/vault/util/VaultContainer.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.lifecycle.TestDescription;
import org.testcontainers.lifecycle.TestLifecycleAware;

import static org.junit.Assume.assumeTrue;
import static com.datapipe.jenkins.vault.util.VaultTestUtil.hasDockerDaemon;
import static org.testcontainers.utility.MountableFile.forHostPath;

/**
* Sets up and exposes utilities for dealing with a Docker-hosted instance of Vault, for integration tests.
*/
public class VaultContainer extends GenericContainer<VaultContainer> implements TestConstants, TestLifecycleAware {
public class VaultContainer extends GenericContainer<VaultContainer> implements TestConstants {

private static final Logger LOGGER = LoggerFactory.getLogger(VaultContainer.class);

Expand All @@ -34,12 +32,16 @@ public class VaultContainer extends GenericContainer<VaultContainer> implements
private String rootToken;
private String unsealKey;

/**
* Establishes a running Docker container, hosting a Vault server instance.
*/
public VaultContainer(String image) {
super(image);
this.withNetwork(CONTAINER_NETWORK)
public VaultContainer() {
super(DEFAULT_IMAGE_AND_TAG);
}

public static VaultContainer createVaultContainer() {
if (!hasDockerDaemon()) {
return null;
}
return new VaultContainer()
.withNetwork(CONTAINER_NETWORK)
.withNetworkAliases("vault")
.withCopyFileToContainer(forHostPath(
TestConstants.class.getResource("vaultTest_server.hcl").getPath()),
Expand All @@ -58,10 +60,6 @@ public VaultContainer(String image) {
.waitingFor(Wait.forLogMessage(".+Vault server started!.+", 1));
}

public VaultContainer() {
this(DEFAULT_IMAGE_AND_TAG);
}

/**
* To be called by a test class method annotated with {@link org.junit.BeforeClass}.
* This logic doesn't work when placed inside of the constructor, presumably
Expand Down Expand Up @@ -264,9 +262,4 @@ private Container.ExecResult runCommand(final String... command) throws IOExcept
}
return result;
}

@Override
public void beforeTest(TestDescription description) {
assumeTrue(DOCKER_AVAILABLE);
}
}
8 changes: 8 additions & 0 deletions src/test/resources/com/datapipe/jenkins/vault/util/gencert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Create a CA root certificate and key
openssl req -newkey rsa:2048 -days 3650 -x509 -nodes -out root-cert.pem -keyout root-privkey.pem -subj '/C=DK/ST=Denmark/L=Copenhagen/O=Jenkins/CN=localhost'
# Create a private key, and a certificate-signing request
openssl req -newkey rsa:1024 -nodes -out vault-csr.pem -keyout vault-privkey.pem -subj '/C=DK/ST=Denmark/L=Copenhagen/O=Jenkins/CN=localhost'
# Create an X509 certificate for the Vault server
echo 000a > serialfile
touch certindex
openssl ca -batch -config libressl.conf -notext -in vault-csr.pem -out vault-cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDMDCCAhgCCQDcqGwmfGnq4jANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJE
SzEQMA4GA1UECAwHRGVubWFyazETMBEGA1UEBwwKQ29wZW5oYWdlbjEQMA4GA1UE
CgwHSmVua2luczESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTE5MTAwOTA4MjMzMloX
DTI5MTAwNjA4MjMzMlowWjELMAkGA1UEBhMCREsxEDAOBgNVBAgMB0Rlbm1hcmsx
EzARBgNVBAcMCkNvcGVuaGFnZW4xEDAOBgNVBAoMB0plbmtpbnMxEjAQBgNVBAMM
CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN+7VkUv
jfVOoTD/CZYegTUr6PitAvU0l9gXXb1ncCO7gg/MPayUL06KlitomO+uBjQo4CWB
OaScjoM/2hmEZYQERY/O0UVOMhudjcf/RbjsJE1jvyghrRgWegC+Ib8IuH9DfDe7
yKvgfo181oIVNlW6dXlbI+itQMLo94aVXJGgOZIm1Ngm49hv6Dq6MIgimmdI9QFr
p0Gc1/OknNRvDpKSAK4q2O+zzGvhwPJTSj/8V4hUgeMEazeNU1FAsz7bnLGQj2ru
VP9oiHJAh3lFjdjBiD4b1buAJHw4l2YX98E0idJUEp0sucoEZQI8u/MUjUHoraUt
zseOS8oLBjx6Mc8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAoC3bmhOJPvwlN8Ny
ciOwF3OEA9CpbfMveEkZKnfQzIKlMii1+q6i60F00230CG8vhU9G/AQz/MFLLwyu
yp8wNjDeEW1/bQCw3pfKvRrdDyV5WbPID48VsJfyeTqAqF14ZMJJG4wGxQkyXwwD
Ykz6qmFOrm8rxOSVptw/TxK7AAJyF/9YPotH1KL5GRnkwgvHWW6FuyNzpTSc4KwC
DbW2zlndQzP80vidyXD5An1Lc6tkseMQ5jWjhys3nPL5XT7/i4LureBfK9ynxQJ8
aRvuWcPvhmBfQKvUk0ZU3Ions4t5ySOvGBt0jLUtX4DaOh0X4oBqth7LZa9MWDiD
pYABpQ==
-----END CERTIFICATE-----

0 comments on commit 67d37cc

Please sign in to comment.