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

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

    Details

    • Similar Issues:

      Description

      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.

       

        Attachments

          Issue Links

            Activity

            Hide
            ikedam 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.

            Show
            ikedam 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.
            Hide
            iamahern 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.

             

            Show
            iamahern 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.  
            Show
            iamahern Michael Ahern added a comment - PR Link: https://github.com/jenkinsci/authorize-project-plugin/pull/32
            Hide
            ikedam 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.

            Show
            ikedam 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.
            Hide
            iamahern 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.

             

            Show
            iamahern 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.  
            Hide
            ikedam 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).

            Show
            ikedam 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).
            Hide
            iamahern Michael Ahern added a comment -

            Closing this down; thanks for all of the insights.

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

              People

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

                Dates

                • Created:
                  Updated:
                  Resolved: