Jenkins Security Architecture

This section is a work in progress. Want to help? Check out the jenkinsci/docs gitter channel. For other ways to contribute to the Jenkins project, see this page about participating and contributing.

Jenkins has a security mechanism that enables the Jenkins administrator to control who gets access to what part of Jenkins. The key components of this mechanism are the followings:

  • Permission, which represents an activity that requires a security privilege. This is usually a verb, like "configure", "administer", "tag", etc.

  • Authentication, which represents the roles and groups for the current user. When a thread runs in Jenkins, it implicitly carries an Authentication object, which represents the user being served by the thread. (If a thread is a part of Jenkins and not serving any user request, such as Executor, then it carries an almighty "system" Authentication object.)

  • ACL, which decides whether the Authentication object carried by the current thread has the given permission or not.

  • AccessControlled, which is implemented by an object who owns ACL.

So the overall picture is this:

  • Various objects in Jenkins (such as Job, Jenkins, User, View, etc.) are AccessControlled objects, and therefore they own ACLs.

  • Plugin code should be written to check the ACL before performing a security-sensitive operation.

For example, the following code is taken from the Jenkins class, which lets you shut down the JVM by requesting /exit. Clearly, random users should not be able to invoke this in a security-sensitive environment, so it makes sure that the caller has the "ADMINISTER" permission of the system before proceeding to do the work:

    public void doExit( StaplerRequest req, StaplerResponse rsp ) throws IOException {
        checkPermission(ADMINISTER); (1)
        LOGGER.severe(String.format("Shutting down VM as requested by %s from %s",
                getAuthentication().getName(), req!=null?req.getRemoteAddr():"???"));
        if (rsp!=null) {
            rsp.setStatus(HttpServletResponse.SC_OK);
            rsp.setContentType("text/plain");
            try (PrintWriter w = rsp.getWriter()) {
                w.println("Shutting down");
            }
        }

        System.exit(0);
    }
1 This throws an exception if the user accessing this URL doesn’t have Administer permission.

If the administrator did not configure a security mechanism, the checkPermission method simply becomes no-op. The administrator could configure matrix-based ACL, in which case every AccessControlled object shares the single ACL (whose contents is controlled by the configuration done by the administrator.) In more elaborate cases, each AccessControlled object could have different ACLs. In all cases, this is the code you need to write.

Controlling read access to AccessControlled objects

The recommended way to control read access to AccessControlled objects is to implement the StaplerProxy interface. See Restricting HTTP Access to AccessControlled Objects for more information.

Protecting Web Methods

Plugins must do the following to protect web methods:

  • Identify the operations in code that can be potentially security sensitive. This includes anything that can change state in the server, have other side effects (like elaborate form validation doCheck methods), or potentially disclose protected information (like auto-completion doAutoComplete methods). These methods should perform checkPermission.

  • Identify the nearest AccessControlled objects to check permissions with. If your 'this' object is already access-controlled, use that. Otherwise, try to look for the nearest logical ancestor. If all else fails, use the Jenkins singleton.

  • Identify the Permission object to use. If you extend from an ExtensionPoint, it might already define some permission objects as public static final fields. If you are defining a sub-system of a substantial size, you may want to create new Permission objects (see the end of the View class for this example). If you are unsure, use Jenkins.ADMINISTER as a starting point.

Based on this information, you can now insert:

AccessControlled ac = ... do the step 2 above ...
Permission p = ... do the step 3 above ...
ac.checkPermission(p)

Checking permissions in Jelly files

If your entire HTML page rendered by Jelly needs to be protected, use the attributes of the <l:layout> tag, like this:

<l:layout permission="${app.ADMINISTER}">

The permission is always checked against the "it" object, so that needs to be an AccessControlled object.

This only prevents access to the view (e.g. configuration form), and does not prevent submissions to the form submission endpoints. This will still need to be done as described in the previous section.

Modify page rendering based on permissions

Disabling a part of page rendering if the user doesn’t have a permission

Page rendering can be modified based on the user’s permissions. For example, if the user cannot delete a project, it does not make sense to show a link to do that. To do this, write Jelly like this:

<j:if test="${h.hasPermission(app.ADMINISTER)}">
  ...
</j:if>
This is not to be confused with the checkPermission invocation in your operation. Users can still hit the URL directly, so you still need to protect the operation itself, in addition to disabling the UI rendering

Authentication ways

Jenkins uses Spring Security as its security engine. With no special plugins that manage authentication installed, a Jenkins instance is packaged with the following authentication ways:

  • Web UI

    • When you use the form on the login page, using the fields j_username and j_password

  • REST API

    • Using Basic with login / password

    • Using Basic with login / apiToken

  • Jenkins CLI jar

    • (deprecated) using remoting transport with login / logout command

    • (deprecated) username / password as parameters on each command

    • -auth argument with username:password or username:apiToken that will do something like HTTP calls

    • using SSH transport mode with your local keys

  • CLI over SSH

    • directly using the native SSH command, without Jenkins CLI

Authentication flow

The processing flow differs drastically depending on the authentication method you use. By flow we mean the involved classes that check your credentials for validity.

Web UI and REST API

Web UI and REST API flow

In the diagram above, each arrow indicates a way to authenticate.

Both the Web UI and the REST API using login / password will flow in the same AbstractPasswordBasedSecurityRealm that delegates the real check to the configured SecurityRealm. The credentials are retrieved for the first method by retrieving information in the POST and for the second by using the Basic Authentication (in header). A point that is important to mention here, the Web UI is the only way (not deprecated) that use the Session to save the credentials.

For the login / apiToken calls, the BasicHeaderApiTokenAuthenticator manages to check if the apiToken corresponds to the user with the given login.

CLI (SSH and native)

The security architecture of Jenkins CLI is a bit more complicated because of the multiplicity of ways to connect.

CLI flow

Remoting is deprecated but explained because it may still be used. The principle is to create a sort of session between the login command and the logout one. The authentication is checked using the same classes that are used for the Web UI or the REST API with password. After the authentication is verified, the credentials are stored in a local cache that enables future calls to be authenticated automatically.

The second way puts the username and the password as additional parameters of the command (--username and --password).

For the third and fourth ways, we pass the parameters to connect like in an HTTP call in the header. The authentication is checked exactly the same way as for the REST API, depending on the provided password or token.

Last possibility for the Jenkins CLI is using the SSH transport mode offered by the SSHD module (also available for plugins). It uses the normal SSH configuration using your local keys to authenticate. It shares the same verifier with the Native CLI way.

Other ways

A plugin may propose a new SecurityRealm or implement some ExtensionPoints (like QueueItemAuthenticator) in order to provide new ways for a user to authenticate.

Support for Locked/Disabled/Expired Accounts

Some authentication providers support additional account validity attributes such as whether or not the account is locked, disabled, or expired. Normally, these sorts of account validity checks are performed by the underlying authentication provider itself when authenticating a user with their password. However, until a user attempts to log in with their password, Jenkins is never notified of account status changes! This means that without explicit support from its corresponding Jenkins authentication provider plugin, Jenkins will otherwise continue to allow the account to authenticate through the above-mentioned authentication methods (SSH keys, API tokens) until the account is also deleted or disabled in Jenkins by an administrator. Authentication providers that can implement account validity checks through means other than attempting to log the user in should throw a subtype of org.springframework.security.authentication.AccountStatusException in SecurityRealm.loadUserByUsername2.

Sections

How-To Guides

References