We are running Jenkins 2.440.3 LTS with the Folder-based Authorization Strategy 6.901.vb_4c7a_da_75da_3.
We have a folder with some internal maintenance pipelines that need to run on the built-in node. For example we have a pipeline to list the installed plugins in a format we can re-feed to our helm charts to help ensure all our jenkins instances have the same versions. An other pipeline runs on regular basis to update reference clones of our larger git repositories so that the workspace folders are smaller and use less inodes, which significantly improves the scalability of the controller (we have run out of inodes because of this before).
This is a potential security risk because we allow our dev teams to create pipelines freely under certain folders so they can experiment and get rapid feedback. It would be simple for someone to create a bad pipeline to run on built-in, and call rm -rf ~/* which would wipe out the controller data.
Is there a mechanism today to limit built-in to only some folders, similar to granting permissions to users based on folders?
No this is not possible.
The best you can do is to set the number of executors to 0 for the built-in (controller) node. This should not stop you to fetch the list of plugins, done the right way you have full access to the FS. With some advanced usage of Jenkins internal java api you can do everything in a pipeline with full access to the file system without consuming an executor anywhere.
Thanks for the tip @mawinter69. Do you have an example of a Jenkinsfile that would do such things? I’m no java developer, and working with the jenkins internal java API is quite challenging. In or case we do have a few groovy scripts and shell scripts with the existing business logic.
Are you talking about “Execute system Groovy script” from the groovy plugin? Groovy
It runs nicely, as long as there is an executor for it, namely built-in.
I tried to create a new scripted pipeline and paste the same script, approved it but I get a long stack trace instead.
[Pipeline] Start of Pipeline
expected to call java.util.LinkedHashMap.sort but wound up catching org.jenkinsci.plugins.workflow.cps.CpsClosure2.call; see: https://jenkins.io/redirect/pipeline-cps-method-mismatches/
[Pipeline] End of Pipeline
Also: org.jenkinsci.plugins.workflow.actions.ErrorAction$ErrorId: 0579bbaa-b3d7-4485-8017-55a8f4720751
groovy.lang.MissingPropertyException: No such property: key for class: java.lang.String
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:66)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getProperty(ScriptBytecodeAdapter.java:471)
at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.getProperty(DefaultInvoker.java:39)
at org.jenkinsci.plugins.workflow.cps.LoggingInvoker.getProperty(LoggingInvoker.java:121)
at com.cloudbees.groovy.cps.impl.PropertyAccessBlock.rawGet(PropertyAccessBlock.java:20)
at WorkflowScript.run(WorkflowScript:5)
at com.cloudbees.groovy.cps.CpsDefaultGroovyMethods.each(CpsDefaultGroovyMethods:2125)
at com.cloudbees.groovy.cps.CpsDefaultGroovyMethods.each(CpsDefaultGroovyMethods:1980)
at WorkflowScript.run(WorkflowScript:5)
at ___cps.transform___(Native Method)
at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.get(PropertyishBlock.java:73)
at com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
at com.cloudbees.groovy.cps.impl.PropertyishBlock$ContinuationImpl.fixName(PropertyishBlock.java:65)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
at com.cloudbees.groovy.cps.impl.ConstantBlock.eval(ConstantBlock.java:21)
at com.cloudbees.groovy.cps.Next.step(Next.java:83)
at com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:147)
at org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:180)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:423)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:331)
at org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:295)
at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.lambda$wrap$4(CpsVmExecutorService.java:136)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
at jenkins.util.ErrorLoggingExecutorService.lambda$wrap$0(ErrorLoggingExecutorService.java:51)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$1.call(CpsVmExecutorService.java:53)
at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$1.call(CpsVmExecutorService.java:50)
at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:136)
at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:275)
at org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.lambda$categoryThreadFactory$0(CpsVmExecutorService.java:50)
at java.base/java.lang.Thread.run(Unknown Source)
Finished: FAILURE
I tried wrapping this in a script { } block or even use the declarative form:
Just doing the echo works fine, but when replace it with the script {} block it fails with the same CSP error.
Interesting enough, this works: versions.each {println " - ${it.key}:${it.value}"}, but I get a stacktrace when I try to sort, which worked in the classic Job with the System script.
I do not get why adding the sorting is triggering a problem. versions.sort { it.key }.each.
The annoying part is that we need to manually approve these scripts but I suppose that an alternative would be for us to create a jenkins-system-lib repository, and to use declarative pipeline instead.
When you pass -Dorg.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval.ADMIN_AUTO_APPROVAL_ENABLED=true -Dorg.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval.ALLOW_ADMIN_APPROVAL_ENABLED=true
to the java command starting Jenkins you no longer need to approve your scripts when you modify them as administrator.
Afaik on latest Jenkins and latest script security you also need to approve system groovy scripts.
You can also put the things in a shared library.
But beware that a shared library can be used by everyone. If you have your Jenkins configuration stored in a repository you can also think of persisting the approved hash so that it is deployed automatically.
If you only execute system groovy inside a freestyle job it can also run on any other node. The script is always executed on the controller (aka built-in).
Thanks @mawinter69 this is very useful information.
In our case we run Jenkins 100% under Kubernetes so the only ‘persistent’ node is the built-in node. All the other nodes have to be declared and instantiated on demand based off agents definition declared in Jenkins files. Security wise only a few trusted folks are allowed to merge to our Jenkins library and PRs need to be approved so we are generally safe from abuse on that side.
On second thought there is a way to restrict who can execute something on a node. With the help of Authorize Project plugin you define as which user a build runs. You can then configure the built-in node by enabling Node-based security` and grant the build permission to only specific users.
The drawback is that you will need to adjust all jobs to run as a specific user and ensure that the jobs in your admin folder run with the system user or a user with corresponding permissions.
So it comes at a price and might be error prone.
It’s probably not so hard to write a plugin that allows restricting which nodes can be used in a folder. Implementing QueueTaskDispatcher (Jenkins core 2.457 API) should do the trick.