Skip to content

Commit

Permalink
Merge pull request #129 from Vlatombe/JENKINS-40533
Browse files Browse the repository at this point in the history
[JENKINS-40533] Allow definition of sandboxed libraries at global scope
  • Loading branch information
dwnusbaum authored Jun 17, 2024
2 parents c56a3b3 + 94f0443 commit e832a92
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 130 deletions.
8 changes: 8 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
<changelist>999999-SNAPSHOT</changelist>
<jenkins.version>2.414.3</jenkins.version>
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>
<useBeta>true</useBeta>
</properties>
<dependencyManagement>
<dependencies>
Expand Down Expand Up @@ -135,6 +136,13 @@
</dependency>

<!-- test only plugins -->
<dependency>
<!-- GlobalUntrustedLibrariesTest#configRoundtrip -->
<groupId>io.jenkins.plugins</groupId>
<artifactId>manage-permission</artifactId>
<version>1.0.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-support</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* The MIT License
*
* Copyright 2024 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.workflow.libs;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.model.ItemGroup;
import hudson.model.Job;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;

/**
* Common code between {@link GlobalLibraries} and {@link GlobalUntrustedLibraries}.
*/
public abstract class AbstractGlobalLibraries extends GlobalConfiguration {
private List<LibraryConfiguration> libraries = new ArrayList<>();

protected AbstractGlobalLibraries() {
load();
}

public abstract String getDescription();

public List<LibraryConfiguration> getLibraries() {
return libraries;
}

public void setLibraries(List<LibraryConfiguration> libraries) {
this.libraries = libraries;
save();
}

@Override public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
if (Jenkins.get().hasPermission(getRequiredGlobalConfigPagePermission())) {

Check warning on line 62 in src/main/java/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 62 is only partially covered, one branch is missing
setLibraries(Collections.emptyList()); // allow last library to be deleted
return super.configure(req, json);
} else {
return true;

Check warning on line 66 in src/main/java/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 66 is not covered by tests
}
}

