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-73470] Option to hide "Use Groovy Sandbox" #907

Merged
merged 11 commits into from
Jul 31, 2024

Conversation

amuniz
Copy link
Member

@amuniz amuniz commented Jul 17, 2024

See JENKINS-73470 for details.

Testing done

Submitter checklist

  • Make sure you are opening from a topic/feature/bugfix branch (right side) and not your main branch!
  • Ensure that the pull request title represents the desired changelog entry
  • Please describe what you did
  • Link to relevant issues in GitHub or Jira
  • Link to relevant pull requests, esp. upstream and downstream changes
  • Ensure you have provided tests - that demonstrates feature works or fixes the issue

@amuniz amuniz requested a review from a team as a code owner July 17, 2024 12:29
Comment on lines 3 to 4
<p>Note that this is not a security configuration, as it does not prevent users to configure and run
pipelines in some other ways without using the sandbox.</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enhancements under discusssion:

  • Throw AbortException if an attempt is made to run a non-sandboxed pipeline.
  • Throw something from the @DataBoundConstructor if !sandbox.

The first option may not work though, because we allow an admin to define a non-sandboxed pipeline (maybe some special admin/diagnostic job), and we would not know when the build starts who edited it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Throw something from the @DataBoundConstructor if !sandbox.

It should also check if the current user is an admin (as they are allowed to disable the sandbox), but jobs can be created by other means which do not necesasrily have a user context (job-dsl for example, or CasC for items when using CloudBees CI). So this would not be a complete solution either. So given that there is not a complete solution, I decided to fix the minimum required to solve the UX issue that happens when using the UI.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jobs can be created by other means which do not necessarily have a user context (job-dsl for example)

In this case the person configuring the DSL job (or approving changes to its source code) is effectively a Jenkins admin whom we trust to decide whether to allow the sandbox to be disabled. Checking hasPermission would work uniformly, since SYSTEM would pass.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been trying to block item creation/update by failing on the @DataBoundConstructor of CpsFlowDefinition. However UX is horrible, getting Angry Jenkins (no matter what exception type I use - tried with RuntimeException and Failure, same result).

An alternative solution would be to unconditionally set sandbox = true when the right conditions are met (CPSConfiguration.get().isHideSandbox() && !sandbox && !Jenkins.get().hasPermission(Jenkins.ADMINISTER)). But then the sandbox would be silently enabled when non-admin users save a job which had the sandbox disabled previously.

