Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft for review of security for plugin developers #4701

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions content/doc/book/security/_chapter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ sections:

# Further references
- services

- controller-isolation/required-role-check

3 changes: 3 additions & 0 deletions content/doc/book/security/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ link:user-content[Rendering User Content]::
By default, Jenkins strictly limits the features useable in user content (files from workspaces, archived artifacts, etc.) it serves.
This chapter discusses how to customize this and make HTML reports and similar content both functional and safe to view. +
_This is set up securely by default._

Information about security practices that are required from plugin developers and maintainters is in
link:/doc/developer/security/[Security for Plugin Developers].
1 change: 1 addition & 0 deletions content/doc/developer/security/_chapter.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
sections:
- security-architecture
- secrets
guides:
- form-validation
Expand Down
195 changes: 40 additions & 155 deletions content/doc/developer/security/index.adoc
Original file line number Diff line number Diff line change
@@ -1,181 +1,66 @@
---
title: Security
title: Security for Plugin Developers
layout: developerchapter
wip: true
references:
- url: https://plugins.jenkins.io/script-security#ScriptSecurityPlugin-Developer%E2%80%99sguide
title: Script Security Developer's Guide
---

:imagesdir: /doc/developer/security/resources
Developers and maintainers of plugins play a crucial role in maintaining Jenkins security.
This chapter discusses the security practices required.

// this is a straight import of https://wiki.jenkins.io/display/JENKINS/Making+your+plugin+behave+in+secured+Jenkins
// TODO check contents and remove wiki page
Monitor security advisories::

Monitor Jenkins
link:https://www.jenkins.io/security/advisories/[Security Advisories]
closely.
It may be necessary to modify your plugin to work and comply with security fixes.

== Security Architecture of Jenkins
Conform to access permissions::

Jenkins has a security mechanism in place so that the administrator of Jenkins can control who gets access to what part of Jenkins.
The key components of this mechanism are the followings:
Understand and conform to the
link:/doc/developer/security/security-architecture/[Security Architecture of Jenkins].
Specifically:

* jenkinsdoc:Permission[], which represents an activity that requires a security privilege.
This is usually a verb, like "configure", "administer", "tag", etc.
* `Authentication`, which represents the current user and roles (AKA groups) he/she has.
When a thread runs in Jenkins, it always carry an `Authentication` object implicitly, which represents the user that the thread is serving. (If a thread is a part of Jenkins and not serving any user request, like `Executor{`}s, then it carries an almighty "system" `Authentication` object.)
* jenkinsdoc:ACL[], which decides whether the `Authentication` object carried by the current thread has the given permission or not.
* jenkinsdoc:AccessControlled[], which is implemented by an object who owns ACL.

So the overall picture is this; various objects in Jenkins (such as jenkinsdoc:Job[], jenkinsdoc:Jenkins[], jenkinsdoc:User[], jenkinsdoc:View[], etc.) are jenkinsdoc:AccessControlled[] objects, and therefore they own ACLs.
The code is then written in such a way that before a security-sensitive operation is performed, it checks ACL.
* Ensure that your code checks the ACL before performing a security-sensitive operation.
* Use the `StaplerProxy` interface to control read access to `AccessControlled` objects.

For example, the following code is taken from the jenkinsdoc:Jenkins[] class, which lets you shut down the JVM by requesting `/exit`.
You can easily imagine that in a security sensitive environment you don't want random users to invoke this, so it makes sure that the caller has the "ADMINISTER" permission of the system before proceeding to do the work:
Store user credentials as secrets::

----
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");
}
}
Protect user credentials by storing them on disk in a field of type `Secret`
and never in a simple `String` field.
Protect user credentials by storing them on disk in a field of type `Secret`
and never in a simple `String` field.
StackScribe marked this conversation as resolved.
Show resolved Hide resolved
Use a getter that returns the same type to access the `Secrets` field
from other code.
See
link:https://www.jenkins.io/doc/developer/security/secrets/[Storing Secrets]
StackScribe marked this conversation as resolved.
Show resolved Hide resolved
for background information, instructions, and code examples.

System.exit(0);
}
----
<1> This throws an exception if the user accessing this URL doesn't have `Administer` permission.
Implement appropriate script security::

If the administrator configured no security mechanism, the checkPermission method simply becomes no-op.
The administrator could configure matrix-based ACL, in which case every `AccessControlled` object will share the single ACL (whose contents is controlled by the configuration done by the administrator.) In more elaborate case, each `AccessControlled` object might have different ACLs.
In all cases, this is the code you need to write.
Be sure that your plugin implements appropriate security
for custom Groovy scripts that users may need to create to customize Jenkins.

== Controlling read access to `AccessControlled` objects
For more information, see the _Developer's Guide_ section of the
link:https://plugins.jenkins.io/script-security/[Script Security] documentation.

The recommended way to control read access to `AccessControlled` objects is to implement the `StaplerProxy` interface.
See link:read-access[Restricting HTTP Access to `AccessControlled` Objects] for more information.
Provide Role Check for Callable::

== What do plugins need to do to protect web methods?
Communication between the Jenkins controller and agents is implemented with the Java
link:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html[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.

* 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 discloses 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, then that's obviously it.
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 in them.
If you are defining a sub-system of a substantial size, you might want to create new `Permission` objects (see the end of the `View` class for this example.) If you don't know, you can use `Jenkins.ADMINISTER` as a starting point.
See
link:/doc/developer/security/remoting-callables/[Remoting Callables] and
link:/doc/book/security/controller-isolation/required-role-check/[Required Role Check]
for more information.

With these three information, you can now insert:

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

See also link:form-validation[Securely implementing form validation].

=== Checking permissions in Jelly files

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.

NOTE: 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>
----

NOTE: 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

image:web_rest_flow.svg["Web UI and REST API flow", role=center]

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.

image:cli_flow.svg["CLI flow", role=center]

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 ``ExtensionPoint``s
(like https://github.com/jenkinsci/jenkins/blob/master/core/src/main/java/jenkins/security/QueueItemAuthenticator.java[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`.

////
https://wiki.jenkins.io/display/JENKINS/Making+your+plugin+behave+in+secured+Jenkins
Expand Down