diff --git a/README.md b/README.md index 00c335a39..8f7b29e9b 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,11 @@ Administrators in security-sensitive environments should carefully consider whic operations to whitelist. Operations which change state of persisted objects (such as Jenkins jobs) should generally be denied. Most `getSomething` methods are harmless. +In case of highly secured environments, where only sandbox scripts are allowed, the +option "Force the use of the sandbox globally in the system" allows forcing the use of the +sandbox globally in the system and will block the creation of new items in the +"In-process Script Approval" screen. + ### ACL-aware methods Be aware however that even some “getter” methods are designed to check specific permissions (using an ACL: access control list), whereas scripts are often run by a system diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java index b3d6b4a92..c831054ad 100644 --- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java +++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java @@ -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); + } + } } diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java index 4a44c4729..386198274 100644 --- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java +++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java @@ -285,6 +285,9 @@ String hashClasspathEntry(URL entry) throws IOException { /** All external classpath entries allowed used for scripts. */ private /*final*/ TreeSet approvedClasspathEntries; + /** when this mode is enabled, the full logic for accepting/rejecting scripts will be hidden */ + private boolean forceSandbox; + /* for test */ synchronized void addApprovedClasspathEntry(ApprovedClasspathEntry acp) { approvedClasspathEntries.add(acp); } @@ -514,7 +517,9 @@ private PendingClasspathEntry getPendingClasspathEntry(@NonNull String hash) { } /* for test */ void addPendingClasspathEntry(PendingClasspathEntry pcp) { - pendingClasspathEntries.add(pcp); + if (!isForceSandboxForCurrentUser()) { + pendingClasspathEntries.add(pcp); + } } @DataBoundConstructor @@ -652,7 +657,9 @@ public synchronized String configuring(@NonNull String script, @NonNull Language if (key != null) { pendingScripts.removeIf(pendingScript -> key.equals(pendingScript.getContext().getKey())); } - pendingScripts.add(new PendingScript(script, language, context)); + if (!isForceSandboxForCurrentUser()) { + pendingScripts.add(new PendingScript(script, language, context)); + } } save(); } @@ -733,7 +740,7 @@ public synchronized void configuring(@NonNull ClasspathEntry entry, @NonNull App approvedClasspathEntries.add(acp); shouldSave = true; } else { - if (pendingClasspathEntries.add(pcp)) { + if (!isForceSandboxForCurrentUser() && pendingClasspathEntries.add(pcp)) { LOG.log(Level.FINE, "{0} ({1}) is pending", new Object[] {url, result.newHash}); shouldSave = true; } @@ -784,7 +791,7 @@ public synchronized void using(@NonNull ClasspathEntry entry) throws IOException if (!result.approved) { // Never approve classpath here. ApprovalContext context = ApprovalContext.create(); - if (pendingClasspathEntries.add(new PendingClasspathEntry(result.newHash, url, context))) { + if (!isForceSandboxForCurrentUser() && pendingClasspathEntries.add(new PendingClasspathEntry(result.newHash, url, context))) { LOG.log(Level.FINE, "{0} ({1}) is pending.", new Object[]{url, result.newHash}); save(); } @@ -815,7 +822,9 @@ public synchronized FormValidation checking(@NonNull String script, @NonNull Lan } if (!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) { - return FormValidation.warningWithMarkup("A Jenkins administrator will need to approve this script before it can be used"); + return FormValidation.warningWithMarkup(isForceSandboxForCurrentUser() ? + Messages.ScriptApproval_ForceSandBoxMessage() : + Messages.ScriptApproval_PipelineMessage()); } else { if ((ALLOW_ADMIN_APPROVAL_ENABLED && (willBeApproved || ADMIN_AUTO_APPROVAL_ENABLED)) || !Jenkins.get().isUseSecurity()) { return FormValidation.okWithMarkup("The script has not yet been approved, but it will be approved on save."); @@ -888,7 +897,7 @@ public synchronized void preapproveAll() { @Deprecated public synchronized RejectedAccessException accessRejected(@NonNull RejectedAccessException x, @NonNull ApprovalContext context) { String signature = x.getSignature(); - if (signature != null && pendingSignatures.add(new PendingSignature(signature, x.isDangerous(), context))) { + if (signature != null && !isForceSandboxForCurrentUser() && pendingSignatures.add(new PendingSignature(signature, x.isDangerous(), context))) { save(); } return x; @@ -982,6 +991,22 @@ public synchronized void setApprovedScriptHashes(String[] scriptHashes) throws I reconfigure(); } + @DataBoundSetter + public void setForceSandbox(boolean forceSandbox) { + this.forceSandbox = forceSandbox; + save(); + } + + + public boolean isForceSandbox() { + return forceSandbox; + } + + //ForceSandbox restrictions does not apply to ADMINISTER users. + public boolean isForceSandboxForCurrentUser() { + return forceSandbox && !Jenkins.get().hasPermission(Jenkins.ADMINISTER); + } + @Restricted(NoExternalUse.class) // Jelly, tests, implementation public synchronized String[] getApprovedScriptHashes() { return approvedScriptHashes.toArray(new String[approvedScriptHashes.size()]); diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalNote.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalNote.java index eb03de04b..adaa19953 100644 --- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalNote.java +++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalNote.java @@ -51,7 +51,8 @@ public class ScriptApprovalNote extends ConsoleNote { public static void print(TaskListener listener, RejectedAccessException x) { try { - String text = Messages.ScriptApprovalNote_message(); + String text = ScriptApproval.get().isForceSandbox() ? + Messages.ScriptApprovalNoteForceSandBox_message() : Messages.ScriptApprovalNote_message(); listener.getLogger().println(x.getMessage() + ". " + new ScriptApprovalNote(text.length()).encode() + text); } catch (IOException x2) { LOGGER.log(Level.WARNING, null, x2); diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/UnapprovedUsageException.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/UnapprovedUsageException.java index bfb7a8a87..0d9ce627c 100644 --- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/UnapprovedUsageException.java +++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/UnapprovedUsageException.java @@ -32,7 +32,7 @@ public final class UnapprovedUsageException extends SecurityException { private final String hash; UnapprovedUsageException(String hash) { - super("script not yet approved for use"); + super(ScriptApproval.get().isForceSandbox() ? Messages.UnapprovedUsage_ForceSandBox() : Messages.UnapprovedUsage_NonApproved()); this.hash = hash; } diff --git a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/config.jelly b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/config.jelly index 057504ee7..221e597ce 100644 --- a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/config.jelly @@ -31,10 +31,12 @@ THE SOFTWARE. - - + + - + diff --git a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/Messages.properties b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/Messages.properties index 2d79849a4..dc0dcce18 100644 --- a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/Messages.properties +++ b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/Messages.properties @@ -2,7 +2,12 @@ ClasspathEntry.path.notExists=Specified path does not exist ClasspathEntry.path.notApproved=This classpath entry is not approved. Require an approval before execution. ClasspathEntry.path.noDirsAllowed=Class directories are not allowed as classpath entries. ScriptApprovalNote.message=Administrators can decide whether to approve or reject this signature. +ScriptApprovalNoteForceSandBox.message=Script signature is not in the default whitelist. ScriptApprovalLink.outstandingScript={0} scripts pending approval ScriptApprovalLink.outstandingSignature={0} signatures pending approval ScriptApprovalLink.outstandingClasspath={0} classpath entries pending approval -ScriptApprovalLink.dangerous={0} approved dangerous signatures \ No newline at end of file +ScriptApprovalLink.dangerous={0} approved dangerous signatures +ScriptApproval.PipelineMessage=A Jenkins administrator will need to approve this script before it can be used +ScriptApproval.ForceSandBoxMessage=Running scripts out of the sandbox is not allowed in the system +UnapprovedUsage.NonApproved=script not yet approved for use +UnapprovedUsage.ForceSandBox=Running scripts out of the sandbox is not allowed in the system diff --git a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/config.jelly b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/config.jelly index 43f54ce00..8d0d0ca76 100644 --- a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/config.jelly +++ b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/config.jelly @@ -1,4 +1,8 @@ - + + + + + diff --git a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/help-forceSandbox.html b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/help-forceSandbox.html new file mode 100644 index 000000000..06275feea --- /dev/null +++ b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/help-forceSandbox.html @@ -0,0 +1,5 @@ +
+

