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

[JENKINS-68404] Add script listener to track usage #416

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import hudson.model.AbstractDescribableImpl;
import hudson.model.Descriptor;
import hudson.model.Item;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.util.FormValidation;

Expand Down Expand Up @@ -374,7 +375,8 @@ public Object evaluate(ClassLoader loader, Binding binding, @CheckForNull TaskLi
memoryProtectedLoader = new CleanGroovyClassLoader(loader);
loaderF.set(sh, memoryProtectedLoader);
}
return sh.evaluate(ScriptApproval.get().using(script, GroovyLanguage.get()));
Run run = (Run) binding.getVariable("build");
return sh.evaluate(ScriptApproval.get().using(script, GroovyLanguage.get(), run));
}

} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

package org.jenkinsci.plugins.scriptsecurity.scripts;

import hudson.model.Run;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import net.sf.json.JSONArray;
Expand Down Expand Up @@ -467,9 +468,32 @@ public synchronized String using(@NonNull String script, @NonNull Language langu
// Probably need not add to pendingScripts, since generally that would have happened already in configuring.
throw new UnapprovedUsageException(hash);
}

return script;
}
/**
* Called when a script is about to be used (evaluated).
* @param script a possibly unapproved script
* @param language the language in which it is written
* @param run the run executing the groovy script.
* @return {@code script}, for convenience
* @throws UnapprovedUsageException in case it has not yet been approved
*/
public synchronized String using(@NonNull String script, @NonNull Language language, @NonNull Run run) throws UnapprovedUsageException {
meiswjn marked this conversation as resolved.
Show resolved Hide resolved
if (script.length() == 0) {
// As a special case, always consider the empty script preapproved, as this is usually the default for new fields,
// and in many cases there is some sensible behavior for an emoty script which we want to permit.
ScriptListener.fireScriptFromPipelineEvent(script, run);
return script;
}
String hash = hash(script, language.getName());
if (!approvedScriptHashes.contains(hash)) {
// Probably need not add to pendingScripts, since generally that would have happened already in configuring.
throw new UnapprovedUsageException(hash);
}
ScriptListener.fireScriptFromPipelineEvent(script, run);
return script;
}

// Only for testing
synchronized boolean isScriptHashApproved(String hash) {
return approvedScriptHashes.contains(hash);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.jenkinsci.plugins.scriptsecurity.scripts;

import hudson.ExtensionPoint;
import hudson.model.Run;
import jenkins.util.Listeners;

/**
* A listener to track usage of Groovy scripts running outside of a sandbox.
*
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(ClassLoader, groovy.lang.Binding, hudson.model.TaskListener)
*/
public interface ScriptListener extends ExtensionPoint {

/**
* Called when a groovy script is executed in a pipeline outside of a sandbox.
*
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(ClassLoader, groovy.lang.Binding, hudson.model.TaskListener)
* @param script The Groovy script that is excecuted.
* @param run The run calling the Groovy script.
*/
void onScriptFromPipeline(String script, Run run);

/**
* Fires the {@link #onScriptFromPipeline(String, Run)} event to track the usage of groovy scripts running outside the sandbox.
*
* @see org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#evaluate(ClassLoader, groovy.lang.Binding, hudson.model.TaskListener)
* @param script The Groovy script that is excecuted.
* @param run The run calling the Groovy script.
*/
static void fireScriptFromPipelineEvent(String script, Run run) {
Listeners.notify(ScriptListener.class, true, listener -> listener.onScriptFromPipeline(script, run));
}
}