Skip to content

Commit

Permalink
Add the ability to inject global passwords in the EnvInject plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
gboissinot committed Feb 19, 2012
1 parent 3faee33 commit 9bbe67a
Show file tree
Hide file tree
Showing 10 changed files with 352 additions and 1 deletion.
@@ -0,0 +1,30 @@
package org.jenkinsci.plugins.envinject;

import hudson.util.Secret;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.Serializable;

/**
* @author Gregory Boissinot
*/
public class EnvInjectGlobalPasswordEntry implements Serializable {

private String name;

private Secret value;

@DataBoundConstructor
public EnvInjectGlobalPasswordEntry(String name, String password) {
this.name = name;
this.value = Secret.fromString(password);
}

public String getName() {
return name;
}

public Secret getValue() {
return value;
}
}
@@ -0,0 +1,155 @@
package org.jenkinsci.plugins.envinject;

import hudson.Extension;
import hudson.Launcher;
import hudson.console.LineTransformationOutputStream;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Run;
import hudson.tasks.BuildWrapper;
import hudson.tasks.BuildWrapperDescriptor;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.lib.envinject.EnvInjectException;
import org.jenkinsci.plugins.envinject.service.EnvInjectGlobalPasswordRetriever;
import org.kohsuke.stapler.DataBoundConstructor;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

/**
* @author Gregory Boissinot
*/
public class EnvInjectGlobalPasswordWrapper extends BuildWrapper {

private static final Logger LOGGER = Logger.getLogger(EnvInjectGlobalPasswordWrapper.class.getName());

@DataBoundConstructor
public EnvInjectGlobalPasswordWrapper() {
}

@Override
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
return new Environment() {
};
}

/**
* Class took from the mask-passwords plugin
*/
class MaskPasswordsOutputStream extends LineTransformationOutputStream {

private final static String MASKED_PASSWORD = "********";

private final OutputStream logger;
private final Pattern passwordsAsPattern;

MaskPasswordsOutputStream(OutputStream logger, Collection<String> passwords) {

this.logger = logger;

if (passwords != null && passwords.size() > 0) {
// passwords are aggregated into a regex which is compiled as a pattern
// for efficiency
StringBuilder regex = new StringBuilder().append('(');

int nbMaskedPasswords = 0;
for (String password : passwords) {
if (StringUtils.isNotEmpty(password)) { // we must not handle empty passwords
regex.append(Pattern.quote(password));
regex.append('|');
nbMaskedPasswords++;
}
}
if (nbMaskedPasswords++ >= 1) { // is there at least one password to mask?
regex.deleteCharAt(regex.length() - 1); // removes the last unuseful pipe
regex.append(')');
passwordsAsPattern = Pattern.compile(regex.toString());
} else { // no passwords to hide
passwordsAsPattern = null;
}
} else { // no passwords to hide
passwordsAsPattern = null;
}
}

@Override
protected void eol(byte[] bytes, int len) throws IOException {
String line = new String(bytes, 0, len);
if (passwordsAsPattern != null) {
line = passwordsAsPattern.matcher(line).replaceAll(MASKED_PASSWORD);
}
logger.write(line.getBytes());
}

}

@Override
public OutputStream decorateLogger(AbstractBuild build, OutputStream logger) throws IOException, InterruptedException, Run.RunnerAbortedException {

List<String> passwords = new ArrayList<String>();
try {
//Put Clear passwords
EnvInjectGlobalPasswordRetriever globalPasswordRetriever = new EnvInjectGlobalPasswordRetriever();
EnvInjectGlobalPasswordEntry[] passwordEntries = globalPasswordRetriever.getGlobalPasswords();
if (passwordEntries != null) {
for (EnvInjectGlobalPasswordEntry globalPasswordEntry : passwordEntries) {
passwords.add(globalPasswordEntry.getValue().getPlainText());
}
}

} catch (EnvInjectException ee) {
throw new IOException(ee);
}

return new MaskPasswordsOutputStream(logger, passwords);
}

@Override
public void makeBuildVariables(AbstractBuild build, Map<String, String> variables) {

try {
//Put Clear passwords
EnvInjectGlobalPasswordRetriever globalPasswordRetriever = new EnvInjectGlobalPasswordRetriever();
EnvInjectGlobalPasswordEntry[] passwordEntries = globalPasswordRetriever.getGlobalPasswords();
if (passwordEntries != null) {
for (EnvInjectGlobalPasswordEntry globalPasswordEntry : passwordEntries) {
variables.put(globalPasswordEntry.getName(),
globalPasswordEntry.getValue().getPlainText());
}
}

} catch (EnvInjectException ee) {
LOGGER.log(Level.SEVERE, "Can't inject global password", ee);
}
}

@Extension
@SuppressWarnings("unused")
public static final class DescriptorImpl extends BuildWrapperDescriptor {

@Override
public boolean isApplicable(AbstractProject<?, ?> item) {
return true;
}

@Override
public String getDisplayName() {
return Messages.envinject_wrapper_globalPasswords_displayName();
}

@Override
public String getHelpFile() {
return "/plugin/envinject/help-buildWrapperGlobalPasswords.html";
}
}


}
Expand Up @@ -12,6 +12,7 @@
import org.jenkinsci.plugins.envinject.model.EnvInjectJobPropertyContributor;
import org.jenkinsci.plugins.envinject.service.EnvInjectActionSetter;
import org.jenkinsci.plugins.envinject.service.EnvInjectEnvVars;
import org.jenkinsci.plugins.envinject.service.EnvInjectGlobalPasswordRetriever;
import org.jenkinsci.plugins.envinject.service.EnvInjectVariableGetter;

import java.io.IOException;
Expand Down Expand Up @@ -286,6 +287,9 @@ public void onCompleted(Run run, TaskListener listener) {
}
}

//Mask passwords
maskGlobalPasswordsIfAny(logger, envVars);

//Add or override EnvInject Action
EnvInjectActionSetter envInjectActionSetter = new EnvInjectActionSetter(getNodeRootPath());
try {
Expand All @@ -302,4 +306,20 @@ public void onCompleted(Run run, TaskListener listener) {
}
}

private void maskGlobalPasswordsIfAny(EnvInjectLogger logger, Map<String, String> envVars) {
try {
EnvInjectGlobalPasswordRetriever globalPasswordRetriever = new EnvInjectGlobalPasswordRetriever();
EnvInjectGlobalPasswordEntry[] passwordEntries = globalPasswordRetriever.getGlobalPasswords();
if (passwordEntries != null) {
for (EnvInjectGlobalPasswordEntry globalPasswordEntry : passwordEntries) {
envVars.put(globalPasswordEntry.getName(),
globalPasswordEntry.getValue().getEncryptedValue());
}
}
} catch (EnvInjectException ee) {
logger.error("Can't mask global password :" + ee.getMessage());
}
}


}
Expand Up @@ -2,10 +2,20 @@

import hudson.Extension;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.Hudson;
import hudson.model.Node;
import hudson.slaves.NodeProperty;
import hudson.slaves.NodePropertyDescriptor;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* @author Gregory Boissinot
Expand All @@ -32,6 +42,23 @@ public String getPropertiesFilePath() {

@Extension
public static class EnvInjectNodePropertyDescriptor extends NodePropertyDescriptor {

private static final Logger LOGGER = Logger.getLogger(EnvInjectNodePropertyDescriptor.class.getName());

private EnvInjectGlobalPasswordEntry[] envInjectGlobalPasswordEntries = new EnvInjectGlobalPasswordEntry[0];
private static final String ENVINJECT_CONFIG = "envInject.xml";

@SuppressWarnings("unused")
public EnvInjectNodePropertyDescriptor() {
load();
}

@SuppressWarnings("unused")
public EnvInjectNodePropertyDescriptor(Class<? extends NodeProperty<?>> clazz) {
super(clazz);
load();
}

@Override
public String getDisplayName() {
return Messages.envinject_nodeProperty_displayName();
Expand All @@ -41,5 +68,44 @@ public String getDisplayName() {
public String getHelpFile() {
return "/plugin/envinject/help-node.html";
}

@SuppressWarnings("unused")
public EnvInjectGlobalPasswordEntry[] getEnvInjectGlobalPasswordEntries() {
return envInjectGlobalPasswordEntries;
}

@Override
public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
List<EnvInjectGlobalPasswordEntry> envInjectGlobalPasswordEntriesList = req.bindParametersToList(EnvInjectGlobalPasswordEntry.class, "envInject.");
envInjectGlobalPasswordEntries = envInjectGlobalPasswordEntriesList.toArray(new EnvInjectGlobalPasswordEntry[envInjectGlobalPasswordEntriesList.size()]);
save();
return true;
}

@Override
public void save() {
try {
getConfigFile().write(this);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to save global passwords ", e);
}
}

@Override
public synchronized void load() {
XmlFile file = getConfigFile();
if (!file.exists())
return;

try {
file.unmarshal(this);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to load " + file, e);
}
}

public static XmlFile getConfigFile() {
return new XmlFile(new File(Hudson.getInstance().getRootDir(), ENVINJECT_CONFIG));
}
}
}
}
@@ -0,0 +1,30 @@
package org.jenkinsci.plugins.envinject.service;

import hudson.XmlFile;
import org.jenkinsci.lib.envinject.EnvInjectException;
import org.jenkinsci.plugins.envinject.EnvInjectGlobalPasswordEntry;
import org.jenkinsci.plugins.envinject.EnvInjectNodeProperty;

import java.io.IOException;
import java.io.Serializable;

/**
* @author Gregory Boissinot
*/
public class EnvInjectGlobalPasswordRetriever implements Serializable {

public EnvInjectGlobalPasswordEntry[] getGlobalPasswords() throws EnvInjectException {
XmlFile xmlFile = EnvInjectNodeProperty.EnvInjectNodePropertyDescriptor.getConfigFile();
if (xmlFile.exists()) {
EnvInjectNodeProperty.EnvInjectNodePropertyDescriptor desc;
try {
desc = (EnvInjectNodeProperty.EnvInjectNodePropertyDescriptor) xmlFile.read();
} catch (IOException ioe) {
throw new EnvInjectException(ioe);
}
return desc.getEnvInjectGlobalPasswordEntries();
}

return null;
}
}
@@ -0,0 +1,32 @@
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:section title="Global Passwords">

<f:entry title="Global Passwords" description="${%Global passwords list}">

<f:repeatable var="inst" items="${descriptor.envInjectGlobalPasswordEntries}">
<table width="100%">

<f:entry title="Name"
help="/descriptor/org.jenkinsci.plugins.envinject.EnvInjectNodeProperty/help/name">
<f:textbox name="envInject.name" value="${inst.name}"/>
</f:entry>

<f:entry title="Passowrd"
help="/descriptor/org.jenkinsci.plugins.envinject.EnvInjectNodeProperty/help/password">
<f:password name="envInject.password" value="${inst.value}" />
</f:entry>

<f:entry title="">
<div align="right">
<f:repeatableDeleteButton/>
</div>
</f:entry>

</table>
</f:repeatable>

</f:entry>

</f:section>
</j:jelly>
@@ -0,0 +1,6 @@
<div>
<p>
Give a name to a global password value. <br/>
This name will be accessible by an environment variable.
</p>
</div>

0 comments on commit 9bbe67a

Please sign in to comment.