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

Different behavior accessing user credentials using RunAs Specific vs. Triggered User

      Using a trivial Pipeline project, we are seeing differing credential behavior with `Run As Specific User` vs `Run As User who Triggered Build` using User Private credentials (https://<server>/user/<user>/credentials/store/user/domain/_/).

       

      Here is the code for the Pipeline. This is configured inline in the Pipeline Job.

      // code placeholder
      node {
          withCredentials([usernameColonPassword(credentialsId: 'user-private-credential', variable: 'SOME_VALUE')]) {
              sh('echo ${SOME_VALUE} | shasum')
          }
      }
      

       

      In the Pipeline above `user-private-credential` is defined for the user executing the script.

      When the Authorization mode is set to "Run As Specific Build", the script is able to successfully retrieve the user's credentials.

       

      If the Authorization mode is set to "Run As User who Triggered Build", then the pipeline is unable to retrieve the credentials.

       

      The desired behavior is to be able to retrieve the credentials in both cases.

       

          [JENKINS-55052] Different behavior accessing user credentials using RunAs Specific vs. Triggered User

          ikedam added a comment -

          That’s a known issue, and a limitation for the security reason of credentials-plugin.
          Please have a look on JENKINS-24750 for details.

          ikedam added a comment - That’s a known issue, and a limitation for the security reason of credentials-plugin. Please have a look on JENKINS-24750 for details.

          Michael Ahern added a comment - - edited

          Apologies, so my use case is slightly different than JENKINS 24750. In that case it regards propagation of Credential Parameter values along a call chain:
          withCredentials([usernameColonPassword(credentialsId: '${CREDENTIAL_PARAM}', variable: 'SOME_VALUE')])
           

          In my case, I am simply attempting to access the User's credential directly by id:
          withCredentials([usernameColonPassword(credentialsId: 'user-private-credential', variable: 'SOME_VALUE')])
           

          After a bit of debugging, I discovered that the issue is that "Queue.WaitingItem.getCause()" is always empty (i.e. the "UserIdCause" is not propagated correctly):

          if (item instanceof Queue.WaitingItem && item.getCauses().isEmpty()) { /* Always true for if (item instanceof Queue.WaitingItem) */ }

          ikedam - is this by design or a defect in the system?

           

          Either way, I extended the plugin with an optional (false by default) checkbox which provides a workaround. Will follow up on a Pull Request with a request for assistance to either track down the underlying defect (if this behavior is not by design) or for pointers as to how to implement a suitable unit test to get the patch accepted.

           

          Michael Ahern added a comment - - edited Apologies, so my use case is slightly different than JENKINS 24750. In that case it regards propagation of Credential Parameter values along a call chain: withCredentials( [usernameColonPassword(credentialsId: '${CREDENTIAL_PARAM}', variable: 'SOME_VALUE')] )   In my case, I am simply attempting to access the User's credential directly by id: withCredentials( [usernameColonPassword(credentialsId: 'user-private-credential', variable: 'SOME_VALUE')] )   After a bit of debugging, I discovered that the issue is that "Queue.WaitingItem.getCause()" is always empty (i.e. the "UserIdCause" is not propagated correctly): if (item instanceof Queue.WaitingItem && item.getCauses().isEmpty()) { /* Always true for if (item instanceof Queue.WaitingItem) */ } ikedam - is this by design or a defect in the system?   Either way, I extended the plugin with an optional (false by default) checkbox which provides a workaround. Will follow up on a Pull Request with a request for assistance to either track down the underlying defect (if this behavior is not by design) or for pointers as to how to implement a suitable unit test to get the patch accepted.  

          Michael Ahern added a comment -

          Michael Ahern added a comment - PR Link: https://github.com/jenkinsci/authorize-project-plugin/pull/32

          ikedam added a comment -

          Sorry for my absence.

          It sounds strange both that `WaitingItem` is passed and that `Cause` is not set for `WaitingItem`.
          Both sounds issues not of authorize-project, but rather of Jenkins core (or may be credentials-plugin).
          All that authorize-project can do is to decide the authentication from information of passed items.

          I still think that this is essentially same to JENKINS-24750, as the point of JENKINS-24750 is not the propagation, but that credentials-plugin decides authentication by attributes not of builds but of jobs.

          The workaround isn’t a good way as you described in that request, and I expect you have deeper look on how `WaitingItem` is passed, and why that workaround works.

          ikedam added a comment - Sorry for my absence. It sounds strange both that `WaitingItem` is passed and that `Cause` is not set for `WaitingItem`. Both sounds issues not of authorize-project, but rather of Jenkins core (or may be credentials-plugin). All that authorize-project can do is to decide the authentication from information of passed items. I still think that this is essentially same to JENKINS-24750 , as the point of JENKINS-24750 is not the propagation, but that credentials-plugin decides authentication by attributes not of builds but of jobs. The workaround isn’t a good way as you described in that request, and I expect you have deeper look on how `WaitingItem` is passed, and why that workaround works.

          Michael Ahern added a comment - - edited

          Cheers, yes I believed the issue was upstream.

          I did some digging and the problem appears to be in Jenkins core. I closed the pull request to do some more investigation after I downloaded and scanned the core Jenkins code as I think there is a less dangerous workaround. I will resubmit after I complete that investigation.

          If you look here:
          https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/jenkins/security/QueueItemAuthenticator.java#L62

          What seems to be happening is that some portion of the code is calling into the Credentials plugin, using the Queue.Task, but not the Queue.Item. When that occurs, it constructs a dummy Queue.WaitingItem with an empty cause list:

          public @CheckForNull Authentication authenticate(Queue.Task task) {
              if (Util.isOverridden(QueueItemAuthenticator.class, getClass(), "authenticate", Queue.Item.class)) {
                  // Need a fake (unscheduled) item. All the other calls assume a BuildableItem but probably it does not matter.
                  return authenticate(new Queue.WaitingItem(Calendar.getInstance(), task, Collections.<Action>emptyList()));
              } else {
                  throw new AbstractMethodError("you must override at least one of the QueueItemAuthenticator.authenticate methods");
              }
          }

          Still need to follow the code upstream to see if there is an appropriate fix there.

          As a safer workaround, I think the Authorize Plugin Queue item Authenticator can be special cased to conditionally override the above method (i.e. if using RunAs = Triggered user && admin opts into setting with security warning). Doing so would provide a suitable workaround for many users (such as my team) and could be done not alter the plugins existing behavior. This should also be more straightforward to police in unit testing.

           

          Michael Ahern added a comment - - edited Cheers, yes I believed the issue was upstream. I did some digging and the problem appears to be in Jenkins core. I closed the pull request to do some more investigation after I downloaded and scanned the core Jenkins code as I think there is a less dangerous workaround. I will resubmit after I complete that investigation. If you look here: https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/jenkins/security/QueueItemAuthenticator.java#L62 What seems to be happening is that some portion of the code is calling into the Credentials plugin, using the Queue.Task, but not the Queue.Item. When that occurs, it constructs a dummy Queue.WaitingItem with an empty cause list: public @CheckForNull Authentication authenticate(Queue.Task task) { if (Util.isOverridden(QueueItemAuthenticator.class, getClass(), "authenticate" , Queue.Item.class)) { // Need a fake (unscheduled) item. All the other calls assume a BuildableItem but probably it does not matter. return authenticate( new Queue.WaitingItem(Calendar.getInstance(), task, Collections.<Action>emptyList())); } else { throw new AbstractMethodError( "you must override at least one of the QueueItemAuthenticator.authenticate methods" ); } } Still need to follow the code upstream to see if there is an appropriate fix there. As a safer workaround, I think the Authorize Plugin Queue item Authenticator can be special cased to conditionally override the above method (i.e. if using RunAs = Triggered user && admin opts into setting with security warning). Doing so would provide a suitable workaround for many users (such as my team) and could be done not alter the plugins existing behavior. This should also be more straightforward to police in unit testing.  

          ikedam added a comment -

          Thanks for the pointer.
          I,ve got what happens.

          Credentials-plugin calls authenticate(Queue.Task) to decide authentication by jobs, not by builds.
          Jenkins creates a fake WaitingItem to handle it with authenticate(Queue.Item). So WaitingItem is actually not a “waiting” item.

          Your workaround returning a new UserIdCause() works as UserIdCause() is set up with the current user of the execution thread, that is already set up with TriggeringUsersAuthorizationStrategy.

          It’s not a good idea to override authenticate(Queue.Task) in this situation, as it’s just for breaking a security guard of another plugin (in this case, credentials-plugin).

          ikedam added a comment - Thanks for the pointer. I,ve got what happens. Credentials-plugin calls authenticate(Queue.Task) to decide authentication by jobs, not by builds. Jenkins creates a fake WaitingItem to handle it with authenticate(Queue.Item). So WaitingItem is actually not a “waiting” item. Your workaround returning a new UserIdCause() works as UserIdCause() is set up with the current user of the execution thread, that is already set up with TriggeringUsersAuthorizationStrategy. It’s not a good idea to override authenticate(Queue.Task) in this situation, as it’s just for breaking a security guard of another plugin (in this case, credentials-plugin).

          Michael Ahern added a comment -

          Closing this down; thanks for all of the insights.

          Michael Ahern added a comment - Closing this down; thanks for all of the insights.

            iamahern Michael Ahern
            iamahern Michael Ahern
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: