-
-
Notifications
You must be signed in to change notification settings - Fork 8.6k
/
Security2458Test.java
116 lines (99 loc) · 4.3 KB
/
Security2458Test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package jenkins.security;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import hudson.ExtensionList;
import hudson.remoting.Callable;
import java.io.IOException;
import java.util.Objects;
import jenkins.agents.AgentComputerUtil;
import jenkins.security.s2m.AdminWhitelistRule;
import jenkins.security.s2m.CallableDirectionChecker;
import org.jenkinsci.remoting.RoleChecker;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.jvnet.hudson.test.JenkinsRule;
public class Security2458Test {
@Rule
public JenkinsRule r = new JenkinsRule();
@Before
public void enableAgentToControllerProtections() {
AdminWhitelistRule rule = ExtensionList.lookupSingleton(AdminWhitelistRule.class);
rule.setMasterKillSwitch(false);
}
@Test
public void rejectBadCallable() throws Throwable {
// If the role check is empty, fail
assertThrowsIOExceptionCausedBySecurityException(() -> Objects.requireNonNull(r.createOnlineSlave().getChannel()).call(new CallableCaller(new BadCallable())));
// If it performs a no-op check, pass. Never do this in your plugin.
Objects.requireNonNull(r.createOnlineSlave().getChannel()).call(new CallableCaller(new EvilCallable()));
// Explicit role check.
Objects.requireNonNull(r.createOnlineSlave().getChannel()).call(new CallableCaller(new GoodCallable()));
// No-op role checks can be disallowed
System.setProperty(CallableDirectionChecker.class.getName() + ".allowAnyRole", "false");
try {
assertThrowsIOExceptionCausedBySecurityException(() -> Objects.requireNonNull(r.createOnlineSlave().getChannel()).call(new CallableCaller(new EvilCallable())));
} finally {
System.clearProperty(CallableDirectionChecker.class.getName() + ".allowAnyRole");
}
}
private static class CallableCaller extends MasterToSlaveCallable<Object, Throwable> {
private final Callable<?, ?> callable;
CallableCaller(Callable<?, ?> callable) {
this.callable = callable;
}
@Override
public Object call() throws Throwable {
Objects.requireNonNull(AgentComputerUtil.getChannelToMaster()).call(callable);
return null;
}
}
private static class BadCallable implements Callable<Object, Exception> {
@Override
public Object call() throws Exception {
return null;
}
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
// Deliberately empty
}
}
private static class EvilCallable implements Callable<Object, Exception> {
@Override
public Object call() throws Exception {
return null;
}
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this); // Never do this
}
}
private static class GoodCallable implements Callable<Object, Exception> {
@Override
public Object call() throws Exception {
return null;
}
@Override
public void checkRoles(RoleChecker checker) throws SecurityException {
checker.check(this, Roles.MASTER); // Manual S2M
}
}
private static SecurityException assertThrowsIOExceptionCausedBySecurityException(ThrowingRunnable runnable) {
return assertThrowsIOExceptionCausedBy(SecurityException.class, runnable);
}
private static <X extends Throwable> X assertThrowsIOExceptionCausedBy(Class<X> causeClass, ThrowingRunnable runnable) {
try {
runnable.run();
} catch (IOException ex) {
final Throwable cause = ex.getCause();
assertTrue("IOException with message: '" + ex.getMessage() + "' wasn't caused by " + causeClass + ": " + (cause == null ? "(null)" : (cause.getClass().getName() + ": " + cause.getMessage())),
cause != null && causeClass.isAssignableFrom(cause.getClass()));
return causeClass.cast(cause);
} catch (Throwable t) {
fail("Threw other Throwable: " + t.getClass() + " with message " + t.getMessage());
}
fail("Expected exception but passed");
return null;
}
}