Skip to content

Commit

Permalink
disable additional classpath entries when security is enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
daspilker committed Mar 24, 2017
1 parent 61f1d65 commit 35ba9ae
Show file tree
Hide file tree
Showing 7 changed files with 22 additions and 116 deletions.
6 changes: 3 additions & 3 deletions docs/Migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

### Script Security

Starting with Job DSL 1.55, script security for Job DSL scripts and classpath entries is enabled by default if Jenkins
Starting with Job DSL 1.55, script security for Job DSL scripts is enabled by default if Jenkins
security is enabled. As a consequence, DSL scripts have either to be approved by an Jenkins administrator or run in an
restricted sandbox. Classpath entries also have to be approved by an Jenkins administrator. To avoid loading arbitrary
code from the workspace without approval, the script directory is no longer added to the classpath.
restricted sandbox. To avoid loading arbitrary code from the workspace without approval, the script directory is not
added to the classpath and additional classpath entries are not supported when security is enabled.

To restore the old behavior, Job DSL script security can be disabled on the "Configure Global Security" page. But this
decision should be taken with care and only if understanding the consequences as it would allow users to run arbitrary
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@ public String getScriptApprovalWarning() {
return isSecurityEnabled() && !Jenkins.getInstance().hasPermission(Jenkins.RUN_SCRIPTS) ? Messages.ScriptSecurity_ScriptApprovalWarning() : "";
}

public String getClasspathApprovalWarning() {
return isSecurityEnabled() && !Jenkins.getInstance().hasPermission(Jenkins.RUN_SCRIPTS) ? Messages.ScriptSecurity_ClasspathApprovalWarning() : "";
}

@Initializer(before = InitMilestone.PLUGINS_STARTED)
public static void addAliases() {
Run.XSTREAM2.addCompatibilityAlias(
Expand All @@ -118,7 +114,7 @@ public static void addAliases() {
);
}

static boolean isSecurityEnabled() {
public boolean isSecurityEnabled() {
Jenkins jenkins = Jenkins.getInstance();
return jenkins.isUseSecurity() && jenkins.getDescriptorByType(GlobalJobDslSecurityConfiguration.class).isUseScriptSecurity();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public void setAdditionalClasspath(String additionalClasspath) {
}

void configure(Item ancestor) {
if (!sandbox && isUsingScriptText() && DescriptorImpl.isSecurityEnabled()) {
if (!sandbox && isUsingScriptText() && ((DescriptorImpl) getDescriptor()).isSecurityEnabled()) {
ScriptApproval.get().configuring(scriptText, GroovyLanguage.get(), ApprovalContext.create().withCurrentUser().withItem(ancestor));
}
}
Expand Down Expand Up @@ -273,7 +273,7 @@ public void perform(@Nonnull Run<?, ?> run, @Nonnull FilePath workspace, @Nonnul
);

JenkinsDslScriptLoader dslScriptLoader;
if (DescriptorImpl.isSecurityEnabled()) {
if (((DescriptorImpl) getDescriptor()).isSecurityEnabled()) {
if (sandbox) {
dslScriptLoader = new SandboxDslScriptLoader(jobManagement, run.getParent());
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package javaposse.jobdsl.plugin

import javaposse.jobdsl.dsl.DslException
import javaposse.jobdsl.dsl.GeneratedItems
import javaposse.jobdsl.dsl.JobManagement
import javaposse.jobdsl.dsl.ScriptRequest
import org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval
import org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedClasspathException

abstract class SecureDslScriptLoader extends JenkinsDslScriptLoader {
protected SecureDslScriptLoader(JobManagement jobManagement) {
Expand All @@ -20,17 +16,8 @@ abstract class SecureDslScriptLoader extends JenkinsDslScriptLoader {

protected Collection<ScriptRequest> createSecureScriptRequests(Collection<ScriptRequest> scriptRequests) {
scriptRequests.collect {
// first root always points to workspace directory -> remove that because directories are considered unsafe
URL[] secureUrlRoots = it.urlRoots.length > 1 ? it.urlRoots[1..-1] : []
secureUrlRoots.each { URL url ->
try {
ScriptApproval.get().using(new ClasspathEntry(url.toString()))
} catch (UnapprovedClasspathException e) {
throw new DslException(e.message, e)
}
}

new ScriptRequest(it.location, it.body, secureUrlRoots, it.ignoreExisting, it.scriptPath)
// it is not safe to use additional classpath entries
new ScriptRequest(it.location, it.body, new URL[0], it.ignoreExisting, it.scriptPath)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
<f:checkbox name="ignoreMissingFiles" checked="${instance.ignoreMissingFiles}"/>
</f:entry>
</f:radioBlock>
<f:entry title="Use Groovy Sandbox:" field="sandbox">
<f:checkbox default="${!h.hasPermission(app.RUN_SCRIPTS)}"/>
</f:entry>
<j:if test="${descriptor.isSecurityEnabled()}">
<f:entry title="Use Groovy Sandbox:" field="sandbox">
<f:checkbox default="${!h.hasPermission(app.RUN_SCRIPTS)}"/>
</f:entry>
</j:if>
<f:entry title="Action for existing jobs and views:" field="ignoreExisting">
<f:checkbox name="ignoreExisting" title="Ignore changes" checked="${instance.ignoreExisting}"
description="What to do with previously generated jobs and views when generated config is not the same?"/>
Expand All @@ -37,9 +39,11 @@
<f:select default="JENKINS_ROOT"/>
</f:entry>

<f:entry title="Additional classpath" field="additionalClasspath" description="${descriptor.classpathApprovalWarning}">
<f:expandableTextbox/>
</f:entry>
<j:if test="${!descriptor.isSecurityEnabled()}">
<f:entry title="Additional classpath" field="additionalClasspath">
<f:expandableTextbox/>
</f:entry>
</j:if>

<f:entry title="Fail build if a plugin must be installed or updated:" field="failOnMissingPlugin">
<f:checkbox name="failOnMissingPlugin" checked="${instance.failOnMissingPlugin}"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,3 @@ UpdateExistingView.ViewTypeDoesNotMatch=\
ScriptSecurity.ScriptApprovalWarning=\
New or modified scripts must either be approved by an Jenkins administrator before they can be used or they must be \
run in the restricted sandbox.
ScriptSecurity.ClasspathApprovalWarning=\
New or modified classpath entries must be approved by an Jenkins administrator before they can be used.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import jenkins.model.Jenkins
import org.jenkinsci.plugins.configfiles.GlobalConfigFiles
import org.jenkinsci.plugins.configfiles.custom.CustomConfig
import org.jenkinsci.plugins.managedscripts.PowerShellConfig
import org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage
import org.junit.Rule
import org.junit.rules.TemporaryFolder
Expand All @@ -36,7 +35,6 @@ import org.jvnet.hudson.test.MockAuthorizationStrategy
import org.jvnet.hudson.test.WithoutJenkins
import spock.lang.Specification
import spock.lang.Unroll
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval

import static hudson.model.Result.FAILURE
Expand Down Expand Up @@ -1307,7 +1305,8 @@ class ExecuteDslScriptsSpec extends Specification {
String script = 'job("test")'

jenkinsRule.instance.securityRealm = jenkinsRule.createDummySecurityRealm()
jenkinsRule.instance.authorizationStrategy = new MockAuthorizationStrategy().grant(Jenkins.READ, Item.READ, Item.CONFIGURE).everywhere().to('dev')
jenkinsRule.instance.authorizationStrategy = new MockAuthorizationStrategy()
.grant(Jenkins.READ, Item.READ, Item.CONFIGURE).everywhere().to('dev')

FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed')
job.buildersList.add(new ExecuteDslScripts(scriptText: script))
Expand All @@ -1326,51 +1325,13 @@ class ExecuteDslScriptsSpec extends Specification {
build.result == SUCCESS
}

def 'can not run approved script with pending classpath approval'() {
setup:
String script = 'job("test")'
File jar = temporaryFolder.newFile()

ScriptApproval.get().configuring(script, GroovyLanguage.get(), ApprovalContext.create())

jenkinsRule.instance.authorizationStrategy = new MockAuthorizationStrategy()

FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed')
job.buildersList.add(new ExecuteDslScripts(scriptText: script, additionalClasspath: jar.absolutePath))

when:
FreeStyleBuild build = job.scheduleBuild2(0).get()

then:
build.result == FAILURE
}

def 'run approved script with approved classpath'() {
setup:
String script = 'job("test")'
File jar = temporaryFolder.newFile()

ScriptApproval.get().configuring(script, GroovyLanguage.get(), ApprovalContext.create())
ScriptApproval.get().configuring(new ClasspathEntry(jar.absolutePath), ApprovalContext.create())

jenkinsRule.instance.authorizationStrategy = new MockAuthorizationStrategy()

FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed')
job.buildersList.add(new ExecuteDslScripts(scriptText: script, additionalClasspath: jar.absolutePath))

when:
FreeStyleBuild build = job.scheduleBuild2(0).get()

then:
build.result == SUCCESS
}

def 'run script in sandbox'() {
setup:
String script = 'job("test") { description("foo") }'

jenkinsRule.instance.securityRealm = jenkinsRule.createDummySecurityRealm()
jenkinsRule.instance.authorizationStrategy = new MockAuthorizationStrategy().grant(Jenkins.READ, Item.READ, Item.CONFIGURE).everywhere().to('dev')
jenkinsRule.instance.authorizationStrategy = new MockAuthorizationStrategy()
.grant(Jenkins.READ, Item.READ, Item.CONFIGURE).everywhere().to('dev')

FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed')
job.buildersList.add(new ExecuteDslScripts(scriptText: script, sandbox: true))
Expand Down Expand Up @@ -1405,46 +1366,6 @@ class ExecuteDslScriptsSpec extends Specification {
build.result == FAILURE
}

def 'run script in sandbox with approved classpath'() {
setup:
String script = 'job("test") { description("foo") }'
File jar = temporaryFolder.newFile()

ScriptApproval.get().configuring(new ClasspathEntry(jar.absolutePath), ApprovalContext.create())

jenkinsRule.instance.authorizationStrategy = new MockAuthorizationStrategy()

FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed')
job.buildersList.add(new ExecuteDslScripts(
scriptText: script, sandbox: true, additionalClasspath: jar.absolutePath
))

when:
FreeStyleBuild build = job.scheduleBuild2(0).get()

then:
build.result == SUCCESS
}

def 'can not run script in sandbox with pending classpath approval'() {
setup:
String script = 'job("test")'
File jar = temporaryFolder.newFile()

jenkinsRule.instance.authorizationStrategy = new MockAuthorizationStrategy()

FreeStyleProject job = jenkinsRule.createFreeStyleProject('seed')
job.buildersList.add(new ExecuteDslScripts(
scriptText: script, sandbox: true, additionalClasspath: jar.absolutePath
))

when:
FreeStyleBuild build = job.scheduleBuild2(0).get()

then:
build.result == FAILURE
}

def 'JENKINS-39137'() {
setup:
Folder folder = jenkinsRule.jenkins.createProject(Folder, 'folder')
Expand Down

0 comments on commit 35ba9ae

Please sign in to comment.