Skip to content

Commit d281dd7

Browse files
authored
Merge pull request #948 from jgarciacloudbees/JENKINS-73941
JENKINS-73941 - Option to hide "Use Groovy Sandbox" for users without Administer permission globally in the system
2 parents 567e2a1 + 3d1ae21 commit d281dd7

File tree

10 files changed

+162
-23
lines changed

10 files changed

+162
-23
lines changed

plugin/pom.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@
5454
<scope>import</scope>
5555
<type>pom</type>
5656
</dependency>
57+
<dependency>
58+
<groupId>org.jenkins-ci.plugins</groupId>
59+
<artifactId>script-security</artifactId>
60+
<version>1367.vdf2fc45f229c</version>
61+
</dependency>
5762
</dependencies>
5863
</dependencyManagement>
5964
<dependencies>
@@ -246,6 +251,16 @@
246251
<version>4.2.2</version>
247252
<scope>test</scope>
248253
</dependency>
254+
<dependency>
255+
<groupId>io.jenkins</groupId>
256+
<artifactId>configuration-as-code</artifactId>
257+
<scope>test</scope>
258+
</dependency>
259+
<dependency>
260+
<groupId>io.jenkins.configuration-as-code</groupId>
261+
<artifactId>test-harness</artifactId>
262+
<scope>test</scope>
263+
</dependency>
249264
</dependencies>
250265
<build>
251266
<resources>

plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowDefinition.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import jenkins.model.Jenkins;
4242
import net.sf.json.JSONObject;
4343
import org.apache.commons.lang.StringUtils;
44-
import org.jenkinsci.plugins.workflow.cps.config.CPSConfiguration;
4544
import org.jenkinsci.plugins.workflow.cps.persistence.PersistIn;
4645
import org.jenkinsci.plugins.workflow.flow.DurabilityHintProvider;
4746
import org.jenkinsci.plugins.workflow.flow.FlowDefinition;
@@ -89,7 +88,7 @@ public CpsFlowDefinition(String script) throws Descriptor.FormException {
8988

9089
@DataBoundConstructor
9190
public CpsFlowDefinition(String script, boolean sandbox) throws Descriptor.FormException {
92-
if (CPSConfiguration.get().isHideSandbox() && !sandbox && !Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
91+
if (!sandbox && ScriptApproval.get().isForceSandboxForCurrentUser()) {
9392
// this will end up in the /oops page until https://github.com/jenkinsci/jenkins/pull/9495 is picked up
9493
throw new Descriptor.FormException("Sandbox cannot be disabled. This Jenkins instance has been configured to not " +
9594
"allow regular users to disable the sandbox in pipelines", "sandbox");
@@ -98,7 +97,6 @@ public CpsFlowDefinition(String script, boolean sandbox) throws Descriptor.FormE
9897
this.script = sandbox ? script : ScriptApproval.get().configuring(script, GroovyLanguage.get(),
9998
ApprovalContext.create().withCurrentUser().withItemAsKey(req != null ? req.findAncestorObject(Item.class) : null), req == null);
10099
this.sandbox = sandbox;
101-
102100
}
103101

104102
private Object readResolve() {
@@ -196,7 +194,7 @@ public JSON doCheckScriptCompile(@AncestorInPath Item job, @QueryParameter Strin
196194
public boolean shouldHideSandbox(@CheckForNull CpsFlowDefinition instance) {
197195
// sandbox checkbox is shown to admins even if the global configuration says otherwise
198196
// it's also shown when sandbox == false, so regular users can enable it
199-
return CPSConfiguration.get().isHideSandbox() && !Jenkins.get().hasPermission(Jenkins.ADMINISTER)
197+
return ScriptApproval.get().isForceSandboxForCurrentUser()
200198
&& (instance == null || instance.sandbox);
201199
}
202200

plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/config/CPSConfiguration.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,33 +24,55 @@
2424

2525
package org.jenkinsci.plugins.workflow.cps.config;
2626

27+
import java.io.IOException;
28+
import java.io.UncheckedIOException;
29+
2730
import edu.umd.cs.findbugs.annotations.NonNull;
2831
import hudson.Extension;
2932
import hudson.ExtensionList;
3033
import jenkins.model.GlobalConfiguration;
3134
import jenkins.model.GlobalConfigurationCategory;
35+
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
3236
import org.jenkinsci.Symbol;
3337

38+
/**
39+
* @deprecated
40+
* This class has been deprecated and its only configuration value is ignored. Do not rely on it or use it in any way.
41+
* In order to force using the system sandbox for pipelines, please use {@link ScriptApproval#isForceSandbox} or {@link ScriptApproval#isForceSandboxForCurrentUser}
42+
**/
3443
@Symbol("cps")
3544
@Extension
45+
@Deprecated
3646
public class CPSConfiguration extends GlobalConfiguration {
3747

3848
/**
3949
* Whether to show the sandbox checkbox in jobs to users without Jenkins.ADMINISTER
4050
*/
41-
private boolean hideSandbox;
51+
private transient boolean hideSandbox;
4252

4353
public CPSConfiguration() {
54+
4455
load();
56+
if (hideSandbox) {
57+
ScriptApproval.get().setForceSandbox(hideSandbox);
58+
}
59+
60+
// Data migration from this configuration to ScriptApproval should be done only once,
61+
// so removing the config file after the previous migration
62+
try {
63+
this.getConfigFile().delete();
64+
} catch (IOException e) {
65+
throw new UncheckedIOException(e);
66+
}
4567
}
4668

4769
public boolean isHideSandbox() {
48-
return hideSandbox;
70+
return ScriptApproval.get().isForceSandbox();
4971
}
5072

5173
public void setHideSandbox(boolean hideSandbox) {
5274
this.hideSandbox = hideSandbox;
53-
save();
75+
ScriptApproval.get().setForceSandbox(hideSandbox);
5476
}
5577

5678
@NonNull

plugin/src/main/resources/org/jenkinsci/plugins/workflow/cps/config/CPSConfiguration/config.jelly

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,4 @@ THE SOFTWARE.
2525

2626
<?jelly escape-by-default='true'?>
2727
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
28-
<f:section title="${%Pipeline Sandbox}">
29-
<f:entry field="hideSandbox" title="${%Hide Sandbox checkbox in pipeline jobs}">
30-
<f:checkbox/>
31-
</f:entry>
32-
</f:section>
3328
</j:jelly>

plugin/src/main/resources/org/jenkinsci/plugins/workflow/cps/config/CPSConfiguration/help-hideSandbox.html

Lines changed: 0 additions & 6 deletions
This file was deleted.

plugin/src/test/java/org/jenkinsci/plugins/workflow/cps/CpsFlowDefinitionTest.java

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
package org.jenkinsci.plugins.workflow.cps;
2626

27+
import hudson.model.Result;
2728
import hudson.util.VersionNumber;
2829
import org.htmlunit.FailingHttpStatusCodeException;
2930
import org.htmlunit.HttpMethod;
@@ -45,12 +46,15 @@
4546
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage;
4647
import org.jenkinsci.plugins.workflow.cps.config.CPSConfiguration;
4748
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
49+
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
4850
import org.junit.Rule;
4951
import org.junit.Test;
5052
import org.jvnet.hudson.test.Issue;
5153
import org.jvnet.hudson.test.JenkinsRule;
5254
import org.jvnet.hudson.test.MockAuthorizationStrategy;
55+
import org.jvnet.hudson.test.recipes.LocalData;
5356

57+
import java.io.File;
5458
import java.nio.charset.StandardCharsets;
5559
import java.util.List;
5660

@@ -59,8 +63,6 @@
5963
import static org.hamcrest.Matchers.equalTo;
6064
import static org.hamcrest.Matchers.hasSize;
6165
import static org.hamcrest.Matchers.not;
62-
import static org.hamcrest.Matchers.notNullValue;
63-
import static org.hamcrest.Matchers.nullValue;
6466
import static org.junit.Assert.assertEquals;
6567
import static org.junit.Assert.assertFalse;
6668
import static org.junit.Assert.assertTrue;
@@ -316,7 +318,7 @@ public void cpsScriptSandboxHide() throws Exception {
316318
}
317319

318320
// non-admins cannot see the sandbox checkbox in jobs if hideSandbox is On globally
319-
CPSConfiguration.get().setHideSandbox(true);
321+
ScriptApproval.get().setForceSandbox(true);
320322
{
321323
HtmlForm config = wcDevel.getPage(p, "configure").getFormByName("config");
322324
assertThat(config.getVisibleText(), not(containsStringIgnoringCase("Use Groovy Sandbox")));
@@ -328,15 +330,15 @@ public void cpsScriptSandboxHide() throws Exception {
328330
}
329331

330332
// admins can always see the sandbox checkbox
331-
CPSConfiguration.get().setHideSandbox(false);
333+
ScriptApproval.get().setForceSandbox(false);
332334
wcDevel.login("admin");
333335
{
334336
HtmlForm config = wcDevel.getPage(p, "configure").getFormByName("config");
335337
assertThat(config.getVisibleText(), containsStringIgnoringCase("Use Groovy Sandbox"));
336338
}
337339

338340
// even when set to hide globally
339-
CPSConfiguration.get().setHideSandbox(true);
341+
ScriptApproval.get().setForceSandbox(true);
340342
{
341343
HtmlForm config = wcDevel.getPage(p, "configure").getFormByName("config");
342344
assertThat(config.getVisibleText(), containsStringIgnoringCase("Use Groovy Sandbox"));
@@ -370,4 +372,67 @@ public void cpsScriptSandboxHide() throws Exception {
370372

371373
}
372374
}
375+
376+
@Test
377+
public void cpsConfigurationSandboxToScriptApprovalSandbox() throws Exception{
378+
//Deprecated CPSConfiguration should update ScriptApproval forceSandbox logic to keep casc compatibility
379+
ScriptApproval.get().setForceSandbox(false);
380+
381+
CPSConfiguration.get().setHideSandbox(true);
382+
assertTrue(ScriptApproval.get().isForceSandbox());
383+
384+
ScriptApproval.get().setForceSandbox(false);
385+
assertFalse(CPSConfiguration.get().isHideSandbox());
386+
}
387+
388+
@Test
389+
public void cpsScriptSignatureException() throws Exception {
390+
ScriptApproval.get().setForceSandbox(false);
391+
WorkflowJob p = jenkins.createProject(WorkflowJob.class);
392+
String script = "jenkins.model.Jenkins.instance";
393+
p.setDefinition(new CpsFlowDefinition(script, true));
394+
WorkflowRun b = jenkins.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get());
395+
jenkins.assertLogContains("Scripts not permitted to use staticMethod jenkins.model.Jenkins getInstance. "
396+
+ "Administrators can decide whether to approve or reject this signature.", b);
397+
398+
ScriptApproval.get().setForceSandbox(true);
399+
b = jenkins.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get());
400+
jenkins.assertLogContains("Scripts not permitted to use staticMethod jenkins.model.Jenkins getInstance. "
401+
+ "Script signature is not in the default whitelist.", b);
402+
}
403+
404+
@Test
405+
@LocalData
406+
public void cpsLoadConfiguration() throws Exception {
407+
//CPSConfiguration file containing <hideSandbox>true</hideSandbox>
408+
// should be promoted to ScriptApproval.get().isForceSandbox()
409+
assertTrue(ScriptApproval.get().isForceSandbox());
410+
411+
//Once the info is promoted, we are removing the config file, so should no longer exist.
412+
//We are checking the injected localData is removed
413+
assertFalse(new File(jenkins.jenkins.getRootDir(),
414+
"org.jenkinsci.plugins.workflow.cps.config.CPSConfiguration.xml").exists());
415+
}
416+
417+
@Test
418+
public void cpsRoundTrip() throws Exception {
419+
jenkins.jenkins.setSecurityRealm(jenkins.createDummySecurityRealm());
420+
421+
MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy();
422+
mockStrategy.grant(Jenkins.READ).everywhere().to("dev");
423+
for (Permission p : Item.PERMISSIONS.getPermissions()) {
424+
mockStrategy.grant(p).everywhere().to("dev");
425+
}
426+
427+
WorkflowJob p = jenkins.createProject(WorkflowJob.class);
428+
p.setDefinition(new CpsFlowDefinition("echo 'Hello'", true));
429+
430+
WorkflowJob roundTrip = jenkins.configRoundtrip(p);
431+
432+
assertEquals(((CpsFlowDefinition)p.getDefinition()).isSandbox(),
433+
((CpsFlowDefinition)roundTrip.getDefinition()).isSandbox());
434+
435+
assertEquals(((CpsFlowDefinition)p.getDefinition()).getScript(),
436+
((CpsFlowDefinition)roundTrip.getDefinition()).getScript());
437+
}
373438
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package org.jenkinsci.plugins.workflow.cps;
2+
3+
import io.jenkins.plugins.casc.ConfigurationContext;
4+
import io.jenkins.plugins.casc.ConfiguratorRegistry;
5+
import io.jenkins.plugins.casc.misc.ConfiguredWithCode;
6+
import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule;
7+
import io.jenkins.plugins.casc.model.CNode;
8+
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
9+
import org.junit.ClassRule;
10+
import org.junit.Test;
11+
12+
import static org.junit.Assert.assertEquals;
13+
import static org.junit.Assert.assertTrue;
14+
import static io.jenkins.plugins.casc.misc.Util.getSecurityRoot;
15+
import static io.jenkins.plugins.casc.misc.Util.toStringFromYamlFile;
16+
import static io.jenkins.plugins.casc.misc.Util.toYamlString;
17+
18+
public class JcascTest {
19+
20+
@ClassRule(order = 1)
21+
@ConfiguredWithCode("casc_test.yaml")
22+
public static JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule();
23+
24+
/**
25+
* Check that CASC for security.cps.hideSandbox is sending the value to ScriptApproval.get().isForceSandbox()
26+
* @throws Exception
27+
*/
28+
@Test
29+
public void cascHideSandBox() throws Exception {
30+
assertTrue(ScriptApproval.get().isForceSandbox());
31+
}
32+
33+
@Test
34+
public void cascExport() throws Exception {
35+
ConfiguratorRegistry registry = ConfiguratorRegistry.get();
36+
ConfigurationContext context = new ConfigurationContext(registry);
37+
CNode yourAttribute = getSecurityRoot(context).get("cps");
38+
String exported = toYamlString(yourAttribute);
39+
String expected = toStringFromYamlFile(this, "casc_test_expected.yaml");
40+
assertEquals(exported, expected);
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version='1.1' encoding='UTF-8'?>
2+
<org.jenkinsci.plugins.workflow.cps.config.CPSConfiguration plugin="workflow-cps">
3+
<hideSandbox>true</hideSandbox>
4+
</org.jenkinsci.plugins.workflow.cps.config.CPSConfiguration>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
security:
2+
cps:
3+
hideSandbox: true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hideSandbox: true

0 commit comments

Comments
 (0)