Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions .mvn/maven.config
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Pconsume-incrementals
9 changes: 4 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.18</version>
<version>4.38</version>
<relativePath />
</parent>

Expand All @@ -14,7 +14,7 @@
<name>Audit Trail</name>
<version>3.11-SNAPSHOT</version>
<properties>
<jenkins.version>2.263.1</jenkins.version>
<jenkins.version>2.332.1</jenkins.version>
<java.level>8</java.level>
<slf4jVersion>1.7.30</slf4jVersion>
</properties>
Expand Down Expand Up @@ -116,8 +116,8 @@
<dependencies>
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-2.263.x</artifactId>
<version>807.v6d348e44c987</version>
<artifactId>bom-2.332.x</artifactId>
<version>1210.vcd41f6657f03</version>
<scope>import</scope>
<type>pom</type>
</dependency>
Expand All @@ -129,7 +129,6 @@
Does not include caffeine in the hpi file. -->
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.1</version>
<exclusions>
<!-- do not bring in the annotations -->
<exclusion>
Expand Down
14 changes: 12 additions & 2 deletions src/main/java/hudson/plugins/audit_trail/AuditTrailPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public class AuditTrailPlugin extends GlobalConfiguration {

private static final Logger LOGGER = Logger.getLogger(AuditTrailPlugin.class.getName());
private boolean logBuildCause = true;

private boolean logCredentialsUsage = true;
private List<AuditLogger> loggers = new ArrayList<>();

private transient String log;
Expand Down Expand Up @@ -108,11 +108,15 @@ public class AuditTrailPlugin extends GlobalConfiguration {
public boolean getLogBuildCause() {
return shouldLogBuildCause();
}

public boolean shouldLogBuildCause() {
return logBuildCause;
}

public boolean getLogCredentialsUsage() { return shouldLogCredentialsUsage(); }

public boolean shouldLogCredentialsUsage() { return logCredentialsUsage; }

public List<AuditLogger> getLoggers() { return loggers; }

public AuditTrailPlugin() {
Expand Down Expand Up @@ -155,6 +159,12 @@ public void setLogBuildCause(boolean logBuildCause) {
save();
}

@DataBoundSetter
public void setLogCredentialsUsage(boolean logCredentialsUsage) {
this.logCredentialsUsage = logCredentialsUsage;
save();
}

private void updateFilterPattern() {
try {
AuditTrailFilter.setPattern(pattern);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package hudson.plugins.audit_trail;

import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsUseListener;
import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials;
import hudson.Extension;
import hudson.model.ModelObject;

import javax.inject.Inject;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Log when credentials are used. Only works if the job decides to access the credentials via the
* {@link com.cloudbees.plugins.credentials.CredentialsProvider}. Credential-types that do not extend
* {@link com.cloudbees.plugins.credentials.Credentials}
*
* @author Jan Meiswinkel
*/
@Extension
public class CredentialUsageListener extends CredentialsUseListener {
@Inject
AuditTrailPlugin configuration;

/**
* Triggered when the {@link com.cloudbees.plugins.credentials.CredentialsProvider} accesses
* {@link com.cloudbees.plugins.credentials.Credentials}
*
* @param c The used Credentials.
* @param obj The object using the credentials.
*/
@Override
public void onUse(Credentials c, ModelObject obj) {
if (!configuration.shouldLogCredentialsUsage()) {
return;
}

StringBuilder builder = new StringBuilder(100);

String objName = obj.toString();
String objType = obj.getClass().toString();

builder.append("'" + objName
+ "' (" + objType + ") ");

String credsType = c.getClass().toString();

if (c instanceof BaseStandardCredentials) {
String credsId = ((BaseStandardCredentials) c).getId();
builder.append("used credentials '" + credsId + "' (" + credsType + ").");
} else {
String nonDefaultWarning = builder + ("used an unsupported credentials type (" + credsType +
") that may potentially not be audit-logged correctly.");
Logger.getLogger(CredentialUsageListener.class.getName()).log(Level.INFO, null, nonDefaultWarning);

builder.append("used credentials '" + c + "' (" + credsType + ") (Note: Used fallback method for log as " +
"credentials type is not supported. See INFO log for more information.");
}

for (AuditLogger logger : configuration.getLoggers()) {
logger.log(builder.toString());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<f:entry title="${%Log how each build is triggered}">
<f:checkbox name="logBuildCause" checked="${descriptor.logBuildCause}"/>
</f:entry>
<f:entry title="${%Log credentials usage}">
<f:checkbox name="logCredentialsUsage" checked="${descriptor.logCredentialsUsage}"/>
</f:entry>
</f:advanced>
</f:section>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

Log\ how\ each\ build\ is\ triggered=Zeichne auf wodurch die jeweiligen Builds angesto\u00DFen worden sind.
Log\ how\ each\ build\ is\ triggered=Aufzeichnen, wodurch die jeweiligen Builds angesto\u00DFen wurden.
Log\ credentials\ usage=Aufzeichnen, welche Objekte auf Credentials zugreifen.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public void createItemLogsTheNewItemName() throws Exception {
form.getInputByName("name").blur();
// not clear to me why the input is not visible in the test (yet it exists in the page)
// for some reason the two next calls are needed
form.getInputByValue("hudson.model.FreeStyleProject").click(false, false, false, true, true, false);
form.getInputByValue("hudson.model.FreeStyleProject").click(false, false, false, true, true, false, false);
form.getInputByValue("hudson.model.FreeStyleProject").setChecked(true);
j.submit(form);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package hudson.plugins.audit_trail;

import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
import hudson.Util;
import hudson.model.FreeStyleProject;
import hudson.model.Item;
import hudson.slaves.DumbSlave;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.jvnet.hudson.test.JenkinsRule;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;

import static org.junit.Assert.assertTrue;

public class CredentialUsageListenerTest {
@Rule
public JenkinsRule r = new JenkinsRule();
@Rule
public TemporaryFolder tmpDir = new TemporaryFolder();

@Test
public void jobCredentialUsageIsLogged() throws Exception {
String logFileName = "jobCredentialUsageIsProperlyLogged.log";
File logFile = new File(tmpDir.getRoot(), logFileName);
JenkinsRule.WebClient wc = r.createWebClient();
new SimpleAuditTrailPluginConfiguratorHelper(logFile).sendConfiguration(r, wc);

FreeStyleProject job = r.createFreeStyleProject("test-job");
String id = "id";
Credentials creds = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL, id, "description", "username", "password");
CredentialsProvider.track(job, creds);

String log = Util.loadFile(new File(tmpDir.getRoot(), logFileName + ".0"), StandardCharsets.UTF_8);
assertTrue("logged actions: " + log, Pattern.compile(".*test-job.*used credentials '" + id + "'.*", Pattern.DOTALL).matcher(log).matches());
}

@Test
public void nodeCredentialUsageIsLogged() throws Exception {
String logFileName = "nodeCredentialUsageIsProperlyLogged.log";
File logFile = new File(tmpDir.getRoot(), logFileName);
JenkinsRule.WebClient wc = r.createWebClient();
new SimpleAuditTrailPluginConfiguratorHelper(logFile).sendConfiguration(r, wc);

DumbSlave dummyAgent = r.createSlave();
dummyAgent.setNodeName("test-agent");
String id = "id";
Credentials creds = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL, id, "description", "username", "password");
CredentialsProvider.track(dummyAgent, creds);

String log = Util.loadFile(new File(tmpDir.getRoot(), logFileName + ".0"), StandardCharsets.UTF_8);
assertTrue("logged actions: " + log, Pattern.compile(".*test-agent.*used credentials '" + id + "'.*", Pattern.DOTALL).matcher(log).matches());
}

@Test
public void itemCredentialUsageIsLogged() throws Exception {
String logFileName = "itemCredentialUsageIsProperlyLogged.log";
File logFile = new File(tmpDir.getRoot(), logFileName);
JenkinsRule.WebClient wc = r.createWebClient();
new SimpleAuditTrailPluginConfiguratorHelper(logFile).sendConfiguration(r, wc);
// 'Folder' because it is a non-traditional item to access credentials.
Item item = r.createFolder("test-item");

String id = "id";
Credentials creds = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL, id, "description", "username", "password");
CredentialsProvider.track(item, creds);
String log = Util.loadFile(new File(tmpDir.getRoot(), logFileName + ".0"), StandardCharsets.UTF_8);
assertTrue("logged actions: " + log, Pattern.compile(".*test-item.*used credentials '" + id + "'.*", Pattern.DOTALL).matcher(log).matches());
}

@Test
public void disabledLoggingOptionIsRespected() throws Exception {
String logFileName = "disabledCredentialUsageIsRespected.log";
File logFile = new File(tmpDir.getRoot(), logFileName);
JenkinsRule.WebClient wc = r.createWebClient();
new SimpleAuditTrailPluginConfiguratorHelper(logFile).withLogCredentialsUsage(false).sendConfiguration(r, wc);

FreeStyleProject job = r.createFreeStyleProject("test-job");
String id = "id";
Credentials creds = new UsernamePasswordCredentialsImpl(
CredentialsScope.GLOBAL, id, "description", "username", "password");
CredentialsProvider.track(job, creds);

String log = Util.loadFile(new File(tmpDir.getRoot(), logFileName + ".0"), StandardCharsets.UTF_8);
assertTrue(log.isEmpty());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ public class SimpleAuditTrailPluginConfiguratorHelper {
private static final String LOG_FILE_LOG_SEPARATOR_INPUT_NAME = "_.logSeparator";
private static final String PATTERN_INPUT_NAME= "pattern";
private static final String LOG_BUILD_CAUSE_INPUT_NAME="logBuildCause";
private static final String LOG_CREDENTIALS_USAGE_INPUT_NAME="logCredentialsUsage";
private static final String ADD_LOGGER_BUTTON_TEXT = "Add Logger";
private static final String LOG_FILE_COMBO_TEXT = new LogFileAuditLogger.DescriptorImpl().getDisplayName();

private final File logFile;

private boolean logBuildCause =true;
private boolean logBuildCause = true;
private boolean logCredentialsUsage = true;
private String pattern = ".*/(?:enable|cancelItem|quietDown|createItem)/?.*";

public SimpleAuditTrailPluginConfiguratorHelper(File logFile) {
Expand All @@ -35,6 +37,10 @@ public SimpleAuditTrailPluginConfiguratorHelper withLogBuildCause(boolean logBui
this.logBuildCause = logBuildCause;
return this;
}
public SimpleAuditTrailPluginConfiguratorHelper withLogCredentialsUsage(boolean logCredentialsUsage) {
this.logCredentialsUsage = logCredentialsUsage;
return this;
}

public SimpleAuditTrailPluginConfiguratorHelper withPattern(String pattern) {
this.pattern = pattern;
Expand All @@ -53,6 +59,7 @@ public void sendConfiguration(JenkinsRule j, JenkinsRule.WebClient wc) throws Ex
form.getInputByName(LOG_FILE_LOG_SEPARATOR_INPUT_NAME).setValueAttribute(DEFAULT_LOG_SEPARATOR);
form.getInputByName(PATTERN_INPUT_NAME).setValueAttribute(pattern);
form.getInputByName(LOG_BUILD_CAUSE_INPUT_NAME).setChecked(logBuildCause);
form.getInputByName(LOG_CREDENTIALS_USAGE_INPUT_NAME).setChecked(logCredentialsUsage);
j.submit(form);
}
}