├── .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 delegates) { 43 | reset(delegates); 44 | } 45 | 46 | public final void reset(Collection 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 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 | --------------------------------------------------------------------------------