Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bypass Script Security Plugin within Code

I have written a few groovy utility methods for use in Jenkins Pipelines. A simple example is:

// src/org/package/utils.groovy
def remove_file(String file) {
  new File(file).delete()
}

However, as expected this throws the following exception in Jenkins Pipeline:

org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new java.io.File java.lang.String

According to the Script Security Plugin documentation, I can bypass this with the @Whitelisted method annotation:

@Whitelisted
def remove_file(String file) {
  new File(file).delete()
}

According to this question, I can bypass this with the @NonCPS annotation (and the code in this method is of course not necessary to be serializable anyway):

@NonCPS
def remove_file(String file) {
  new File(file).delete()
}

The shared library is being used in a global var like the following:

// vars/var.groovy
import org.package.utils

def method(body) {
  ...
  new utils().remove_file('file')
  ...
}

The library/var is being imported and used in a Jenkinsfile like the following:

library identifier: 'lib@master', retriever: modernSCM(
  [$class: 'GitSCMSource',
   remote: 'https://github.com/org/repo.git'])

pipeline {
  ...
  script {
    var.method {
      // body
    }
  }
  ...
}

Of the listed code-based approaches to importing a Pipeline shared library, the approach I utilized above seemed to be the only one that actually works with the standard Jenkins Pipeline plugin set, so that was the approach I took.

Both of these have failed at bypassing the security exception that Jenkins Pipeline is throwing. What is the best solution to bypass the script security plugin using code and avoiding manual/human-error solutions such as modifying the whitelist in the GUI?

like image 962
Matt Schuchard Avatar asked Jan 09 '18 14:01

Matt Schuchard


2 Answers

I think I understand what is going on based on your last comment and your updates to your original question. I'll try and clarify the differences between global shared libraries and "regular" shared libraries.


Registering Libraries

There are (at the time of writing) a few different ways to register a Shared Library with a Jenkins instance.

  1. Globally to the Jenkins installation - available to all pipeline executions.
  2. For a Folder - all items in the folder have the pipeline library available to them
  3. Automatic libraries - the example provided in the documentation is the GitHub Branch Source Plugin

Library registration requires some setup:

  • Name - Library name to be used with library step and @Library annotation
  • Default version - a commit, branch, tag, or some other identifier
  • Load implicitly - if the library should be loaded implicitly (i.e. consumers do not need to use @Library or library)
  • Retrieval method - how the SCM is retrieved (like with Git or Mercurial)
    • Allow default version to be overridden - if consumers can use a different version than Default version

The Global Shared Libraries referenced above bypasses the security sandbox, which means they can do anything. Here is some relevant documentation:

Since these libraries will be globally usable, any Pipeline in the system can utilize functionality implemented in these libraries.

These libraries are considered "trusted:" they can run any methods in Java, Groovy, Jenkins internal APIs, Jenkins plugins, or third-party libraries. This allows you to define libraries which encapsulate individually unsafe APIs in a higher-level wrapper safe for use from any Pipeline. Beware that anyone able to push commits to this SCM repository could obtain unlimited access to Jenkins. You need the Overall/RunScripts permission to configure these libraries (normally this will be granted to Jenkins administrators).

These are the only pipeline libraries that bypass the security sandbox checks. The other kinds (like Folder libraries) are security checked, which means you cannot do things like new File(file).delete().


Consumption

In a pipeline, users can access all of the shared libraries available to them. Implicitly loaded libraries from the installation or the folder (above noted as the Load implicitly option) are automatically available on the script classpath. Other libraries must be brought in using the @Library annotation or the library step.

For example, say you had a library with these options:

  • Name - my-shared-lib
  • Default version - master
  • Load implicitly - false
  • Retrieval method - Modern Git SCM setup to some repository
    • Allow default version to be overridden - true

A few ways you could pull in this specific library from a pipeline would be:

  • @Library('my-shared-lib') - use with default version
  • @Library('my-shared-lib@develop') - use with overridden version of develop
  • `def myLib = library('my-shared-lib') - use with default version
  • def myLib = library('my-shared-lib@develop') - use with overridden version of develop

These will all use the previously configured library.

In your example, you are using the library step, but dynamically loading from a remote repository instead of using a registered version. That is also why you are specifying the retriever parameter. Remember that any library that is not a Global Shared Library cannot bypass the security sandbox.

If you want to have your library bypass the sandbox you need to register it with the Jenkins installation as a Global Shared Library.

A side note about new File(file).delete() - be careful writing "normal" Groovy inside of Jenkins pipelines (see this answer for some details why).

like image 138
mkobit Avatar answered Oct 21 '22 20:10

mkobit


If you are using Jenkins Job-DSL to create your jobs in a programmatic approach then you might use the following code to approve your pipeline script automatically.

 def pipelineScript = 'your pipeline script'

 def scriptApproval = Jenkins.instance.getExtensionList('org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval')[0]

 scriptApproval.approveScript(scriptApproval.hash(pipelineScript, 'groovy'))

 script pipelineScript

This code might need a few tweaks depending on the version of the plugin you are using, else this should make things easier.

Peace!

like image 2
Surendra Deshpande Avatar answered Oct 21 '22 20:10

Surendra Deshpande