Security for Plugin Developers

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.

Developers and maintainers of plugins play a crucial role in maintaining Jenkins security. This chapter discusses the security practices required.

Monitor security advisories

Monitor Jenkins Security Advisories closely. It may be necessary to modify your plugin to work and comply with security fixes.

Conform to access permissions

Understand and conform to the Security Architecture of Jenkins. Specifically:

  • Ensure that your code checks the ACL before performing a security-sensitive operation.

  • Use the StaplerProxy interface to control read access to AccessControlled objects.

Store user credentials as secrets

Protect user credentials by storing them on disk in a field of type Secret and never in a simple String field. Use a getter that returns the same type to access the Secrets field from other code. See Storing Secrets for background information, instructions, and code examples.

    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");
            }
        }
Implement appropriate script security

Be sure that your plugin implements appropriate security for custom Groovy scripts that users may need to create to customize Jenkins.

For more information, see the Developer’s Guide section of the Script Security documentation.

Provide Role Check for Callable

Communication between the Jenkins controller and agents is implemented with the Java Callable interface. Plugins should always implement a role check that runs after a Callable message to ensure that the object executes on the proper side of the controller-agent communication. Jenkins 2.319 and Jenkins LTS 2.303.3 and later releases enforce this behavior. A plugin that does not comply throws a SecurityException and logs an error message.

See Remoting Callables and Required Role Check for more information.

Protect form validation methods from unauthorized accesss

Form validation code can be used to compromise private information. To protect against this use a call to Jenkins#checkPermission to form validation code:

AccessControlled ac = ... do the step 2 above ...
Permission p = ... do the step 3 above ...
ac.checkPermission(p)
  • Only accept POST requests for form validation code with side effects for plugins that run on current Jenkins releases. This protects your plugin from cross-site request forgery (CSRF) attacks.

  • Forms in plugins that run on older Jenkins releases may be sent using GET and need to use the checkMethod attribute.

If your entire HTML page rendered by Jelly needs to be protected, you can 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.

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

Sometimes you’d like to change the page rendering, based on the user’s permissions. For example, if the user cannot delete a project, it doesn’t 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

In Jenkins the security engine that is used is Spring Security. Without any special plugins to manage authentication, an instance of Jenkins 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

Depending on the authentication method you use, the processing flow will differ drastically. By flow we mean the involved classes that will 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)

For the CLI part, the things become a bit more complicated, not by the complexity but more by the multiplicity of way to connect.

CLI flow

The first case (remoting) is deprecated but explained as potentially it’s still 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 we use for the Web UI or the REST API with password. Once the authentication is verified, the credentials are stored in a local cache that will enable future calls to be authenticated automatically.

The second way put 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 SSHD module (also available for plugins). It uses normal SSH configuration using your local keys to authenticate. It shares the same verifier with the Native CLI way.

Other ways

The plugin have the possibility to propose a new SecurityRealm or implements 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