getLoggers() { return loggers; }
public AuditTrailPlugin() {
@@ -165,6 +169,11 @@ public void setLogCredentialsUsage(boolean logCredentialsUsage) {
save();
}
+ @DataBoundSetter
+ public void setLogScriptUsage(boolean logScriptUsage) {
+ this.logScriptUsage = logScriptUsage;
+ save();
+ }
private void updateFilterPattern() {
try {
AuditTrailFilter.setPattern(pattern);
diff --git a/src/main/java/hudson/plugins/audit_trail/ScriptUsageListener.java b/src/main/java/hudson/plugins/audit_trail/ScriptUsageListener.java
new file mode 100644
index 0000000..a1eec96
--- /dev/null
+++ b/src/main/java/hudson/plugins/audit_trail/ScriptUsageListener.java
@@ -0,0 +1,62 @@
+package hudson.plugins.audit_trail;
+
+import hudson.Extension;
+import hudson.model.User;
+import jenkins.model.Jenkins;
+import jenkins.model.ScriptListener;
+import org.kohsuke.stapler.StaplerRequest;
+
+import javax.inject.Inject;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Log when a (privileged) Groovy script is executed.
+ *
+ * @see Jenkins#_doScript(StaplerRequest, org.kohsuke.stapler.StaplerResponse, javax.servlet.RequestDispatcher, hudson.remoting.VirtualChannel, hudson.security.ACL)
+ * @see hudson.cli.GroovyCommand#run()
+ * @see hudson.cli.GroovyshCommand#run()
+ * @see org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval#using(String, org.jenkinsci.plugins.scriptsecurity.scripts.Language, String)
+ *
+ * @author Jan Meiswinkel
+ */
+@Extension
+public class ScriptUsageListener implements ScriptListener {
+ private static final Logger LOGGER = Logger.getLogger(ScriptUsageListener.class.getName());
+
+ @Inject
+ AuditTrailPlugin configuration;
+
+ /**
+ * Called when a (privileged) groovy script is executed.
+ *
+ * @see Jenkins#_doScript(StaplerRequest, org.kohsuke.stapler.StaplerResponse, javax.servlet.RequestDispatcher, hudson.remoting.VirtualChannel, hudson.security.ACL)
+ * @param script The script to be executed.
+ * @param origin Descriptive identifier of the origin where the script is executed (Controller, Agent ID, Run ID).
+ * @param u If available, the user that executed the script. Can be null.
+ */
+
+ @Override
+ public void onScript(String script, String origin, User u) {
+ if (!configuration.getLogScriptUsage()) {
+ return;
+ }
+ StringBuilder builder = new StringBuilder();
+
+ if (u != null) {
+ builder.append(String.format("A groovy script was executed by user '%s'. Origin: %s. ", u.getId(), origin));
+ } else {
+ builder.append(String.format("A groovy script was executed. Origin: %s.", origin));
+ }
+
+ builder.append("\nThe executed script: \n");
+ builder.append(script);
+ String log = builder.toString();
+ if (LOGGER.isLoggable(Level.FINE)) {
+ LOGGER.log(Level.FINE, "Detected groovy script usage, details: {0}", new Object[]{log});
+ }
+ for (AuditLogger logger : configuration.getLoggers()) {
+ logger.log(log);
+ }
+ }
+}
diff --git a/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/config.jelly b/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/config.jelly
index f61bf35..e08da29 100644
--- a/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/config.jelly
+++ b/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/config.jelly
@@ -18,6 +18,9 @@
+
+
+
diff --git a/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/config_de.properties b/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/config_de.properties
index 6392094..995c034 100644
--- a/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/config_de.properties
+++ b/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/config_de.properties
@@ -22,3 +22,4 @@
Log\ how\ each\ build\ is\ triggered=Aufzeichnen, wodurch die jeweiligen Builds angesto\u00DFen wurden.
Log\ credentials\ usage=Aufzeichnen, welche Objekte auf Credentials zugreifen.
+Log\ Groovy\ scripts=Groovy Skripte aufzeichnen.
\ No newline at end of file
diff --git a/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/help-logScriptUsage.html b/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/help-logScriptUsage.html
new file mode 100644
index 0000000..4e94ef7
--- /dev/null
+++ b/src/main/resources/hudson/plugins/audit_trail/AuditTrailPlugin/help-logScriptUsage.html
@@ -0,0 +1,4 @@
+
+ When this option is enabled, Groovy scripts that run with privileged rights are logged. This includes the script console, CLI and runs using scripts outside the sandbox.
+
+
diff --git a/src/test/java/hudson/plugins/audit_trail/ConfigurationAsCodeTest.java b/src/test/java/hudson/plugins/audit_trail/ConfigurationAsCodeTest.java
index 7cf1c3d..955bdc2 100644
--- a/src/test/java/hudson/plugins/audit_trail/ConfigurationAsCodeTest.java
+++ b/src/test/java/hudson/plugins/audit_trail/ConfigurationAsCodeTest.java
@@ -32,6 +32,7 @@ public void should_support_configuration_as_code() {
assertEquals(".*/(?:configSubmit|doUninstall|doDelete|postBuildResult|enable|disable|cancelQueue|stop|toggleLogKeep|doWipeOutWorkspace|createItem|createView|toggleOffline|cancelQuietDown|quietDown|restart|exit|safeExit)", plugin.getPattern());
assertTrue(plugin.getLogBuildCause());
assertTrue(plugin.shouldLogCredentialsUsage());
+ assertTrue(plugin.shouldLogBuildCause());
assertEquals(3, plugin.getLoggers().size());
//first logger
diff --git a/src/test/java/hudson/plugins/audit_trail/ScriptUsageListenerTest.java b/src/test/java/hudson/plugins/audit_trail/ScriptUsageListenerTest.java
new file mode 100644
index 0000000..e6dae98
--- /dev/null
+++ b/src/test/java/hudson/plugins/audit_trail/ScriptUsageListenerTest.java
@@ -0,0 +1,106 @@
+package hudson.plugins.audit_trail;
+
+import hudson.Util;
+import hudson.cli.GroovyCommand;
+import hudson.cli.GroovyshCommand;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.jvnet.hudson.test.JenkinsRule;
+import org.kohsuke.stapler.StaplerRequest;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import org.kohsuke.stapler.StaplerResponse;
+
+import javax.servlet.RequestDispatcher;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ScriptUsageListenerTest {
+ @Rule
+ public JenkinsRule r = new JenkinsRule();
+ @Rule
+ public TemporaryFolder tmpDir = new TemporaryFolder();
+ private final String script = "println('light of the world')";
+
+ @Test
+ public void consoleUsageIsLogged() throws Exception {
+ String logFileName = "consoleUsageIsProperlyLogged.log";
+ File logFile = new File(tmpDir.getRoot(), logFileName);
+ JenkinsRule.WebClient wc = r.createWebClient();
+ new SimpleAuditTrailPluginConfiguratorHelper(logFile).sendConfiguration(r, wc);
+
+ RequestDispatcher view = mock(RequestDispatcher.class);
+ StaplerRequest req = mock(StaplerRequest.class);
+ StaplerResponse rsp = mock(StaplerResponse.class);
+
+ when(req.getMethod()).thenReturn("POST");
+ when(req.getParameter("script")).thenReturn(script);
+ when(req.getView(r.jenkins, "_scriptText.jelly")).thenReturn(view);
+ r.jenkins.doScriptText(req, rsp);
+
+ String log = Util.loadFile(new File(tmpDir.getRoot(), logFileName + ".0"), StandardCharsets.UTF_8);
+ assertTrue("logged actions: " + log, Pattern.compile(
+ ".*A groovy script was executed by user 'SYSTEM'\\. Origin: Script Console Controller\\..*The " +
+ "executed script:.*" + Pattern.quote(script) + ".*", Pattern.DOTALL).matcher(log).matches());
+ }
+
+ @Test
+ public void groovyCliUsageIsLogged() throws Exception {
+ String logFileName = "cliUsageIsProperlyLogged.log";
+ File logFile = new File(tmpDir.getRoot(), logFileName);
+ JenkinsRule.WebClient wc = r.createWebClient();
+ new SimpleAuditTrailPluginConfiguratorHelper(logFile).sendConfiguration(r, wc);
+
+ GroovyCommand cmd = new GroovyCommand();
+ cmd.script = "=";
+ InputStream scriptStream = new ByteArrayInputStream(script.getBytes(StandardCharsets.UTF_8));
+ cmd.main(new ArrayList<>(), Locale.ENGLISH, scriptStream, System.out, System.err);
+
+ String log = Util.loadFile(new File(tmpDir.getRoot(), logFileName + ".0"), StandardCharsets.UTF_8);
+ assertTrue("logged actions: " + log, Pattern.compile(
+ ".*A groovy script was executed\\. Origin: CLI/GroovyCommand.*The executed script:.*"
+ + Pattern.quote(script) + ".*", Pattern.DOTALL).matcher(log).matches());
+ }
+
+ @Test
+ public void groovyShCliUsageIsLogged() throws Exception {
+ String logFileName = "cliUsageIsProperlyLogged2.log";
+ File logFile = new File(tmpDir.getRoot(), logFileName);
+ JenkinsRule.WebClient wc = r.createWebClient();
+ new SimpleAuditTrailPluginConfiguratorHelper(logFile).sendConfiguration(r, wc);
+
+ GroovyshCommand cmd = new GroovyshCommand();
+ InputStream scriptStream = new ByteArrayInputStream(script.getBytes(StandardCharsets.UTF_8));
+ cmd.main(new ArrayList<>(), Locale.ENGLISH, scriptStream, System.out, System.err);
+
+ String log = Util.loadFile(new File(tmpDir.getRoot(), logFileName + ".0"), StandardCharsets.UTF_8);
+ assertTrue("logged actions: " + log, Pattern.compile(
+ ".*A groovy script was executed\\. Origin: CLI/GroovySh.*The executed script:.*"
+ + Pattern.quote(script) + ".*", 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).withLogScriptUsage(false).sendConfiguration(r, wc);
+
+ GroovyCommand cmd = new GroovyCommand();
+ cmd.script = "=";
+ InputStream scriptStream = new ByteArrayInputStream(script.getBytes(StandardCharsets.UTF_8));
+ cmd.main(new ArrayList<>(), Locale.ENGLISH, scriptStream, System.out, System.err);
+
+ String log = Util.loadFile(new File(tmpDir.getRoot(), logFileName + ".0"), StandardCharsets.UTF_8);
+ assertTrue(log.isEmpty());
+ }
+}
diff --git a/src/test/java/hudson/plugins/audit_trail/SimpleAuditTrailPluginConfiguratorHelper.java b/src/test/java/hudson/plugins/audit_trail/SimpleAuditTrailPluginConfiguratorHelper.java
index 6320e33..88baf77 100644
--- a/src/test/java/hudson/plugins/audit_trail/SimpleAuditTrailPluginConfiguratorHelper.java
+++ b/src/test/java/hudson/plugins/audit_trail/SimpleAuditTrailPluginConfiguratorHelper.java
@@ -20,6 +20,7 @@ public class SimpleAuditTrailPluginConfiguratorHelper {
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 LOG_SCRIPT_USAGE_INPUT_NAME="logScriptUsage";
private static final String ADD_LOGGER_BUTTON_TEXT = "Add Logger";
private static final String LOG_FILE_COMBO_TEXT = new LogFileAuditLogger.DescriptorImpl().getDisplayName();
@@ -27,6 +28,7 @@ public class SimpleAuditTrailPluginConfiguratorHelper {
private boolean logBuildCause = true;
private boolean logCredentialsUsage = true;
+ private boolean logScriptUsage = true;
private String pattern = ".*/(?:enable|cancelItem|quietDown|createItem)/?.*";
public SimpleAuditTrailPluginConfiguratorHelper(File logFile) {
@@ -37,11 +39,17 @@ public SimpleAuditTrailPluginConfiguratorHelper withLogBuildCause(boolean logBui
this.logBuildCause = logBuildCause;
return this;
}
+
public SimpleAuditTrailPluginConfiguratorHelper withLogCredentialsUsage(boolean logCredentialsUsage) {
this.logCredentialsUsage = logCredentialsUsage;
return this;
}
+ public SimpleAuditTrailPluginConfiguratorHelper withLogScriptUsage(boolean logScriptUsage) {
+ this.logScriptUsage = logScriptUsage;
+ return this;
+ }
+
public SimpleAuditTrailPluginConfiguratorHelper withPattern(String pattern) {
this.pattern = pattern;
return this;
@@ -60,6 +68,7 @@ public void sendConfiguration(JenkinsRule j, JenkinsRule.WebClient wc) throws Ex
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);
+ form.getInputByName(LOG_SCRIPT_USAGE_INPUT_NAME).setChecked(logScriptUsage);
j.submit(form);
}
}
diff --git a/src/test/resources/hudson/plugins/audit_trail/expected.yml b/src/test/resources/hudson/plugins/audit_trail/expected.yml
index e777a75..ca692e4 100644
--- a/src/test/resources/hudson/plugins/audit_trail/expected.yml
+++ b/src/test/resources/hudson/plugins/audit_trail/expected.yml
@@ -1,5 +1,6 @@
logBuildCause: true
logCredentialsUsage: true
+logScriptUsage: true
loggers:
- console:
dateFormat: "yyyy-MM-dd HH:mm:ss:SSS"