Skip to content

Commit

Permalink
JENKINS-73941 - Add forceSandbox logic to SecureGroovyScript
Browse files Browse the repository at this point in the history
  • Loading branch information
jgarciacloudbees committed Oct 25, 2024
1 parent 2a9fe7c commit 1019813
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,14 @@ public FormValidation doCheckScript(@QueryParameter String value, @QueryParamete
return sandbox ? FormValidation.ok() : ScriptApproval.get().checking(value, GroovyLanguage.get(), !StringUtils.equals(oldScript, value));
}

@Restricted(NoExternalUse.class) // stapler
public boolean shouldHideSandbox(@CheckForNull SecureGroovyScript instance) {
// sandbox checkbox is shown to admins even if the global configuration says otherwise
// it's also shown when sandbox == false, so regular users can enable it
return ScriptApproval.get().isForceSandboxForCurrentUser()
&& (instance == null || instance.sandbox);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ THE SOFTWARE.
<!-- TODO https://github.com/stapler/stapler-adjunct-codemirror/issues/1 means no true Groovy support -->
<f:textarea checkMethod="post"/> <!-- TODO codemirror-mode="clike" codemirror-config="'onBlur': cmChange" -->
</f:entry>
<f:entry field="sandbox">
<f:checkbox title="${%Use Groovy Sandbox}" default="${!h.hasPermission(app.ADMINISTER)}" />
<f:entry field="sandbox" class="${descriptor.shouldHideSandbox(instance) ? 'jenkins-hidden' : ''}">
<f:checkbox title="${%Use Groovy Sandbox}"
default="${!h.hasPermission(app.ADMINISTER) || descriptor.shouldHideSandbox(instance)}" />
</f:entry>
<f:entry title="${%Additional classpath}" field="classpath">
<f:entry title="${%Additional classpath}" field="classpath"
class="${descriptor.shouldHideSandbox(instance) ?'jenkins-hidden' : ''}">
<f:repeatableProperty add="${%Add entry}" header="${%Classpath entry}" field="classpath"/>
</f:entry>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,52 @@ private void addPostBuildAction(HtmlPage page) throws IOException {
assertEquals("P#3", r.assertBuildStatusSuccess(p.scheduleBuild2(0)).getDescription());
}

/**
* Basic approval test where the user doesn't have ADMINISTER privs and forceSandbox is enabled
* Sandbox checkbox should not be visible, but set to true by default
* Should result in script going to pending approval.
*/
@Test public void basicApproval_ForceSandbox() throws Exception {
ScriptApproval.get().setForceSandbox(true);

r.jenkins.setSecurityRealm(r.createDummySecurityRealm());

MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy();
mockStrategy.grant(Jenkins.READ).everywhere().to("devel");
for (Permission p : Item.PERMISSIONS.getPermissions()) {
mockStrategy.grant(p).everywhere().to("devel");
}
r.jenkins.setAuthorizationStrategy(mockStrategy);

FreeStyleProject p = r.createFreeStyleProject("p");
JenkinsRule.WebClient wc = r.createWebClient();
wc.login("devel");
HtmlPage page = wc.getPage(p, "configure");
HtmlForm config = page.getFormByName("config");
HtmlFormUtil.getButtonByCaption(config, "Add post-build action").click(); // lib/hudson/project/config-publishers2.jelly
addPostBuildAction(page);
wc.waitForBackgroundJavaScript(10000);
List<HtmlTextArea> scripts = config.getTextAreasByName("_.script");
// Get the last one, because previous ones might be from Lockable Resources during PCT.
HtmlTextArea script = scripts.get(scripts.size() - 1);
String groovy = "build.externalizableId";
script.setText(groovy);

//As the user is not admin and we are forcing Sandbox use,
// the Sandbox checkbox should be hidden and enabled by default.
List<HtmlInput> sandboxes = config.getInputsByName("_.sandbox");
HtmlCheckBoxInput sandboxcb = (HtmlCheckBoxInput) sandboxes.get(sandboxes.size() - 1);
assertTrue(sandboxcb.isChecked());

r.submit(config);

List<Publisher> publishers = p.getPublishersList();
assertEquals(1, publishers.size());
TestGroovyRecorder publisher = (TestGroovyRecorder) publishers.get(0);
assertEquals(groovy, publisher.getScript().getScript());
assertTrue(publisher.getScript().isSandbox());
}


/**
* Test where the user has ADMINISTER privs, default to non sandbox mode, but require approval
Expand Down Expand Up @@ -227,12 +273,20 @@ private void addPostBuildAction(HtmlPage page) throws IOException {
// Test that the script is executable. If it's not, we will get an UnapprovedUsageException
Exception ex = assertThrows(UnapprovedUsageException.class,
() -> ScriptApproval.get().using(groovy,GroovyLanguage.get()));
assertEquals(Messages.UnapprovedUsage_NonApproved(), ex.getMessage());
assertEquals(ScriptApproval.get().isForceSandbox()
? Messages.UnapprovedUsage_ForceSandBox()
: Messages.UnapprovedUsage_NonApproved()
, ex.getMessage());
}

/**
* Test where the user has ADMINISTER privs + forceSandboxEnabled
* logic should not change to the default admin behavior
* Except for the messages
*/
@Test public void testSandboxDefault_with_ADMINISTER_privs_ForceSandbox() throws Exception {
ScriptApproval.get().setForceSandbox(true);
ex = assertThrows(UnapprovedUsageException.class,
() -> ScriptApproval.get().using(groovy,GroovyLanguage.get()));
assertEquals(Messages.UnapprovedUsage_ForceSandBox(), ex.getMessage());
testSandboxDefault_with_ADMINISTER_privs();
}

/**
Expand Down

0 comments on commit 1019813

Please sign in to comment.