Executing external Groovy script in Jenkins 2

Hi.

I have some Groovy scripts that worked just fine in Jenkins 1.x but are now broken in 2.x. This is actually blocking my update, so I would be grateful for any suggestions on how to proceed.

I execute a system Groovy script in a post build step. The script is executed from the main instance or a node like so:
/var/www/jenkins-groovy-helpers/JenkinsBuildHelper/UniwersalBuildChecker.groovy

I already used Permissive Script Security to try and disable additional security added in Jenkins 2.x (which honestly I don’t need as I’m the only person editing jobs).

It now breaks on this code I think:

// load class
def scriptDir = "/var/www/jenkins-groovy-helpers/JenkinsBuildHelper";
def parent = getClass().getClassLoader();
def loader = new GroovyClassLoader(parent);
def buildLogChecker = loader.parseClass(new File(scriptDir, "BuildLogChecker.groovy")).newInstance();

Can I load a groovy class in some other way? Or maybe I can enable some standalone/single-user/single-admin mode or something that disables all this security checks?

As you can imagine BuildLogChecker is used to check log, but also needs access to job description as it sets descriptions based on errors in the job log (build.setDescription(...)). It also sets results:

if (!buildLogChecker.firstLogLines.error.isEmpty()) {
    build.setResult(Result.FAILURE);
} else if (!buildLogChecker.firstLogLines.warn.isEmpty()) {
    build.setResult(Result.UNSTABLE);
}

Jenkins setup:

Jenkins: 2.401.3
OS: Linux - 5.15.0-78-generic
Java: 17.0.7 - Private Build (OpenJDK 64-Bit Server VM)

ansicolor:1.0.2
ant:497.v94e7d9fffa_b_9
antisamy-markup-formatter:159.v25b_c67cd35fb_
apache-httpcomponents-client-4-api:4.5.14-150.v7a_b_9d17134a_5
bootstrap5-api:5.3.0-1
bouncycastle-api:2.29
branch-api:2.1122.v09cb_8ea_8a_724
build-timeout:1.31
build-user-vars-plugin:1.9
build-with-parameters:76.v9382db_f78962
caffeine-api:3.1.6-115.vb_8b_b_328e59d8
checks-api:2.0.0
cloudbees-folder:6.815.v0dd5a_cb_40e0e
command-launcher:100.v2f6722292ee8
commons-lang3-api:3.12.0-36.vd97de6465d5b_
commons-text-api:1.10.0-36.vc008c8fcda_7b_
copyartifact:705.v5295cffec284
credentials:1271.v54b_1c2c6388a_
credentials-binding:626.v8d9034b_8ea_cc
description-setter:1.10
display-url-api:2.3.7
durable-task:510.v324450f8dca_4
echarts-api:5.4.0-5
email-ext:2.100
extended-choice-parameter:375.v72e4b_a_d33d33
font-awesome-api:6.4.0-2
git:5.2.0
git-client:4.4.0
github:1.37.1
github-api:1.314-431.v78d72a_3fe4c3
github-branch-source:1728.v859147241f49
gradle:2.8.1
groovy:453.vcdb_a_c5c99890
instance-identity:173.va_37c494ec4e5
ionicons-api:56.v1b_1c8c49374e
jackson2-api:2.15.2-350.v0c2f3f8fc595
jakarta-activation-api:2.0.1-3
jakarta-mail-api:2.0.1-3
javadoc:233.vdc1a_ec702cff
javax-activation-api:1.2.0-6
javax-mail-api:1.6.2-9
jaxb:2.3.8-1
jdk-tool:66.vd8fa_64ee91b_d
jjwt-api:0.11.5-77.v646c772fddb_0
jquery3-api:3.7.0-1
jsch:0.2.8-65.v052c39de79b_2
junit:1217.v4297208a_a_b_ce
ldap:682.v7b_544c9d1512
mailer:457.v3f72cb_e015e5
mapdb-api:1.0.9-28.vf251ce40855d
matrix-auth:3.1.10
matrix-project:789.v57a_725b_63c79
mina-sshd-api-common:2.10.0-69.v28e3e36d18eb_
mina-sshd-api-core:2.10.0-69.v28e3e36d18eb_
okhttp-api:4.11.0-145.vcb_8de402ef81
pam-auth:1.10
parameterized-trigger:2.46
permissive-script-security:0.7
pipeline-build-step:496.v2449a_9a_221f2
pipeline-github-lib:42.v0739460cda_c4
pipeline-graph-analysis:202.va_d268e64deb_3
pipeline-groovy-lib:656.va_a_ceeb_6ffb_f7
pipeline-input-step:468.va_5db_051498a_4
pipeline-milestone-step:111.v449306f708b_7
pipeline-model-api:2.2144.v077a_d1928a_40
pipeline-model-definition:2.2144.v077a_d1928a_40
pipeline-model-extensions:2.2144.v077a_d1928a_40
pipeline-rest-api:2.33
pipeline-stage-step:305.ve96d0205c1c6
pipeline-stage-tags-metadata:2.2144.v077a_d1928a_40
pipeline-stage-view:2.33
plain-credentials:143.v1b_df8b_d3b_e48
plugin-util-api:3.3.0
postbuild-task:1.9
postbuildscript:3.2.0-460.va_fda_0fa_26720
publish-over:0.22
publish-over-ssh:1.25
rebuild:320.v5a_0933a_e7d61
resource-disposer:0.22
rich-text-publisher-plugin:1.5
role-strategy:670.vc71a_a_c00039e
run-condition:1.6
scm-api:676.v886669a_199a_a_
script-security:1251.vfe552ed55f8d
snakeyaml-api:1.33-95.va_b_a_e3e47b_fa_4
ssh-credentials:305.v8f4381501156
ssh-slaves:2.877.v365f5eb_a_b_eec
sshd:3.303.vefc7119b_ec23
structs:324.va_f5d6774f3a_d
subversion:2.17.2
text-finder:1.24
text-finder-run-condition:6.vdf94e6f8d2c3
throttle-concurrents:2.14
timestamper:1.25
token-macro:359.vb_cde11682e0c
trilead-api:2.84.v72119de229b_7
variant:59.vf075fe829ccb
workflow-aggregator:596.v8c21c963d92d
workflow-api:1241.v4edc8b_44933b_
workflow-basic-steps:1017.vb_45b_302f0cea_
workflow-cps:3722.v85ce2a_c6240b_
workflow-durable-task-step:1247.v7f9dfea_b_4fd0
workflow-job:1316.vd2290d3341a_f
workflow-multibranch:756.v891d88f2cd46
workflow-scm-step:415.v434365564324
workflow-step-api:639.v6eca_cd8c04a_a_
workflow-support:848.v5a_383b_d14921
ws-cleanup:0.45

