Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JENKINS-74820 - forceSandBox - Hide command-launcher drop down from non-administrators #103

Merged
merged 17 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0b18ac3
BEE-52312 - forceSandBox - Hide command-launcher drop down from non-a…
jgarciacloudbees Nov 6, 2024
c5ab3e9
BEE-52312 - forceSandBox - Hide command-launcher drop down from non-a…
jgarciacloudbees Nov 8, 2024
1e1d95d
BEE-52312 - forceSandBox - Hide command-launcher drop down from non-a…
jgarciacloudbees Nov 11, 2024
230ae87
BEE-52312 - forceSandBox - Hide command-launcher drop down from non-a…
jgarciacloudbees Nov 11, 2024
ee82534
BEE-52312 - forceSandBox - Hide command-launcher drop down from non-a…
jgarciacloudbees Nov 11, 2024
95404f1
BEE-52312 - forceSandBox - Hide command-launcher drop down from non-a…
jgarciacloudbees Nov 11, 2024
2945dec
BEE-52312 - forceSandBox - Hide command-launcher drop down from non-a…
jgarciacloudbees Nov 11, 2024
886fb9e
BEE-52312 - forceSandBox - Hide command-launcher drop down from non-a…
jgarciacloudbees Nov 11, 2024
7f12293
JENKINS-74820 - Apply suggestions from code review
jgarciacloudbees Nov 12, 2024
905f8e8
BEE-52312 - change constructor signature to avoid breaking changes.
jgarciacloudbees Nov 12, 2024
5ba1477
BEE-52312 - bump pom version
jgarciacloudbees Nov 12, 2024
a70002a
JENKINS-74820 - Apply suggestions from code review
jgarciacloudbees Nov 12, 2024
ff5a9dc
BEE-52312 - add pom dependecies
jgarciacloudbees Nov 12, 2024
d5d3cba
BEE-52312 - add pom dependecies
jgarciacloudbees Nov 12, 2024
a1700c5
BEE-52312 - SuggestedChanges
jgarciacloudbees Nov 12, 2024
f31f604
BEE-52312 - SuggestedChanges - Test
jgarciacloudbees Nov 12, 2024
51eaba7
BEE-52312 - final changes
jgarciacloudbees Nov 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>4.86</version>
<version>4.88</version>
<relativePath />
</parent>
<artifactId>command-launcher</artifactId>
Expand All @@ -13,7 +13,7 @@
<properties>
<changelist>999999-SNAPSHOT</changelist>
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>
<jenkins.version>2.440.3</jenkins.version>
<jenkins.version>2.452.4</jenkins.version>
</properties>
<name>Command Agent Launcher Plugin</name>
<description>Allows agents to be launched using a specified command.</description>
Expand Down Expand Up @@ -46,8 +46,8 @@
<dependencies>
<dependency>
<groupId>io.jenkins.tools.bom</groupId>
<artifactId>bom-2.440.x</artifactId>
<version>3234.v5ca_5154341ef</version>
<artifactId>bom-2.452.x</artifactId>
<version>3654.v237e4a_f2d8da_</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down
64 changes: 63 additions & 1 deletion src/main/java/hudson/slaves/CommandLauncher.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@
*/
package hudson.slaves;

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.Extension;
import hudson.Functions;
import hudson.Util;
import hudson.model.ComputerSet;
import hudson.model.Descriptor;
import hudson.model.DescriptorVisibilityFilter;
import hudson.model.Slave;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
Expand All @@ -39,6 +42,7 @@
import hudson.util.StreamCopyThread;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
Expand All @@ -49,6 +53,7 @@
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
import org.jenkinsci.plugins.scriptsecurity.scripts.UnapprovedUsageException;
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.SystemCommandLanguage;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.Stapler;
Expand Down Expand Up @@ -80,10 +85,11 @@
* @see #CommandLauncher(String command, EnvVars env)
*/
@DataBoundConstructor
public CommandLauncher(String command) {
public CommandLauncher(String command) throws Descriptor.FormException {
checkSandbox();
jgarciacloudbees marked this conversation as resolved.
Show resolved Hide resolved
agentCommand = command;
env = null;
// TODO add withKey if we can determine the Slave.nodeName being configured

Check warning on line 92 in src/main/java/hudson/slaves/CommandLauncher.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: add withKey if we can determine the Slave.nodeName being configured
ScriptApproval.get().configuring(command, SystemCommandLanguage.get(), ApprovalContext.create().withCurrentUser(), Stapler.getCurrentRequest() == null);
}

Expand All @@ -105,6 +111,18 @@
this.agentCommand = command;
this.env = env;
}

/**
* Check if the current user is forced to use the Sandbox when creating a new instance.
* In this case, we don't allow saving new instances of the CommandLauncher object by throwing a new exception
*/
private void checkSandbox() throws Descriptor.FormException {
if (ScriptApproval.get().isForceSandboxForCurrentUser()) {
throw new Descriptor.FormException(
"This Launch Method requires scripts executions out of the sandbox."
+ " This Jenkins instance has been configured to not allow regular users to disable the sandbox", "command");
}
}

private Object readResolve() {
ScriptApproval.get().configuring(agentCommand, SystemCommandLanguage.get(), ApprovalContext.create(), true);
Expand Down Expand Up @@ -250,4 +268,48 @@
return ScriptApproval.get().checking(value, SystemCommandLanguage.get(), !StringUtils.equals(value, oldCommand));
}
}

/**
* In case the flag
* {@link ScriptApproval#isForceSandboxForCurrentUser} is true, we don't show the {@link DescriptorImpl descriptor}
* for the current user, except if we are editing a node that already has the launcher {@link CommandLauncher}
*/
@Extension
public static class DescriptorVisibilityFilterForceSandBox extends DescriptorVisibilityFilter {
@Override
public boolean filter(@CheckForNull Object context, @NonNull Descriptor descriptor) {
if(descriptor instanceof DescriptorImpl) {
return !ScriptApproval.get().isForceSandboxForCurrentUser() ||

Check warning on line 282 in src/main/java/hudson/slaves/CommandLauncher.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 282 is only partially covered, one branch is missing
(context instanceof Slave && ((Slave) context).getLauncher() instanceof CommandLauncher);
}
return true;
}

@Override
public boolean filterType(@NonNull Class<?> contextClass, @NonNull Descriptor descriptor) {
if(descriptor instanceof DescriptorImpl)
{
//If we are creating a new object, check ScriptApproval.get().isForceSandboxForCurrentUser()
//If we are NOT creating a new object, return true, and delegate the logic to #filter
return !(isCreatingNewObject() && ScriptApproval.get().isForceSandboxForCurrentUser());
}
return true;
}

private boolean isCreatingNewObject() {
var req = Stapler.getCurrentRequest();
if (req != null) {

Check warning on line 301 in src/main/java/hudson/slaves/CommandLauncher.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 301 is only partially covered, one branch is missing
List<Ancestor> ancs = req.getAncestors();
for (Ancestor anc : ancs) {
if (anc.getObject() instanceof ComputerSet) {
String uri = req.getOriginalRequestURI();
if (uri.endsWith("createItem")) {
return true;
}
}
}
}
return false;
}
}
}
202 changes: 202 additions & 0 deletions src/test/java/hudson/slaves/CommandLauncherForceSandboxTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package hudson.slaves;

import java.io.IOException;

import org.htmlunit.html.HtmlForm;
import org.jenkinsci.plugins.scriptsecurity.scripts.ScriptApproval;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.JenkinsRule.WebClient;
import org.jvnet.hudson.test.MockAuthorizationStrategy;

import hudson.model.Computer;
import hudson.model.Descriptor;
import hudson.model.User;
import hudson.security.ACL;
import hudson.security.ACLContext;
import jenkins.model.Jenkins;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;

