It is important to ensure that the less-privileged end of the channel (typically agents) cannot run arbitrary code on the more-privileged end (typically a controller), so care needs to be taken when implementing Callables
.
The remoting library offers the RoleSensitive
interface for this which Callable
extends since Jenkins 1.587 and 1.580.1:
Callables can use it to limit where they can be executed by implementing #checkRoles(RoleChecker)
.
The abstract classes MasterToSlaveCallable
and SlaveToMasterCallable
implement this interface with the two most common modes:
-
MasterToSlaveCallable
can be sent from a controller to an agent.
In general, you should write your code so it works with this implementation.
-
SlaveToMasterCallable
can be sent from an agent to a controller.
This is less safe, as malicious agents can use these implementations to run code on a controller.
Despite their names, either implementation can be executed anywhere, the name just describes through which channels it can be sent for execution:
MasterToSlaveCallable
can be executed on the controller, just not sent from an agent to a controller for execution, unlike SlaveToMasterCallable
, which can be sent through a controller/agent channel through either direction.
In addition to the above, NotReallyRoleSensitiveCallable
can be used for a Callable
that is not intended to be sent through a channel.
Its #checkRoles
method will throw an exception, thereby preventing it from being executed on any side of a communication channel that performs a role check.
It is recommended to use one of these classes in your plugin, rather than implementing #checkRoles(RoleChecker)
directly.
Mandatory role checks
Since Jenkins 2.319 and LTS 2.303.3, Callable
implementations providing an implementation of #checkRoles(RoleChecker)
that neither throws an exception nor calls RoleChecker#check
will result in the Callable
being rejected when sent through the channel to a side that requires a role check before execution.
A typical implementation that breaks will look like the following:
class MyCallable implements Callable<ReturnType,ExceptionType> {
private String parameter;
public MyCallable(String parameter) {
this.parameter = parameter;
}
public ReturnType call() throws ExceptionType {
return Some.code().operatesOn(this.parameter);
}
public void checkRules(RoleChecker checker) {
// this is an empty block (1)
}
}
1 |
Nothing is being done here even though a call to RoleChecker#check(…) is expected. |
If a plugin implementing an inadequate role check (like this example) attempts to send a Callable
through the remoting channel from the agent to the controller, the security improvement will detect it and throw an exception before #call()
would be invoked.
While administrators can allow specific Callable
subtypes to bypass this protection mechanism (see documentation), plugin developers are advised to update their plugins:
Callable
implementations should extend from one of the classes mentioned above, with a strong preference for MasterToSlaveCallable
, which should work in almost all cases.
If that doesn’t work and the plugin cannot be restructured to work with a MasterToSlaveCallable
, the Callable
implementation should be changed to a SlaveToMasterCallable
, and the best practices recommended below need to be implemented to ensure it cannot be bypassed.
|
While this protection can easily be circumvented by calling RoleChecker#check(RoleSensitive) or extending SlaveToMasterCallable without any further validation, any plugin doing this with unsafe Callable implementations will be suspended from distribution for deliberately bypassing a critical protection mechanism.
|