How exactly are you executing the post build groovy script. Are your jobs freestyle jobs or already pipeline?

Also what is the exact error message you get?

It’s a freestyle job. I actually have a lot of jobs using this script.

The message from Jenkins 2.x says:

[PostBuildScript] - [INFO] Executing post build scripts.
ERROR: Build step failed with exception
groovy.lang.MissingMethodException: No signature of method: java.lang.Class.newInstance() is applicable for argument types: (java.util.LinkedHashMap) values: [[ignoreMode:false, ignoreLevels:, maxLevel:error]]
Possible solutions: newInstance(), newInstance(), newInstance([Ljava.lang.Object;), isInstance(java.lang.Object)
at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onMethodCall(SandboxInterceptor.java:159)

I’m exectuing this as a set of scripts.

Have you unchecked the Run in Groovy Sandbox. This is a must to avoid that script security denies access to most Jenkins internals.

I’m running a script from a file and the file is common for a lot of jobs. I don’t have the Sandbox option for running a file (only for runing Groovy command)

You probably should consider switching to modern and supported ways to achieve your goals instead ( as you did a extremly huge jump in versions, actually over 8 years in time). I recommend switching to use the warningsng plugin to do any build log evaluation.

and check e.g. Dodging Security Issues of the Jenkins Groovy Plugin – A Java Developer's Weblog

1 Like

Hmm… It sounds like running Groovy scripts is no longer a viable option, and I need to create plugins instead. Or am I missing the point?

Security-wise, this seems a bit concerning. If users can edit jobs, they can potentially add shell scripts that could cause more damage than just breaking a Jenkins installation. While you can set up Jenkins nodes with a DMZ for strict security, it’s not required by Jenkins and admin can decide on what is safe. Blocking execution of Groovy scripts in a specific way without a clear opt-out option, while keeping shell script executions free, seems like a peculiar move (since both basically have the same security risks).

The article below only confirms my fears that once you allow certain functions, they are permitted in all jobs to be used in any way, rather than just allowing the execution of specific scripts that the admin confirmed were safe. And it seems, it is no longer possible to perform maintenance of Jenkins from within Jenkins using Groovy scripts, right? Or did any of this improve since 2017?

system groovy scripts still work in latest Jenkins (I’m making heavy use of this). It might be just your specific use case with trying to load a groovy script from within another groovy script that is broken. This might be related to having a newer groovy packaged now compared to your old Jenkins.

How do you mange running same script in multiple tasks then?

we have thousands of jobs, that is impossible to maintain by hand. So we have a generator for the jobs (proprietary solution). But you can also use JobDSL to manage the jobs.
But most jobs don’t need any groovy on our side.
If I would have to start over again I would try to make use of pipeline and have the common parts moved into a shared library.

The problem was in another place. I thought it wasn’t able to create an instance of a loaded class, but that was not the case.

This was the class I was trying to load:

class BuildLogChecker {
	public def defaultOptions = [
		ignoreMode : false,	// e.g.: echo "[UniwersalBuildChecker] ignore:start" (reset with ignore:stop)
		ignoreLevels : [],	// e.g.: echo "[UniwersalBuildChecker] ignore:info, ignore:warn" (reset with ignore:stop)
		maxLevel : "error",	// e.g.: echo "[UniwersalBuildChecker] maxlevel:warn" (reset with maxlevel:error)
	];
	// current options
	private def options = defaultOptions.getClass().newInstance(defaultOptions);
// ...
}

This was not working and the exception was confusing because it was at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.

Still not sure if this a bug in the scriptsecurity plugin, but the workaround is not to use arrays like defaultOptions to create a new instance.

In this case I was able to use a simple function instead:

class BuildLogChecker {
	private def defaultOptions () {
		return [
			ignoreMode : false,	// e.g.: echo "[UniwersalBuildChecker] ignore:start" (reset with ignore:stop)
			ignoreLevels : [],	// e.g.: echo "[UniwersalBuildChecker] ignore:info, ignore:warn" (reset with ignore:stop)
			maxLevel : "error",	// e.g.: echo "[UniwersalBuildChecker] maxlevel:warn" (reset with maxlevel:error)
		];
	}
	// current options
	private def options = [];
	
	/**
	 * Default constructor.
	 */
	BuildLogChecker() {
		options = defaultOptions();
	}
// ...
}

So this works now in a Groovy script:

// load class
def scriptDir = "/var/www/jenkins-groovy-helpers/JenkinsBuildHelper";
def parent = getClass().getClassLoader();
def loader = new GroovyClassLoader(parent);
def buildLogChecker = loader.parseClass(new File(scriptDir, "BuildLogChecker.groovy")).newInstance();

So far so good :slight_smile:

Permissive Security Plugin seems to mostly do it’s job, but I’ll have to do more testing.