public class CommandLauncherForceSandboxTest {

@Rule
public JenkinsRule j = new JenkinsRule();

@Before
public void configureTest() throws IOException {
Jenkins.MANAGE.setEnabled(true);

j.jenkins.setSecurityRealm(j.createDummySecurityRealm());

MockAuthorizationStrategy strategy = new MockAuthorizationStrategy().
grant(Jenkins.ADMINISTER).everywhere().to("admin").
grant(Jenkins.MANAGE).everywhere().to("devel").
grant(Jenkins.READ, Computer.CONFIGURE).everywhere().to("devel");

SlaveComputer.PERMISSIONS.getPermissions().forEach(p -> strategy.grant(p).everywhere().to("devel"));

j.jenkins.setAuthorizationStrategy(strategy);
}

@Test
public void newCommandLauncher() throws Exception {
try (ACLContext ctx = ACL.as(User.getById("devel", true))) {
//With forceSandbox enabled, nonadmin users should not create agents with Launcher = CommandLauncher
ScriptApproval.get().setForceSandbox(true);
Descriptor.FormException ex = assertThrows(Descriptor.FormException.class, () ->
new DumbSlave("s", "/",new CommandLauncher("echo unconfigured")));

assertEquals("This Launch Method requires scripts executions out of the sandbox."
+ " This Jenkins instance has been configured to not allow regular users to disable the sandbox",
ex.getMessage());

//With forceSandbox disabled, nonadmin users can create agents with Launcher = CommandLauncher
ScriptApproval.get().setForceSandbox(false);
new DumbSlave("s", "/", new CommandLauncher("echo unconfigured"));
}

try (ACLContext ctx = ACL.as(User.getById("admin", true))) {
//admin users can create agents with Launcher = CommandLauncher independently of forceSandbox flag.
ScriptApproval.get().setForceSandbox(true);
new DumbSlave("s", "/", new CommandLauncher("echo unconfigured"));

ScriptApproval.get().setForceSandbox(false);
new DumbSlave("s", "/", new CommandLauncher("echo unconfigured"));
}
}

@Test
public void editCommandLauncherUI_ForceSandboxTrue() throws Exception {
ScriptApproval.get().setForceSandbox(true);

DumbSlave commandLauncherAgent = new DumbSlave("commandLauncherAgent", "/", new CommandLauncher("echo unconfigured"));
DumbSlave noCommandLauncherAgent = new DumbSlave("noCommandLauncherAgent", "/", new JNLPLauncher());
j.jenkins.addNode(commandLauncherAgent);
j.jenkins.addNode(noCommandLauncherAgent);

try (WebClient wc = j.createWebClient().login("devel")) {
//Edit noCommandLauncher Agent.
//We are not admin and Sandbox is true,
//We don't have any html object for CommandLauncher
HtmlForm form = wc.getPage(noCommandLauncherAgent, "configure").getFormByName("config");
assertTrue(form.getInputsByValue(CommandLauncher.class.getName()).isEmpty());

//Edit CommandLauncher Agent.
//We are not admin and Sandbox is true
// As the agent is already a commandLauncher one we have some html object for CommandLauncher
form = wc.getPage(commandLauncherAgent, "configure").getFormByName("config");
assertFalse(form.getInputsByValue(CommandLauncher.class.getName()).isEmpty());
}

try (WebClient wc = j.createWebClient().login("admin")) {
//Edit noCommandLauncher Agent.
//We areadmin and Sandbox is true,
//We have some html object for CommandLauncher
HtmlForm form = wc.getPage(noCommandLauncherAgent, "configure").getFormByName("config");
assertFalse(form.getInputsByValue(CommandLauncher.class.getName()).isEmpty());

//Edit CommandLauncher Agent.
//Wwe not admin and Sandbox is true
//We have some html object for CommandLauncher
form = wc.getPage(commandLauncherAgent, "configure").getFormByName("config");
assertFalse(form.getInputsByValue(CommandLauncher.class.getName()).isEmpty());
} }

@Test
public void editCommandLauncherUI_ForceSandboxFalse() throws Exception {
ScriptApproval.get().setForceSandbox(false);

DumbSlave commandLauncherAgent = new DumbSlave("commandLauncherAgent", "/", new CommandLauncher("echo unconfigured"));
DumbSlave noCommandLauncherAgent = new DumbSlave("noCommandLauncherAgent", "/", new JNLPLauncher());
j.jenkins.addNode(commandLauncherAgent);
j.jenkins.addNode(noCommandLauncherAgent);

try (WebClient wc = j.createWebClient().login("devel")) {
//Edit noCommandLauncher Agent.
//We are not admin and Sandbox is false,
//We have some html object for CommandLauncher
HtmlForm form = wc.getPage(noCommandLauncherAgent, "configure").getFormByName("config");
assertFalse(form.getInputsByValue(CommandLauncher.class.getName()).isEmpty());

//Edit CommandLauncher Agent.
//Wwe are not admin and Sandbox is false
//We have some html object for CommandLauncher
form = wc.getPage(commandLauncherAgent, "configure").getFormByName("config");
assertFalse(form.getInputsByValue(CommandLauncher.class.getName()).isEmpty());
}

try (WebClient wc = j.createWebClient().login("admin")) {
//Edit noCommandLauncher Agent.
//We areadmin and Sandbox is false,
//We have some html object for CommandLauncher
HtmlForm form = wc.getPage(noCommandLauncherAgent, "configure").getFormByName("config");
assertFalse(form.getInputsByValue(CommandLauncher.class.getName()).isEmpty());

//Edit CommandLauncher Agent.
//Wwe not admin and Sandbox is false
//We have some html object for CommandLauncher
form = wc.getPage(commandLauncherAgent, "configure").getFormByName("config");
assertFalse(form.getInputsByValue(CommandLauncher.class.getName()).isEmpty());
}
}

@Test
public void createCommandLauncherUI_ForceSandboxTrue() throws Exception {
ScriptApproval.get().setForceSandbox(true);

try (WebClient wc = j.createWebClient().login("devel")) {
//Create Permanent Agent.
//We are not admin and Sandbox is true,
//We don't have any html object for CommandLauncher
HtmlForm form = wc.goTo("computer/new").getFormByName("createItem");
form.getInputByName("name").setValue("devel_ComandLauncher");
form.getInputsByValue(DumbSlave.class.getName()).stream().findFirst().get().setChecked(true);
HtmlForm createNodeForm = j.submit(form).getFormByName("config");
assertTrue(createNodeForm.getInputsByValue(CommandLauncher.class.getName()).isEmpty());
}

try (WebClient wc = j.createWebClient().login("admin")) {
//Create Permanent Agent.
//We are admin and Sandbox is true,
//We have some html object for CommandLauncher
HtmlForm form = wc.goTo("computer/new").getFormByName("createItem");
form.getInputByName("name").setValue("devel_ComandLauncher");
form.getInputsByValue(DumbSlave.class.getName()).stream().findFirst().get().setChecked(true);
HtmlForm createNodeForm = j.submit(form).getFormByName("config");
assertFalse(createNodeForm.getInputsByValue(CommandLauncher.class.getName()).isEmpty());
}
}

@Test
public void createCommandLauncherUI_ForceSandboxFalse() throws Exception {
ScriptApproval.get().setForceSandbox(false);

try (WebClient wc = j.createWebClient().login("devel")) {
//Create Permanent Agent.
//We are not admin and Sandbox is false,
//We have some html object for CommandLauncher
HtmlForm form = wc.goTo("computer/new").getFormByName("createItem");
form.getInputByName("name").setValue("devel_ComandLauncher");
form.getInputsByValue(DumbSlave.class.getName()).stream().findFirst().get().setChecked(true);
HtmlForm createNodeForm = j.submit(form).getFormByName("config");
assertFalse(createNodeForm.getInputsByValue(CommandLauncher.class.getName()).isEmpty());
}

try (WebClient wc = j.createWebClient().login("admin")) {
//Create Permanent Agent.
//We are admin and Sandbox is true,
//We have some html object for CommandLauncher
HtmlForm form = wc.goTo("computer/new").getFormByName("createItem");
form.getInputByName("name").setValue("devel_ComandLauncher");
form.getInputsByValue(DumbSlave.class.getName()).stream().findFirst().get().setChecked(true);
HtmlForm createNodeForm = j.submit(form).getFormByName("config");
assertFalse(createNodeForm.getInputsByValue(CommandLauncher.class.getName()).isEmpty());
}
}
}