Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIXED JENKINS-48499] Support Ansible Vault #18

Merged
merged 2 commits into from
Jan 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@
<artifactId>ssh-credentials</artifactId>
<version>${ssh-credentials.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plain-credentials</artifactId>
<version>${plain-credentials.version}</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-step-api</artifactId>
Expand Down Expand Up @@ -135,6 +140,7 @@
<maven-resources-plugin.version>2.6</maven-resources-plugin.version>
<maven-release-plugin.version>2.5.2</maven-release-plugin.version>
<ssh-credentials.version>1.10</ssh-credentials.version>
<plain-credentials.version>1.4</plain-credentials.version>
<credentials.version>1.16.1</credentials.version>
<workflow-step-api.version>1.10</workflow-step-api.version>
<junit.version>4.12</junit.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardListBoxModel;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
import org.jenkinsci.plugins.plaincredentials.FileCredentials;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;
import hudson.model.AbstractProject;
import hudson.model.Project;
import hudson.tasks.BuildStepDescriptor;
Expand Down Expand Up @@ -50,6 +53,15 @@ public ListBoxModel doFillCredentialsIdItems(@AncestorInPath Project project) {
CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, project));
}

public ListBoxModel doFillVaultCredentialsIdItems(@AncestorInPath Project project) {
return new StandardListBoxModel()
.withEmptySelection()
.withMatching(anyOf(
instanceOf(FileCredentials.class),
instanceOf(StringCredentials.class)),
CredentialsProvider.lookupCredentials(StandardCredentials.class, project));
}

public List<InventoryDescriptor> getInventories() {
return Jenkins.getActiveInstance().getDescriptorList(Inventory.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.regex.Pattern;

import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials;
import hudson.EnvVars;
Expand All @@ -32,6 +33,8 @@
import hudson.util.ArgumentListBuilder;
import hudson.util.Secret;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.plaincredentials.FileCredentials;
import org.jenkinsci.plugins.plaincredentials.StringCredentials;

/**
* Ansible command invocation
Expand All @@ -47,12 +50,14 @@ abstract class AbstractAnsibleInvocation<T extends AbstractAnsibleInvocation<T>>
protected int forks;
protected boolean sudo;
protected String sudoUser;
protected StandardCredentials vaultCredentials;
protected StandardUsernameCredentials credentials;
protected List<ExtraVar> extraVars;
protected String additionalParameters;

private FilePath key = null;
private FilePath script = null;
private FilePath vaultPassword = null;
private Inventory inventory;
private boolean copyCredentialsInWorkspace = false;
private final FilePath ws;
Expand Down Expand Up @@ -186,6 +191,11 @@ public T setCredentials(StandardUsernameCredentials credentials, boolean copyCre
return setCredentials(credentials);
}

public T setVaultCredentials(StandardCredentials vaultCredentials) {
this.vaultCredentials = vaultCredentials;
return (T) this;
}

protected ArgumentListBuilder prependPasswordCredentials(ArgumentListBuilder args) {
if (credentials instanceof UsernamePasswordCredentials) {
UsernamePasswordCredentials passwordCredentials = (UsernamePasswordCredentials)credentials;
Expand Down Expand Up @@ -218,6 +228,23 @@ protected ArgumentListBuilder appendCredentials(ArgumentListBuilder args)
return args;
}

protected ArgumentListBuilder appendVaultPasswordFile(ArgumentListBuilder args)
throws IOException, InterruptedException
{
if(vaultCredentials != null){
if (vaultCredentials instanceof FileCredentials) {
FileCredentials secretFile = (FileCredentials)vaultCredentials;
vaultPassword = Utils.createVaultPasswordFile(vaultPassword, ws, secretFile);
args.add("--vault-password-file").add(vaultPassword.getRemote().replace("%", "%%"));
} else if (vaultCredentials instanceof StringCredentials) {
StringCredentials secretText = (StringCredentials)vaultCredentials;
vaultPassword = Utils.createVaultPasswordFile(vaultPassword, ws, secretText);
args.add("--vault-password-file").add(vaultPassword.getRemote().replace("%", "%%"));
}
}
return args;
}

public T setUnbufferedOutput(boolean unbufferedOutput) {
if (unbufferedOutput) {
environment.put("PYTHONUNBUFFERED", "1");
Expand Down Expand Up @@ -251,6 +278,7 @@ public boolean execute(CLIRunner runner) throws IOException, InterruptedExceptio
}
Utils.deleteTempFile(key, listener);
Utils.deleteTempFile(script, listener);
Utils.deleteTempFile(vaultPassword, listener);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import javax.annotation.Nonnull;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import hudson.AbortException;
import hudson.EnvVars;
Expand Down Expand Up @@ -51,6 +52,8 @@ public class AnsibleAdHocCommandBuilder extends Builder implements SimpleBuildSt
*/
public String credentialsId = null;

public String vaultCredentialsId = null;

public final String hostPattern;

/**
Expand Down Expand Up @@ -117,6 +120,11 @@ public void setCredentialsId(String credentialsId) {
this.credentialsId = credentialsId;
}

@DataBoundSetter
public void setVaultCredentialsId(String vaultCredentialsId) {
this.vaultCredentialsId = vaultCredentialsId;
}

@DataBoundSetter
public void setSudo(boolean sudo) {
this.sudo = sudo;
Expand Down Expand Up @@ -177,6 +185,9 @@ public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath ws, @Nonnull Launc
invocation.setCredentials(StringUtils.isNotBlank(credentialsId) ?
CredentialsProvider.findCredentialById(credentialsId, StandardUsernameCredentials.class, run) :
null);
invocation.setVaultCredentials(StringUtils.isNotBlank(vaultCredentialsId) ?
CredentialsProvider.findCredentialById(vaultCredentialsId, StandardCredentials.class, run) :
null);
invocation.setExtraVars(extraVars);
invocation.setAdditionalParameters(additionalParameters);
invocation.setHostKeyCheck(hostKeyChecking);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ protected ArgumentListBuilder buildCommandLine()
appendSudo(args);
appendForks(args);
appendCredentials(args);
appendVaultPasswordFile(args);
appendExtraVars(args);
appendAdditionalParameters(args);
return args;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* The command to be launched
*/
public enum AnsibleCommand {
ANSIBLE("ansible"), ANSIBLE_PLAYBOOK("ansible-playbook");
ANSIBLE("ansible"), ANSIBLE_PLAYBOOK("ansible-playbook"), ANSIBLE_VAULT("ansible-vault");

private final String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import javax.annotation.Nonnull;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import hudson.AbortException;
import hudson.EnvVars;
Expand Down Expand Up @@ -65,6 +66,8 @@ public class AnsiblePlaybookBuilder extends Builder implements SimpleBuildStep
*/
public String credentialsId = null;

public String vaultCredentialsId = null;

public boolean sudo = false;

public String sudoUser = "root";
Expand Down Expand Up @@ -147,6 +150,11 @@ public void setCredentialsId(String credentialsId, boolean copyCredentialsInWork
this.copyCredentialsInWorkspace = copyCredentialsInWorkspace;
}

@DataBoundSetter
public void setVaultCredentialsId(String vaultCredentialsId) {
this.vaultCredentialsId = vaultCredentialsId;
}

@DataBoundSetter
public void setSudo(boolean sudo) {
this.sudo = sudo;
Expand Down Expand Up @@ -217,6 +225,8 @@ public void perform(@Nonnull Run<?, ?> run, @Nonnull Node node, @Nonnull FilePat
invocation.setCredentials(StringUtils.isNotBlank(credentialsId) ?
CredentialsProvider.findCredentialById(credentialsId, StandardUsernameCredentials.class, run) : null,
copyCredentialsInWorkspace);
invocation.setVaultCredentials(StringUtils.isNotBlank(vaultCredentialsId) ?
CredentialsProvider.findCredentialById(vaultCredentialsId, StandardCredentials.class, run) : null);
invocation.setExtraVars(extraVars);
invocation.setAdditionalParameters(additionalParameters);
invocation.setHostKeyCheck(hostKeyChecking);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ protected ArgumentListBuilder buildCommandLine() throws InterruptedException, An
appendSudo(args);
appendForks(args);
appendCredentials(args);
appendVaultPasswordFile(args);
appendExtraVars(args);
appendAdditionalParameters(args);
return args;
Expand Down
158 changes: 158 additions & 0 deletions src/main/java/org/jenkinsci/plugins/ansible/AnsibleVaultBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
* 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.jenkinsci.plugins.ansible;

import java.io.IOException;
import javax.annotation.Nonnull;

import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.Util;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import jenkins.tasks.SimpleBuildStep;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.QueryParameter;

/**
* A builder which wraps an Ansible vault invocation.
*
* @author Michael Cresswell
*/
public class AnsibleVaultBuilder extends Builder implements SimpleBuildStep
{

public String ansibleName = null;

public String action = "encrypt_string";

public String vaultCredentialsId = null;

public String newVaultCredentialsId = null;

public String content = null;

public String input = null;

public String output = null;

@DataBoundConstructor
public AnsibleVaultBuilder() {

}

@DataBoundSetter
public void setAnsibleName(String ansibleName) {
this.ansibleName = ansibleName;
}

@DataBoundSetter
public void setAction(String action) {
this.action = action;
}

@DataBoundSetter
public void setVaultCredentialsId(String vaultCredentialsId) {
this.vaultCredentialsId = vaultCredentialsId;
}

@DataBoundSetter
public void setNewVaultCredentialsId(String newVaultCredentialsId) {
this.newVaultCredentialsId = newVaultCredentialsId;
}

@DataBoundSetter
public void setContent(String content) {
this.content = content;
}

@DataBoundSetter
public void setInput(String input) {
this.input = input;
}

@DataBoundSetter
public void setOutput(String output) {
this.output = output;
}

@Override
public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath ws, @Nonnull Launcher launcher, @Nonnull TaskListener listener)
throws InterruptedException, IOException
{
Computer computer = Computer.currentComputer();
Node node;
if (computer == null || (node = computer.getNode()) == null) {
throw new AbortException("The ansible vault build step requires to be launched on a node");
}
perform(run, node, ws, launcher, listener, run.getEnvironment(listener));
}

public void perform(@Nonnull Run<?, ?> run, @Nonnull Node node, @Nonnull FilePath ws, @Nonnull Launcher launcher, @Nonnull TaskListener listener, EnvVars envVars)
throws InterruptedException, IOException
{
try {
CLIRunner runner = new CLIRunner(run, ws, launcher, listener);
String exe = AnsibleInstallation.getExecutable(ansibleName, AnsibleCommand.ANSIBLE_VAULT, node, listener, envVars);
AnsibleVaultInvocation invocation = new AnsibleVaultInvocation(exe, run, ws, listener, envVars);
invocation.setAction(action);
invocation.setVaultCredentials(StringUtils.isNotBlank(vaultCredentialsId) ?
CredentialsProvider.findCredentialById(vaultCredentialsId, StandardCredentials.class, run) : null);
invocation.setNewVaultCredentials(StringUtils.isNotBlank(newVaultCredentialsId) ?
CredentialsProvider.findCredentialById(newVaultCredentialsId, StandardCredentials.class, run) : null);
invocation.setContent(content);
invocation.setInput(input);
invocation.setOutput(output);
if (!invocation.execute(runner)) {
throw new AbortException("Ansible vault execution failed");
}
} catch (IOException ioe) {
Util.displayIOException(ioe, listener);
ioe.printStackTrace(listener.fatalError(hudson.tasks.Messages.CommandInterpreter_CommandFailed()));
throw ioe;
} catch (AnsibleInvocationException aie) {
listener.fatalError(aie.getMessage());
throw new AbortException(aie.getMessage());
}
}

@Override
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.NONE;
}

@Extension
public static final class DescriptorImpl extends AbstractAnsibleBuilderDescriptor
{
public DescriptorImpl() {
super("Invoke Ansible Vault");
}

public FormValidation doCheckVaultCredentialsId(@QueryParameter String vaultCredentialsId) {
return checkNotNullOrEmpty(vaultCredentialsId, "Vault credentials must not be empty");
}
}
}
Loading