├── .github
├── CODEOWNERS
├── dependabot.yml
└── workflows
│ ├── cd.yaml
│ └── jenkins-security-scan.yml
├── .gitignore
├── .mvn
├── extensions.xml
└── maven.config
├── Jenkinsfile
├── README.md
├── pom.xml
└── src
├── main
├── java
│ └── org
│ │ └── jenkinsci
│ │ └── plugins
│ │ └── scriptsecurity
│ │ ├── sandbox
│ │ ├── RejectedAccessException.java
│ │ ├── Whitelist.java
│ │ ├── groovy
│ │ │ ├── ClassLoaderWhitelist.java
│ │ │ ├── GroovyCallSiteSelector.java
│ │ │ ├── GroovySandbox.java
│ │ │ ├── RejectASTTransformsCustomizer.java
│ │ │ ├── SandboxInterceptor.java
│ │ │ ├── SandboxResolvingClassLoader.java
│ │ │ └── SecureGroovyScript.java
│ │ └── whitelists
│ │ │ ├── AbstractWhitelist.java
│ │ │ ├── AclAwareWhitelist.java
│ │ │ ├── AnnotatedWhitelist.java
│ │ │ ├── BlanketWhitelist.java
│ │ │ ├── EnumeratingWhitelist.java
│ │ │ ├── GenericWhitelist.java
│ │ │ ├── ProxyWhitelist.java
│ │ │ ├── StaticWhitelist.java
│ │ │ └── Whitelisted.java
│ │ └── scripts
│ │ ├── ApprovalContext.java
│ │ ├── ApprovalListener.java
│ │ ├── ClasspathEntry.java
│ │ ├── Language.java
│ │ ├── ScriptApproval.java
│ │ ├── ScriptApprovalLink.java
│ │ ├── ScriptApprovalNote.java
│ │ ├── UnapprovedClasspathException.java
│ │ ├── UnapprovedUsageException.java
│ │ └── languages
│ │ ├── GroovyLanguage.java
│ │ ├── GroovyShellLanguage.java
│ │ ├── GroovyXmlLanguage.java
│ │ ├── JellyLanguage.java
│ │ ├── JexlLanguage.java
│ │ └── SystemCommandLanguage.java
└── resources
│ ├── index.jelly
│ └── org
│ └── jenkinsci
│ └── plugins
│ └── scriptsecurity
│ ├── sandbox
│ ├── groovy
│ │ ├── Messages.properties
│ │ └── SecureGroovyScript
│ │ │ ├── JENKINS-15604.js
│ │ │ ├── config.jelly
│ │ │ ├── help-classpath.html
│ │ │ └── help-sandbox.html
│ └── whitelists
│ │ ├── blacklist
│ │ ├── generic-whitelist
│ │ └── jenkins-whitelist
│ └── scripts
│ ├── ApprovalContext
│ └── index.jelly
│ ├── ClasspathEntry
│ ├── config.jelly
│ ├── help-path.html
│ └── resources.js
│ ├── Messages.properties
│ └── ScriptApproval
│ ├── FormValidationPageDecorator
│ ├── header.jelly
│ └── validate.js
│ ├── config.jelly
│ ├── deprecated-approvedClasspaths-clear-btn-hide.js
│ ├── deprecated-approvedClasspaths-clear-btn-show.js
│ ├── help-forceSandbox.html
│ ├── index.jelly
│ └── script-approval.js
└── test
├── java
└── org
│ └── jenkinsci
│ └── plugins
│ └── scriptsecurity
│ ├── sandbox
│ ├── groovy
│ │ ├── GroovyCallSiteSelectorTest.java
│ │ ├── GroovyLanguageCoverageTest.java
│ │ ├── GroovyMemoryLeakTest.java
│ │ ├── SandboxInterceptorTest.java
│ │ ├── SandboxResolvingClassLoaderTest.java
│ │ ├── SecureGroovyScriptTest.java
│ │ └── TestGroovyRecorder.java
│ └── whitelists
│ │ ├── EnumeratingWhitelistTest.java
│ │ ├── GenericWhitelistTest.java
│ │ ├── JenkinsWhitelistTest.java
│ │ ├── ProxyWhitelistTest.java
│ │ └── StaticWhitelistTest.java
│ └── scripts
│ ├── AbstractApprovalTest.java
│ ├── Approvable.java
│ ├── ClasspathEntryTest.java
│ ├── EntryApprovalTest.java
│ ├── HasherScriptApprovalTest.java
│ ├── JcascTest.java
│ ├── Manager.java
│ ├── ScriptApprovalLoadingTest.java
│ ├── ScriptApprovalNoteTest.java
│ └── ScriptApprovalTest.java
└── resources
└── org
└── jenkinsci
└── plugins
└── scriptsecurity
├── sandbox
└── groovy
│ ├── SandboxInterceptorTest
│ └── all.groovy
│ ├── SecureGroovyScriptTest
│ ├── README.md
│ ├── script-security-plugin-testjar.jar
│ └── updated
│ │ └── script-security-plugin-testjar.jar
│ ├── TestGroovyRecorder
│ └── config.jelly
│ └── somejar.jar
└── scripts
├── ScriptApprovalTest
├── dangerousApproved.zip
├── malformedScriptApproval.zip
├── reload
│ └── scriptApproval.xml
└── upgradeSmokes
│ └── scriptApproval.xml
├── smoke_test.yaml
└── smoke_test_expected.yaml
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @jenkinsci/script-security-plugin-developers
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 | - package-ecosystem: github-actions
8 | directory: /
9 | schedule:
10 | interval: daily
11 |
--------------------------------------------------------------------------------
/.github/workflows/cd.yaml:
--------------------------------------------------------------------------------
1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins
2 |
3 | name: cd
4 | on:
5 | workflow_dispatch:
6 | check_run:
7 | types:
8 | - completed
9 |
10 | jobs:
11 | maven-cd:
12 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1
13 | secrets:
14 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
15 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/jenkins-security-scan.yml:
--------------------------------------------------------------------------------
1 | name: Jenkins Security Scan
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 | types: [ opened, synchronize, reopened ]
9 | workflow_dispatch:
10 |
11 | permissions:
12 | security-events: write
13 | contents: read
14 | actions: read
15 |
16 | jobs:
17 | security-scan:
18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2
19 | with:
20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate.
21 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | work
3 | /.classpath
4 | /.project
5 | /.settings/
6 | .idea
7 | *.iml
8 | .*.sw*
9 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | io.jenkins.tools.incrementals
4 | git-changelist-maven-extension
5 | 1.8
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | -Pconsume-incrementals
2 | -Pmight-produce-incrementals
3 | -Dchangelist.format=%d.v%s
4 |
--------------------------------------------------------------------------------
/Jenkinsfile:
--------------------------------------------------------------------------------
1 | buildPlugin(
2 | useContainerAgent: true,
3 | configurations: [
4 | [platform: 'linux', jdk: 21],
5 | [platform: 'windows', jdk: 17],
6 | ])
7 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | org.jenkins-ci.plugins
6 | plugin
7 | 5.10
8 |
9 |
10 |
11 | script-security
12 | ${changelist}
13 | hpi
14 | Script Security Plugin
15 | https://github.com/jenkinsci/${project.artifactId}-plugin
16 |
17 | 999999-SNAPSHOT
18 |
19 | 2.479
20 | ${jenkins.baseline}.1
21 | jenkinsci/${project.artifactId}-plugin
22 |
23 |
24 |
25 | MIT License
26 | https://opensource.org/licenses/MIT
27 |
28 |
29 |
30 | scm:git:https://github.com/${gitHubRepo}.git
31 | scm:git:git@github.com:${gitHubRepo}.git
32 | https://github.com/${gitHubRepo}
33 | ${scmTag}
34 |
35 |
36 |
37 |
38 | repo.jenkins-ci.org
39 | https://repo.jenkins-ci.org/public/
40 |
41 |
42 |
43 |
44 |
45 | repo.jenkins-ci.org
46 | https://repo.jenkins-ci.org/public/
47 |
48 |
49 |
50 |
51 |
52 |
53 | io.jenkins.tools.bom
54 | bom-${jenkins.baseline}.x
55 | 3893.v213a_42768d35
56 | import
57 | pom
58 |
59 |
60 |
61 |
62 |
63 |
64 | org.kohsuke
65 | groovy-sandbox
66 | 1.34
67 |
68 |
69 | org.codehaus.groovy
70 | groovy
71 |
72 |
73 |
74 |
75 | io.jenkins.plugins
76 | caffeine-api
77 |
78 |
79 | io.jenkins
80 | configuration-as-code
81 | test
82 |
83 |
84 | io.jenkins.configuration-as-code
85 | test-harness
86 | test
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/RejectedAccessException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox;
26 |
27 | import edu.umd.cs.findbugs.annotations.CheckForNull;
28 | import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox;
29 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist;
30 |
31 | /**
32 | * Thrown when access to a language element was not permitted.
33 | * @see GroovySandbox#runInSandbox(Runnable, Whitelist)
34 | */
35 | public final class RejectedAccessException extends SecurityException {
36 |
37 | private final String signature;
38 | private boolean dangerous;
39 |
40 | /**
41 | * Rejects access to a well-described script element.
42 | * Normally called from {@link StaticWhitelist#rejectMethod} or similar.
43 | * @param type e.g. {@code field}
44 | * @param details e.g. {@code some.Class fieldName}
45 | */
46 | public RejectedAccessException(String type, String details) {
47 | super("Scripts not permitted to use " + type + " " + details);
48 | signature = type + " " + details;
49 | }
50 |
51 | /**
52 | * Rejects access to a well-described script element.
53 | * Normally called from {@link StaticWhitelist#rejectMethod} or similar.
54 | * @param type e.g. {@code field}
55 | * @param details e.g. {@code some.Class fieldName}
56 | * @param info some additional information if appropriate
57 | */
58 | public RejectedAccessException(String type, String details, String info) {
59 | super("Scripts not permitted to use " + type + " " + details + " (" + info + ")");
60 | signature = type + " " + details;
61 | }
62 |
63 | /**
64 | * Rejects access to something which the current {@link StaticWhitelist} format could not describe.
65 | * @param message a descriptive message in no particular format
66 | */
67 | public RejectedAccessException(String message) {
68 | super(message);
69 | signature = null;
70 | }
71 |
72 | /**
73 | * Gets the signature of the member to which access was rejected.
74 | * @return a line in the format understood by {@link StaticWhitelist}, or null in case something was rejected for which a known exemption is not available
75 | */
76 | public @CheckForNull String getSignature() {
77 | return signature;
78 | }
79 |
80 | /**
81 | * True if {@link #getSignature} is non-null but it would be a bad idea for an administrator to approve it.
82 | * @since 1.16
83 | */
84 | public boolean isDangerous() {
85 | return dangerous;
86 | }
87 |
88 | /**
89 | * You may set this flag if you think it would be a security risk for this signature to be approved.
90 | * @throws IllegalArgumentException in case you tried to set this to true when using the nonspecific {@link #RejectedAccessException(String)} constructor
91 | * @since 1.16
92 | */
93 | public void setDangerous(boolean dangerous) {
94 | if (signature == null && dangerous) {
95 | throw new IllegalArgumentException("you cannot mark this dangerous without specifying a signature");
96 | }
97 | this.dangerous = dangerous;
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/Whitelist.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox;
26 |
27 | import hudson.Extension;
28 | import hudson.ExtensionList;
29 | import hudson.ExtensionPoint;
30 | import java.lang.reflect.Constructor;
31 | import java.lang.reflect.Field;
32 | import java.lang.reflect.Method;
33 | import java.util.Map;
34 | import java.util.WeakHashMap;
35 | import java.util.logging.Level;
36 | import java.util.logging.Logger;
37 | import edu.umd.cs.findbugs.annotations.CheckForNull;
38 | import edu.umd.cs.findbugs.annotations.NonNull;
39 | import jenkins.model.Jenkins;
40 | import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox;
41 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.ProxyWhitelist;
42 |
43 | /**
44 | * Determines which methods and similar members which scripts may call.
45 | */
46 | public abstract class Whitelist implements ExtensionPoint {
47 |
48 | private static final Logger LOGGER = Logger.getLogger(Whitelist.class.getName());
49 |
50 | /**
51 | * Checks whether a given virtual method may be invoked.
52 | *
Note that {@code method} should not be implementing or overriding a method in a supertype;
53 | * in such a case the caller must pass that supertype method instead.
54 | * In other words, call site selection is the responsibility of the caller (such as {@link GroovySandbox}), not the whitelist.
55 | * @param method a method defined in the JVM
56 | * @param receiver {@code this}, the receiver of the method call
57 | * @param args zero or more arguments
58 | * @return true to allow the method to be called, false to reject it
59 | */
60 | public abstract boolean permitsMethod(@NonNull Method method, @NonNull Object receiver, @NonNull Object[] args);
61 |
62 | public abstract boolean permitsConstructor(@NonNull Constructor> constructor, @NonNull Object[] args);
63 |
64 | public abstract boolean permitsStaticMethod(@NonNull Method method, @NonNull Object[] args);
65 |
66 | public abstract boolean permitsFieldGet(@NonNull Field field, @NonNull Object receiver);
67 |
68 | public abstract boolean permitsFieldSet(@NonNull Field field, @NonNull Object receiver, @CheckForNull Object value);
69 |
70 | public abstract boolean permitsStaticFieldGet(@NonNull Field field);
71 |
72 | public abstract boolean permitsStaticFieldSet(@NonNull Field field, @CheckForNull Object value);
73 |
74 | /**
75 | * Checks for all whitelists registered as {@link Extension}s and aggregates them.
76 | * @return an aggregated default list
77 | */
78 | public static synchronized @NonNull Whitelist all() {
79 | Jenkins j = Jenkins.getInstanceOrNull();
80 | if (j == null) {
81 | LOGGER.log(Level.WARNING, "No Jenkins.instance", new Throwable("here"));
82 | return new ProxyWhitelist();
83 | }
84 | Whitelist all = allByJenkins.get(j);
85 | if (all == null) {
86 | ExtensionList allWhitelists = j.getExtensionList(Whitelist.class);
87 | if (allWhitelists.isEmpty()) {
88 | LOGGER.log(Level.WARNING, "No Whitelist instances registered", new Throwable("here"));
89 | return new ProxyWhitelist();
90 | } else {
91 | LOGGER.fine(() -> "Loading whitelists: " + allWhitelists);
92 | }
93 | all = new ProxyWhitelist(allWhitelists);
94 | allByJenkins.put(j, all);
95 | }
96 | return all;
97 | }
98 | private static final Map allByJenkins = new WeakHashMap<>();
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/ClassLoaderWhitelist.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;
2 |
3 | import edu.umd.cs.findbugs.annotations.NonNull;
4 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
5 |
6 | import java.lang.reflect.Constructor;
7 | import java.lang.reflect.Field;
8 | import java.lang.reflect.Method;
9 | import java.lang.reflect.Modifier;
10 |
11 | /**
12 | * {@link Whitelist} that allows everything defined from a specific classloader.
13 | *
14 | * @author Jesse Glick
15 | */
16 | public final class ClassLoaderWhitelist extends Whitelist {
17 | private final ClassLoader scriptLoader;
18 |
19 | public ClassLoaderWhitelist(ClassLoader scriptLoader) {
20 | this.scriptLoader = scriptLoader;
21 | }
22 |
23 | private boolean permits(Class> declaringClass) {
24 | return declaringClass.getClassLoader() == scriptLoader;
25 | }
26 |
27 | @Override public boolean permitsMethod(@NonNull Method method, @NonNull Object receiver, @NonNull Object[] args) {
28 | return permits(method.getDeclaringClass()) && !isIllegalSyntheticMethod(method);
29 | }
30 |
31 | @Override public boolean permitsConstructor(@NonNull Constructor> constructor, @NonNull Object[] args) {
32 | return permits(constructor.getDeclaringClass()) && !isIllegalSyntheticConstructor(constructor);
33 | }
34 |
35 | @Override public boolean permitsStaticMethod(@NonNull Method method, @NonNull Object[] args) {
36 | return permits(method.getDeclaringClass()) && !isIllegalSyntheticMethod(method);
37 | }
38 |
39 | @Override public boolean permitsFieldGet(@NonNull Field field, @NonNull Object receiver) {
40 | return permits(field.getDeclaringClass()) && !isIllegalSyntheticField(field);
41 | }
42 |
43 | @Override public boolean permitsFieldSet(@NonNull Field field, @NonNull Object receiver, Object value) {
44 | return permits(field.getDeclaringClass()) && !isIllegalSyntheticField(field);
45 | }
46 |
47 | @Override public boolean permitsStaticFieldGet(@NonNull Field field) {
48 | return permits(field.getDeclaringClass()) && !isIllegalSyntheticField(field);
49 | }
50 |
51 | @Override public boolean permitsStaticFieldSet(@NonNull Field field, Object value) {
52 | return permits(field.getDeclaringClass()) && !isIllegalSyntheticField(field);
53 | }
54 |
55 | /**
56 | * Checks whether a given field was synthetically created by the Groovy compiler and should be inaccessible even if
57 | * it is declared by a class defined by the specified class loader.
58 | */
59 | private static boolean isIllegalSyntheticField(Field field) {
60 | if (!field.isSynthetic()) {
61 | return false;
62 | }
63 | Class> declaringClass = field.getDeclaringClass();
64 | Class> enclosingClass = declaringClass.getEnclosingClass();
65 | if (field.getType() == enclosingClass && field.getName().startsWith("this$")) {
66 | // Synthetic field added to inner classes to reference the outer class.
67 | return false;
68 | } else if (declaringClass.isEnum() && Modifier.isStatic(field.getModifiers()) && field.getName().equals("$VALUES")) {
69 | // Synthetic field added by Groovy to enum classes to hold the enum constants.
70 | return false;
71 | }
72 | return true;
73 | }
74 |
75 | /**
76 | * Checks whether a given method was synthetically created by the Groovy compiler and should be inaccessible even
77 | * if it is declared by a class defined by the specified class loader.
78 | */
79 | private static boolean isIllegalSyntheticMethod(Method method) {
80 | if (!method.isSynthetic()) {
81 | return false;
82 | } else if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().isEnum() && method.getName().equals("$INIT")) {
83 | // Synthetic method added by Groovy to enum classes used to initialize the enum constants.
84 | return false;
85 | }
86 | return true;
87 | }
88 |
89 | /**
90 | * Checks whether a given constructor was created by the Groovy compiler (or groovy-sandbox) and
91 | * should be inaccessible even if it is declared by a class defined by the specified class loader.
92 | */
93 | private static boolean isIllegalSyntheticConstructor(Constructor constructor) {
94 | if (!constructor.isSynthetic()) {
95 | return false;
96 | }
97 | Class> declaringClass = constructor.getDeclaringClass();
98 | Class> enclosingClass = declaringClass.getEnclosingClass();
99 | if (enclosingClass != null && constructor.getParameters().length > 0 && constructor.getParameterTypes()[0] == enclosingClass) {
100 | // Synthetic constructor added by Groovy to anonymous classes.
101 | return false;
102 | }
103 | return true;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/RejectASTTransformsCustomizer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2018, CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;
26 |
27 | import groovy.lang.Grab;
28 | import groovy.lang.GrabConfig;
29 | import groovy.lang.GrabExclude;
30 | import groovy.lang.GrabResolver;
31 | import groovy.lang.Grapes;
32 | import groovy.transform.ASTTest;
33 | import groovy.transform.AnnotationCollector;
34 | import org.codehaus.groovy.ast.AnnotatedNode;
35 | import org.codehaus.groovy.ast.AnnotationNode;
36 | import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
37 | import org.codehaus.groovy.ast.ClassNode;
38 | import org.codehaus.groovy.ast.ImportNode;
39 | import org.codehaus.groovy.ast.ModuleNode;
40 | import org.codehaus.groovy.classgen.GeneratorContext;
41 | import org.codehaus.groovy.control.CompilationFailedException;
42 | import org.codehaus.groovy.control.CompilePhase;
43 | import org.codehaus.groovy.control.SourceUnit;
44 | import org.codehaus.groovy.control.customizers.CompilationCustomizer;
45 |
46 | import java.util.ArrayList;
47 | import java.util.Arrays;
48 | import java.util.Collections;
49 | import java.util.List;
50 |
51 | public class RejectASTTransformsCustomizer extends CompilationCustomizer {
52 | private static final List BLOCKED_TRANSFORMS = Collections.unmodifiableList(Arrays.asList(ASTTest.class.getCanonicalName(), Grab.class.getCanonicalName(),
53 | GrabConfig.class.getCanonicalName(), GrabExclude.class.getCanonicalName(), GrabResolver.class.getCanonicalName(),
54 | Grapes.class.getCanonicalName(), AnnotationCollector.class.getCanonicalName()));
55 |
56 | public RejectASTTransformsCustomizer() {
57 | super(CompilePhase.CONVERSION);
58 | }
59 |
60 | @Override
61 | public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
62 | new RejectASTTransformsVisitor(source).visitClass(classNode);
63 | }
64 |
65 | // Note: Methods in this visitor that override methods from the superclass should call the implementation from the
66 | // superclass to ensure that any nested AST nodes are traversed.
67 | private static class RejectASTTransformsVisitor extends ClassCodeVisitorSupport {
68 | private SourceUnit source;
69 |
70 | public RejectASTTransformsVisitor(SourceUnit source) {
71 | this.source = source;
72 | }
73 |
74 | @Override
75 | protected SourceUnit getSourceUnit() {
76 | return source;
77 | }
78 |
79 | @Override
80 | public void visitImports(ModuleNode node) {
81 | if (node != null) {
82 | for (ImportNode importNode : node.getImports()) {
83 | checkImportForBlockedAnnotation(importNode);
84 | }
85 | for (ImportNode importStaticNode : node.getStaticImports().values()) {
86 | checkImportForBlockedAnnotation(importStaticNode);
87 | }
88 | }
89 | super.visitImports(node);
90 | }
91 |
92 | private void checkImportForBlockedAnnotation(ImportNode node) {
93 | if (node != null && node.getType() != null) {
94 | for (String blockedAnnotation : getBlockedTransforms()) {
95 | if (blockedAnnotation.equals(node.getType().getName()) || blockedAnnotation.endsWith("." + node.getType().getName())) {
96 | throw new SecurityException("Annotation " + node.getType().getName() + " cannot be used in the sandbox.");
97 | }
98 | }
99 | }
100 | }
101 |
102 | /**
103 | * If the node is annotated with one of the blocked transform annotations, throw a security exception.
104 | *
105 | * @param node the node to process
106 | */
107 | @Override
108 | public void visitAnnotations(AnnotatedNode node) {
109 | for (AnnotationNode an : node.getAnnotations()) {
110 | for (String blockedAnnotation : getBlockedTransforms()) {
111 | if (blockedAnnotation.equals(an.getClassNode().getName()) || blockedAnnotation.endsWith("." + an.getClassNode().getName())) {
112 | throw new SecurityException("Annotation " + an.getClassNode().getName() + " cannot be used in the sandbox.");
113 | }
114 | }
115 | }
116 | super.visitAnnotations(node);
117 | }
118 | }
119 |
120 | private static List getBlockedTransforms() {
121 | List blocked = new ArrayList<>(BLOCKED_TRANSFORMS);
122 |
123 | String additionalBlocked = System.getProperty(RejectASTTransformsCustomizer.class.getName() + ".ADDITIONAL_BLOCKED_TRANSFORMS");
124 |
125 | if (additionalBlocked != null) {
126 | for (String b : additionalBlocked.split(",")) {
127 | blocked.add(b.trim());
128 | }
129 | }
130 |
131 | return blocked;
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/AbstractWhitelist.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import java.lang.reflect.Constructor;
28 | import java.lang.reflect.Field;
29 | import java.lang.reflect.Method;
30 |
31 | import edu.umd.cs.findbugs.annotations.NonNull;
32 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
33 |
34 | /**
35 | * Convenience whitelist base class that denies everything by default.
36 | * Thus you need only override things you wish to explicitly allow.
37 | * Also reduces the risk of incompatibilities in case further {@code abstract} methods are added to {@link Whitelist}.
38 | */
39 | public abstract class AbstractWhitelist extends Whitelist {
40 |
41 | @Override public boolean permitsMethod(@NonNull Method method, @NonNull Object receiver, @NonNull Object[] args) {
42 | return false;
43 | }
44 |
45 | @Override public boolean permitsConstructor(@NonNull Constructor> constructor, @NonNull Object[] args) {
46 | return false;
47 | }
48 |
49 | @Override public boolean permitsStaticMethod(@NonNull Method method, @NonNull Object[] args) {
50 | return false;
51 | }
52 |
53 | @Override public boolean permitsFieldSet(@NonNull Field field, @NonNull Object receiver, Object value) {
54 | return false;
55 | }
56 |
57 | @Override public boolean permitsFieldGet(@NonNull Field field, @NonNull Object receiver) {
58 | return false;
59 | }
60 |
61 | @Override public boolean permitsStaticFieldSet(@NonNull Field field, Object value) {
62 | return false;
63 | }
64 |
65 | @Override public boolean permitsStaticFieldGet(@NonNull Field field) {
66 | return false;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/AclAwareWhitelist.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import hudson.security.ACL;
29 | import java.lang.reflect.Constructor;
30 | import java.lang.reflect.Field;
31 | import java.lang.reflect.Method;
32 | import jenkins.model.Jenkins;
33 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
34 |
35 | /**
36 | * Delegating whitelist which allows certain calls to be made only when a non-{@link ACL#SYSTEM2} user is making them.
37 | * First there is a list of unrestricted signatures; these can always be run.
38 | *
Then there is a (probably much smaller) list of restricted signatures.
39 | * These can be run only when the {@linkplain Jenkins#getAuthentication2 current user} is a real user or even {@linkplain Jenkins#ANONYMOUS2}, but not when {@link ACL#SYSTEM2}.
40 | * Restricted methods should be limited to those which actually perform a permissions check, typically using {@link ACL#checkPermission}.
41 | * Allowing the system pseudo-user to run these would be dangerous, since we do not know “on whose behalf” a script is running, and this “user” is permitted to do anything.
42 | */
43 | public class AclAwareWhitelist extends Whitelist {
44 |
45 | private final Whitelist unrestricted, restricted;
46 |
47 | /**
48 | * Creates a delegating whitelist.
49 | * @param unrestricted a general whitelist; anything permitted by this one will be permitted in any context
50 | * @param restricted a whitelist of method/constructor calls (field accesses never consulted) for which ACL checks are expected
51 | */
52 | public AclAwareWhitelist(Whitelist unrestricted, Whitelist restricted) {
53 | this.unrestricted = unrestricted;
54 | this.restricted = restricted;
55 | }
56 |
57 | private static boolean authenticated() {
58 | return !ACL.SYSTEM2.equals(Jenkins.getAuthentication2());
59 | }
60 |
61 | @Override public boolean permitsMethod(@NonNull Method method, @NonNull Object receiver, @NonNull Object[] args) {
62 | return unrestricted.permitsMethod(method, receiver, args) || authenticated() && restricted.permitsMethod(method, receiver, args);
63 | }
64 |
65 | @Override public boolean permitsConstructor(@NonNull Constructor> constructor, @NonNull Object[] args) {
66 | return unrestricted.permitsConstructor(constructor, args) || authenticated() && restricted.permitsConstructor(constructor, args);
67 | }
68 |
69 | @Override public boolean permitsStaticMethod(@NonNull Method method, @NonNull Object[] args) {
70 | return unrestricted.permitsStaticMethod(method, args) || authenticated() && restricted.permitsStaticMethod(method, args);
71 | }
72 |
73 | @Override public boolean permitsFieldGet(@NonNull Field field, @NonNull Object receiver) {
74 | return unrestricted.permitsFieldGet(field, receiver);
75 | }
76 |
77 | @Override public boolean permitsFieldSet(@NonNull Field field, @NonNull Object receiver, Object value) {
78 | return unrestricted.permitsFieldSet(field, receiver, value);
79 | }
80 |
81 | @Override public boolean permitsStaticFieldGet(@NonNull Field field) {
82 | return unrestricted.permitsStaticFieldGet(field);
83 | }
84 |
85 | @Override public boolean permitsStaticFieldSet(@NonNull Field field, Object value) {
86 | return unrestricted.permitsStaticFieldSet(field, value);
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/AnnotatedWhitelist.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import hudson.Extension;
28 | import java.lang.reflect.AccessibleObject;
29 | import java.lang.reflect.Constructor;
30 | import java.lang.reflect.Field;
31 | import java.lang.reflect.Method;
32 | import edu.umd.cs.findbugs.annotations.NonNull;
33 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
34 | import org.kohsuke.accmod.Restricted;
35 | import org.kohsuke.accmod.restrictions.NoExternalUse;
36 |
37 | /**
38 | * Whitelists anything marked with {@link Whitelisted}.
39 | */
40 | @Restricted(NoExternalUse.class)
41 | @Extension public final class AnnotatedWhitelist extends AclAwareWhitelist {
42 |
43 | public AnnotatedWhitelist() {
44 | super(new Impl(false), new Impl(true));
45 | }
46 |
47 | private static final class Impl extends Whitelist {
48 |
49 | private final boolean restricted;
50 |
51 | Impl(boolean restricted) {
52 | this.restricted = restricted;
53 | }
54 |
55 | private boolean allowed(@NonNull AccessibleObject o) {
56 | Whitelisted ann = o.getAnnotation(Whitelisted.class);
57 | if (ann == null) {
58 | return false;
59 | }
60 | return ann.restricted() == restricted;
61 | }
62 |
63 | @Override public boolean permitsMethod(@NonNull Method method, @NonNull Object receiver, @NonNull Object[] args) {
64 | return allowed(method);
65 | }
66 |
67 | @Override public boolean permitsConstructor(@NonNull Constructor> constructor, @NonNull Object[] args) {
68 | return allowed(constructor);
69 | }
70 |
71 | @Override public boolean permitsStaticMethod(@NonNull Method method, @NonNull Object[] args) {
72 | return allowed(method);
73 | }
74 |
75 | @Override public boolean permitsFieldGet(@NonNull Field field, @NonNull Object receiver) {
76 | return allowed(field);
77 | }
78 |
79 | @Override public boolean permitsFieldSet(@NonNull Field field, @NonNull Object receiver, Object value) {
80 | return allowed(field);
81 | }
82 |
83 | @Override public boolean permitsStaticFieldGet(@NonNull Field field) {
84 | return allowed(field);
85 | }
86 |
87 | @Override public boolean permitsStaticFieldSet(@NonNull Field field, Object value) {
88 | return allowed(field);
89 | }
90 |
91 | }
92 |
93 | }
94 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/BlanketWhitelist.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 Jesse Glick.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import java.lang.reflect.Constructor;
28 | import java.lang.reflect.Field;
29 | import java.lang.reflect.Method;
30 |
31 | import edu.umd.cs.findbugs.annotations.NonNull;
32 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
33 |
34 | /**
35 | * Whitelists everything.
36 | * This is probably only useful for tests.
37 | */
38 | public final class BlanketWhitelist extends Whitelist {
39 |
40 | @Override public boolean permitsMethod(@NonNull Method method, @NonNull Object receiver, @NonNull Object[] args) {
41 | return true;
42 | }
43 |
44 | @Override public boolean permitsConstructor(@NonNull Constructor> constructor, @NonNull Object[] args) {
45 | return true;
46 | }
47 |
48 | @Override public boolean permitsStaticMethod(@NonNull Method method, @NonNull Object[] args) {
49 | return true;
50 | }
51 |
52 | @Override public boolean permitsFieldSet(@NonNull Field field, @NonNull Object receiver, Object value) {
53 | return true;
54 | }
55 |
56 | @Override public boolean permitsFieldGet(@NonNull Field field, @NonNull Object receiver) {
57 | return true;
58 | }
59 |
60 | @Override public boolean permitsStaticFieldSet(@NonNull Field field, Object value) {
61 | return true;
62 | }
63 |
64 | @Override public boolean permitsStaticFieldGet(@NonNull Field field) {
65 | return true;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/GenericWhitelist.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import java.io.IOException;
28 |
29 | /**
30 | * @deprecated replaced by {@link StaticWhitelist#stockWhitelists}, now used only in tests
31 | */
32 | @Deprecated
33 | public final class GenericWhitelist extends ProxyWhitelist {
34 |
35 | public GenericWhitelist() throws IOException {
36 | super(StaticWhitelist.from(GenericWhitelist.class.getResource("generic-whitelist")));
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/ProxyWhitelist.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import java.lang.reflect.Constructor;
29 | import java.lang.reflect.Field;
30 | import java.lang.reflect.Method;
31 | import java.util.Arrays;
32 | import java.util.Collection;
33 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
34 |
35 | /**
36 | * Aggregates several whitelists.
37 | */
38 | public class ProxyWhitelist extends Whitelist {
39 |
40 | private volatile Whitelist[] delegates;
41 |
42 | public ProxyWhitelist(Collection extends Whitelist> delegates) {
43 | reset(delegates);
44 | }
45 |
46 | public final void reset(Collection extends Whitelist> delegates) {
47 | this.delegates = delegates.toArray(Whitelist[]::new);
48 | }
49 |
50 | public ProxyWhitelist(Whitelist... delegates) {
51 | this(Arrays.asList(delegates));
52 | }
53 |
54 | /**
55 | * Called before {@link #permitsMethod} and similar methods.
56 | * May call {@link #reset(Collection)}.
57 | */
58 | protected void beforePermits() {}
59 |
60 | @Override public final boolean permitsMethod(@NonNull Method method, @NonNull Object receiver, @NonNull Object[] args) {
61 | beforePermits();
62 | for (Whitelist delegate : delegates) {
63 | if (delegate.permitsMethod(method, receiver, args)) {
64 | return true;
65 | }
66 | }
67 | return false;
68 | }
69 |
70 | @Override public final boolean permitsConstructor(@NonNull Constructor> constructor, @NonNull Object[] args) {
71 | beforePermits();
72 | for (Whitelist delegate : delegates) {
73 | if (delegate.permitsConstructor(constructor, args)) {
74 | return true;
75 | }
76 | }
77 | return false;
78 | }
79 |
80 | @Override public final boolean permitsStaticMethod(@NonNull Method method, @NonNull Object[] args) {
81 | beforePermits();
82 | for (Whitelist delegate : delegates) {
83 | if (delegate.permitsStaticMethod(method, args)) {
84 | return true;
85 | }
86 | }
87 | return false;
88 | }
89 |
90 | @Override public final boolean permitsFieldGet(@NonNull Field field, @NonNull Object receiver) {
91 | beforePermits();
92 | for (Whitelist delegate : delegates) {
93 | if (delegate.permitsFieldGet(field, receiver)) {
94 | return true;
95 | }
96 | }
97 | return false;
98 | }
99 |
100 | @Override public final boolean permitsFieldSet(@NonNull Field field, @NonNull Object receiver, Object value) {
101 | beforePermits();
102 | for (Whitelist delegate : delegates) {
103 | if (delegate.permitsFieldSet(field, receiver, value)) {
104 | return true;
105 | }
106 | }
107 | return false;
108 | }
109 |
110 | @Override public final boolean permitsStaticFieldGet(@NonNull Field field) {
111 | beforePermits();
112 | for (Whitelist delegate : delegates) {
113 | if (delegate.permitsStaticFieldGet(field)) {
114 | return true;
115 | }
116 | }
117 | return false;
118 | }
119 |
120 | @Override public final boolean permitsStaticFieldSet(@NonNull Field field, Object value) {
121 | beforePermits();
122 | for (Whitelist delegate : delegates) {
123 | if (delegate.permitsStaticFieldSet(field, value)) {
124 | return true;
125 | }
126 | }
127 | return false;
128 | }
129 |
130 | @Override public String toString() {
131 | return super.toString() + Arrays.toString(delegates);
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/Whitelisted.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import java.lang.annotation.ElementType;
28 | import java.lang.annotation.Retention;
29 | import java.lang.annotation.RetentionPolicy;
30 | import java.lang.annotation.Target;
31 |
32 | /**
33 | * Marks a member as being whitelisted by default for purposes of sandboxed scripts.
34 | */
35 | @Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})
36 | @Retention(RetentionPolicy.RUNTIME)
37 | public @interface Whitelisted {
38 |
39 | /**
40 | * True to only whitelist this member when invoked by an actual user.
41 | * (Relevant only on methods and constructors.)
42 | * @see AclAwareWhitelist
43 | */
44 | boolean restricted() default false;
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ApprovalContext.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import hudson.model.Item;
28 | import hudson.model.User;
29 | import hudson.security.ACL;
30 | import edu.umd.cs.findbugs.annotations.CheckForNull;
31 | import jenkins.model.Jenkins;
32 | import org.kohsuke.stapler.DataBoundConstructor;
33 |
34 | /**
35 | * Represents background information about who requested that a script or signature be approved and for what purpose.
36 | * When created from a thread that generally carries authentication, such as within a {@link DataBoundConstructor}, be sure to use {@link #withCurrentUser}.
37 | * Also use {@link #withItem} or {@link #withKey} or {@link #withItemAsKey} whenever possible.
38 | */
39 | public final class ApprovalContext {
40 |
41 | private final @CheckForNull String user;
42 | private final @CheckForNull String item;
43 | private final @CheckForNull String key;
44 |
45 | private ApprovalContext(@CheckForNull String user, @CheckForNull String item, @CheckForNull String key) {
46 | this.user = user;
47 | this.item = item;
48 | this.key = key;
49 | }
50 |
51 | /**
52 | * Creates a new context with no information.
53 | */
54 | public static ApprovalContext create() {
55 | return new ApprovalContext(null, null, null);
56 |
57 | }
58 |
59 | /**
60 | * Creates a context with a specified user ID.
61 | * ({@link ACL#SYSTEM2} is automatically ignored.)
62 | */
63 | public ApprovalContext withUser(@CheckForNull String user) {
64 | return new ApprovalContext(ACL.SYSTEM2.getName().equals(user) ? null : user, item, key);
65 | }
66 |
67 | /**
68 | * Creates a context with the user associated with the current thread.
69 | * ({@link ACL#SYSTEM2} is automatically ignored, but the user might be {@link Jenkins#ANONYMOUS2}.)
70 | */
71 | public ApprovalContext withCurrentUser() {
72 | User u = User.current();
73 | return withUser(u != null ? u.getId() : Jenkins.ANONYMOUS2.getName());
74 | }
75 |
76 | /**
77 | * Gets the associated {@linkplain User#getId user ID}, if any.
78 | */
79 | public @CheckForNull String getUser() {
80 | return user;
81 | }
82 |
83 | /**
84 | * Associates an item with this approval, used only for display purposes.
85 | */
86 | public ApprovalContext withItem(@CheckForNull Item item) {
87 | return item != null ? new ApprovalContext(user, item.getFullName(), key) : this;
88 | }
89 |
90 | /**
91 | * Gets any associated item which should be displayed to an administrator.
92 | */
93 | public @CheckForNull Item getItem() {
94 | // TODO if getItemByFullName == null, we should removal the approval
95 | return item != null ? Jenkins.get().getItemByFullName(item) : null;
96 | }
97 |
98 | /**
99 | * Associates a unique key with this approval.
100 | * If not null, any previous approval of the same kind with the same key will be canceled and replaced by this one.
101 | * Only considered for {@linkplain ScriptApproval#configuring whole-script approvals}, not {@linkplain ScriptApproval#accessRejected signature approvals} which are generic.
102 | */
103 | public ApprovalContext withKey(@CheckForNull String key) {
104 | return key != null ? new ApprovalContext(user, item, key) : this;
105 | }
106 |
107 | /**
108 | * Gets the unique key, if any.
109 | */
110 | public @CheckForNull String getKey() {
111 | return key;
112 | }
113 |
114 | /**
115 | * Associates an item with this approval for display, as well as setting a unique key
116 | * based on the {@link Item#getFullName} which would cancel any previous approvals for the same item.
117 | * Note that this only makes sense in cases where it is guaranteed that at most one approvable script
118 | * is configured on a given item, so do not use this with (for example) build steps.
119 | */
120 | public ApprovalContext withItemAsKey(@CheckForNull Item item) {
121 | if (item == null) {
122 | return this;
123 | }
124 | String n = item.getFullName();
125 | return new ApprovalContext(user, n, n);
126 | }
127 |
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ApprovalListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import hudson.ExtensionPoint;
28 | import java.net.URL;
29 |
30 | /**
31 | * Receives notifications on approval-related events.
32 | */
33 | public abstract class ApprovalListener implements ExtensionPoint {
34 |
35 | /**
36 | * Called when a script is approved.
37 | * @param hash an opaque token as in {@link UnapprovedUsageException#getHash}
38 | */
39 | public abstract void onApproved(String hash);
40 |
41 | /**
42 | * Called when a classpath entry is approved.
43 | * @param hash an opaque token as in {@link UnapprovedClasspathException#getHash}
44 | * @param url its location
45 | */
46 | public void onApprovedClasspathEntry(String hash, URL url) {}
47 |
48 | // TODO as needed: onDenied, onCleared
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntry.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright (c) 2014 IKEDA Yasuyuki
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
28 | import java.io.File;
29 | import java.io.Serializable;
30 |
31 | import jenkins.model.Jenkins;
32 | import org.apache.commons.lang.StringUtils;
33 | import org.kohsuke.accmod.Restricted;
34 | import org.kohsuke.accmod.restrictions.NoExternalUse;
35 | import org.kohsuke.stapler.DataBoundConstructor;
36 | import org.kohsuke.stapler.DataBoundSetter;
37 | import org.kohsuke.stapler.QueryParameter;
38 |
39 | import hudson.Extension;
40 | import hudson.init.InitMilestone;
41 | import hudson.init.Initializer;
42 | import hudson.model.AbstractDescribableImpl;
43 | import hudson.model.Descriptor;
44 | import hudson.model.Items;
45 | import hudson.util.FormValidation;
46 | import java.net.MalformedURLException;
47 | import java.net.URI;
48 | import java.net.URISyntaxException;
49 | import java.net.URL;
50 | import edu.umd.cs.findbugs.annotations.CheckForNull;
51 | import edu.umd.cs.findbugs.annotations.NonNull;
52 |
53 | /**
54 | * A classpath entry used for a script.
55 | */
56 | public final class ClasspathEntry extends AbstractDescribableImpl implements Serializable {
57 |
58 | private static final long serialVersionUID = -2873408550951192200L;
59 | private final @NonNull URL url;
60 | private transient String oldPath;
61 | private transient boolean shouldBeApproved;
62 |
63 | @SuppressFBWarnings(value = {"SE_TRANSIENT_FIELD_NOT_RESTORED"}, justification = "Null is the expected value for deserealized instances of this class")
64 | @DataBoundConstructor
65 | public ClasspathEntry(@NonNull String path) throws MalformedURLException {
66 | url = pathToURL(path);
67 | }
68 |
69 | static URL pathToURL(String path) throws MalformedURLException {
70 | if (path.isEmpty()) {
71 | throw new MalformedURLException("JENKINS-37599: empty classpath entries not allowed");
72 | }
73 | try {
74 | return new URL(path);
75 | } catch (MalformedURLException x) {
76 | File f = new File(path);
77 | if (f.isAbsolute()) {
78 | return f.toURI().toURL();
79 | } else {
80 | throw new MalformedURLException("Classpath entry ‘" + path + "’ does not look like either a URL or an absolute file path");
81 | }
82 | }
83 | }
84 |
85 | /** Returns {@code null} if another protocol or unable to perform the conversion. */
86 | private static File urlToFile(@NonNull URL url) {
87 | if (url.getProtocol().equals("file")) {
88 | try {
89 | return new File(url.toURI());
90 | } catch (URISyntaxException x) {
91 | // ?
92 | }
93 | }
94 | return null;
95 | }
96 |
97 | static String urlToPath(URL url) {
98 | final File file = urlToFile(url);
99 | return file != null ? file.getAbsolutePath() : url.toString();
100 | }
101 |
102 | /**
103 | * Checks whether an URL would be considered a class directory by {@link java.net.URLClassLoader}.
104 | * According to its
105 | * an URL will be considered an class directory if it ends with /.
106 | * In the case the URL uses a {@code file:} protocol a check is performed to see if it is a directory as an additional guard
107 | * in case a different class loader is used by other {@link Language} implementation.
108 | */
109 | static boolean isClassDirectoryURL(@NonNull URL url) {
110 | final File file = urlToFile(url);
111 | if (file != null && file.isDirectory()) {
112 | return true;
113 | // If the URL is a file but does not exist we fallback to default behaviour
114 | // as non existence will be dealt with when trying to use it.
115 | }
116 | String u = url.toExternalForm();
117 | return u.endsWith("/") && !u.startsWith("jar:");
118 | }
119 |
120 | /**
121 | * Checks whether the entry would be considered a class directory.
122 | * @see #isClassDirectoryURL(URL)
123 | */
124 | public boolean isClassDirectory() {
125 | return isClassDirectoryURL(url);
126 | }
127 |
128 | public @NonNull String getPath() {
129 | return urlToPath(url);
130 | }
131 |
132 | public @NonNull URL getURL() {
133 | return url;
134 | }
135 |
136 | private @CheckForNull URI getURI() {
137 | try {
138 | return url.toURI();
139 | } catch(URISyntaxException ex) {
140 | return null;
141 | }
142 | }
143 |
144 | @Restricted(NoExternalUse.class) // for jelly view
145 | public String getOldPath() {
146 | return oldPath;
147 | }
148 |
149 | @DataBoundSetter
150 | public void setOldPath(String oldPath) {
151 | this.oldPath = oldPath;
152 | }
153 |
154 | public boolean isShouldBeApproved() {
155 | return shouldBeApproved;
156 | }
157 |
158 | @DataBoundSetter
159 | public void setShouldBeApproved(boolean shouldBeApproved) {
160 | this.shouldBeApproved = shouldBeApproved;
161 | }
162 |
163 | @Restricted(NoExternalUse.class) // for jelly view
164 | public boolean isScriptAutoApprovalEnabled() {
165 | return ScriptApproval.ADMIN_AUTO_APPROVAL_ENABLED;
166 | }
167 |
168 | @Restricted(NoExternalUse.class) // for jelly view
169 | public boolean isEntryApproved() {
170 | return ScriptApproval.get().isClasspathEntryApproved(url);
171 | }
172 |
173 | @Override
174 | public String toString() {
175 | return url.toString();
176 | }
177 |
178 | @Override
179 | @SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL",
180 | justification = "Method call has been optimized, but we still need URLs as a fallback")
181 | public boolean equals(Object obj) {
182 | if (!(obj instanceof ClasspathEntry)) {
183 | return false;
184 | }
185 | // Performance optimization to avoid domain name resolution
186 | final ClasspathEntry cmp = (ClasspathEntry)obj;
187 | final URI uri = getURI();
188 | return uri != null ? uri.equals(cmp.getURI()) : url.equals(cmp.url);
189 | }
190 |
191 | @SuppressFBWarnings(value = "DMI_BLOCKING_METHODS_ON_URL",
192 | justification = "Method call has been optimized, but we still need URLs as a fallback")
193 | @Override public int hashCode() {
194 | // Performance optimization to avoid domain name resolution
195 | final URI uri = getURI();
196 | return uri != null ? uri.hashCode() : url.hashCode();
197 | }
198 |
199 | @Extension
200 | public static class DescriptorImpl extends Descriptor {
201 | @NonNull
202 | @Override
203 | public String getDisplayName() {
204 | return "ClasspathEntry";
205 | }
206 |
207 | public FormValidation doCheckPath(@QueryParameter String value, @QueryParameter String oldPath, @QueryParameter boolean shouldBeApproved) {
208 | if(!Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
209 | return FormValidation.ok();
210 | }
211 | if (StringUtils.isBlank(value)) {
212 | return FormValidation.warning("Enter a file path or URL."); // TODO I18N
213 | }
214 | try {
215 | ClasspathEntry entry = new ClasspathEntry(value);
216 | entry.setShouldBeApproved(shouldBeApproved);
217 | entry.setOldPath(oldPath);
218 | return ScriptApproval.get().checking(entry);
219 | } catch (MalformedURLException x) {
220 | return FormValidation.error(x, "Could not parse: " + value); // TODO I18N
221 | }
222 | }
223 | }
224 |
225 | @Initializer(before=InitMilestone.EXTENSIONS_AUGMENTED) public static void alias() {
226 | Items.XSTREAM2.alias("entry", ClasspathEntry.class);
227 | }
228 |
229 | }
230 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/Language.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import hudson.ExtensionPoint;
28 | import edu.umd.cs.findbugs.annotations.CheckForNull;
29 | import edu.umd.cs.findbugs.annotations.NonNull;
30 |
31 | /**
32 | * A language for which we can request {@link ScriptApproval}.
33 | */
34 | public abstract class Language implements ExtensionPoint {
35 |
36 | /**
37 | * Unique, permanent, internal identifier of this language.
38 | * @return a short unlocalized identifier, such as might be used for a filename extension
39 | */
40 | public abstract @NonNull String getName();
41 |
42 | /**
43 | * Display name of the language for use in the UI.
44 | * @return a localized name
45 | */
46 | public abstract @NonNull String getDisplayName();
47 |
48 | /**
49 | * A CodeMirror mode string, for purposes of displaying scripts in HTML.
50 | * @return a mode such as {@code clike}, or null
51 | */
52 | public @CheckForNull String getCodeMirrorMode() {
53 | return null;
54 | }
55 |
56 | // TODO hooks for configuring/using/checking, as needed
57 |
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalLink.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import hudson.Extension;
29 | import hudson.model.ManagementLink;
30 | import hudson.security.Permission;
31 | import jenkins.management.Badge;
32 | import jenkins.model.Jenkins;
33 | import org.kohsuke.accmod.Restricted;
34 | import org.kohsuke.accmod.restrictions.NoExternalUse;
35 |
36 | @Restricted(NoExternalUse.class) // implementation
37 | @Extension public final class ScriptApprovalLink extends ManagementLink {
38 |
39 | @Override public String getIconFileName() {
40 | if (ScriptApproval.get().isEmpty()) {
41 | return null;
42 | }
43 | return "symbol-edit-note";
44 | }
45 |
46 | @Override public String getUrlName() {
47 | return ScriptApproval.get().getUrlName();
48 | }
49 |
50 | @Override public String getDisplayName() {
51 | return "In-process Script Approval";
52 | }
53 |
54 | @Override public String getDescription() {
55 | String message = "Allows a Jenkins administrator to review proposed scripts (written e.g. in Groovy) which run inside the Jenkins process and so could bypass security restrictions.";
56 | int outstanding = ScriptApproval.get().getPendingScripts().size();
57 | if (outstanding > 0) {
58 | // TODO consider using like Manage Plugins does (but better for this to be defined in Jenkins CSS)
59 | message += " " + outstanding + " scripts pending approval. ";
60 | }
61 | outstanding = ScriptApproval.get().getPendingSignatures().size();
62 | if (outstanding > 0) {
63 | message += " " + outstanding + " signatures pending approval. ";
64 | }
65 | outstanding = ScriptApproval.get().getPendingClasspathEntries().size();
66 | if (outstanding > 0) {
67 | message += " " + outstanding + " classpath entries pending approval. ";
68 | }
69 | int dangerous = ScriptApproval.get().getDangerousApprovedSignatures().length;
70 | if (dangerous > 0) {
71 | message += " " + dangerous + " dangerous signatures previously approved which ought not have been. ";
72 | }
73 | return message;
74 | }
75 |
76 | @NonNull
77 | @Override public Permission getRequiredPermission() {
78 | return Jenkins.ADMINISTER;
79 | }
80 |
81 | @NonNull
82 | @Override
83 | public Category getCategory() {
84 | return Category.SECURITY;
85 | }
86 |
87 | @Override
88 | public Badge getBadge() {
89 | int pendingScripts = ScriptApproval.get().getPendingScripts().size();
90 | int pendingSignatures = ScriptApproval.get().getPendingSignatures().size();
91 | int pendingClasspathEntries = ScriptApproval.get().getPendingClasspathEntries().size();
92 | int dangerous = ScriptApproval.get().getDangerousApprovedSignatures().length;
93 | int total = 0;
94 | StringBuilder toolTip = new StringBuilder();
95 | if (pendingScripts > 0) {
96 | total += pendingScripts;
97 | toolTip.append(Messages.ScriptApprovalLink_outstandingScript(pendingScripts));
98 | }
99 | if (pendingSignatures > 0) {
100 | if (total > 0) {
101 | toolTip.append("\n");
102 | }
103 | toolTip.append(Messages.ScriptApprovalLink_outstandingSignature(pendingSignatures));
104 | total += pendingSignatures;
105 | }
106 | if (pendingClasspathEntries > 0) {
107 | if (total > 0) {
108 | toolTip.append("\n");
109 | }
110 | toolTip.append(Messages.ScriptApprovalLink_outstandingClasspath(pendingClasspathEntries));
111 | total += pendingClasspathEntries;
112 | }
113 | if (total > 0 || dangerous > 0) {
114 | StringBuilder text = new StringBuilder();
115 | if (total > 0) {
116 | text.append(total);
117 | }
118 | if (dangerous > 0) {
119 | if (total > 0) {
120 | toolTip.append("\n");
121 | text.append("/");
122 | }
123 | text.append(dangerous);
124 | toolTip.append(Messages.ScriptApprovalLink_dangerous(dangerous));
125 | }
126 | Badge.Severity severity = dangerous > 0 ? Badge.Severity.DANGER : Badge.Severity.WARNING;
127 | return new Badge(text.toString(), toolTip.toString(), severity);
128 | }
129 |
130 | return null;
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalNote.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2019 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import hudson.Extension;
28 | import hudson.MarkupText;
29 | import hudson.console.ConsoleAnnotationDescriptor;
30 | import hudson.console.ConsoleAnnotator;
31 | import hudson.console.ConsoleNote;
32 | import hudson.model.TaskListener;
33 | import java.io.IOException;
34 | import java.util.logging.Level;
35 | import java.util.logging.Logger;
36 | import jenkins.model.Jenkins;
37 | import org.jenkinsci.Symbol;
38 | import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException;
39 | import org.kohsuke.accmod.Restricted;
40 | import org.kohsuke.accmod.restrictions.NoExternalUse;
41 | import org.kohsuke.stapler.Stapler;
42 | import org.kohsuke.stapler.StaplerRequest2;
43 |
44 | /**
45 | * Offers a link to {@link ScriptApproval}.
46 | */
47 | @Restricted(NoExternalUse.class)
48 | public class ScriptApprovalNote extends ConsoleNote {
49 |
50 | private static final Logger LOGGER = Logger.getLogger(ScriptApprovalNote.class.getName());
51 |
52 | public static void print(TaskListener listener, RejectedAccessException x) {
53 | try {
54 | String text = ScriptApproval.get().isForceSandbox() ?
55 | Messages.ScriptApprovalNoteForceSandBox_message() : Messages.ScriptApprovalNote_message();
56 | listener.getLogger().println(x.getMessage() + ". " + new ScriptApprovalNote(text.length()).encode() + text);
57 | } catch (IOException x2) {
58 | LOGGER.log(Level.WARNING, null, x2);
59 | }
60 | }
61 |
62 | private final int length;
63 |
64 | private ScriptApprovalNote(int length) {
65 | this.length = length;
66 | }
67 |
68 | @Override
69 | public ConsoleAnnotator annotate(Object context, MarkupText text, int charPos) {
70 | if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
71 | String url = ScriptApproval.get().getUrlName();
72 | StaplerRequest2 req = Stapler.getCurrentRequest2();
73 | if (req != null) {
74 | // if we are serving HTTP request, we want to use app relative URL
75 | url = req.getContextPath() + "/" + url;
76 | } else {
77 | // otherwise presumably this is rendered for e-mails and other non-HTTP stuff
78 | url = Jenkins.get().getRootUrl() + url;
79 | }
80 | text.addMarkup(charPos, charPos + length, "", " ");
81 | }
82 | return null;
83 | }
84 |
85 |
86 | @Symbol("scriptApprovalLink")
87 | @Extension public static class DescriptorImpl extends ConsoleAnnotationDescriptor {}
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/UnapprovedClasspathException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import java.net.URL;
28 |
29 | /**
30 | * Exception thrown by {@link ScriptApproval#using(ClasspathEntry)}.
31 | */
32 | public final class UnapprovedClasspathException extends SecurityException {
33 |
34 | private static final long serialVersionUID = -4774006715053263794L;
35 | private final URL url;
36 | private final String hash;
37 |
38 | UnapprovedClasspathException(String format, URL url, String hash) {
39 | super(String.format(format, url, hash));
40 | this.url = url;
41 | this.hash = hash;
42 | }
43 |
44 | UnapprovedClasspathException(URL url, String hash) {
45 | this("classpath entry %s (%s) not yet approved for use", url, hash);
46 | }
47 |
48 | public URL getURL() {
49 | return url;
50 | }
51 |
52 | /**
53 | * Gets a token which identifies the contents of the unapproved classpath entry.
54 | * @return the SHA-1 of the entry
55 | */
56 | public String getHash() {
57 | return hash;
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/UnapprovedUsageException.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | /**
28 | * Exception thrown by {@link ScriptApproval#using}.
29 | */
30 | public final class UnapprovedUsageException extends SecurityException {
31 |
32 | private final String hash;
33 |
34 | UnapprovedUsageException(String hash) {
35 | super(ScriptApproval.get().isForceSandbox() ? Messages.UnapprovedUsage_ForceSandBox() : Messages.UnapprovedUsage_NonApproved());
36 | this.hash = hash;
37 | }
38 |
39 | /**
40 | * Gets a token which identifies the actual script to be rejected.
41 | * @return an opaque token as in {@link ApprovalListener#onApproved}
42 | */
43 | public String getHash() {
44 | return hash;
45 | }
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/languages/GroovyLanguage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts.languages;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import org.jenkinsci.plugins.scriptsecurity.scripts.Language;
29 | import hudson.Extension;
30 | import hudson.ExtensionList;
31 |
32 | /**
33 | * Language for Groovy scripts.
34 | */
35 | @Extension public final class GroovyLanguage extends Language {
36 |
37 | public static Language get() {
38 | return ExtensionList.lookup(Language.class).get(GroovyLanguage.class);
39 | }
40 |
41 | @NonNull
42 | @Override public String getName() {
43 | return "groovy";
44 | }
45 |
46 | @NonNull
47 | @Override public String getDisplayName() {
48 | return "Groovy";
49 | }
50 |
51 | @Override public String getCodeMirrorMode() {
52 | return "groovy";
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/languages/GroovyShellLanguage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts.languages;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import org.jenkinsci.plugins.scriptsecurity.scripts.Language;
29 | import hudson.Extension;
30 | import hudson.ExtensionList;
31 |
32 | /**
33 | * Language for Groovy Template scripts generating Bourne Shell script.
34 | * The protection here is about the Groovy scripting,
35 | * similar to {@link GroovyLanguage} or {@link GroovyXmlLanguage}.
36 | * Not to be confused with unprocessed strings controlled by {@link SystemCommandLanguage}.
37 | */
38 | @Extension public final class GroovyShellLanguage extends Language {
39 |
40 | public static Language get() {
41 | return ExtensionList.lookup(Language.class).get(GroovyShellLanguage.class);
42 | }
43 |
44 | @NonNull
45 | @Override public String getName() {
46 | return "groovy-sh";
47 | }
48 |
49 | @NonNull
50 | @Override public String getDisplayName() {
51 | return "Groovy Template for Shell";
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/languages/GroovyXmlLanguage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts.languages;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import org.jenkinsci.plugins.scriptsecurity.scripts.Language;
29 | import hudson.Extension;
30 | import hudson.ExtensionList;
31 |
32 | /**
33 | * Language for Groovy Template scripts generating XML.
34 | */
35 | @Extension public final class GroovyXmlLanguage extends Language {
36 |
37 | public static Language get() {
38 | return ExtensionList.lookup(Language.class).get(GroovyXmlLanguage.class);
39 | }
40 |
41 | @NonNull
42 | @Override public String getName() {
43 | return "groovy-xml";
44 | }
45 |
46 | @NonNull
47 | @Override public String getDisplayName() {
48 | return "Groovy Template for XML";
49 | }
50 |
51 | @Override public String getCodeMirrorMode() {
52 | return "xml"; // not exactly, but close enough?
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/languages/JellyLanguage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts.languages;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import org.jenkinsci.plugins.scriptsecurity.scripts.Language;
29 | import hudson.Extension;
30 | import hudson.ExtensionList;
31 |
32 | /**
33 | * Language for Jelly scripts.
34 | */
35 | @Extension public final class JellyLanguage extends Language {
36 |
37 | public static Language get() {
38 | return ExtensionList.lookup(Language.class).get(JellyLanguage.class);
39 | }
40 |
41 | @NonNull
42 | @Override public String getName() {
43 | return "jelly";
44 | }
45 |
46 | @NonNull
47 | @Override public String getDisplayName() {
48 | return "Jelly";
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/languages/JexlLanguage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts.languages;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import org.jenkinsci.plugins.scriptsecurity.scripts.Language;
29 | import hudson.Extension;
30 | import hudson.ExtensionList;
31 |
32 | /**
33 | * Language for JEXL scripts.
34 | */
35 | @Extension public final class JexlLanguage extends Language {
36 |
37 | public static Language get() {
38 | return ExtensionList.lookup(Language.class).get(JexlLanguage.class);
39 | }
40 |
41 | @NonNull
42 | @Override public String getName() {
43 | return "jexl";
44 | }
45 |
46 | @NonNull
47 | @Override public String getDisplayName() {
48 | return "JEXL";
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/languages/SystemCommandLanguage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2017 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts.languages;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import hudson.Extension;
29 | import hudson.ExtensionList;
30 | import hudson.Util;
31 | import org.jenkinsci.plugins.scriptsecurity.scripts.Language;
32 |
33 | /**
34 | * Language for launched processes, as per {@link Util#tokenize(String)} and {@link ProcessBuilder}.
35 | * Typically the launch is on the master so arbitrary content could be dangerous.
36 | */
37 | @Extension
38 | public class SystemCommandLanguage extends Language {
39 |
40 | public static Language get() {
41 | return ExtensionList.lookup(Language.class).get(SystemCommandLanguage.class);
42 | }
43 |
44 | @NonNull
45 | @Override
46 | public String getName() {
47 | return "system-command";
48 | }
49 |
50 | @NonNull
51 | @Override
52 | public String getDisplayName() {
53 | return "System Commands";
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/src/main/resources/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 | Allows Jenkins administrators to control what in-process scripts can be run by less-privileged users.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/Messages.properties:
--------------------------------------------------------------------------------
1 | GroovySandbox.useOfInsecureRunOverload=GroovySandbox.run(Script, Whitelist) is insecure and deprecated. \
2 | GroovySandbox.run(GroovyShell, String, Whitelist) should be used instead.
3 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/JENKINS-15604.js:
--------------------------------------------------------------------------------
1 | // https://issues.jenkins-ci.org/browse/JENKINS-15604 workaround:
2 | function cmChange(editor, change) {
3 | editor.save();
4 | document.querySelectorAll('.validated').forEach(function (e) {e.onchange();});
5 | }
6 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
37 |
38 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/help-classpath.html:
--------------------------------------------------------------------------------
1 |
2 | Additional classpath entries accessible from the script.
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript/help-sandbox.html:
--------------------------------------------------------------------------------
1 |
2 | If checked, run this Groovy script in a sandbox with limited abilities.
3 | If unchecked, and you are not a Jenkins administrator, you will need to wait for an administrator to approve the script.
4 |
5 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/blacklist:
--------------------------------------------------------------------------------
1 | # SECURITY-538
2 | staticMethod groovy.json.JsonOutput toJson groovy.lang.Closure
3 | staticMethod groovy.json.JsonOutput toJson java.lang.Object
4 |
5 | # Reflective access to Groovy is too open-ended. Approve only specific GroovyObject subclass methods.
6 | method groovy.lang.GroovyObject getMetaClass
7 | method groovy.lang.GroovyObject getProperty java.lang.String
8 | method groovy.lang.GroovyObject invokeMethod java.lang.String java.lang.Object
9 | method groovy.lang.GroovyObject setMetaClass groovy.lang.MetaClass
10 | method groovy.lang.GroovyObject setProperty java.lang.String java.lang.Object
11 |
12 | # Variant of Jenkins.getInstance, see below.
13 | staticMethod hudson.model.Hudson getInstance
14 |
15 | # Up to no good…
16 | staticMethod hudson.model.User current
17 | staticMethod hudson.model.User get java.lang.String
18 | staticMethod hudson.model.User get java.lang.String boolean
19 | staticMethod hudson.model.User get java.lang.String boolean java.util.Map
20 | staticMethod hudson.model.User getAll
21 |
22 | # Raw file operations could be used to compromise the Jenkins controller.
23 | staticMethod java.io.File createTempFile java.lang.String java.lang.String
24 | staticMethod java.io.File createTempFile java.lang.String java.lang.String java.io.File
25 | new java.io.File java.lang.String
26 | new java.io.File java.lang.String java.lang.String
27 | new java.io.File java.net.URI
28 | staticMethod java.io.File listRoots
29 | new java.io.FileInputStream java.lang.String
30 | new java.io.FileOutputStream java.lang.String
31 | new java.io.FileOutputStream java.lang.String boolean
32 | new java.io.FileReader java.lang.String
33 | new java.io.FileWriter java.lang.String
34 | new java.io.FileWriter java.lang.String boolean
35 | new java.io.PrintStream java.lang.String
36 | new java.io.PrintStream java.lang.String java.lang.String
37 | new java.io.PrintWriter java.lang.String
38 | new java.io.PrintWriter java.lang.String java.lang.String
39 | new java.io.RandomAccessFile java.lang.String java.lang.String
40 |
41 | # No reflection!
42 | method java.lang.Class getConstructor java.lang.Class[]
43 | method java.lang.Class getConstructors
44 | method java.lang.Class getDeclaredConstructor java.lang.Class[]
45 | method java.lang.Class getDeclaredConstructors
46 | method java.lang.Class getDeclaredField java.lang.String
47 | method java.lang.Class getDeclaredFields
48 | method java.lang.Class getDeclaredMethod java.lang.String java.lang.Class[]
49 | method java.lang.Class getDeclaredMethods
50 | method java.lang.Class getField java.lang.String
51 | method java.lang.Class getFields
52 | method java.lang.Class getMethod java.lang.String java.lang.Class[]
53 | method java.lang.Class getMethods
54 | method java.lang.Class getResource java.lang.String
55 | method java.lang.Class getResourceAsStream java.lang.String
56 | method java.lang.Class newInstance
57 |
58 | # Same for local process execution.
59 | staticMethod java.lang.Runtime getRuntime
60 |
61 | # Duh.
62 | staticMethod java.lang.System exit int
63 |
64 | # Leak information.
65 | staticMethod java.lang.System getProperties
66 | staticMethod java.lang.System getProperty java.lang.String
67 | staticMethod java.lang.System getProperty java.lang.String java.lang.String
68 | staticMethod java.lang.System getenv
69 | staticMethod java.lang.System getenv java.lang.String
70 |
71 | # SECURITY-683 speculative approach to Spectre/Meltdown
72 | staticMethod java.lang.System nanoTime
73 |
74 | # Maybe could bypass other protections.
75 | staticMethod java.lang.System setProperty java.lang.String java.lang.String
76 |
77 | # Could be used to read local files.
78 | method java.net.URL getContent
79 | method java.net.URL getContent java.lang.Class[]
80 | method java.net.URL openConnection
81 | method java.net.URL openStream
82 |
83 | # NIO file operations must start with a Path:
84 | staticMethod java.nio.file.Paths get java.lang.String java.lang.String[]
85 | staticMethod java.nio.file.Paths get java.net.URI
86 |
87 | # Do not even get started…
88 | staticMethod jenkins.model.Jenkins getInstance
89 |
90 | # More process execution, Groovy-style:
91 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String
92 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String java.lang.String[] java.io.File
93 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String java.util.List java.io.File
94 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String[]
95 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String[] java.lang.String[] java.io.File
96 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.lang.String[] java.util.List java.io.File
97 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.util.List
98 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.util.List java.lang.String[] java.io.File
99 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods execute java.util.List java.util.List java.io.File
100 |
101 | # SECURITY-538
102 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Object java.lang.String
103 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getMetaPropertyValues java.lang.Object
104 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getProperties java.lang.Object
105 | staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods putAt java.lang.Object java.lang.String java.lang.Object
106 |
107 | # SECURITY-1353
108 | staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter asType java.lang.Object java.lang.Class
109 | staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter castToType java.lang.Object java.lang.Class
110 |
111 | # TODO do we need a @Blacklisted annotation?
112 | method org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper getRawBuild
113 |
114 | # SECURITY-1754
115 | new org.kohsuke.groovy.sandbox.impl.Checker$SuperConstructorWrapper java.lang.Object[]
116 | new org.kohsuke.groovy.sandbox.impl.Checker$ThisConstructorWrapper java.lang.Object[]
117 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/jenkins-whitelist:
--------------------------------------------------------------------------------
1 | # Useful Jenkins API methods:
2 | new hudson.EnvVars
3 | method hudson.model.AbstractBuild getEnvironments
4 | method hudson.model.BallColor getHtmlBaseColor
5 | staticMethod hudson.model.Environment create hudson.EnvVars
6 | method hudson.model.Item getParent
7 | method hudson.model.ModelObject getDisplayName
8 | staticField hudson.model.Result ABORTED
9 | staticField hudson.model.Result FAILURE
10 | staticField hudson.model.Result NOT_BUILT
11 | staticField hudson.model.Result SUCCESS
12 | staticField hudson.model.Result UNSTABLE
13 | field hudson.model.Result color
14 | staticMethod hudson.model.Result fromString java.lang.String
15 | method hudson.model.Run getFullDisplayName
16 | method hudson.model.User getFullName
17 | method hudson.model.User getId
18 | method hudson.scm.ChangeLogSet getItems
19 | method hudson.scm.ChangeLogSet getKind
20 | method hudson.scm.ChangeLogSet isEmptySet
21 | method hudson.scm.ChangeLogSet$AffectedFile getEditType
22 | method hudson.scm.ChangeLogSet$AffectedFile getPath
23 | method hudson.scm.ChangeLogSet$Entry getAffectedFiles
24 | method hudson.scm.ChangeLogSet$Entry getAffectedPaths
25 | method hudson.scm.ChangeLogSet$Entry getAuthor
26 | method hudson.scm.ChangeLogSet$Entry getCommitId
27 | method hudson.scm.ChangeLogSet$Entry getMsg
28 | method hudson.scm.ChangeLogSet$Entry getMsgAnnotated
29 | method hudson.scm.ChangeLogSet$Entry getMsgEscaped
30 | method hudson.scm.ChangeLogSet$Entry getTimestamp
31 | staticField hudson.scm.EditType ADD
32 | staticField hudson.scm.EditType DELETE
33 | staticField hudson.scm.EditType EDIT
34 | method hudson.scm.EditType getName
35 | method hudson.tools.ToolInstallation getHome
36 | method hudson.tools.ToolInstallation getName
37 | method jenkins.model.CauseOfInterruption getShortDescription
38 | method jenkins.model.CauseOfInterruption$UserInterruption getUserId
39 | method jenkins.model.HistoricalBuild getFullDisplayName
40 | staticField jenkins.model.Jenkins VERSION
41 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ApprovalContext/index.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 | from ${it.user}
30 |
31 |
32 |
33 | in ${item.fullDisplayName}
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntry/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntry/help-path.html:
--------------------------------------------------------------------------------
1 |
2 | A path or URL to a JAR file.
3 | This path should be approved by an administrator or a user with the RUN_SCRIPT permission, or the script fails.
4 | If the file or files are once approved, they are treated approved even located in another path.
5 |
6 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntry/resources.js:
--------------------------------------------------------------------------------
1 | document.addEventListener("DOMContentLoaded", function() {
2 | function adjustCheckboxAvailability(event) {
3 | // event.target is the path/url input
4 | // parent is the first common parent for root level elements in config.jelly
5 | // from parent we can target child elements via querySelector to not harm other elements on the page
6 | const parent = event.target.parentElement.parentElement.parentElement;
7 |
8 | const classpathEntryPath = parent.querySelector(".secure-groovy-script__classpath-entry-path");
9 | const classpathApproveCheckbox = parent.querySelector(".secure-groovy-script__classpath-approve");
10 | if (!classpathApproveCheckbox) {
11 | return;
12 | }
13 |
14 | const classpathEntryOldPath = parent.querySelector(".secure-groovy-script__classpath-entry-old-path");
15 | const classpathEntryPathEdited = classpathEntryPath.value !== classpathEntryOldPath.value;
16 |
17 | if (classpathEntryPathEdited) {
18 | classpathApproveCheckbox.setAttribute("disabled", "true");
19 | } else {
20 | classpathApproveCheckbox.removeAttribute("disabled");
21 | }
22 | }
23 |
24 | const classpaths = document.querySelectorAll(".secure-groovy-script__classpath-entry-path");
25 | classpaths.forEach(function(classpath) {
26 | classpath.addEventListener("blur", adjustCheckboxAvailability);
27 | })
28 | });
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/Messages.properties:
--------------------------------------------------------------------------------
1 | ClasspathEntry.path.notExists=Specified path does not exist
2 | ClasspathEntry.path.notApproved=This classpath entry is not approved. Require an approval before execution.
3 | ClasspathEntry.path.noDirsAllowed=Class directories are not allowed as classpath entries.
4 | ScriptApprovalNote.message=Administrators can decide whether to approve or reject this signature.
5 | ScriptApprovalNoteForceSandBox.message=Script signature is not in the default whitelist.
6 | ScriptApprovalLink.outstandingScript={0} scripts pending approval
7 | ScriptApprovalLink.outstandingSignature={0} signatures pending approval
8 | ScriptApprovalLink.outstandingClasspath={0} classpath entries pending approval
9 | ScriptApprovalLink.dangerous={0} approved dangerous signatures
10 | ScriptApproval.PipelineMessage=A Jenkins administrator will need to approve this script before it can be used
11 | ScriptApproval.ForceSandBoxMessage=Running scripts out of the sandbox is not allowed in the system
12 | UnapprovedUsage.NonApproved=script not yet approved for use
13 | UnapprovedUsage.ForceSandBox=Running scripts out of the sandbox is not allowed in the system
14 | ScriptApproval.SandboxCantBeDisabled=Sandbox cannot be disabled. This Jenkins instance has been configured to not allow regular users to disable the sandbox in the system
15 | ScriptApproval.AdminUserAlert=Sandbox is enabled globally in the system.
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/FormValidationPageDecorator/header.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/FormValidationPageDecorator/validate.js:
--------------------------------------------------------------------------------
1 | Behaviour.specify('.script-approval-approve-link', 'ScriptApproval.FormValidationPageDecorator', 0, function (element) {
2 | element.onclick = function (ev) {
3 | const approvalUrl = ev.target.dataset.baseUrl;
4 | const hash = ev.target.dataset.hash;
5 | const xmlhttp = new XMLHttpRequest();
6 | xmlhttp.onload = function () {
7 | alert('Script approved');
8 | };
9 | xmlhttp.open('POST', approvalUrl + "/approveScriptHash");
10 | const data = new FormData();
11 | data.append('hash', hash);
12 | xmlhttp.setRequestHeader(crumb.fieldName, crumb.value);
13 | xmlhttp.send(data);
14 | }
15 | });
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/deprecated-approvedClasspaths-clear-btn-hide.js:
--------------------------------------------------------------------------------
1 | document.getElementById('deprecated-approvedClasspaths-clear-btn').style.display = 'none';
2 | document.getElementById('deprecated-approvedClasspaths-clear-spinner').style.display = '';
3 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/deprecated-approvedClasspaths-clear-btn-show.js:
--------------------------------------------------------------------------------
1 | document.getElementById('deprecated-approvedClasspaths-clear-btn').style.display = '';
2 | document.getElementById('deprecated-approvedClasspaths-clear-spinner').style.display = 'none';
3 |
--------------------------------------------------------------------------------
/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/help-forceSandbox.html:
--------------------------------------------------------------------------------
1 |
2 |
Controls whether the "Use Groovy Sandbox" is shown in the system to users without Overall/Administer permission.
3 |
This can be used to simplify the UX in highly secured environments where all Pipelines and any other Groovy execution are
4 | required to run in the sandbox (i.e., running arbitrary code is never approved).
5 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/GroovyCallSiteSelectorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;
26 |
27 | import groovy.lang.GString;
28 | import hudson.EnvVars;
29 | import hudson.model.BooleanParameterValue;
30 | import hudson.model.Hudson;
31 | import java.io.IOException;
32 | import java.io.OutputStream;
33 | import java.io.PrintWriter;
34 | import java.lang.reflect.Method;
35 | import java.util.ArrayList;
36 | import java.util.Arrays;
37 | import java.util.List;
38 |
39 | import hudson.model.ParameterValue;
40 | import hudson.model.ParametersAction;
41 | import hudson.model.StringParameterValue;
42 | import jenkins.model.Jenkins;
43 | import org.codehaus.groovy.runtime.GStringImpl;
44 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.EnumeratingWhitelist;
45 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.EnumeratingWhitelistTest;
46 | import static org.junit.Assert.*;
47 | import org.junit.Test;
48 | import org.jvnet.hudson.test.Issue;
49 |
50 | public class GroovyCallSiteSelectorTest {
51 |
52 | @Test public void arrays() throws Exception {
53 | Method m = EnumeratingWhitelistTest.C.class.getDeclaredMethod("m", Object[].class);
54 | assertEquals("literal call", m, GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[] {new Object[] {"a", "b"}}));
55 | assertNull("we assume the interceptor has dealt with varargs", GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[]{"a", "b"}));
56 | assertEquals("array cast", m, GroovyCallSiteSelector.method(new EnumeratingWhitelistTest.C(), "m", new Object[] {new String[] {"a", "b"}}));
57 | }
58 |
59 | @Test public void overloads() throws Exception {
60 | PrintWriter receiver = new PrintWriter(new OutputStream() {
61 | @Override
62 | public void write(int b) throws IOException {
63 | // Do nothing, we do not care
64 | }
65 | });
66 | assertEquals(PrintWriter.class.getMethod("print", Object.class), GroovyCallSiteSelector.method(receiver, "print", new Object[] {new Object()}));
67 | assertEquals(PrintWriter.class.getMethod("print", String.class), GroovyCallSiteSelector.method(receiver, "print", new Object[] {"message"}));
68 | assertEquals(PrintWriter.class.getMethod("print", int.class), GroovyCallSiteSelector.method(receiver, "print", new Object[] {42}));
69 | }
70 |
71 | @Issue("JENKINS-29541")
72 | @Test public void methodsOnGString() throws Exception {
73 | GStringImpl gString = new GStringImpl(new Object[0], new String[] {"x"});
74 | assertEquals(String.class.getMethod("substring", int.class), GroovyCallSiteSelector.method(gString, "substring", new Object[] {99}));
75 | assertEquals(GString.class.getMethod("getValues"), GroovyCallSiteSelector.method(gString, "getValues", new Object[0]));
76 | assertEquals(GString.class.getMethod("getStrings"), GroovyCallSiteSelector.method(gString, "getStrings", new Object[0]));
77 | }
78 |
79 | @Issue("JENKINS-31701")
80 | @Test public void primitives() throws Exception {
81 | assertEquals(Primitives.class.getMethod("m1", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m1", new Object[] {Long.MAX_VALUE}));
82 | assertEquals(Primitives.class.getMethod("m1", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m1", new Object[] {99}));
83 | assertEquals(Primitives.class.getMethod("m2", long.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m2", new Object[] {Long.MAX_VALUE}));
84 | assertEquals(Primitives.class.getMethod("m2", int.class), GroovyCallSiteSelector.staticMethod(Primitives.class, "m2", new Object[] {99}));
85 | }
86 | public static class Primitives {
87 | public static void m1(long x) {}
88 | public static void m2(int x) {}
89 | public static void m2(long x) {}
90 | }
91 |
92 | @Test public void staticMethodsCannotBeOverridden() throws Exception {
93 | assertEquals(Jenkins.class.getMethod("getInstance"), GroovyCallSiteSelector.staticMethod(Jenkins.class, "getInstance", new Object[0]));
94 | assertEquals(Hudson.class.getMethod("getInstance"), GroovyCallSiteSelector.staticMethod(Hudson.class, "getInstance", new Object[0]));
95 | }
96 |
97 | @Issue("JENKINS-45117")
98 | @Test public void constructorVarargs() throws Exception {
99 | assertEquals(EnvVars.class.getConstructor(), GroovyCallSiteSelector.constructor(EnvVars.class, new Object[0]));
100 | assertEquals(EnvVars.class.getConstructor(String[].class), GroovyCallSiteSelector.constructor(EnvVars.class, new Object[]{"x"}));
101 | List params = new ArrayList<>();
102 | params.add(new StringParameterValue("someParam", "someValue"));
103 | params.add(new BooleanParameterValue("someBool", true));
104 | params.add(new StringParameterValue("someOtherParam", "someOtherValue"));
105 | assertEquals(ParametersAction.class.getConstructor(List.class),
106 | GroovyCallSiteSelector.constructor(ParametersAction.class, new Object[]{params}));
107 | assertEquals(ParametersAction.class.getConstructor(ParameterValue[].class),
108 | GroovyCallSiteSelector.constructor(ParametersAction.class, new Object[]{params.get(0)}));
109 | assertEquals(ParametersAction.class.getConstructor(ParameterValue[].class),
110 | GroovyCallSiteSelector.constructor(ParametersAction.class, params.toArray()));
111 | assertEquals(EnumeratingWhitelist.MethodSignature.class.getConstructor(Class.class, String.class, Class[].class),
112 | GroovyCallSiteSelector.constructor(EnumeratingWhitelist.MethodSignature.class,
113 | new Object[]{String.class, "foo", Integer.class, Float.class}));
114 | }
115 |
116 | @Issue("JENKINS-47159")
117 | @Test
118 | public void varargsFailureCases() throws Exception {
119 | // If there's a partial match, we should get a ClassCastException
120 | final ClassCastException e = assertThrows(ClassCastException.class,
121 | () -> assertNull(GroovyCallSiteSelector.constructor(ParametersAction.class,
122 | new Object[]{new BooleanParameterValue("someBool", true), "x"})));
123 | assertEquals("Cannot cast object 'x' with class 'java.lang.String' to class 'hudson.model.ParameterValue'",
124 | e.getMessage());
125 | // If it's a complete non-match, we just shouldn't get a constructor.
126 | assertNull(GroovyCallSiteSelector.constructor(ParametersAction.class, new Object[]{"a", "b"}));
127 | }
128 |
129 | @Issue("JENKINS-37257")
130 | @Test
131 | public void varargsArrayElementTypeMismatch() throws Exception {
132 | List l = Arrays.asList("a", "b", "c");
133 | assertEquals(String.class.getMethod("join", CharSequence.class, Iterable.class),
134 | GroovyCallSiteSelector.staticMethod(String.class, "join", new Object[]{",", l}));
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/GroovyLanguageCoverageTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;
2 |
3 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
4 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.GenericWhitelist;
5 | import org.junit.Ignore;
6 | import org.junit.Rule;
7 | import org.junit.Test;
8 | import org.junit.rules.ErrorCollector;
9 | import org.jvnet.hudson.test.Issue;
10 |
11 | public class GroovyLanguageCoverageTest {
12 |
13 | @Rule
14 | public ErrorCollector errors = new ErrorCollector();
15 |
16 | private void assertEvaluate(Whitelist whitelist, Object expected, String script) {
17 | SandboxInterceptorTest.assertEvaluate(whitelist, expected, script, errors);
18 | }
19 |
20 | @Ignore("This fails on m.\"fruit${bKey}\" returning null due to JENKINS-46327")
21 | @Issue("JENKINS-46327")
22 | @Test
23 | public void quotedIdentifiers() throws Exception {
24 | // See http://groovy-lang.org/syntax.html#_quoted_identifiers
25 | assertEvaluate(new GenericWhitelist(),
26 | true,
27 | "def m = [fruitA: 'apple', fruitB: 'banana']\n" +
28 | "assert m.fruitA == 'apple'\n" +
29 | "assert m.'fruitA' == 'apple'\n" +
30 | "assert m.\"fruitA\" == 'apple'\n" +
31 | "assert m.'''fruitA''' == 'apple'\n" +
32 | "assert m.\"\"\"fruitA\"\"\" == 'apple'\n" +
33 | "assert m./fruitA/ == 'apple'\n" +
34 | "assert m.$/fruitA/$ == 'apple'\n" +
35 | "def bKey = 'B'\n" +
36 | "assert m.\"fruit${bKey}\" == 'banana'\n" +
37 | "assert m.\"\"\"fruit${bKey}\"\"\" == 'banana'" +
38 | "assert m./fruit${bKey}/ == 'banana'\n" +
39 | "assert m.$/fruit${bKey}/$ == 'banana'\n" +
40 | "return true\n"
41 | );
42 | }
43 |
44 | @Test
45 | public void gStringWithClosure() throws Exception {
46 | // see http://groovy-lang.org/syntax.html#_special_case_of_interpolating_closure_expressions
47 | assertEvaluate(new GenericWhitelist(),
48 | true,
49 | "def number = 1\n" +
50 | "def eagerGString = \"value == ${number}\"\n" +
51 | "def lazyGString = \"value == ${ -> number }\"\n" +
52 | "assert eagerGString == 'value == 1'\n" +
53 | "assert lazyGString.toString() == 'value == 1'\n" +
54 | "number = 2\n" +
55 | "assert eagerGString == 'value == 1'\n" +
56 | "assert lazyGString == 'value == 2'\n" +
57 | "return true\n"
58 | );
59 | }
60 |
61 | @Test
62 | public void arithmeticOperators() throws Exception {
63 | // see http://groovy-lang.org/operators.html#_arithmetic_operators
64 | assertEvaluate(new GenericWhitelist(),
65 | true,
66 | "assert 1 + 2 == 3\n" +
67 | "assert 4 - 3 == 1\n" +
68 | "assert 3 * 5 == 15\n" +
69 | "assert 3 / 2 == 1.5\n" +
70 | "assert 10 % 3 == 1\n" +
71 | "assert 2 ** 3 == 8\n" +
72 | "assert +3 == 3\n" +
73 | "assert -4 == 0 - 4\n" +
74 | "assert -(-1) == 1 \n" +
75 | "def a = 2\n" +
76 | "def b = a++ * 3\n" +
77 | "assert a == 3 && b == 6\n" +
78 | "def c = 3\n" +
79 | "def d = c-- * 2\n" +
80 | "assert c == 2 && d == 6\n" +
81 | "def e = 1\n" +
82 | "def f = ++e + 3\n" +
83 | "assert e == 2 && f == 5\n" +
84 | "def g = 4\n" +
85 | "def h = --g + 1\n" +
86 | "assert g == 3 && h == 4\n" +
87 | "def a2 = 4\n" +
88 | "a2 += 3\n" +
89 | "assert a2 == 7\n" +
90 | "def b2 = 5\n" +
91 | "b2 -= 3\n" +
92 | "assert b2 == 2\n" +
93 | "def c2 = 5\n" +
94 | "c2 *= 3\n" +
95 | "assert c2 == 15\n" +
96 | "def d2 = 10\n" +
97 | "d2 /= 2\n" +
98 | "assert d2 == 5\n" +
99 | "def e2 = 10\n" +
100 | "e2 %= 3\n" +
101 | "assert e2 == 1\n" +
102 | "def f2 = 3\n" +
103 | "f2 **= 2\n" +
104 | "assert f2 == 9\n" +
105 | "return true\n"
106 | );
107 | }
108 |
109 | @Test
110 | public void bigDecimalOperators() throws Exception {
111 | // see http://groovy-lang.org/operators.html#_arithmetic_operators
112 | assertEvaluate(new GenericWhitelist(),
113 | true,
114 | "assert 1.0 + 2.0 == 3.0\n" +
115 | "assert 4.0 - 3.0 == 1.0\n" +
116 | "assert 3.0 * 5.0 == 15.0\n" +
117 | "assert 3.0 / 2.0 == 1.5\n" +
118 | "assert 2.0 ** 3.0 == 8.0\n" +
119 | "assert +3.0 == 3.0\n" +
120 | "assert -4.0 == 0 - 4.0\n" +
121 | "assert -(-1.0) == 1.0\n" +
122 | "def a = 2.0\n" +
123 | "def b = a++ * 3.0\n" +
124 | "assert a == 3.0 && b == 6.0\n" +
125 | "def c = 3.0\n" +
126 | "def d = c-- * 2.0\n" +
127 | "assert c == 2.0 && d == 6.0\n" +
128 | "def e = 1.0\n" +
129 | "def f = ++e + 3.0\n" +
130 | "assert e == 2.0 && f == 5.0\n" +
131 | "def g = 4.0\n" +
132 | "def h = --g + 1.0\n" +
133 | "assert g == 3.0 && h == 4.0\n" +
134 | "def a2 = 4.0\n" +
135 | "a2 += 3.0\n" +
136 | "assert a2 == 7.0\n" +
137 | "def b2 = 5.0\n" +
138 | "b2 -= 3.0\n" +
139 | "assert b2 == 2.0\n" +
140 | "def c2 = 5.0\n" +
141 | "c2 *= 3.0\n" +
142 | "assert c2 == 15.0\n" +
143 | "def d2 = 10.0\n" +
144 | "d2 /= 2.0\n" +
145 | "assert d2 == 5.0\n" +
146 | "def f2 = 3.0\n" +
147 | "f2 **= 2.0\n" +
148 | "assert f2 == 9.0\n" +
149 | "return true\n"
150 | );
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/GroovyMemoryLeakTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;
2 |
3 | import groovy.lang.MetaClass;
4 | import hudson.PluginManager;
5 | import hudson.model.FreeStyleProject;
6 | import java.lang.ref.WeakReference;
7 | import java.lang.reflect.Method;
8 | import java.util.ArrayList;
9 | import java.util.Collections;
10 | import java.util.List;
11 | import java.util.logging.Level;
12 | import org.codehaus.groovy.reflection.ClassInfo;
13 | import org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry;
14 | import org.junit.After;
15 | import static org.junit.Assert.*;
16 | import org.junit.ClassRule;
17 | import org.junit.Rule;
18 | import org.junit.Test;
19 | import org.jvnet.hudson.test.BuildWatcher;
20 | import org.jvnet.hudson.test.JenkinsRule;
21 | import org.jvnet.hudson.test.LoggerRule;
22 | import org.jvnet.hudson.test.MemoryAssert;
23 |
24 | /**
25 | * Tests for memory leak cleanup successfully purging the most common memory leak.
26 | */
27 | public class GroovyMemoryLeakTest {
28 | @ClassRule
29 | public static BuildWatcher buildWatcher = new BuildWatcher();
30 | @Rule
31 | public JenkinsRule r = new JenkinsRule();
32 | @Rule public LoggerRule logger = new LoggerRule().record(SecureGroovyScript.class, Level.FINER);
33 |
34 | @After
35 | public void clearLoaders() {
36 | LOADERS.clear();
37 | }
38 | private static final List> LOADERS = new ArrayList<>();
39 |
40 | public static void register(Object o) {
41 | System.err.println("registering " + o);
42 | for (ClassLoader loader = o.getClass().getClassLoader(); !(loader instanceof PluginManager.UberClassLoader); loader = loader.getParent()) {
43 | System.err.println("…from " + loader);
44 | LOADERS.add(new WeakReference<>(loader));
45 | }
46 | }
47 |
48 | @Test
49 | public void loaderReleased() throws Exception {
50 | FreeStyleProject p = r.jenkins.createProject(FreeStyleProject.class, "p");
51 | String cp = GroovyMemoryLeakTest.class.getResource("somejar.jar").toString();
52 | p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript(
53 | GroovyMemoryLeakTest.class.getName() + ".register(this); new somepkg.SomeClass()",
54 | false, Collections.singletonList(new ClasspathEntry(cp)))));
55 | r.buildAndAssertSuccess(p);
56 |
57 | assertFalse(LOADERS.isEmpty());
58 | { // TODO it seems that the call to GroovyMemoryLeakTest.register(Object) on a Script1 parameter creates a MetaMethodIndex.Entry.cachedStaticMethod.
59 | // In other words any call to a foundational API might leak classes. Why does Groovy need to do this?
60 | // Unclear whether this is a problem in a realistic environment; for the moment, suppressing it so the test can run with no SoftReference.
61 | MetaClass metaClass = ClassInfo.getClassInfo(GroovyMemoryLeakTest.class).getMetaClass();
62 | Method clearInvocationCaches = metaClass.getClass().getDeclaredMethod("clearInvocationCaches");
63 | clearInvocationCaches.setAccessible(true);
64 | clearInvocationCaches.invoke(metaClass);
65 | }
66 | for (WeakReference loaderRef : LOADERS) {
67 | MemoryAssert.assertGC(loaderRef, false);
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxResolvingClassLoaderTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2019 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;
26 |
27 | import com.github.benmanes.caffeine.cache.stats.CacheStats;
28 | import org.junit.Test;
29 | import org.jvnet.hudson.test.Issue;
30 |
31 | import static org.hamcrest.MatcherAssert.assertThat;
32 | import static org.hamcrest.Matchers.containsString;
33 | import static org.hamcrest.Matchers.equalTo;
34 | import static org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.CLASS_NOT_FOUND;
35 | import static org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.parentClassCache;
36 | import static org.junit.Assert.assertThrows;
37 |
38 | public class SandboxResolvingClassLoaderTest {
39 |
40 | private final ClassLoader parentLoader = SandboxResolvingClassLoaderTest.class.getClassLoader();
41 | private final SandboxResolvingClassLoader loader = new SandboxResolvingClassLoader(parentLoader);
42 |
43 | @Issue("JENKINS-59587")
44 | @Test public void classCacheDoesNotHoldClassValuesTooWeakly() throws Exception {
45 | // Load a class that does exist.
46 | assertThat(loader.loadClass("java.lang.String", false), equalTo(String.class));
47 | // Load a class that does not exist.
48 | final ClassNotFoundException e = assertThrows(ClassNotFoundException.class,
49 | () -> loader.loadClass("this.does.not.Exist", false));
50 | assertThat(e.getMessage(), containsString("this.does.not.Exist"));
51 | // The result of both of the class loading attempts should exist in the cache.
52 | assertThat(parentClassCache.get(parentLoader).getIfPresent("java.lang.String"), equalTo(String.class));
53 | assertThat(parentClassCache.get(parentLoader).getIfPresent("this.does.not.Exist"), equalTo(CLASS_NOT_FOUND));
54 | // Make sure that both of the entries are still in the cache after a GC.
55 | System.gc();
56 | assertThat(parentClassCache.get(parentLoader).getIfPresent("java.lang.String"), equalTo(String.class));
57 | assertThat(parentClassCache.get(parentLoader).getIfPresent("this.does.not.Exist"), equalTo(CLASS_NOT_FOUND));
58 | CacheStats stats = parentClassCache.get(parentLoader).stats();
59 | // Before the fix for JENKINS-59587, the original inner cache was removed after the call to `System.gc()`, so
60 | // the miss count was 2 and the hit count was 0.
61 | assertThat(stats.missCount(), equalTo(2L)); // The two calls to `loadClass()`
62 | assertThat(stats.hitCount(), equalTo(4L)); // The four calls to `getIfPresent()`
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/TestGroovyRecorder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 Jesse Glick.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;
26 |
27 | import edu.umd.cs.findbugs.annotations.NonNull;
28 | import groovy.lang.Binding;
29 | import hudson.Extension;
30 | import hudson.Launcher;
31 | import hudson.model.AbstractBuild;
32 | import hudson.model.AbstractProject;
33 | import hudson.model.BuildListener;
34 | import hudson.tasks.BuildStepDescriptor;
35 | import hudson.tasks.BuildStepMonitor;
36 | import hudson.tasks.Publisher;
37 | import hudson.tasks.Recorder;
38 | import java.io.IOException;
39 | import jenkins.model.Jenkins;
40 | import org.kohsuke.stapler.DataBoundConstructor;
41 |
42 | /**
43 | * Sample of integrating {@link SecureGroovyScript}.
44 | * The result of the configured Groovy script is set as the build description.
45 | */
46 | @SuppressWarnings({"unchecked", "rawtypes"})
47 | public final class TestGroovyRecorder extends Recorder {
48 |
49 | private final SecureGroovyScript script;
50 | private transient Binding binding;
51 |
52 | @DataBoundConstructor public TestGroovyRecorder(SecureGroovyScript script) {
53 | this.script = script.configuringWithKeyItem();
54 | }
55 |
56 | public SecureGroovyScript getScript() {
57 | return script;
58 | }
59 |
60 | public void setBinding(Binding binding) {
61 | this.binding = binding;
62 | }
63 |
64 | @Override public boolean perform(AbstractBuild, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
65 | try {
66 | if (binding == null) {
67 | binding = new Binding();
68 | }
69 | binding.setVariable("build", build);
70 | build.setDescription(String.valueOf(script.evaluate(Jenkins.get().getPluginManager().uberClassLoader, binding, listener)));
71 | } catch (Exception x) {
72 | throw new IOException(x);
73 | }
74 | return true;
75 | }
76 |
77 | @Override public BuildStepMonitor getRequiredMonitorService() {
78 | return BuildStepMonitor.NONE;
79 | }
80 |
81 | @Extension public static final class DescriptorImpl extends BuildStepDescriptor {
82 |
83 | @NonNull
84 | @Override public String getDisplayName() {
85 | return "Test Groovy Recorder";
86 | }
87 |
88 | @Override public boolean isApplicable(Class extends AbstractProject> jobType) {
89 | return true;
90 | }
91 |
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/GenericWhitelistTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2016 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
28 | import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxInterceptorTest;
29 | import org.junit.Rule;
30 | import org.junit.Test;
31 | import org.junit.rules.ErrorCollector;
32 | import org.jvnet.hudson.test.Issue;
33 |
34 | public class GenericWhitelistTest {
35 |
36 | @Rule public ErrorCollector errors = new ErrorCollector();
37 |
38 | @Test public void sanity() throws Exception {
39 | StaticWhitelistTest.sanity(StaticWhitelist.class.getResource("generic-whitelist"));
40 | }
41 |
42 | @Issue("SECURITY-538")
43 | @Test public void dynamicSubscript() throws Exception {
44 | String dangerous = Dangerous.class.getName();
45 | Whitelist wl = new ProxyWhitelist(new GenericWhitelist(), new AnnotatedWhitelist());
46 | // Control cases—explicit method call:
47 | SandboxInterceptorTest.assertRejected(wl, "staticMethod " + dangerous + " isSecured", dangerous + ".isSecured()", errors);
48 | SandboxInterceptorTest.assertRejected(wl, "staticMethod " + dangerous + " setSecured boolean", dangerous + ".setSecured(false)", errors);
49 | SandboxInterceptorTest.assertRejected(wl, "method " + dangerous + " getSecret", "new " + dangerous + "().getSecret()", errors);
50 | SandboxInterceptorTest.assertRejected(wl, "method " + dangerous + " setSecret java.lang.String", "new " + dangerous + "().setSecret('')", errors);
51 | // Control cases—statically resolvable property accesses:
52 | SandboxInterceptorTest.assertRejected(wl, "staticMethod " + dangerous + " isSecured", dangerous + ".secured", errors);
53 | SandboxInterceptorTest.assertRejected(wl, "staticMethod " + dangerous + " setSecured boolean", dangerous + ".secured = false", errors);
54 | SandboxInterceptorTest.assertRejected(wl, "method " + dangerous + " getSecret", "new " + dangerous + "().secret", errors);
55 | SandboxInterceptorTest.assertRejected(wl, "method " + dangerous + " setSecret java.lang.String", "new " + dangerous + "().secret = ''", errors);
56 | // Test cases—dynamically resolved property accesses:
57 | String getAt = "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods getAt java.lang.Object java.lang.String";
58 | String putAt = "staticMethod org.codehaus.groovy.runtime.DefaultGroovyMethods putAt java.lang.Object java.lang.String java.lang.Object";
59 | SandboxInterceptorTest.assertRejected(wl, getAt, dangerous + "['secured']", errors);
60 | SandboxInterceptorTest.assertRejected(wl, putAt, dangerous + "['secured'] = false", errors);
61 | SandboxInterceptorTest.assertRejected(wl, getAt, "new " + dangerous + "()['secret']", errors);
62 | SandboxInterceptorTest.assertRejected(wl, putAt, "new " + dangerous + "()['secret'] = ''", errors);
63 | // Test cases via JsonOutput.
64 | SandboxInterceptorTest.assertRejected(wl, "staticMethod groovy.json.JsonOutput toJson java.lang.Object", "groovy.json.JsonOutput.toJson(new " + dangerous + "())", errors);
65 | // toJson(Closure) seems blocked anyway by lack of access to JsonDelegate.content, directly or via GroovyObject.setProperty
66 | }
67 | public static class Dangerous {
68 | @Whitelisted
69 | public Dangerous() {}
70 | public static boolean isSecured() {return true;}
71 | public static void setSecured(boolean secured) {}
72 | public String getSecret() {return null;}
73 | public void setSecret(String secret) {}
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/JenkinsWhitelistTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2016 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import org.junit.Test;
28 |
29 | public class JenkinsWhitelistTest {
30 |
31 | @Test public void sanity() throws Exception {
32 | StaticWhitelistTest.sanity(StaticWhitelist.class.getResource("jenkins-whitelist"));
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/ProxyWhitelistTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
28 | import org.junit.Test;
29 | import org.jvnet.hudson.test.Issue;
30 |
31 | import java.io.IOException;
32 | import java.io.StringReader;
33 | import java.util.Arrays;
34 | import java.util.Collections;
35 | import java.util.List;
36 | import java.util.concurrent.ExecutorService;
37 | import java.util.concurrent.Executors;
38 | import java.util.concurrent.TimeUnit;
39 |
40 | import static org.junit.Assert.assertEquals;
41 | import static org.junit.Assert.assertFalse;
42 | import static org.junit.Assert.assertTrue;
43 |
44 | public class ProxyWhitelistTest {
45 |
46 | @Test public void reset() throws Exception {
47 | ProxyWhitelist pw1 = new ProxyWhitelist(new StaticWhitelist(new StringReader("method java.lang.String length")));
48 | ProxyWhitelist pw2 = new ProxyWhitelist(pw1);
49 | assertTrue(pw2.permitsMethod(String.class.getMethod("length"), "x", new Object[0]));
50 | assertFalse(pw2.permitsMethod(Object.class.getMethod("hashCode"), "x", new Object[0]));
51 | pw1.reset(Collections.singleton(new StaticWhitelist(new StringReader("method java.lang.String length\nmethod java.lang.Object hashCode"))));
52 | assertTrue(pw2.permitsMethod(String.class.getMethod("length"), "x", new Object[0]));
53 | assertTrue(pw2.permitsMethod(Object.class.getMethod("hashCode"), "x", new Object[0]));
54 | pw1.reset(Collections.emptySet());
55 | assertFalse(pw2.permitsMethod(String.class.getMethod("length"), "x", new Object[0]));
56 | assertFalse(pw2.permitsMethod(Object.class.getMethod("hashCode"), "x", new Object[0]));
57 | }
58 |
59 | @Test public void resetStaticField() throws Exception {
60 | ProxyWhitelist pw1 = new ProxyWhitelist(new StaticWhitelist(new StringReader("staticField java.util.Collections EMPTY_LIST")));
61 | ProxyWhitelist pw2 = new ProxyWhitelist(pw1);
62 | assertTrue(pw2.permitsStaticFieldGet(Collections.class.getField("EMPTY_LIST")));
63 | pw1.reset(Collections.emptySet());
64 | assertFalse(pw2.permitsStaticFieldGet(Collections.class.getField("EMPTY_LIST")));
65 | }
66 |
67 | /**
68 | * Test concurrent modification of delegates when initializing a ProxyWhitelist. This may cause concurrent threads
69 | * to enter an infinite loop when using a {@link java.util.WeakHashMap} to hold the delegates as it is not
70 | * synchronized. This is a rare case that is also related to Garbage Collection.
71 | * If contention is acceptable and/or the delegates collection behave, this test should be quite fast, just a few
72 | * seconds. A timeout is allowed to process the creation of 1000000 {@link ProxyWhitelist} using an original list of
73 | * 2 delegates. If the tasks have not completed by then, we can assume that there is a problem.
74 | */
75 | @Issue("JENKINS-41797")
76 | @Test(timeout = 30000)
77 | public void testConcurrent() throws InterruptedException, IOException {
78 | int threadPoolSize = 2;
79 | List delegates = Arrays.asList(
80 | new ProxyWhitelist(new StaticWhitelist(new StringReader("staticField java.util.Collections EMPTY_LIST"))),
81 | new ProxyWhitelist(new StaticWhitelist(new StringReader("method java.lang.String length")))
82 | );
83 |
84 | ExecutorService es = Executors.newFixedThreadPool(threadPoolSize);
85 |
86 | for (int i = 1; i < 1000000; i++) {
87 | es.submit(() -> {
88 | new ProxyWhitelist(delegates);
89 | });
90 | }
91 |
92 | es.shutdown();
93 | // If interrupted after the timeout, something went wrong
94 | assert es.awaitTermination(15000, TimeUnit.MILLISECONDS);
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/StaticWhitelistTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2015 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists;
26 |
27 | import groovy.lang.GroovyObject;
28 | import java.io.BufferedReader;
29 | import java.io.File;
30 | import java.io.InputStream;
31 | import java.io.InputStreamReader;
32 | import java.net.URL;
33 | import java.nio.charset.StandardCharsets;
34 | import java.text.MessageFormat;
35 | import java.util.ArrayList;
36 | import java.util.Arrays;
37 | import java.util.Collection;
38 | import java.util.Collections;
39 | import java.util.HashSet;
40 | import java.util.List;
41 | import java.util.Random;
42 | import java.util.Set;
43 | import java.util.regex.Matcher;
44 | import java.util.regex.MatchResult;
45 |
46 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.EnumeratingWhitelist.MethodSignature;
47 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.EnumeratingWhitelist.NewSignature;
48 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.EnumeratingWhitelist.Signature;
49 | import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.EnumeratingWhitelist.StaticMethodSignature;
50 | import org.junit.Assert;
51 | import org.junit.Test;
52 | import static org.junit.Assert.*;
53 |
54 | public class StaticWhitelistTest {
55 |
56 | @Test public void dangerous() throws Exception {
57 | assertFalse(StaticWhitelist.rejectMethod(Collection.class.getMethod("clear")).isDangerous());
58 | assertTrue(StaticWhitelist.rejectNew(File.class.getConstructor(String.class)).isDangerous());
59 | assertTrue(StaticWhitelist.rejectMethod(GroovyObject.class.getMethod("invokeMethod", String.class, Object.class)).isDangerous());
60 | }
61 |
62 | static void sanity(URL definition) throws Exception {
63 | StaticWhitelist wl = StaticWhitelist.from(definition);
64 | List sigs = new ArrayList<>();
65 | try (InputStream is = definition.openStream(); InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader br = new BufferedReader(isr)) {
66 | String line;
67 | while ((line = br.readLine()) != null) {
68 | line = StaticWhitelist.filter(line);
69 | if (line == null) {
70 | continue;
71 | }
72 | sigs.add(StaticWhitelist.parse(line));
73 | }
74 | }
75 |
76 | HashSet existingSigs = new HashSet<>(sigs.size());
77 | boolean hasDupes = false;
78 | for (EnumeratingWhitelist.Signature sig : sigs) {
79 | if (!existingSigs.add(sig)) {
80 | System.out.println("Duplicate whitelist signature: "+sig);
81 | hasDupes = true;
82 | }
83 | }
84 | Assert.assertFalse("Whitelist contains duplicate entries, and this is not allowed! Please see list above.", hasDupes);
85 |
86 | ArrayList sorted = new ArrayList<>(sigs);
87 | Collections.sort(sorted);
88 |
89 | boolean isUnsorted = false;
90 | for (int i=0; i KNOWN_GOOD_SIGNATURES = new HashSet<>(Arrays.asList(
119 | // From workflow-cps, which is not a dependency of this plugin.
120 | new NewSignature("org.jenkinsci.plugins.workflow.cps.CpsScript"),
121 | // From workflow-support, which is not a dependency of this plugin.
122 | new MethodSignature("org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper", "getRawBuild"),
123 | // From groovy-cps, which is not a dependency of this plugin.
124 | new StaticMethodSignature("com.cloudbees.groovy.cps.CpsDefaultGroovyMethods", "each",
125 | "java.util.Iterator", "groovy.lang.Closure"),
126 | // Overrides CharSequence.isEmpty in Java 15+.
127 | new MethodSignature(String.class, "isEmpty"),
128 | // Do not exist until Java 15.
129 | new MethodSignature(CharSequence.class, "isEmpty"),
130 | new MethodSignature(String.class, "stripIndent"),
131 | // Override the corresponding RandomGenerator methods in Java 17+.
132 | new MethodSignature(Random.class, "nextBoolean"),
133 | new MethodSignature(Random.class, "nextBytes", byte[].class),
134 | new MethodSignature(Random.class, "nextDouble"),
135 | new MethodSignature(Random.class, "nextFloat"),
136 | new MethodSignature(Random.class, "nextGaussian"),
137 | new MethodSignature(Random.class, "nextInt"),
138 | new MethodSignature(Random.class, "nextInt", int.class),
139 | new MethodSignature(Random.class, "nextLong"),
140 | // Do not exist until Java 17.
141 | new MethodSignature("java.util.random.RandomGenerator", "nextBoolean"),
142 | new MethodSignature("java.util.random.RandomGenerator", "nextBytes", "byte[]"),
143 | new MethodSignature("java.util.random.RandomGenerator", "nextDouble"),
144 | new MethodSignature("java.util.random.RandomGenerator", "nextFloat"),
145 | new MethodSignature("java.util.random.RandomGenerator", "nextGaussian"),
146 | new MethodSignature("java.util.random.RandomGenerator", "nextInt"),
147 | new MethodSignature("java.util.random.RandomGenerator", "nextInt", "int"),
148 | new MethodSignature("java.util.random.RandomGenerator", "nextLong"),
149 | // Override the corresponding MatchResult methods in Java 20+.
150 | new MethodSignature(Matcher.class, "end", String.class),
151 | new MethodSignature(Matcher.class, "group", String.class),
152 | new MethodSignature(Matcher.class, "start", String.class),
153 | // Do not exist until Java 20.
154 | new MethodSignature(MatchResult.class, "end", String.class),
155 | new MethodSignature(MatchResult.class, "group", String.class),
156 | new MethodSignature(MatchResult.class, "hasMatch"),
157 | new MethodSignature(MatchResult.class, "namedGroups"),
158 | new MethodSignature(MatchResult.class, "start", String.class),
159 | // TODO No longer exists after Jenkins includes https://github.com/jenkinsci/jenkins/pull/9674
160 | // Remove once plugin depends on Jenkins 2.477 or newer
161 | // Remove from jenkins-whitelist at the same time
162 | new MethodSignature("hudson.model.Run", "getFullDisplayName"),
163 | // TODO Do not exist until Jenkins includes https://github.com/jenkinsci/jenkins/pull/9674
164 | new MethodSignature("jenkins.model.HistoricalBuild", "getFullDisplayName")
165 | ));
166 |
167 | @Test public void sanity() throws Exception {
168 | sanity(StaticWhitelist.class.getResource("blacklist"));
169 | }
170 |
171 | }
172 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/AbstractApprovalTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 Jesse Glick, CloudBees Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import org.junit.Rule;
28 | import org.junit.Test;
29 | import org.jvnet.hudson.test.JenkinsRule;
30 |
31 | public abstract class AbstractApprovalTest> {
32 |
33 | @Rule public JenkinsRule r = new JenkinsRule();
34 |
35 | /** Creates a new approvable to test. */
36 | abstract T create() throws Exception;
37 |
38 | @Test public void noSecurity() throws Exception {
39 | create().use();
40 | }
41 |
42 | @Test public void withSecurity() throws Exception {
43 | configureSecurity();
44 | // Cannot use until approved
45 | create().assertCannotUse().approve().use();
46 | }
47 |
48 | void configureSecurity() {
49 | r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
50 | }
51 |
52 | abstract void clearAllApproved() throws Exception;
53 |
54 | /** Returns the id of the section containing the clear all button. */
55 | String getClearAllApprovedId() {
56 | return null;
57 | }
58 |
59 | private Approvable>[] createFiveEntries() throws Exception {
60 | final Approvable>[] entries = new Approvable>[5];
61 | for (int i = 0; i < entries.length; i++) {
62 | entries[i] = create().assertPending();
63 | }
64 | return entries;
65 | }
66 |
67 | @Test public void approveInternal() throws Exception {
68 | configureSecurity();
69 | final Approvable>[] entries = createFiveEntries();
70 | entries[0].approve().assertApproved();
71 | entries[1].approve().assertApproved();
72 | entries[2].deny().assertDeleted();
73 | entries[3].approve().assertApproved();
74 | if (entries[3].canDelete()) {
75 | entries[3].delete().assertDeleted();
76 | }
77 | clearAllApproved();
78 | for (int i = 0; i < 4; i++) {
79 | entries[i].assertDeleted();
80 | }
81 | entries[4].assertPending();
82 | }
83 |
84 |
85 | @Test public void approveExternal() throws Exception {
86 | configureSecurity();
87 | final Approvable>[] entries = createFiveEntries();
88 |
89 | final Manager manager = new Manager(r);
90 |
91 | for (Approvable> entry : entries) {
92 | entry.pending(manager);
93 | }
94 |
95 | entries[0].pending(manager).approve().approved(manager);
96 | entries[1].pending(manager).approve().approved(manager);
97 | entries[2].pending(manager).deny().assertDeleted();
98 | entries[3].pending(manager).approve().approved(manager);
99 | if (entries[3].canDelete()) {
100 | entries[3].approved(manager).delete().assertDeleted(manager);
101 | }
102 |
103 | // clear all classpaths
104 | final String clearId = getClearAllApprovedId();
105 | if (clearId != null) {
106 | manager.click(clearId);
107 | for (int i = 0; i < 4; i++) {
108 | entries[i].assertDeleted(manager);
109 | }
110 | }
111 | // The last one remains pending
112 | entries[4].pending(manager);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/Approvable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2016 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import static org.junit.Assert.assertFalse;
28 | import static org.junit.Assert.assertTrue;
29 | import static org.junit.Assert.fail;
30 |
31 | /**
32 | * Base class for approvable entities.
33 | */
34 | abstract class Approvable> {
35 | Approvable() {
36 | }
37 |
38 | @SuppressWarnings("unchecked")
39 | final T self() {
40 | return (T) this;
41 | }
42 |
43 | abstract boolean findPending();
44 |
45 | abstract boolean findApproved();
46 |
47 | abstract T use() throws Exception;
48 |
49 | abstract boolean canUse() throws Exception;
50 |
51 | final T assertCannotUse() throws Exception {
52 | if (canUse()) {
53 | fail(this + "should have been rejected");
54 | }
55 | return self();
56 | }
57 |
58 | final T assertPending() {
59 | assertTrue(this + " should be pending", findPending());
60 | assertFalse(this + " shouldn't be approved", findApproved());
61 | return self();
62 | }
63 |
64 | final T assertApproved() {
65 | assertFalse(this + " shouldn't be pending", findPending());
66 | assertTrue(this + " should be approved", findApproved());
67 | return self();
68 | }
69 |
70 | final T assertDeleted() {
71 | assertFalse(this + " shouldn't be pending", findPending());
72 | assertFalse(this + " shouldn't be approved", findApproved());
73 | return self();
74 | }
75 |
76 | abstract T approve() throws Exception;
77 |
78 | abstract T deny() throws Exception;
79 |
80 | /** Returns whether the approvable supports deleting approved instances. */
81 | boolean canDelete() {
82 | return false;
83 | }
84 |
85 | /** If deletion is supported, it is implemented here. */
86 | T delete() throws Exception {
87 | throw new UnsupportedOperationException();
88 | }
89 |
90 | abstract Manager.Element pending(Manager manager);
91 |
92 | abstract Manager.Element approved(Manager manager);
93 |
94 | abstract T assertDeleted(Manager manager);
95 |
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ClasspathEntryTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2014 Jesse Glick.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import hudson.Functions;
28 |
29 | import java.io.File;
30 | import java.net.MalformedURLException;
31 | import java.net.URL;
32 | import java.nio.file.Files;
33 | import java.nio.file.Path;
34 |
35 | import jenkins.model.Jenkins;
36 | import org.htmlunit.html.HtmlPage;
37 | import org.junit.Rule;
38 | import org.junit.Test;
39 | import org.junit.rules.TemporaryFolder;
40 |
41 | import static org.hamcrest.Matchers.containsString;
42 | import static org.hamcrest.Matchers.emptyString;
43 | import static org.junit.Assert.*;
44 |
45 | import org.jvnet.hudson.test.*;
46 |
47 | public class ClasspathEntryTest {
48 | @Rule public TemporaryFolder rule = new TemporaryFolder();
49 | @Rule public JenkinsRule jr = new JenkinsRule();
50 |
51 | @Issue("SECURITY-3447")
52 | @Test
53 | public void testDoCheckPath() throws Exception {
54 | jr.jenkins.setSecurityRealm(jr.createDummySecurityRealm());
55 | jr.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
56 | grant(Jenkins.ADMINISTER).everywhere().to("admin")
57 | .grant(Jenkins.READ).everywhere().to("dev"));
58 | Path path = Files.createTempDirectory("temp dir");
59 | try(JenkinsRule.WebClient webClient = jr.createWebClient()) {
60 | webClient.login("admin");
61 | final HtmlPage adminPage = webClient.goTo("descriptor/org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry/checkPath?value=" + path.toUri());
62 | final String adminContent = adminPage.asXml();
63 | assertThat(adminContent, containsString("Class directories are not allowed as classpath entries."));
64 | }
65 | try (JenkinsRule.WebClient devWebClient = jr.createWebClient()) {
66 | devWebClient.login("dev");
67 | final HtmlPage devPage = devWebClient.goTo("descriptor/org.jenkinsci.plugins.scriptsecurity.scripts.ClasspathEntry/checkPath?value=" + path.toUri());
68 | final String devContent = devPage.asNormalizedText();
69 | assertThat(devContent, emptyString());
70 | }
71 | Files.deleteIfExists(path);
72 |
73 | }
74 |
75 | @WithoutJenkins
76 | @Test public void pathURLConversion() throws Exception {
77 | if (!Functions.isWindows()) {
78 | assertRoundTrip("/tmp/x.jar", "file:/tmp/x.jar");
79 | } else {
80 | assertRoundTrip("C:\\tmp\\x.jar", "file:/C:/tmp/x.jar");
81 | }
82 | assertRoundTrip("jar:file:/tmp/x.jar!/subjar.jar", "jar:file:/tmp/x.jar!/subjar.jar");
83 | }
84 |
85 | private static void assertRoundTrip(String path, String url) throws Exception {
86 | assertEquals(path, ClasspathEntry.urlToPath(new URL(url)));
87 | assertEquals(url, ClasspathEntry.pathToURL(path).toString());
88 | }
89 |
90 | @WithoutJenkins
91 | @Test public void classDirDetected() throws Exception {
92 | final File tmpDir = rule.newFolder();
93 | assertTrue("Existing directory must be detected", ClasspathEntry.isClassDirectoryURL(tmpDir.toURI().toURL()));
94 | tmpDir.delete();
95 | final File notExisting = new File(tmpDir, "missing");
96 | final URL missing = tmpDir.toURI().toURL();
97 | assertFalse("Non-existing file is not considered class directory", ClasspathEntry.isClassDirectoryURL(missing));
98 | final URL oneDir = new URL(missing.toExternalForm() + "/");
99 | assertTrue("Non-existing file is considered class directory if ending in /", ClasspathEntry.isClassDirectoryURL(oneDir));
100 | assertTrue("Generic URLs ending in / are considered class directories", ClasspathEntry.isClassDirectoryURL(new URL("http://example.com/folder/")));
101 | assertFalse("Generic URLs ending in / are not considered class directories", ClasspathEntry.isClassDirectoryURL(new URL("http://example.com/file")));
102 | }
103 |
104 | @WithoutJenkins
105 | @Issue("JENKINS-37599")
106 | @Test public void pathToURL() throws Exception {
107 | ClasspathEntry ignore = new ClasspathEntry("http://nowhere.net/");
108 | ignore = new ClasspathEntry(rule.newFile("x.jar").getAbsolutePath());
109 | ignore = new ClasspathEntry(rule.newFolder().getAbsolutePath());
110 |
111 | assertThrows(MalformedURLException.class, () -> new ClasspathEntry(""));
112 | assertThrows(MalformedURLException.class, () -> new ClasspathEntry(" "));
113 | assertThrows(MalformedURLException.class, () -> new ClasspathEntry("relative"));
114 | }
115 |
116 | }
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/EntryApprovalTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2016 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import hudson.Util;
28 | import org.apache.commons.io.FileUtils;
29 | import org.junit.Rule;
30 | import org.junit.Test;
31 | import org.junit.rules.TemporaryFolder;
32 | import org.jvnet.hudson.test.WithoutJenkins;
33 |
34 | import java.io.File;
35 | import java.io.IOException;
36 | import java.net.URL;
37 | import java.security.SecureRandom;
38 | import java.util.TreeSet;
39 |
40 | import static org.junit.Assert.assertEquals;
41 | import static org.junit.Assert.assertTrue;
42 |
43 | public final class EntryApprovalTest extends AbstractApprovalTest {
44 |
45 | @Rule public TemporaryFolder tmpFolderRule = new TemporaryFolder();
46 |
47 | private final SecureRandom random = new SecureRandom();
48 |
49 | @Override
50 | Entry create() throws Exception {
51 | final File file = tmpFolderRule.newFile();
52 | final byte[] bytes = new byte[1024];
53 | random.nextBytes(bytes);
54 | FileUtils.writeByteArrayToFile(file, bytes);
55 | return entry(file);
56 | }
57 |
58 | @Override
59 | void clearAllApproved() throws Exception {
60 | ScriptApproval.get().clearApprovedClasspathEntries();
61 | }
62 |
63 | @Override
64 | String getClearAllApprovedId() {
65 | return "approvedClasspathEntries-clear";
66 | }
67 |
68 |
69 | @Test public void classDirRejectedEvenWithNoSecurity() throws Exception {
70 | entry(tmpFolderRule.newFolder());
71 | assertTrue("Class directory shouldn't be pending", ScriptApproval.get().getPendingClasspathEntries().isEmpty());
72 | assertTrue("Class directory shouldn't be accepted", ScriptApproval.get().getApprovedClasspathEntries().isEmpty());
73 | }
74 |
75 | // http://stackoverflow.com/a/25393190/12916
76 | @WithoutJenkins
77 | @Test public void getPendingClasspathEntry() throws Exception {
78 | TreeSet pendingClasspathEntries = new TreeSet<>();
79 | for (int i = 1; i < 100; i++) {
80 | pendingClasspathEntries.add(new ScriptApproval.PendingClasspathEntry(hashOf(i), new URL("file:/x" + i + ".jar"), ApprovalContext.create()));
81 | }
82 | ScriptApproval.PendingClasspathEntry dummy = new ScriptApproval.PendingClasspathEntry(hashOf(77), null, null);
83 | ScriptApproval.PendingClasspathEntry real = pendingClasspathEntries.floor(dummy);
84 | assertEquals(real, dummy);
85 | assertEquals("file:/x77.jar", real.getURL().toString());
86 | }
87 | private static String hashOf(int i) {
88 | return Util.getDigestOf("hash #" + i);
89 | }
90 |
91 | private static Entry entry(File f) throws Exception {
92 | return new Entry(new ClasspathEntry(f.toURI().toURL().toExternalForm()));
93 | }
94 |
95 | static final class Entry extends Approvable {
96 | private final ClasspathEntry entry;
97 | private final String hash;
98 |
99 | Entry(ClasspathEntry entry) throws IOException {
100 | this.entry = entry;
101 | ScriptApproval.get().configuring(entry, ApprovalContext.create());
102 | // If configure is successful, calculate the hash
103 | this.hash = ScriptApproval.DEFAULT_HASHER.hashClasspathEntry(entry.getURL());
104 | }
105 |
106 | @Override
107 | Entry use() throws IOException {
108 | ScriptApproval.get().using(entry);
109 | return this;
110 | }
111 |
112 | @Override
113 | boolean canUse() throws Exception {
114 | try {
115 | use();
116 | } catch(UnapprovedClasspathException e) {
117 | return false;
118 | }
119 | return true;
120 | }
121 |
122 | @Override
123 | boolean findPending() {
124 | for (ScriptApproval.PendingClasspathEntry pending : ScriptApproval.get().getPendingClasspathEntries()) {
125 | if (pending.getHash().equals(hash)) {
126 | return true;
127 | }
128 | }
129 | return false;
130 | }
131 |
132 | @Override
133 | boolean findApproved() {
134 | for (ScriptApproval.ApprovedClasspathEntry approved : ScriptApproval.get().getApprovedClasspathEntries()) {
135 | if (approved.getHash().equals(hash)) {
136 | return true;
137 | }
138 | }
139 | return false;
140 | }
141 |
142 | @Override
143 | Entry approve() throws IOException {
144 | assertPending();
145 | ScriptApproval.get().approveClasspathEntry(hash);
146 | return this;
147 | }
148 |
149 | @Override
150 | Entry deny() throws IOException {
151 | assertPending();
152 | ScriptApproval.get().denyClasspathEntry(hash);
153 | return this;
154 | }
155 |
156 | @Override
157 | boolean canDelete() {
158 | return true;
159 | }
160 |
161 | @Override
162 | Entry delete() throws IOException {
163 | assertApproved();
164 | ScriptApproval.get().denyApprovedClasspathEntry(hash);
165 | return this;
166 | }
167 |
168 | private String acp() {
169 | return "acp-" + hash;
170 | }
171 |
172 | private String pcp() {
173 | return "pcp-" + hash;
174 | }
175 |
176 | @Override
177 | Manager.Element pending(Manager manager) {
178 | assertPending();
179 | return manager.notFound(this, acp()).found(this, pcp());
180 | }
181 |
182 | @Override
183 | Manager.Element approved(Manager manager) {
184 | assertApproved();
185 | return manager.notFound(this, pcp()).found(this, acp());
186 | }
187 |
188 | @Override
189 | Entry assertDeleted(Manager manager) {
190 | assertDeleted();
191 | manager.notFound(this, pcp()).notFound(this, acp());
192 | return this;
193 | }
194 |
195 | @Override
196 | public String toString() {
197 | return String.format("ClasspathEntry[%s]", entry.getURL());
198 | }
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/HasherScriptApprovalTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.scriptsecurity.scripts;
2 |
3 | import org.hamcrest.Matcher;
4 | import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage;
5 | import edu.umd.cs.findbugs.annotations.NonNull;
6 | import org.junit.Rule;
7 | import org.junit.Test;
8 | import org.jvnet.hudson.test.Issue;
9 | import org.jvnet.hudson.test.JenkinsSessionRule;
10 | import org.jvnet.hudson.test.LoggerRule;
11 |
12 | import java.io.IOException;
13 | import java.net.MalformedURLException;
14 | import java.net.URL;
15 | import java.util.logging.Level;
16 |
17 | import static org.hamcrest.MatcherAssert.assertThat;
18 | import static org.hamcrest.Matchers.containsInRelativeOrder;
19 | import static org.hamcrest.Matchers.containsString;
20 | import static org.hamcrest.Matchers.hasItem;
21 | import static org.hamcrest.Matchers.not;
22 | import static org.junit.Assert.assertEquals;
23 | import static org.junit.Assert.assertTrue;
24 |
25 | public class HasherScriptApprovalTest {
26 | @Rule
27 | public JenkinsSessionRule session = new JenkinsSessionRule();
28 | @Rule
29 | public LoggerRule log = new LoggerRule();
30 |
31 | @Test
32 | @Issue("SECURITY-2564")
33 | public void hasherMatchesItsOwnHashes() throws Throwable {
34 | session.then(r -> {
35 | for (ScriptApproval.Hasher hasher : ScriptApproval.Hasher.values()) {
36 | assertTrue(hasher.pattern().matcher(hasher.hash("Hello World", "Text")).matches());
37 | }
38 | });
39 | }
40 |
41 | @Test
42 | @Issue("SECURITY-2564")
43 | public void warnsAndClearsDeprecatedScriptHashes() throws Throwable {
44 | session.then(r -> {
45 | final ScriptApproval approval = ScriptApproval.get();
46 | approval.approveScript(ScriptApproval.Hasher.SHA1.hash("Hello World", "Text"));
47 | approval.approveScript(ScriptApproval.Hasher.SHA1.hash("node { echo 'Hello World' }", "Groovy"));
48 | approval.approveScript(ScriptApproval.DEFAULT_HASHER.hash("have you tried it sometime?", "Text"));
49 | });
50 | log.record(ScriptApproval.class.getName(), Level.FINE).capture(10000);
51 | session.then(r -> {
52 | final ScriptApproval approval = ScriptApproval.get();
53 | assertEquals(2, approval.countDeprecatedApprovedScriptHashes());
54 | assertThat(log.getMessages(), hasItem(
55 | containsString("There are 2 deprecated approved script hashes " +
56 | "and 0 deprecated approved classpath hashes.")));
57 | approval.clearDeprecatedApprovedScripts();
58 | assertEquals(0, approval.countDeprecatedApprovedScriptHashes());
59 | });
60 | }
61 |
62 | @Test
63 | @Issue("SECURITY-2564")
64 | public void convertsScriptApprovalsOnUse() throws Throwable {
65 | final String script = "node { echo 'Hello World' }";
66 | final Matcher> logMatcher = containsInRelativeOrder(
67 | containsString("A script is approved with an old hash algorithm. Converting now, "));
68 | session.then(r -> {
69 | final ScriptApproval approval = ScriptApproval.get();
70 | approval.approveScript(ScriptApproval.Hasher.SHA1.hash("Hello World", "Text"));
71 | approval.approveScript(ScriptApproval.Hasher.SHA1.hash(script, GroovyLanguage.get().getName()));
72 | approval.approveScript(ScriptApproval.DEFAULT_HASHER.hash("have you tried it sometime?", "Text"));
73 | });
74 | log.record(ScriptApproval.class.getName(), Level.FINE).capture(10000);
75 | session.then(r -> {
76 | final ScriptApproval approval = ScriptApproval.get();
77 | assertEquals(2, approval.countDeprecatedApprovedScriptHashes());
78 | approval.using(script, GroovyLanguage.get());
79 | assertEquals(1, approval.countDeprecatedApprovedScriptHashes());
80 | assertThat(log.getMessages(), logMatcher);
81 | });
82 | log.capture(10000);
83 | session.then(r -> {
84 | final ScriptApproval approval = ScriptApproval.get();
85 | assertEquals(1, approval.countDeprecatedApprovedScriptHashes());
86 | approval.using(script, GroovyLanguage.get());
87 | assertEquals(1, approval.countDeprecatedApprovedScriptHashes());
88 | assertThat(log.getMessages(), not(logMatcher));
89 | });
90 | }
91 |
92 | @Test
93 | @Issue("SECURITY-2564")
94 | public void testConvertApprovedClasspathEntries() throws Throwable {
95 | session.then(r -> {
96 | final ScriptApproval approval = ScriptApproval.get();
97 | addApprovedClasspathEntries(approval);
98 | assertEquals(2, approval.countDeprecatedApprovedClasspathHashes());
99 | });
100 | log.record(ScriptApproval.class.getName(), Level.FINE).capture(10000);
101 | session.then(r -> {
102 | final ScriptApproval approval = ScriptApproval.get();
103 | assertEquals(2, approval.countDeprecatedApprovedClasspathHashes());
104 |
105 | assertThat(log.getMessages(), hasItem(
106 | containsString("There are 0 deprecated approved script hashes " +
107 | "and 2 deprecated approved classpath hashes.")));
108 |
109 | approval.convertDeprecatedApprovedClasspathEntries();
110 | assertThat(log.getMessages(), containsInRelativeOrder(
111 | containsString("Scheduling conversion of 2 deprecated approved classpathentry hashes."),
112 | containsString("Background conversion task scheduled.")));
113 | try {
114 | while (approval.isConvertingDeprecatedApprovedClasspathEntries()) {
115 | Thread.sleep(500);
116 | }
117 | } catch (InterruptedException ignored) {
118 | }
119 | assertEquals(0, approval.countDeprecatedApprovedClasspathHashes());
120 | });
121 | }
122 |
123 | @Test
124 | @Issue("SECURITY-2564")
125 | public void testClasspathEntriesConvertedOnUse() throws Throwable {
126 | session.then(r -> {
127 | final ScriptApproval approval = ScriptApproval.get();
128 | addApprovedClasspathEntries(approval);
129 | assertEquals(2, approval.countDeprecatedApprovedClasspathHashes());
130 | });
131 | log.record(ScriptApproval.class.getName(), Level.FINE).capture(10000);
132 | session.then(r -> {
133 | final ScriptApproval approval = ScriptApproval.get();
134 | assertEquals(2, approval.countDeprecatedApprovedClasspathHashes());
135 | URL url = getJar("org/apache/commons/lang3/StringUtils.class");
136 | approval.using(new ClasspathEntry(url.toString()));
137 | assertEquals(1, approval.countDeprecatedApprovedClasspathHashes());
138 | final Matcher> logMatcher = containsInRelativeOrder(
139 | containsString("A classpath is approved with an old hash algorithm. Converting now, "));
140 | assertThat(log.getMessages(), logMatcher);
141 | log.capture(1000);
142 | approval.using(new ClasspathEntry(url.toString())); //Using it again should not convert it again.
143 | assertThat(log.getMessages(), not(logMatcher));
144 | assertEquals(1, approval.countDeprecatedApprovedClasspathHashes());
145 | });
146 | }
147 |
148 | private void addApprovedClasspathEntries(final ScriptApproval approval) throws IOException {
149 | URL url = getJar("org/apache/commons/lang3/StringUtils.class");
150 | ScriptApproval.ApprovedClasspathEntry acp = new ScriptApproval.ApprovedClasspathEntry(
151 | ScriptApproval.Hasher.SHA1.hashClasspathEntry(url),
152 | url
153 | );
154 | approval.addApprovedClasspathEntry(acp);
155 |
156 | url = getJar("net/sf/json/JSON.class");
157 | acp = new ScriptApproval.ApprovedClasspathEntry(
158 | ScriptApproval.Hasher.SHA1.hashClasspathEntry(url),
159 | url
160 | );
161 | approval.addApprovedClasspathEntry(acp);
162 | approval.save();
163 | }
164 |
165 | @NonNull
166 | private URL getJar(final String resource) throws MalformedURLException {
167 | URL url = getClass().getClassLoader().getResource(resource);
168 | String path = url.getPath();
169 | path = path.substring(0, path.indexOf('!'));
170 | return new URL(path);
171 | }
172 | }
173 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/JcascTest.java:
--------------------------------------------------------------------------------
1 | package org.jenkinsci.plugins.scriptsecurity.scripts;
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 |
8 | import io.jenkins.plugins.casc.model.CNode;
9 | import org.junit.ClassRule;
10 | import org.junit.Test;
11 | import org.jvnet.hudson.test.LoggerRule;
12 |
13 | import java.util.logging.Level;
14 |
15 | import static io.jenkins.plugins.casc.misc.Util.getSecurityRoot;
16 | import static io.jenkins.plugins.casc.misc.Util.toStringFromYamlFile;
17 | import static io.jenkins.plugins.casc.misc.Util.toYamlString;
18 | import static org.hamcrest.MatcherAssert.assertThat;
19 | import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
20 | import static org.hamcrest.core.StringContains.containsString;
21 | import static org.junit.Assert.assertEquals;
22 | import static org.junit.Assert.assertTrue;
23 |
24 | public class JcascTest {
25 |
26 | @ClassRule(order = 1)
27 | public static LoggerRule logger = new LoggerRule().record(ScriptApproval.class.getName(), Level.WARNING)
28 | .capture(100);
29 |
30 | @ClassRule(order = 2)
31 | @ConfiguredWithCode("smoke_test.yaml")
32 | public static JenkinsConfiguredWithCodeRule j = new JenkinsConfiguredWithCodeRule();
33 |
34 |
35 |
36 | @Test
37 | public void smokeTestEntry() throws Exception {
38 | String[] approved = ScriptApproval.get().getApprovedSignatures();
39 | assertEquals(1, approved.length);
40 | assertEquals(approved[0], "method java.net.URI getHost");
41 | String[] approvedScriptHashes = ScriptApproval.get().getApprovedScriptHashes();
42 | assertEquals(1, approvedScriptHashes.length);
43 | assertEquals(approvedScriptHashes[0], "fccae58c5762bdd15daca97318e9d74333203106");
44 | assertThat(logger.getMessages(), containsInAnyOrder(
45 | containsString("Adding deprecated script hash " +
46 | "that will be converted on next use: fccae58c5762bdd15daca97318e9d74333203106")));
47 | assertTrue(ScriptApproval.get().isForceSandbox());
48 | }
49 |
50 | @Test
51 | public void smokeTestExport() throws Exception {
52 | ConfiguratorRegistry registry = ConfiguratorRegistry.get();
53 | ConfigurationContext context = new ConfigurationContext(registry);
54 | CNode yourAttribute = getSecurityRoot(context).get("scriptApproval");
55 | String exported = toYamlString(yourAttribute);
56 | String expected = toStringFromYamlFile(this, "smoke_test_expected.yaml");
57 | assertEquals(exported, expected);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/Manager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2016 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import org.htmlunit.ConfirmHandler;
28 | import org.htmlunit.html.DomElement;
29 | import org.htmlunit.html.HtmlElement;
30 | import org.htmlunit.html.HtmlPage;
31 | import org.jvnet.hudson.test.JenkinsRule;
32 |
33 | import java.io.IOException;
34 |
35 | import static org.junit.Assert.assertNotNull;
36 | import static org.junit.Assert.assertNull;
37 | import static org.junit.Assert.assertTrue;
38 |
39 | /**
40 | * Object representing the script approval page.
41 | */
42 | final class Manager {
43 | private final JenkinsRule.WebClient wc;
44 | private final HtmlPage page;
45 |
46 | Manager(JenkinsRule rule) throws Exception {
47 | this.wc = rule.createWebClient();
48 | // click "OK" for all confirms.
49 | wc.setConfirmHandler((ConfirmHandler) (page, message) -> true);
50 | this.page = wc.goTo(ScriptApproval.get().getUrlName());
51 | }
52 |
53 | private void clickAndWait(HtmlElement e) throws IOException {
54 | e.click();
55 | wc.waitForBackgroundJavaScript(10000);
56 | }
57 |
58 | Manager click(String id) throws IOException {
59 | clickAndWait(page.getElementById(id).getElementsByTagName("button").get(0));
60 | return this;
61 | }
62 |
63 | Manager notFound(Approvable> a, String id) {
64 | assertNull(String.format("%s : Element %s should not exist", a, id), page.getElementById(id));
65 | return this;
66 | }
67 |
68 | private DomElement assertFound(Approvable> a, String id) {
69 | DomElement dom = page.getElementById(id);
70 | assertNotNull(String.format("%s : Element %s should exist", this, id), dom);
71 | return dom;
72 | }
73 |
74 | > Element found(T entry, String id) {
75 | return new Element<>(entry, assertFound(entry, id));
76 | }
77 |
78 | final class Element> {
79 | final T approvable;
80 | private final DomElement element;
81 |
82 | Element(T approvable, DomElement element) {
83 | this.approvable = approvable;
84 | this.element = element;
85 | }
86 |
87 | T click(String value) throws IOException {
88 | for (HtmlElement e : element.getElementsByTagName("button")) {
89 | if (e.hasAttribute("class") && value.equals(e.getAttribute("class"))) {
90 | clickAndWait(e);
91 | return approvable;
92 | }
93 | }
94 | throw new AssertionError(String.format("Unable to find button with class [%s] in element [%s]", value, approvable));
95 | }
96 |
97 | T approve() throws IOException {
98 | approvable.assertPending();
99 | return click("approve");
100 | }
101 |
102 | T deny() throws IOException {
103 | approvable.assertPending();
104 | return click("deny");
105 | }
106 |
107 | T delete() throws IOException {
108 | assertTrue(approvable + "must support deletion", approvable.canDelete());
109 | approvable.assertApproved();
110 | return click("delete");
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalLoadingTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2024 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import java.io.File;
28 | import jenkins.RestartRequiredException;
29 | import org.apache.commons.io.FileUtils;
30 | import static org.hamcrest.MatcherAssert.assertThat;
31 | import static org.hamcrest.Matchers.arrayContaining;
32 | import org.junit.AssumptionViolatedException;
33 | import org.junit.Rule;
34 | import org.junit.Test;
35 | import org.jvnet.hudson.test.JenkinsRule;
36 | import org.jvnet.hudson.test.RealJenkinsRule;
37 |
38 | public final class ScriptApprovalLoadingTest {
39 |
40 | @Rule public final RealJenkinsRule rr = new RealJenkinsRule();
41 |
42 | @Test public void dynamicLoading() throws Throwable {
43 | rr.then(ScriptApprovalLoadingTest::_dynamicLoading1);
44 | rr.then(ScriptApprovalLoadingTest::_dynamicLoading2);
45 | }
46 |
47 | private static void _dynamicLoading1(JenkinsRule r) throws Throwable {
48 | File plugin = new File(r.jenkins.root, "plugins/script-security.jpl");
49 | FileUtils.copyFile(plugin, new File(plugin + ".bak"));
50 | r.jenkins.pluginManager.getPlugin("script-security").doDoUninstall();
51 | }
52 |
53 | private static void _dynamicLoading2(JenkinsRule r) throws Throwable {
54 | File plugin = new File(r.jenkins.root, "plugins/script-security.jpl");
55 | FileUtils.copyFile(new File(plugin + ".bak"), plugin);
56 | try {
57 | r.jenkins.pluginManager.dynamicLoad(plugin);
58 | } catch (RestartRequiredException x) {
59 | throw new AssumptionViolatedException("perhaps running in PCT, where this cannot be tested", x);
60 | }
61 | ScriptApproval sa = ScriptApproval.get();
62 | sa.approveSignature("method java.lang.Object wait");
63 | assertThat(sa.getApprovedSignatures(), arrayContaining("method java.lang.Object wait"));
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalNoteTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License
3 | *
4 | * Copyright 2019 CloudBees, Inc.
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | * THE SOFTWARE.
23 | */
24 |
25 | package org.jenkinsci.plugins.scriptsecurity.scripts;
26 |
27 | import org.htmlunit.TextPage;
28 | import org.htmlunit.html.DomNodeUtil;
29 | import org.htmlunit.html.HtmlPage;
30 | import hudson.model.FreeStyleBuild;
31 | import hudson.model.FreeStyleProject;
32 | import hudson.model.Item;
33 | import hudson.model.Result;
34 | import jenkins.model.Jenkins;
35 |
36 | import static org.hamcrest.MatcherAssert.assertThat;
37 | import static org.hamcrest.Matchers.*;
38 | import static org.junit.Assert.assertEquals;
39 |
40 | import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript;
41 | import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.TestGroovyRecorder;
42 | import org.junit.ClassRule;
43 | import org.junit.Test;
44 | import org.junit.Rule;
45 | import org.jvnet.hudson.test.BuildWatcher;
46 | import org.jvnet.hudson.test.Issue;
47 | import org.jvnet.hudson.test.JenkinsRule;
48 | import org.jvnet.hudson.test.MockAuthorizationStrategy;
49 |
50 | public class ScriptApprovalNoteTest {
51 |
52 | @ClassRule public static BuildWatcher buildWatcher = new BuildWatcher();
53 |
54 | @Rule public JenkinsRule r = new JenkinsRule();
55 |
56 | @Issue("JENKINS-34973")
57 | @Test public void smokes() throws Exception {
58 | r.jenkins.setSecurityRealm(r.createDummySecurityRealm());
59 | r.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy().
60 | grant(Jenkins.ADMINISTER, Jenkins.READ, Item.READ).everywhere().to("adminUser").
61 | grant(Jenkins.READ, Item.READ).everywhere().to("otherUser"));
62 | FreeStyleProject p = r.createFreeStyleProject("p");
63 | p.getPublishersList().add(new TestGroovyRecorder(new SecureGroovyScript("jenkins.model.Jenkins.instance", true, null)));
64 | FreeStyleBuild b = r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get());
65 | r.assertLogContains("org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use staticMethod jenkins.model.Jenkins getInstance", b);
66 | r.assertLogContains("Scripts not permitted to use staticMethod jenkins.model.Jenkins getInstance. " + Messages.ScriptApprovalNote_message(), b);
67 |
68 | JenkinsRule.WebClient wc = r.createWebClient();
69 |
70 | wc.login("adminUser");
71 | // make sure we see the annotation for the ADMINISTER user.
72 | HtmlPage rsp = wc.getPage(b, "console");
73 | assertEquals(1, DomNodeUtil.selectNodes(rsp, "//A[@href='" + r.contextPath + "/scriptApproval']").size());
74 |
75 | // make sure raw console output doesn't include the garbage and has the right message.
76 | TextPage raw = (TextPage)wc.goTo(b.getUrl()+"consoleText","text/plain");
77 | assertThat(raw.getContent(), containsString(" getInstance. " + Messages.ScriptApprovalNote_message()));
78 |
79 | wc.login("otherUser");
80 | // make sure we don't see the link for the other user.
81 | HtmlPage rsp2 = wc.getPage(b, "console");
82 | assertEquals(0, DomNodeUtil.selectNodes(rsp2, "//A[@href='" + r.contextPath + "/scriptApproval']").size());
83 |
84 | // make sure raw console output doesn't include the garbage and has the right message.
85 | TextPage raw2 = (TextPage)wc.goTo(b.getUrl()+"consoleText","text/plain");
86 | assertThat(raw2.getContent(), containsString(" getInstance. " + Messages.ScriptApprovalNote_message()));
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptorTest/all.groovy:
--------------------------------------------------------------------------------
1 | // A script that should exercise all keywords and operators.
2 | // TODO:
3 | // ===
4 | // !==
5 | // ||=
6 | // &&=
7 | // \
8 | // \=
9 | // native
10 | // KEYWORD_DEFMACRO
11 | // mixin
12 | // KEYWORD_IMPORT
13 | // KEYWORD_DO
14 |
15 | package test
16 |
17 | def fun() {
18 | return [];
19 | }
20 |
21 | arr = fun();
22 | assert arr.collect { it -> it } == [];
23 | arr << 0;
24 | assert arr == [0];
25 |
26 | assert true in [true, false];
27 | (1..3).each {};
28 | [1..3]*.toString();
29 | assert 42 as String == "42"
30 | assert 0 == [1:0][1];
31 |
32 | assert "asdf" =~ /sd/
33 | assert "asdf" ==~ /asdf/
34 | assert ~/asdf/ instanceof java.util.regex.Pattern
35 | assert 'asdf'[0..2] == 'asd'
36 |
37 | assert 1 < 2 && 1 <= 2
38 | assert 2 > 1 && 2 >= 1
39 | assert 1 <=> 1 == 0
40 | assert false || !false
41 |
42 | assert (6 / 3 + 4) * (2 ** 3 - (3 % 2)) == 42
43 |
44 | int a = 1
45 | a += 1
46 | a *= 2
47 | a **= 2
48 | a /= 2
49 | a %= 5
50 | assert a == 3
51 |
52 | l = 1;
53 | r = 3;
54 | assert ++l-- == --r++;
55 | assert +0 == -0
56 |
57 | assert (2 << 1) + (8 >> 1) == (16 >>> 1);
58 |
59 | def b = 8;
60 | b >>= 1;
61 | b >>>= 1;
62 | b <<= 2;
63 | assert b == 8;
64 |
65 | assert !(false ? true : false)
66 | assert false?:true;
67 | assert null?.hashCode() == null
68 |
69 | assert (1 | 2) == (1 & 3) + (1 ^ 3);
70 |
71 | int bin = 15;
72 | bin ^= 5;
73 | bin |= 3;
74 | bin &= 6;
75 | assert bin == 2;
76 |
77 | interface I {}
78 |
79 | abstract strictfp class A implements I {
80 | final transient int v = 42;
81 | volatile double a;
82 | long l;
83 | float f;
84 | char ch;
85 | {}
86 | static {}
87 | protected static synchronized byte s() throws IOException {}
88 | public abstract void f();
89 | private short p() {};
90 | def d(String... s) { return 0 };
91 | }
92 |
93 | class E extends A {
94 | public void f() {
95 | def superP = super.&p;
96 | superP();
97 | def arr = ["Lorem", "ipsum"];
98 | this.d(*arr);
99 | }
100 | private int field;
101 | }
102 |
103 | assert new E() {} instanceof I;
104 | new E().@field = 42;
105 |
106 | if (false) {
107 | assert false;
108 | } else {
109 | assert true;
110 | }
111 | label: for (i in [1, 2]) { continue label; }
112 |
113 | switch (false) {
114 | case null: break;
115 | case true: break;
116 | default: break;
117 | }
118 |
119 | try {
120 | throw new Exception();
121 | } catch (Exception _) {
122 | } finally {
123 | }
124 |
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest/README.md:
--------------------------------------------------------------------------------
1 | libraries in this directory are from https://github.com/ikedam/script-security-plugin-testjar.
2 |
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest/script-security-plugin-testjar.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/script-security-plugin/a4daab20016c3b5a1b144072c033e594cd16e9fd/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest/script-security-plugin-testjar.jar
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest/updated/script-security-plugin-testjar.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/script-security-plugin/a4daab20016c3b5a1b144072c033e594cd16e9fd/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScriptTest/updated/script-security-plugin-testjar.jar
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/TestGroovyRecorder/config.jelly:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/somejar.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/script-security-plugin/a4daab20016c3b5a1b144072c033e594cd16e9fd/src/test/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/somejar.jar
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest/dangerousApproved.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/script-security-plugin/a4daab20016c3b5a1b144072c033e594cd16e9fd/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest/dangerousApproved.zip
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest/malformedScriptApproval.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jenkinsci/script-security-plugin/a4daab20016c3b5a1b144072c033e594cd16e9fd/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest/malformedScriptApproval.zip
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest/reload/scriptApproval.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | fccae58c5762bdd15daca97318e9d74333203106
5 |
6 |
7 | staticMethod jenkins.model.Jenkins getInstance
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest/upgradeSmokes/scriptApproval.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | fccae58c5762bdd15daca97318e9d74333203106
5 |
6 |
7 | staticMethod jenkins.model.Jenkins getInstance
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test.yaml:
--------------------------------------------------------------------------------
1 | security:
2 | scriptApproval:
3 | approvedSignatures:
4 | - method java.net.URI getHost
5 | approvedScriptHashes:
6 | - fccae58c5762bdd15daca97318e9d74333203106
7 | forceSandbox: true
--------------------------------------------------------------------------------
/src/test/resources/org/jenkinsci/plugins/scriptsecurity/scripts/smoke_test_expected.yaml:
--------------------------------------------------------------------------------
1 | approvedScriptHashes:
2 | - "fccae58c5762bdd15daca97318e9d74333203106"
3 | approvedSignatures:
4 | - "method java.net.URI getHost"
5 | forceSandbox: true
6 |
--------------------------------------------------------------------------------