The recent Jenkins security advisory discloses a (quite obvious, in retrospect) security issue regarding the “Execute system Groovy script” build step (SECURITY-292). The recommended solution is to update the Groovy plugin to version 2.0+, which integrates the script security plugin. Sounds easy, but can be very hard (almost impossible) in practice…
Approval for Inline Scripts
After updating the Groovy plugin to version 2.0 and restarting Jenkins, every job that contains a system script must be approved by an administrator. Running a job with an “inline” system script without prior approval would fail:
ERROR: Build step failed with exception org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedUsageException: script not yet approved for use at org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval.using(ScriptApproval.java:459) at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript.evaluate(SecureGroovyScript.java:170) at hudson.plugins.groovy.SystemGroovy.run(SystemGroovy.java:95) at hudson.plugins.groovy.SystemGroovy.perform(SystemGroovy.java:59) [...] Build step 'Execute system Groovy script' marked build as failure
When navigating to “Manage Jenkins” -> “In-process Script Approval”, a list of all system Groovy scripts is displayed. As soon as an administrator approves these scripts, the jobs are running again. (Unfortunately, the “script approval” page only shows the content of the scripts without reference to the owning job, which makes it hard to maintain large Jenkins instances.)
An alternative way to approve a system script is to open the configuration of the corresponding job and save it without modifications (if the “save” operation is performed by an administrator, all system scripts will be approved automatically).
Approval for Script Files
I prefer using script files instead of inline scripts because they are easier to maintain (I usually commit them to the SCM). This is the point when things start getting ugly.
Of course, Jenkins cannot approve such a script file a priori, because the content may change over time (a user with low privileges may commit a malicious change to the script file, and Jenkins would happily execute it during the next build). Instead, Jenkins asks for permission of each and every statement within the script file.
ERROR: Build step failed with exception org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new java.io.File java.lang.String at org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist.rejectNew(StaticWhitelist.java:187) at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onNewInstance(SandboxInterceptor.java:130) at org.kohsuke.groovy.sandbox.impl.Checker$3.call(Checker.java:191) [...] Build step 'Execute system Groovy script' marked build as failure
In this situation, you will probably find yourself in an almost infinite loop:
- execute the job
- check the job console for errors
- approve the command at the “script approval” page
- start over
For example, look at this very simple script:
For this three-line script, no less than five approvals are required:
new java.io.File java.lang.String staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods setText java.io.File java.lang.String method groovy.lang.Script println java.lang.Object method java.io.File getAbsolutePath staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getText java.io.File
This is very cumbersome, and there are many cases where an approval isn’t possible at all (due to the Groovy syntax).
For example, when changing the second line of the above script to
file.text = "Hello world", the script would fail with an error:
ERROR: Build step failed with exception org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: unclassified field java.io.File text at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.unclassifiedField(SandboxInterceptor.java:367) at org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptor.onSetProperty(SandboxInterceptor.java:217) at org.kohsuke.groovy.sandbox.impl.Checker$5.call(Checker.java:297) [...] Build step 'Execute system Groovy script' marked build as failure
JENKINS-27306 lists such errors, no fix or workaround yet.
System Script Files: Say Goodbye to Security
But here comes the really nasty part: once that such methods have been approved, they are globally granted.
This means that (given the example above) from now on any job may read and write arbitrary files because of the
The script security plugin is aware of this, as the
new java.io.File java.lang.String method is accompanied with the message:
Signatures already approved which may have introduced a security vulnerability (recommend clearing)
At this point, you probably have to choose one of three options:
- Don’t use System Groovy scripts at all. This is of course the most secure option, but it probably requires a complete redesign of such jobs. Furthermore, it is no longer possible to perform Jenkins maintenance tasks with Jenkins jobs.
- Inline all System Groovy scripts. This may be fine with maintenance tasks (like automatically creating new jobs for new SCM branches), but is a bad idea for scripts that are part of the build process (which should be versionized together with the source code).
- Turn off script security. Only do this if you are running a private Jenkins instance with 100% trusted users.