Controls whether the "Use Groovy Sandbox" is shown in the system to users without Overall/Administer permission.

+

This can be used to simplify the UX in highly secured environments where all Pipelines and any other Groovy execution are + required to run in the sandbox (i.e., running arbitrary code is never approved).

+
\ No newline at end of file diff --git a/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java b/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java index 9518cac43..8d99740b9 100644 --- a/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java +++ b/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest.java @@ -34,6 +34,7 @@ import org.apache.tools.ant.AntClassLoader; import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException; import org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry; +import org.jenkinsci.plugins.scriptsecurity.scripts.Messages; import org.htmlunit.html.HtmlForm; import org.htmlunit.html.HtmlFormUtil; import org.htmlunit.html.HtmlPage; @@ -182,6 +183,51 @@ 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 + */ + @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 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 sandboxes = config.getInputsByName("_.sandbox"); + HtmlCheckBoxInput sandboxcb = (HtmlCheckBoxInput) sandboxes.get(sandboxes.size() - 1); + assertTrue(sandboxcb.isChecked()); + + r.submit(config); + + List 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 @@ -210,6 +256,11 @@ private void addPostBuildAction(HtmlPage page) throws IOException { HtmlTextArea script = scripts.get(scripts.size() - 1); String groovy = "build.externalizableId"; script.setText(groovy); + List sandboxes = config.getInputsByName("_.sandbox"); + HtmlCheckBoxInput sandboxcb = (HtmlCheckBoxInput) sandboxes.get(sandboxes.size() - 1); + assertTrue(sandboxcb.isChecked()); + sandboxcb.setChecked(false); + r.submit(config); List publishers = p.getPublishersList(); assertEquals(1, publishers.size()); @@ -224,7 +275,22 @@ private void addPostBuildAction(HtmlPage page) throws IOException { assertEquals(1, pendingScripts.size()); // Test that the script is executable. If it's not, we will get an UnapprovedUsageException - assertThrows(UnapprovedUsageException.class, () -> ScriptApproval.get().using(groovy, GroovyLanguage.get())); + Exception ex = assertThrows(UnapprovedUsageException.class, + () -> ScriptApproval.get().using(groovy,GroovyLanguage.get())); + 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); + testSandboxDefault_with_ADMINISTER_privs(); } /** @@ -1325,6 +1391,9 @@ public void testScriptAtFieldInitializers() throws Exception { HtmlTextArea script = scripts.get(scripts.size() - 1); String groovy = "build.externalizableId"; script.setText(groovy); + List sandboxes = config.getInputsByName("_.sandbox"); + HtmlCheckBoxInput sandboxcb = (HtmlCheckBoxInput) sandboxes.get(sandboxes.size() - 1); + sandboxcb.setChecked(false); // nothing is approved or pending (no save) assertThat(ScriptApproval.get().getPendingScripts(), is(empty())); assertThat(ScriptApproval.get().getApprovedScriptHashes(), is(emptyArray())); diff --git a/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/JcascTest.java b/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/JcascTest.java index 67763a443..59b9fd11f 100644 --- a/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/JcascTest.java +++ b/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/JcascTest.java @@ -19,6 +19,7 @@ import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.hamcrest.core.StringContains.containsString; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class JcascTest { @@ -43,6 +44,7 @@ public void smokeTestEntry() throws Exception { assertThat(logger.getMessages(), containsInAnyOrder( containsString("Adding deprecated script hash " + "that will be converted on next use: fccae58c5762bdd15daca97318e9d74333203106"))); + assertTrue(ScriptApproval.get().isForceSandbox()); } @Test diff --git a/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java b/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java index 1405c0141..b8f1f218e 100644 --- a/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java +++ b/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java @@ -26,11 +26,19 @@ import org.htmlunit.html.HtmlPage; import org.htmlunit.html.HtmlTextArea; +import hudson.model.FreeStyleBuild; import hudson.model.FreeStyleProject; +import hudson.model.Item; import hudson.model.Result; +import hudson.model.User; +import hudson.security.ACL; +import hudson.security.ACLContext; +import hudson.security.Permission; import hudson.util.VersionNumber; +import hudson.util.FormValidation; import jenkins.model.Jenkins; import org.hamcrest.Matchers; +import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException; import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist; import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript; import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.TestGroovyRecorder; @@ -40,10 +48,12 @@ import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; import org.jvnet.hudson.test.LoggerRule; +import org.jvnet.hudson.test.MockAuthorizationStrategy; import org.jvnet.hudson.test.recipes.LocalData; import org.xml.sax.SAXException; import java.io.IOException; +import java.net.URL; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; @@ -53,7 +63,9 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class ScriptApprovalTest extends AbstractApprovalTest { @@ -195,6 +207,119 @@ public void reload() throws Exception { r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get())); } + @Test + public void forceSandboxTests() throws Exception { + r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); + + ScriptApproval.get().setForceSandbox(true); + + MockAuthorizationStrategy mockStrategy = new MockAuthorizationStrategy(); + mockStrategy.grant(Jenkins.READ).everywhere().to("devel"); + for (Permission p : Item.PERMISSIONS.getPermissions()) { + mockStrategy.grant(p).everywhere().to("devel"); + } + + mockStrategy.grant(Jenkins.READ).everywhere().to("admin"); + mockStrategy.grant(Jenkins.ADMINISTER).everywhere().to("admin"); + for (Permission p : Item.PERMISSIONS.getPermissions()) { + mockStrategy.grant(p).everywhere().to("admin"); + } + + r.jenkins.setAuthorizationStrategy(mockStrategy); + + try (ACLContext ctx = ACL.as(User.getById("devel", true))) { + assertTrue(ScriptApproval.get().isForceSandbox()); + assertTrue(ScriptApproval.get().isForceSandboxForCurrentUser()); + + final ApprovalContext ac = ApprovalContext.create(); + + //Insert new PendingScript - As the user is not admin and ForceSandbox is enabled, nothing should be added + { + ScriptApproval.get().configuring("testScript", GroovyLanguage.get(), ac, true); + assertTrue(ScriptApproval.get().getPendingScripts().isEmpty()); + } + + //Insert new PendingSignature - As the user is not admin and ForceSandbox is enabled, nothing should be added + { + ScriptApproval.get().accessRejected( + new RejectedAccessException("testSignatureType", "testSignatureDetails"), ac); + assertTrue(ScriptApproval.get().getPendingSignatures().isEmpty()); + } + + //Insert new Pending Classpath - As the user is not admin and ForceSandbox is enabled, nothing should be added + { + ClasspathEntry cpe = new ClasspathEntry("https://www.jenkins.io"); + ScriptApproval.get().configuring(cpe, ac); + ScriptApproval.get().addPendingClasspathEntry( + new ScriptApproval.PendingClasspathEntry("hash", new URL("https://www.jenkins.io"), ac)); + assertThrows(UnapprovedClasspathException.class, () -> ScriptApproval.get().using(cpe)); + // As we are forcing sandbox, none of the previous operations are able to create new pending ClasspathEntries + assertTrue(ScriptApproval.get().getPendingClasspathEntries().isEmpty()); + } + } + + try (ACLContext ctx = ACL.as(User.getById("admin", true))) { + assertTrue(ScriptApproval.get().isForceSandbox()); + assertFalse(ScriptApproval.get().isForceSandboxForCurrentUser()); + + final ApprovalContext ac = ApprovalContext.create(); + + //Insert new PendingScript - As the user is admin, the behavior does not change + { + ScriptApproval.get().configuring("testScript", GroovyLanguage.get(), ac, true); + assertEquals(1, ScriptApproval.get().getPendingScripts().size()); + } + + //Insert new PendingSignature - - As the user is admin, the behavior does not change + { + ScriptApproval.get().accessRejected( + new RejectedAccessException("testSignatureType", "testSignatureDetails"), ac); + assertEquals(1, ScriptApproval.get().getPendingSignatures().size()); + } + + //Insert new Pending ClassPatch - - As the user is admin, the behavior does not change + { + ClasspathEntry cpe = new ClasspathEntry("https://www.jenkins.io"); + ScriptApproval.get().configuring(cpe, ac); + ScriptApproval.get().addPendingClasspathEntry( + new ScriptApproval.PendingClasspathEntry("hash", new URL("https://www.jenkins.io"), ac)); + assertEquals(1, ScriptApproval.get().getPendingClasspathEntries().size()); + } + } + } + + @Test + public void forceSandboxScriptSignatureException() throws Exception { + ScriptApproval.get().setForceSandbox(true); + FreeStyleProject p = r.createFreeStyleProject("p"); + p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript("jenkins.model.Jenkins.instance", true, null))); + FreeStyleBuild b = r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()); + r.assertLogContains("Scripts not permitted to use staticMethod jenkins.model.Jenkins getInstance. " + Messages.ScriptApprovalNoteForceSandBox_message(), b); + } + + @Test + public void forceSandboxFormValidation() throws Exception { + r.jenkins.setSecurityRealm(r.createDummySecurityRealm()); + r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy(). + grant(Jenkins.READ, Item.READ).everywhere().to("dev")); + + try (ACLContext ctx = ACL.as(User.getById("devel", true))) { + ScriptApproval.get().setForceSandbox(true); + { + FormValidation result = ScriptApproval.get().checking("test", GroovyLanguage.get(), false); + assertEquals(FormValidation.Kind.WARNING, result.kind); + assertEquals(Messages.ScriptApproval_ForceSandBoxMessage(), result.getMessage()); + } + + ScriptApproval.get().setForceSandbox(false); + { + FormValidation result = ScriptApproval.get().checking("test", GroovyLanguage.get(), false); + assertEquals(FormValidation.Kind.WARNING, result.kind); + assertEquals(Messages.ScriptApproval_PipelineMessage(), result.getMessage()); + } + } + } + private Script script(String groovy) { return new Script(groovy); } diff --git a/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test.yaml b/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test.yaml index 366aa49de..ee6e9902c 100644 --- a/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test.yaml +++ b/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test.yaml @@ -4,3 +4,4 @@ security: - method java.net.URI getHost approvedScriptHashes: - fccae58c5762bdd15daca97318e9d74333203106 + forceSandbox: true \ No newline at end of file diff --git a/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test_expected.yaml b/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test_expected.yaml index 73bc4b2da..2eb11955a 100644 --- a/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test_expected.yaml +++ b/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test_expected.yaml @@ -2,3 +2,4 @@ approvedScriptHashes: - "fccae58c5762bdd15daca97318e9d74333203106" approvedSignatures: - "method java.net.URI getHost" +forceSandbox: true