abstract static class AbstractForJob extends LibraryResolver {
@NonNull
protected abstract AbstractGlobalLibraries getConfiguration();

@NonNull @Override public final Collection<LibraryConfiguration> forJob(@NonNull Job<?,?> job, @NonNull Map<String,String> libraryVersions) {
return getLibraries();
}

@NonNull @Override public final Collection<LibraryConfiguration> fromConfiguration(@NonNull StaplerRequest request) {
if (Jenkins.get().hasPermission(getConfiguration().getRequiredGlobalConfigPagePermission())) {
return getLibraries();
}
return Collections.emptySet();
}

@NonNull @Override public final Collection<LibraryConfiguration> suggestedConfigurations(@NonNull ItemGroup<?> group) {
return getLibraries();

Check warning on line 86 in src/main/java/org/jenkinsci/plugins/workflow/libs/AbstractGlobalLibraries.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 86 is not covered by tests
}

private List<LibraryConfiguration> getLibraries() {
return getConfiguration()
.getLibraries()
.stream()
.map(this::mayWrapLibrary)
.collect(Collectors.toList());
}

@NonNull
protected abstract LibraryConfiguration mayWrapLibrary(@NonNull LibraryConfiguration library);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,78 +24,49 @@

package org.jenkinsci.plugins.workflow.libs;

import hudson.Extension;
import hudson.model.ItemGroup;
import hudson.model.Job;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import edu.umd.cs.findbugs.annotations.NonNull;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.StaplerRequest;
import hudson.Extension;
import hudson.ExtensionList;

/**
* Manages libraries available to any job in the system.
*/
@Extension public class GlobalLibraries extends GlobalConfiguration {

public static @NonNull GlobalLibraries get() {
GlobalLibraries instance = GlobalConfiguration.all().get(GlobalLibraries.class);
if (instance == null) { // TODO would be useful to have an ExtensionList.getOrFail
throw new IllegalStateException();
}
return instance;
}

private List<LibraryConfiguration> libraries = new ArrayList<>();
@Extension public class GlobalLibraries extends AbstractGlobalLibraries {

public GlobalLibraries() {
load();
super();
}

public List<LibraryConfiguration> getLibraries() {
return libraries;
@Override
public String getDescription() {
return Messages.GlobalLibraries_Description();
}

public void setLibraries(List<LibraryConfiguration> libraries) {
this.libraries = libraries;
save();
@NonNull
@Override
public String getDisplayName() {
return Messages.GlobalLibraries_DisplayName();
}

@Override public boolean configure(StaplerRequest req, JSONObject json) throws FormException {
if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
setLibraries(Collections.emptyList()); // allow last library to be deleted
return super.configure(req, json);
} else {
return true;
}
public static @NonNull GlobalLibraries get() {
return ExtensionList.lookupSingleton(GlobalLibraries.class);
}

@Extension(ordinal=0) public static class ForJob extends LibraryResolver {

@Override public boolean isTrusted() {
return true;
}

@NonNull @Override public Collection<LibraryConfiguration> forJob(@NonNull Job<?,?> job, @NonNull Map<String,String> libraryVersions) {
return GlobalLibraries.get().getLibraries();
@Extension(ordinal=0) public static class ForJob extends AbstractForJob {
@NonNull
protected GlobalLibraries getConfiguration() {
return get();
}

@NonNull @Override public Collection<LibraryConfiguration> fromConfiguration(@NonNull StaplerRequest request) {
if (Jenkins.get().hasPermission(Jenkins.ADMINISTER)) {
return GlobalLibraries.get().getLibraries();
}
return Collections.emptySet();
@Override
public boolean isTrusted() {
return true;
}

@NonNull @Override public Collection<LibraryConfiguration> suggestedConfigurations(@NonNull ItemGroup<?> group) {
return GlobalLibraries.get().getLibraries();
@NonNull
@Override
protected LibraryConfiguration mayWrapLibrary(@NonNull LibraryConfiguration library) {
return library;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* The MIT License
*
* Copyright 2024 CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.workflow.libs;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.security.Permission;
import jenkins.model.Jenkins;

/**
* Manages untrusted libraries available to any job in the system.
*/
@Extension public class GlobalUntrustedLibraries extends AbstractGlobalLibraries {

public GlobalUntrustedLibraries() {
super();
}

@Override
public String getDescription() {
return Messages.GlobalUntrustedLibraries_Description();
}

@NonNull
@Override
public String getDisplayName() {
return Messages.GlobalUntrustedLibraries_DisplayName();
}

public static @NonNull GlobalUntrustedLibraries get() {
return ExtensionList.lookupSingleton(GlobalUntrustedLibraries.class);
}

@NonNull
@Override
public Permission getRequiredGlobalConfigPagePermission() {
return Jenkins.MANAGE;
}

@Extension(ordinal=1) public static class ForJob extends AbstractForJob {
@NonNull
protected GlobalUntrustedLibraries getConfiguration() {
return get();
}

@Override
public boolean isTrusted() {
return false;
}

@NonNull
@Override
protected LibraryConfiguration mayWrapLibrary(@NonNull LibraryConfiguration library) {
return new ResolvedLibraryConfiguration(library, getClass().getName());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ THE SOFTWARE.

<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<j:if test="${h.hasPermission(app.ADMINISTER)}">
<f:section title="${%Global Pipeline Libraries}">
<j:if test="${h.hasPermission(instance.requiredGlobalConfigPagePermission)}">
<f:section title="${instance.displayName}">
<f:block>
<j:out value="${%blurb}"/>
<j:out value="${instance.description}"/>
</f:block>
<f:entry>
<f:repeatableProperty field="libraries" header="${%Library}">
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ LibraryDecorator.could_not_find_any_definition_of_librari=Could not find any def
ResourceStep.library_resource_ambiguous_among_librari=Library resource {0} ambiguous among libraries {1}
ResourceStep.no_such_library_resource_could_be_found_=No such library resource {0} could be found.
SCMSourceRetriever.library_path_no_double_dot=Library path may not contain ".."
GlobalLibraries.DisplayName=Global Trusted Pipeline Libraries
GlobalLibraries.Description=Sharable libraries available to any Pipeline jobs running on this system. \
These libraries will be trusted, meaning they run without \u201csandbox\u201d restrictions and may use <code>@Grab</code>.
GlobalUntrustedLibraries.Description=Sharable libraries available to any Pipeline jobs running on this system. \
These libraries will be untrusted, meaning they run with \u201csandbox\u201d restrictions and cannot use <code>@Grab</code>.
GlobalUntrustedLibraries.DisplayName=Global Untrusted Pipeline Libraries
Original file line number Diff line number Diff line change
Expand Up @@ -172,19 +172,10 @@ public class FolderLibrariesTest {

/** @see GrapeTest#outsideLibrarySandbox */
@Test public void noGrape() throws Exception {
sampleRepo1.init();
sampleRepo1.write("src/pkg/Wrapper.groovy",
"package pkg\n" +
"@Grab('commons-primitives:commons-primitives:1.0')\n" +
"import org.apache.commons.collections.primitives.ArrayIntList\n" +
"class Wrapper {static def list() {new ArrayIntList()}}");
sampleRepo1.git("add", "src");
sampleRepo1.git("commit", "--message=init");
Folder d = r.jenkins.createProject(Folder.class, "d");
d.getProperties().add(new FolderLibraries(Collections.singletonList(new LibraryConfiguration("grape", new SCMSourceRetriever(new GitSCMSource(null, sampleRepo1.toString(), "", "*", "", true))))));
d.getProperties().add(new FolderLibraries(List.of(LibraryTestUtils.defineLibraryUsingGrab("grape", sampleRepo1))));
WorkflowJob p = d.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("@Library('grape@master') import pkg.Wrapper; echo(/should not have been able to run ${pkg.Wrapper.list()}/)", true));
ScriptApproval.get().approveSignature("new org.apache.commons.collections.primitives.ArrayIntList");
r.assertLogContains("Annotation Grab cannot be used in the sandbox", r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0)));
}

Expand Down
Loading

0 comments on commit e832a92

Please sign in to comment.