Given all that, I think we should not open that can of worms and just hide the checkbox (current state of the PR).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff --git a/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowDefinition.java b/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowDefinition.java
index aeae1865..153b2352 100644
--- a/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowDefinition.java
+++ b/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/CpsFlowDefinition.java
@@ -91,6 +91,7 @@ public class CpsFlowDefinition extends FlowDefinition {
         this.script = sandbox ? script : ScriptApproval.get().configuring(script, GroovyLanguage.get(),
                 ApprovalContext.create().withCurrentUser().withItemAsKey(req != null ? req.findAncestorObject(Item.class) : null), req == null);
         this.sandbox = sandbox;
+        throw new Failure("Cannot save!!");
     }
 
     private Object readResolve() {

And try to save a pipeline.

Copy link
Member

@jglick jglick Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, because it is wrapped in Descriptor.newInstance as you can see in the stack trace. I tried Descriptor.FormException, which is supposed to be for this purpose. (In fact we could even revert the code to hide the checkbox, and instead use FormValidation.error—this is arguably better UX.) Unfortunately right now it is getting wrapped by RequestImpl.invokeConstructor then RequestImpl.TypePair.convertJSON. Seems like a Stapler bug 👀

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@amuniz amuniz Jul 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Picked jenkinsci/stapler#571 + jenkinsci/jenkins#9495 up. It's working.

However, I just realized that we can't block the create-job CLI as it's not going through the data-bound constructor (and I think the same will happen with the HTTP API).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, both of those would go through readResolve as called by XStream. At least the CLI will treat certain exception types like IllegalArgumentException specially for purposes of error handling, if it matters.

@amuniz
Copy link
Member Author

amuniz commented Jul 26, 2024

Build is failing with:

10:51:55  [INFO] Rule 2: org.apache.maven.enforcer.rules.version.RequireJavaVersion passed
10:51:55  [INFO] Restricted to JDK 11 yet org.jenkins-ci.main:websocket-spi:jar:2.470-rc35157.fa_9b_5f84f785:provided contains jenkins/websocket/Provider$Handler.class targeted to JDK 17
10:51:55  [INFO] Restricted to JDK 11 yet org.kohsuke.stapler:stapler-groovy:jar:1891.v4efde02f1509:provided contains org/kohsuke/stapler/jelly/groovy/GroovierJellyScript$AliasMetaMethod.class targeted to JDK 17
10:51:55  [INFO] Restricted to JDK 11 yet org.kohsuke.stapler:stapler-jelly:jar:1891.v4efde02f1509:provided contains org/kohsuke/stapler/framework/adjunct/Adjunct$Kind.class targeted to JDK 17
10:51:55  [INFO] Restricted to JDK 11 yet org.kohsuke.stapler:stapler:jar:1891.v4efde02f1509:provided contains org/apache/commons/fileupload/FileItem$1.class targeted to JDK 17
10:51:55  [INFO] Restricted to JDK 11 yet org.jenkins-ci.main:cli:jar:2.470-rc35157.fa_9b_5f84f785:provided contains hudson/cli/CLI$1.class targeted to JDK 17
10:51:55  [INFO] Restricted to JDK 11 yet org.jenkins-ci.main:jenkins-core:jar:2.470-rc35157.fa_9b_5f84f785:provided contains antlr/ANTLRException.class targeted to JDK 17

Coming from the incremental release of jenkinsci/jenkins#9495 - are incrementals building without JDK 11 release compatibility?

@jglick
Copy link
Member

jglick commented Jul 26, 2024

Coming from the incremental release of jenkins

Which requires Java 17, so to specify such a jenkins.version you would need to switch the plugin to Java 17 using instructions defined in the parent POM docs (I hope).

@amuniz
Copy link
Member Author

amuniz commented Jul 26, 2024

How do you usually handle this type of changes? Wait until the change is integrated in a weekly and bump to the weekly here? Or wait until the change is in an LTS? (regarless the required Java version, that's an aside).

@jglick
Copy link
Member

jglick commented Jul 26, 2024

Neither. Revert the jenkins.version bump, and skip the test unless the version in fact includes the fix. You are not using a new API. Someone with an older core will merely see an angry Jenkins rather than a polite message.

@amuniz amuniz marked this pull request as ready for review July 26, 2024 15:01
@amuniz
Copy link
Member Author

amuniz commented Jul 26, 2024

Ready for final review.

@amuniz amuniz marked this pull request as draft July 26, 2024 15:38
@jglick
Copy link
Member

jglick commented Jul 29, 2024

Core PR merged.

Co-authored-by: Jesse Glick <jglick@cloudbees.com>
@amuniz amuniz marked this pull request as ready for review July 29, 2024 13:51
this(script, false);
}

@DataBoundConstructor
public CpsFlowDefinition(String script, boolean sandbox) {
public CpsFlowDefinition(String script, boolean sandbox) throws Descriptor.FormException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: source-incompatible but binary-compatible.

<div>
<p>Controls whether the "Use Groovy Sandbox" is shown in pipeline jobs configuration page to users without Overall/Administer permission.</p>
<p>This can be used to get a better UX in highly secured environments where all pipelines are required to run in the sandbox (ie. running arbitrary code is never approved)</p>
<p>Note that this does not prevent users to configure and run pipelines with sandbox disabled if they create or update jobs by other means (like CLI or HTTP API).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for now we are not touching readResolve, deliberately?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I don't see how to differentiate from a regular call during start up and a call from a CLI/HTTP API call.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deserialization during startup would run as SYSTEM, which of course has ADMINISTER; deserialization during a CLI/HTTP call would run as some user authentication.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, we should leave this as is and revisit it separately if/when needed/requested.

StaplerRequest req = Stapler.getCurrentRequest();
this.script = sandbox ? script : ScriptApproval.get().configuring(script, GroovyLanguage.get(),
ApprovalContext.create().withCurrentUser().withItemAsKey(req != null ? req.findAncestorObject(Item.class) : null), req == null);
this.sandbox = sandbox;

}

private Object readResolve() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note 👇

Thanks @jglick for catching it during code review!
@jglick jglick merged commit af73b7c into jenkinsci:master Jul 31, 2024
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants