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 checkRoles(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 does not 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 must be implemented to ensure it cannot be bypassed.