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.
Callables can use it to limit where they can be executed by implementing #checkRoles(RoleChecker)
.
The abstract supertypes ControllerToAgentCallable
and SlaveToMasterCallable
implement this interface with two well-defined modes:
-
ControllerToAgentCallable
can be sent from a controller to an agent.
In practice, almost all Callable
implementations will be ControllerToAgentCallable
s.
(Prior to Jenkins 2.485 when `ControllerToAgentCallable
was introduced, use the abstract superclass `MasterToSlaveCallable
.)
-
SlaveToMasterCallable
can be sent from an agent to a controller.
This is extremely risky, as malicious agents can use poorly-reviewed 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.
ControllerToAgentCallable
can be executed on the controller, just not sent from an agent to a controller for execution;
whereas SlaveToMasterCallable
can be sent through a controller/agent channel in 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.
This would only be used when some API is defined using hudson.remoting.Callable
rather than a more generic functional interface,
for example because it has a Throwable
type parameter.
It is recommended to use one of these supertypes 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 ControllerToAgentCallable
, which should work in almost all cases.
If that does not work and the plugin cannot be restructured to work with a ControllerToAgentCallable
, the Callable
implementation should be changed to a SlaveToMasterCallable
, and the best practices recommended below must be implemented to ensure it cannot be bypassed.