Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-69606

Ability to whitelist steps in Jenkinsfiles while allowing all steps in global pipeline libraries

      We are attempting to block access to pipeline steps that we consider a security risk (e.g. the Kubernetes plugin steps) from user provided Jenkinsfiles, but allowing access to all the steps from global pipeline libraries. Ideally we would only allow access to steps within our global pipeline library.

      The only way we have come up with is to create StepListener extension. The extension uses reflection on the StepContext to check whether the step was executed via the Jenkinsfile or a global pipeline library.

      As I understand it, this is not considered good practice and may break in future.

      Is there some existing functionality to achieve what we need here or the possibility of extending the StepListener API to provide it?

       

          [JENKINS-69606] Ability to whitelist steps in Jenkinsfiles while allowing all steps in global pipeline libraries

          Jesse Glick added a comment -

          I think we could add a new method to StepListener for global variables.

          Jesse Glick added a comment - I think we could add a new method to StepListener for global variables.

          Sam Gleske added a comment -

          StepListener itself is currently lacking a few things. It makes a difference between a global shared libary calling a step (or var) and an end-user from sandboxed Jenkinsfile.

          Currently, if you block a step with StepListener it will block it for both users and global shared pipelines. This isn't really the desired result, though it does allow me to block some steps.

          Sam Gleske added a comment - StepListener itself is currently lacking a few things. It makes a difference between a global shared libary calling a step (or var) and an end-user from sandboxed Jenkinsfile. Currently, if you block a step with StepListener it will block it for both users and global shared pipelines. This isn't really the desired result, though it does allow me to block some steps.

          Sam Gleske added a comment -

          Any ideas for how to detect within StepListener if someone is calling that step from a non-sandboxed global lib as opposed to a sandboxed Jenkinsfile?

          Ideally, the admin libs wouldn't be blocked from using steps but end-user Jenkinsfiles would. The current implementation of restrict-steps-plugin even blocks global shared libs from calling those steps (it even blocks declarative).

          But ideally, optionally including admin vars and blocking only within the scope of the Jenkinsfile would be more desirable.

          Sam Gleske added a comment - Any ideas for how to detect within StepListener if someone is calling that step from a non-sandboxed global lib as opposed to a sandboxed Jenkinsfile? Ideally, the admin libs wouldn't be blocked from using steps but end-user Jenkinsfiles would. The current implementation of restrict-steps-plugin even blocks global shared libs from calling those steps (it even blocks declarative). But ideally, optionally including admin vars and blocking only within the scope of the Jenkinsfile would be more desirable.

          Sam Gleske added a comment -

          Can https://github.com/jenkinsci/script-security-plugin/blob/master/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptor.java be extended to enable plugins to provide block lists? For example, a plugin blocking certain vars or steps.

          Sam Gleske added a comment - Can https://github.com/jenkinsci/script-security-plugin/blob/master/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptor.java be extended to enable plugins to provide block lists? For example, a plugin blocking certain vars or steps.

          Jesse Glick added a comment -

          I doubt Pipeline can easily be made to enforce the sort of EDSL you describe. (Nor can I recommend JTE, which has serious design issues and makes use of Pipeline internals that are not guaranteed to be stable at all.) I would rather advise not offering Pipeline Groovy directly to users at all; instead load some list of requested “steps” for example from a YAML file, and essentially interpret it as a plain DSL.

          Jesse Glick added a comment - I doubt Pipeline can easily be made to enforce the sort of EDSL you describe. (Nor can I recommend JTE, which has serious design issues and makes use of Pipeline internals that are not guaranteed to be stable at all.) I would rather advise not offering Pipeline Groovy directly to users at all; instead load some list of requested “steps” for example from a YAML file, and essentially interpret it as a plain DSL.

          Sam Gleske added a comment -

          To clarify, I'm not trying to enforce the DSL and am fine with users making use of most of the Jenkins steps.

          There's a few vars which pose issues if users use them directly due to what they do in the runtime and are only meant for reuse in other vars. Currently Jenkins doesn't have a concept of private vars which is essentially what I want.

          The other idea was to rely on sandbox to prevent var usage but it doesn't have the infrastructure set up in it. I've considered a YAML only spec but there's a lot of benefits to letting users do some free form pipeline. The DSL I recommend is just that; a recommendation with exception for the vars I wish could be private scope.

          Sam Gleske added a comment - To clarify, I'm not trying to enforce the DSL and am fine with users making use of most of the Jenkins steps. There's a few vars which pose issues if users use them directly due to what they do in the runtime and are only meant for reuse in other vars. Currently Jenkins doesn't have a concept of private vars which is essentially what I want. The other idea was to rely on sandbox to prevent var usage but it doesn't have the infrastructure set up in it. I've considered a YAML only spec but there's a lot of benefits to letting users do some free form pipeline. The DSL I recommend is just that; a recommendation with exception for the vars I wish could be private scope.

          Jesse Glick added a comment -

          If you do not want a variable to be used, then why declare it as a variable at all? Make it a utility inside some class.

          // vars/supported1.groovy
          def call() {
            my.lib.Impl.supported1()
          }
          // vars/supported2.groovy
          def call() {
            my.lib.Impl.supported2()
          }
          // src/my/lib/Impl.groovy
          package my.lib
          public class Impl {
            public static void supported1() {
              internal('one')
            }
            public static void supported2() {
              internal('two')
            }
            private static void internal(String generalValuesForbidden) {
              // …
            }
          }
          

          Now if this is a trusted global library and security is your concern rather than merely enforcing best practices by coworkers operating in good faith, beware that Groovy basically ignores Java access control and might let a malicious pipeline directly call Impl.internal.

          Jesse Glick added a comment - If you do not want a variable to be used, then why declare it as a variable at all? Make it a utility inside some class. // vars/supported1.groovy def call() { my.lib.Impl.supported1() } // vars/supported2.groovy def call() { my.lib.Impl.supported2() } // src/my/lib/Impl.groovy package my.lib public class Impl { public static void supported1() { internal( 'one' ) } public static void supported2() { internal( 'two' ) } private static void internal( String generalValuesForbidden) { // … } } Now if this is a trusted global library and security is your concern rather than merely enforcing best practices by coworkers operating in good faith, beware that Groovy basically ignores Java access control and might let a malicious pipeline directly call Impl.internal .

          Sam Gleske added a comment - - edited

          I have issues with Shared pipeline src directory.

          1. It violates standard conventions of Java (typically set by Maven); for example it has no src/main or src/test just src. It should not have adopted existing conventions and violated them.
          2. It is CPS groovy, like vars, meaning all of the strangeness and restrictions of CPS groovy apply without the NonCPS annotation.
          3. Utilities in src are still accessible by users. Functionally, there's zero difference in a user's ability to use the code in src vs var and so at best it is conceptually organized but not functionally or restrictively separate.

          I need code to be reusable across multiple vars. However, if it is reusable across multiple vars then end users are also able to access it. This was discovered through practice and attempting to self-exploit via own Jenkinsfiles. The only truly effective way is to not make the code available in shared libraries at all and instead package a binary Jar as a one-off custom library-only plugin.

          By the way, I have a library-only custom Jenkins plugin already but there's still the gap and need of quickly extensible but private from end-user Jenkinsfile code necessary.

          In Groovy, even if you explicitly declare a private; it is public

          Sam Gleske added a comment - - edited I have issues with Shared pipeline src directory. It violates standard conventions of Java (typically set by Maven); for example it has no src/main or src/test just src. It should not have adopted existing conventions and violated them. It is CPS groovy, like vars, meaning all of the strangeness and restrictions of CPS groovy apply without the NonCPS annotation. Utilities in src are still accessible by users. Functionally, there's zero difference in a user's ability to use the code in src vs var and so at best it is conceptually organized but not functionally or restrictively separate. I need code to be reusable across multiple vars. However, if it is reusable across multiple vars then end users are also able to access it. This was discovered through practice and attempting to self-exploit via own Jenkinsfiles. The only truly effective way is to not make the code available in shared libraries at all and instead package a binary Jar as a one-off custom library-only plugin. By the way, I have a library-only custom Jenkins plugin already but there's still the gap and need of quickly extensible but private from end-user Jenkinsfile code necessary. In Groovy, even if you explicitly declare a private; it is public

          Sam Gleske added a comment - - edited

          Now if this is a trusted global library and security is your concern
          rather than merely enforcing best practices by coworkers operating in
          good faith, beware that Groovy basically ignores Java access control and
          might let a malicious pipeline directly call Impl.internal.

          I forgot to mention, this is a global shared library and security is a concern. It's not about coworkers operating in good faith. Once you have over 1000 Jenkins users (heck even over 100) you need to start operating Jenkins like it is a public service on the internet (even though it is private).

          In my case, there needs to be a concept of global shared library steps that are available only to global shared libraries and not available to user Jenkinsfiles. It's not really sustainable to offer plugins in all scenarios because even with plugins you might want to call them but not allow user Jenkinsfiles to call them.

          I think extending script security would be a better design for this; enabling block lists for pipeline steps. User Jenkinsfile code is subject to sandbox where global shared pipeline libraries are not.

          Sam Gleske added a comment - - edited Now if this is a trusted global library and security is your concern rather than merely enforcing best practices by coworkers operating in good faith, beware that Groovy basically ignores Java access control and might let a malicious pipeline directly call Impl.internal. I forgot to mention, this is a global shared library and security is a concern. It's not about coworkers operating in good faith. Once you have over 1000 Jenkins users (heck even over 100) you need to start operating Jenkins like it is a public service on the internet (even though it is private). In my case, there needs to be a concept of global shared library steps that are available only to global shared libraries and not available to user Jenkinsfiles. It's not really sustainable to offer plugins in all scenarios because even with plugins you might want to call them but not allow user Jenkinsfiles to call them. I think extending script security would be a better design for this; enabling block lists for pipeline steps. User Jenkinsfile code is subject to sandbox where global shared pipeline libraries are not.

          Jesse Glick added a comment -

          This just sounds like it is outside the design space for Pipeline. If you do not want users freely writing Pipeline script, then you need to deny them the ability to write Pipeline script at all, and only offer templates with predefined parameters, or some sort of (non-embedded) DSL.

          Jesse Glick added a comment - This just sounds like it is outside the design space for Pipeline. If you do not want users freely writing Pipeline script, then you need to deny them the ability to write Pipeline script at all, and only offer templates with predefined parameters, or some sort of (non-embedded) DSL.

            sag47 Sam Gleske
            matthewwalker Matthew Walker
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: