package jenkins.security; import hudson.model.Node.Mode; import hudson.model.Slave; import hudson.remoting.Channel; import hudson.remoting.Which; import hudson.slaves.DumbSlave; import hudson.slaves.JNLPLauncher; import hudson.slaves.RetentionStrategy; import org.apache.tools.ant.util.JavaEnvUtils; import org.codehaus.groovy.runtime.Security218; import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import java.io.Serializable; import java.util.Collections; import static org.junit.Assert.*; /** * @author Kohsuke Kawaguchi */ @Issue("SECURITY-218") public class Security218Test implements Serializable { @Rule public transient JenkinsRule j = new JenkinsRule(); /** * JNLP slave. */ private transient Process jnlp; /** * Makes sure SECURITY-218 fix also applies to slaves. * * This test is for regular dumb slave */ @Test public void dumbSlave() throws Exception { check(j.createOnlineSlave()); } /** * Makes sure SECURITY-218 fix also applies to slaves. * * This test is for JNLP slave */ @Test public void jnlpSlave() throws Exception { DumbSlave s = createJnlpSlave("test"); launchJnlpSlave(s); check(s); } /** * The attack scenario here is that a master sends a normal command to a slave and a slave * inserts a malicious response. */ @SuppressWarnings("ConstantConditions") private void check(DumbSlave s) throws Exception { try { s.getComputer().getChannel().call(new MasterToSlaveCallable<Object, RuntimeException>() { public Object call() { return new Security218(); } }); fail("Expected the connection to die"); } catch (SecurityException e) { assertTrue(e.getMessage().contains(Security218.class.getName())); } } // TODO: reconcile this duplicate with JnlpAccessWithSecuredHudsonTest /** * Creates a new slave that needs to be launched via JNLP. * * @see #launchJnlpSlave(Slave) */ public DumbSlave createJnlpSlave(String name) throws Exception { DumbSlave s = new DumbSlave(name, "", System.getProperty("java.io.tmpdir") + '/' + name, "2", Mode.NORMAL, "", new JNLPLauncher(), RetentionStrategy.INSTANCE, Collections.EMPTY_LIST); j.jenkins.addNode(s); return s; } // TODO: reconcile this duplicate with JnlpAccessWithSecuredHudsonTest /** * Launch a JNLP slave created by {@link #createJnlpSlave(String)} */ public Channel launchJnlpSlave(Slave slave) throws Exception { j.createWebClient().goTo("computer/"+slave.getNodeName()+"/slave-agent.jnlp?encrypt=true", "application/octet-stream"); String secret = slave.getComputer().getJnlpMac(); // To watch it fail: secret = secret.replace('1', '2'); ProcessBuilder pb = new ProcessBuilder(JavaEnvUtils.getJreExecutable("java"), "-jar", Which.jarFile(hudson.remoting.Launcher.class).getAbsolutePath(), "-jnlpUrl", j.getURL() + "computer/"+slave.getNodeName()+"/slave-agent.jnlp", "-secret", secret); pb.inheritIO(); System.err.println("Running: " + pb.command()); jnlp = pb.start(); for (int i = 0; i < /* one minute */600; i++) { if (slave.getComputer().isOnline()) { return slave.getComputer().getChannel(); } Thread.sleep(100); } throw new AssertionError("JNLP slave agent failed to connect"); } @After public void tearDown() { if (jnlp !=null) jnlp.destroy(); } }