Remoting Callables
This site is the new docs site currently being tested. For the actual docs in use please go to https://www.jenkins.io/doc. |
Remoting is the library implementing communication between Jenkins processes using Java serialization. This applies to controller/agent communications and agents communicating with the Maven process in Maven Integration Plugin.
hudson.remoting.Callable
is the basic building block of this communication channel, representing a message and corresponding response.
Objects implementing this interface get serialized and sent to the other end of the channel.
They’re deserialized on the other end, where the #call()
method is invoked.
Its return value is then serialized and sent back through the channel.
Think of each Callable
implementation as an interface or API of Jenkins:
Jenkins will execute Callables
it receives through a remoting channel, with fields in the Callable
subtype being like parameters.
Remoting roles
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.
Best practices
Prohibit sending to controllers wherever possible
Use MasterToSlaveCallable
wherever possible, restructure your plugin code as needed to allow this.
For Callables not intended to be sent through a channel, extend NotReallyRoleSensitiveCallable
instead of implementing Callable
directly.
Never run unsafe code during deserialization or role check
All Callables
are still deserialized on the controller, and #checkRoles
is invoked to determine whether the Callable
can be executed.
Do not add code to #readResolve
and related methods, or to #checkRoles
, that would be unsafe to execute, as these methods will still be executed on the controller between receiving the Callable
and invoking #call()
.
Minimal, safe implementations
If a Callable
needs to be sent from an agent to a controller (SlaveToMasterCallable
), keep the implementations minimal:
Use few, simple parameters that are easy to reason about and validate, do not send complex trees of objects.
Do not use (anonymous) inner classes for your Callables, instead make them top-level or static nested classes so they do not have a reference to an instance of the surrounding class, reducing complexity.
Perform parameter validation inside #call()
or #readResolve()
rather than (only) in constructors and setters, and do not rely on private
or final
modifiers for your fields to protect from unexpected values:
reflection can be used to set fields to arbitrary, attacker-chosen values.
The vast majority of plugins should have their Callables
extend from MasterToSlaveCallable
(if they’re implementing Callable
) or MasterToSlaveFileCallable
(if they’re implementing FileCallable
).
This will allow the controller-side of a connection to initiate the Callable submission and invocation on both the controller and any agents and is the safest approach.
This is mostly equivalent to implementing checkRoles
as follows:
public void checkRoles(RoleChecker checker) {
checker.check(this,Roles.SLAVE);
}
}
In very care cases, the agent-side of a connection will send a Callable
to the controller for execution there.
This is tricky to do safely and it’s generally recommended plugins are restructured so this isn’t needed.
If this isn’t possible, plugins can implement SlaveToMasterCallable
or SlaveToMasterFileCallable
, or implement #checkRoles
as follows:
public void checkRoles(RoleChecker checker) {
checker.check(this,Roles.MASTER);
}
}
Make sure to carefully consider what your Callable
implementation is able to do on the controller when sent from an untrusted agent.
Carefully review the best practices above and adapt your Callable
accordingly.