package jenkins.security.s2m; import hudson.Extension; import jenkins.util.SystemProperties; import hudson.remoting.Callable; import hudson.remoting.ChannelBuilder; import jenkins.security.ChannelConfigurator; import jenkins.security.Roles; import org.jenkinsci.remoting.Role; import org.jenkinsci.remoting.RoleChecker; import org.jenkinsci.remoting.RoleSensitive; import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.DoNotUse; import org.kohsuke.accmod.restrictions.NoExternalUse; import javax.annotation.Nonnull; import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; /** * Inspects {@link Callable}s that run on the master. * * @author Kohsuke Kawaguchi * @since 1.THU */ @Restricted(NoExternalUse.class) // used implicitly via listener public class CallableDirectionChecker extends RoleChecker { /** * Context parameter given to {@link ChannelConfigurator#onChannelBuilding(ChannelBuilder, Object)}. */ private final Object context; private static final String BYPASS_PROP = CallableDirectionChecker.class.getName()+".allow"; /** * Switch to disable all the defense mechanism completely. * * This is an escape hatch in case the fix breaks something critical, to allow the user * to keep operation. */ public static boolean BYPASS = SystemProperties.getBoolean(BYPASS_PROP); private CallableDirectionChecker(Object context) { this.context = context; } @Override public void check(RoleSensitive subject, @Nonnull Collection<Role> expected) throws SecurityException { final String name = subject.getClass().getName(); if (expected.contains(Roles.MASTER)) { LOGGER.log(Level.FINE, "Executing {0} is allowed since it is targeted for the master role", name); return; // known to be safe } if (isWhitelisted(subject,expected)) { // this subject is dubious, but we are letting it through as per whitelisting LOGGER.log(Level.FINE, "Explicitly allowing {0} to be sent from agent to master", name); return; } throw new SecurityException("Sending " + name + " from agent to master is prohibited.\nSee http://jenkins-ci.org/security-144 for more details"); } /** * Is this subject class name whitelisted? */ private boolean isWhitelisted(RoleSensitive subject, Collection<Role> expected) { for (CallableWhitelist w : CallableWhitelist.all()) { if (w.isWhitelisted(subject, expected, context)) return true; } return false; } /** * Installs {@link CallableDirectionChecker} to every channel. */ @Restricted(DoNotUse.class) // impl @Extension public static class ChannelConfiguratorImpl extends ChannelConfigurator { @Override public void onChannelBuilding(ChannelBuilder builder, Object context) { // if the big red emergency button is pressed, then we need to disable the defense mechanism, // including enabling classloading. if (!BYPASS) { builder.withRemoteClassLoadingAllowed(false); } // In either of the above cases, the check method will return normally, but may log things. builder.withRoleChecker(new CallableDirectionChecker(context)); } } /** * Whitelist rule based on system properties. * * For the bypass "kill" switch to be effective, it needs to have a high enough priority */ @Extension(ordinal=100) public static class DefaultWhitelist extends CallableWhitelist { @Override public boolean isWhitelisted(RoleSensitive subject, Collection<Role> expected, Object context) { return BYPASS; } } private static final Logger LOGGER = Logger.getLogger(CallableDirectionChecker.class.getName()); }