diff --git a/bom/pom.xml b/bom/pom.xml
index 991070392674..d57d6aced5e3 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -191,7 +191,7 @@ THE SOFTWARE.
com.github.jnr
jnr-posix
- 3.1.11
+ 3.1.12
org.kohsuke
@@ -288,6 +288,11 @@ THE SOFTWARE.
robust-http-client
1.2
+
+ org.jenkins-ci
+ symbol-annotation
+ 1.1
+
com.sun.mail
jakarta.mail
diff --git a/core/pom.xml b/core/pom.xml
index a1e62367dde5..7af7df22c56d 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -479,10 +479,9 @@ THE SOFTWARE.
org.jvnet.robust-http-client
robust-http-client
-
+
org.jenkins-ci
symbol-annotation
- 1.1
diff --git a/core/src/main/java/hudson/model/NullTaskListener.java b/core/src/main/java/hudson/model/NullTaskListener.java
new file mode 100644
index 000000000000..5d0454c512b7
--- /dev/null
+++ b/core/src/main/java/hudson/model/NullTaskListener.java
@@ -0,0 +1,42 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2021 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 hudson.model;
+
+import java.io.PrintStream;
+import org.apache.commons.io.output.NullPrintStream;
+
+/**
+ * @see TaskListener#NULL
+ */
+class NullTaskListener implements TaskListener {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public PrintStream getLogger() {
+ return new NullPrintStream();
+ }
+
+}
diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java
index a15dedce5a6e..64af981be6fc 100644
--- a/core/src/main/java/hudson/model/Queue.java
+++ b/core/src/main/java/hudson/model/Queue.java
@@ -3103,7 +3103,12 @@ public static Queue getInstance() {
*/
@Initializer(after=JOB_CONFIG_ADAPTED)
public static void init(Jenkins h) {
- h.getQueue().load();
+ Queue queue = h.getQueue();
+ Item[] items = queue.getItems();
+ if (items.length > 0) {
+ LOGGER.warning(() -> "Loading queue will discard previously scheduled items: " + Arrays.toString(items));
+ }
+ queue.load();
}
/**
diff --git a/core/src/main/java/hudson/model/TaskListener.java b/core/src/main/java/hudson/model/TaskListener.java
index 4b05ede48c0f..a466613d027f 100644
--- a/core/src/main/java/hudson/model/TaskListener.java
+++ b/core/src/main/java/hudson/model/TaskListener.java
@@ -27,7 +27,6 @@
import hudson.console.ConsoleNote;
import hudson.console.HyperlinkNote;
import hudson.remoting.Channel;
-import hudson.util.NullStream;
import hudson.util.StreamTaskListener;
import java.io.IOException;
import java.io.OutputStreamWriter;
@@ -155,5 +154,5 @@ default PrintWriter fatalError(String format, Object... args) {
/**
* {@link TaskListener} that discards the output.
*/
- TaskListener NULL = new StreamTaskListener(new NullStream());
+ TaskListener NULL = new NullTaskListener();
}
diff --git a/core/src/main/java/hudson/slaves/Channels.java b/core/src/main/java/hudson/slaves/Channels.java
index 9f0a6c80921f..9fb5cab11bb6 100644
--- a/core/src/main/java/hudson/slaves/Channels.java
+++ b/core/src/main/java/hudson/slaves/Channels.java
@@ -27,6 +27,7 @@
import hudson.Launcher.LocalLauncher;
import hudson.Proc;
import hudson.model.Computer;
+import hudson.model.Executor;
import hudson.model.TaskListener;
import hudson.remoting.Channel;
import hudson.remoting.ChannelBuilder;
@@ -108,8 +109,10 @@ public synchronized void join() throws InterruptedException {
};
cb.withHeaderStream(header);
+ Executor executor = Executor.currentExecutor();
+ Object context = executor != null ? executor.getOwner() : proc;
for (ChannelConfigurator cc : ChannelConfigurator.all()) {
- cc.onChannelBuilding(cb,null); // TODO: what to pass as a context?
+ cc.onChannelBuilding(cb, context);
}
return cb.build(in,out);
@@ -145,8 +148,10 @@ public synchronized void join() throws InterruptedException {
};
cb.withHeaderStream(header);
+ Executor executor = Executor.currentExecutor();
+ Object context = executor != null ? executor.getOwner() : proc;
for (ChannelConfigurator cc : ChannelConfigurator.all()) {
- cc.onChannelBuilding(cb,null); // TODO: what to pass as a context?
+ cc.onChannelBuilding(cb, context);
}
return cb.build(proc.getInputStream(),proc.getOutputStream());
diff --git a/core/src/main/java/jenkins/formelementpath/FormElementPathPageDecorator.java b/core/src/main/java/jenkins/formelementpath/FormElementPathPageDecorator.java
new file mode 100644
index 000000000000..07efd8f0ddd1
--- /dev/null
+++ b/core/src/main/java/jenkins/formelementpath/FormElementPathPageDecorator.java
@@ -0,0 +1,20 @@
+package jenkins.formelementpath;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import hudson.Extension;
+import hudson.Main;
+import hudson.model.PageDecorator;
+import jenkins.util.SystemProperties;
+
+@Extension
+public class FormElementPathPageDecorator extends PageDecorator {
+
+ @SuppressFBWarnings("MS_SHOULD_BE_FINAL")
+ private static /*almost final */ boolean ENABLED = Main.isUnitTest ||
+ SystemProperties.getBoolean(FormElementPathPageDecorator.class.getName() + ".enabled");
+
+ public boolean isEnabled() {
+ return ENABLED;
+ }
+
+}
diff --git a/core/src/main/java/jenkins/security/ChannelConfigurator.java b/core/src/main/java/jenkins/security/ChannelConfigurator.java
index 4bd9f0638f21..77923f50fe48 100644
--- a/core/src/main/java/jenkins/security/ChannelConfigurator.java
+++ b/core/src/main/java/jenkins/security/ChannelConfigurator.java
@@ -3,9 +3,13 @@
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.ExtensionList;
import hudson.ExtensionPoint;
+import hudson.Proc;
import hudson.remoting.Channel;
import hudson.remoting.ChannelBuilder;
+import hudson.slaves.Channels;
import hudson.slaves.SlaveComputer;
+import java.io.OutputStream;
+import java.util.concurrent.ExecutorService;
/**
* Intercepts the new creation of {@link Channel} and tweak its configuration.
@@ -29,6 +33,8 @@ public abstract class ChannelConfigurator implements ExtensionPoint {
*
* - {@link SlaveComputer}
*
- When a channel is being established to talk to a agent.
+ *
- {@link Proc}
+ *
- When {@link Channels#forProcess(String, ExecutorService, Process, OutputStream)} or overloads are used without a contextual {@link SlaveComputer}.
*
*/
public void onChannelBuilding(ChannelBuilder builder, @Nullable Object context) {}
diff --git a/core/src/main/java/jenkins/security/s2m/DefaultFilePathFilter.java b/core/src/main/java/jenkins/security/s2m/DefaultFilePathFilter.java
index ef4955213db7..c591827ec8ee 100644
--- a/core/src/main/java/jenkins/security/s2m/DefaultFilePathFilter.java
+++ b/core/src/main/java/jenkins/security/s2m/DefaultFilePathFilter.java
@@ -26,12 +26,17 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Extension;
+import hudson.ExtensionList;
import hudson.remoting.ChannelBuilder;
+import hudson.remoting.Command;
+import hudson.remoting.Request;
import java.io.File;
+import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.ReflectiveFilePathFilter;
import jenkins.security.ChannelConfigurator;
+import jenkins.telemetry.impl.SlaveToMasterFileCallableUsage;
import jenkins.util.SystemProperties;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -59,6 +64,17 @@ protected boolean op(String op, File f) throws SecurityException {
LOGGER.log(Level.FINE, "agent allowed to {0} {1}", new Object[] {op, f});
return true;
} else {
+ try {
+ Field current = Request.class.getDeclaredField("CURRENT");
+ current.setAccessible(true);
+ Field createdAt = Command.class.getDeclaredField("createdAt");
+ createdAt.setAccessible(true);
+ Throwable trace = (Throwable) createdAt.get(((ThreadLocal) current.get(null)).get());
+ ExtensionList.lookupSingleton(SlaveToMasterFileCallableUsage.class).recordTrace(trace);
+ LOGGER.log(Level.WARNING, "Permitting agent-to-controller '" + op + "' on '" + f + "'. This is deprecated and will soon be rejected. Learn more: https://www.jenkins.io/redirect/permitted-agent-to-controller-file-access", trace);
+ } catch (Exception x) {
+ LOGGER.log(Level.WARNING, null, x);
+ }
return false;
}
}
diff --git a/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java b/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java
new file mode 100644
index 000000000000..dd628b767773
--- /dev/null
+++ b/core/src/main/java/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage.java
@@ -0,0 +1,75 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2021 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 jenkins.telemetry.impl;
+
+import hudson.Extension;
+import hudson.Functions;
+import java.time.LocalDate;
+import java.util.Collections;
+import java.util.Set;
+import java.util.TreeSet;
+import jenkins.SlaveToMasterFileCallable;
+import jenkins.security.s2m.DefaultFilePathFilter;
+import jenkins.telemetry.Telemetry;
+import net.sf.json.JSONObject;
+import org.kohsuke.accmod.Restricted;
+import org.kohsuke.accmod.restrictions.NoExternalUse;
+
+/**
+ * Records when {@link DefaultFilePathFilter} found {@link SlaveToMasterFileCallable} or similar being used.
+ */
+@Extension
+@Restricted(NoExternalUse.class)
+public class SlaveToMasterFileCallableUsage extends Telemetry {
+
+ private Set traces = new TreeSet<>();
+
+ @Override
+ public String getDisplayName() {
+ return "Access to files on controllers from code running on an agent";
+ }
+
+ @Override
+ public LocalDate getStart() {
+ return LocalDate.of(2021, 11, 4); // https://www.jenkins.io/security/advisory/2021-11-04/
+ }
+
+ @Override
+ public LocalDate getEnd() {
+ return LocalDate.of(2022, 3, 1);
+ }
+
+ @Override
+ public synchronized JSONObject createContent() {
+ JSONObject json = JSONObject.fromObject(Collections.singletonMap("traces", traces));
+ traces.clear();
+ return json;
+ }
+
+ public synchronized void recordTrace(Throwable trace) {
+ traces.add(Functions.printThrowable(trace).replaceAll("@[a-f0-9]+", "@…"));
+ }
+
+}
diff --git a/core/src/main/resources/jenkins/formelementpath/FormElementPathPageDecorator/footer.jelly b/core/src/main/resources/jenkins/formelementpath/FormElementPathPageDecorator/footer.jelly
new file mode 100644
index 000000000000..1b92a93950fe
--- /dev/null
+++ b/core/src/main/resources/jenkins/formelementpath/FormElementPathPageDecorator/footer.jelly
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/core/src/main/resources/jenkins/formelementpath/form-element-path.js b/core/src/main/resources/jenkins/formelementpath/form-element-path.js
new file mode 100644
index 000000000000..0bba9171438e
--- /dev/null
+++ b/core/src/main/resources/jenkins/formelementpath/form-element-path.js
@@ -0,0 +1,196 @@
+/**
+ * Adds a 'path' attribute to form elements in the DOM.
+ * This is useful for providing stable selectors for UI testing.
+ *
+ * Instead of selecting by xpath with something like div/span/input[text() = 'Name']
+ * You can use the path attribute: /org-jenkinsci-plugins-workflow-libs-FolderLibraries/libraries/name
+ */
+document.addEventListener("DOMContentLoaded", function(){
+ // most of this is copied from hudson-behaviour.js
+ function buildFormTree(form) {
+ form.formDom = {}; // root object
+
+ var doms = []; // DOMs that we added 'formDom' for.
+ doms.push(form);
+
+ function addProperty(parent, name, value) {
+ name = shortenName(name);
+ if (parent[name] != null) {
+ if (parent[name].push == null) // is this array?
+ parent[name] = [parent[name]];
+ parent[name].push(value);
+ } else {
+ parent[name] = value;
+ }
+ }
+
+ // find the grouping parent node, which will have @name.
+ // then return the corresponding object in the map
+ function findParent(e) {
+ var p = findFormParent(e, form);
+ if (p == null) return {};
+
+ var m = p.formDom;
+ if (m == null) {
+ // this is a new grouping node
+ doms.push(p);
+ p.formDom = m = {};
+ addProperty(findParent(p), p.getAttribute("name"), p);
+ }
+ return m;
+ }
+
+ var jsonElement = null;
+
+ for (var i = 0; i < form.elements.length; i++) {
+ var e = form.elements[i];
+ if (e.name == "json") {
+ jsonElement = e;
+ continue;
+ }
+ if (e.tagName == "FIELDSET")
+ continue;
+ if (e.tagName == "SELECT" && e.multiple) {
+ addProperty(findParent(e), e.name, e);
+ continue;
+ }
+
+ var p;
+ var type = e.getAttribute("type");
+ if (type == null) type = "";
+ switch (type.toLowerCase()) {
+ case "button":
+ var element
+ // modern buttons aren't wrapped in spans
+ if (e.classList.contains('jenkins-button')) {
+ element = e
+ } else {
+ p = findParent(e);
+ element = e.parentNode.parentNode; // YUI's surrounding that has interesting classes
+ }
+ var name = null;
+ ["repeatable-add", "repeatable-delete", "hetero-list-add", "expand-button", "advanced-button", "apply-button", "validate-button"]
+ .forEach(function (clazz) {
+ if (element.classList.contains(clazz)) {
+ name = clazz;
+ }
+ });
+ if (name == null) {
+ if (name == null) {
+ element = element.parentNode.previousSibling;
+ if (element != null && element.classList && element.classList.contains('repeatable-insertion-point')) {
+ name = "hetero-list-add";
+ }
+ }
+ }
+ if (name != null) {
+ addProperty(p, name, e);
+ }
+ break;
+ case "submit":
+ break;
+ case "checkbox":
+ case "radio":
+ p = findParent(e);
+ if (e.groupingNode) {
+ e.formDom = {};
+ }
+ addProperty(p, e.name, e);
+ break;
+ case "file":
+ // to support structured form submission with file uploads,
+ // rename form field names to unique ones, and leave this name mapping information
+ // in JSON. this behavior is backward incompatible, so only do
+ // this when
+ p = findParent(e);
+ if (e.getAttribute("jsonAware") != null) {
+ var on = e.getAttribute("originalName");
+ if (on != null) {
+ addProperty(p, on, e);
+ } else {
+ addProperty(p, e.name, e);
+ }
+ }
+ break;
+ // otherwise fall through
+ default:
+ p = findParent(e);
+ addProperty(p, e.name, e);
+ break;
+ }
+ }
+
+ function annotate(e, path) {
+ e.setAttribute("path", path);
+ var o = e.formDom || {};
+ for (var key in o) {
+ var v = o[key];
+
+ function child(v, i) {
+ var suffix = null;
+ var newKey = key;
+ if (v.parentNode.className && v.parentNode.className.indexOf("one-each") > -1 && v.parentNode.className.indexOf("honor-order") > -1) {
+ suffix = v.getAttribute("descriptorId").split(".").pop()
+ } else if (v.getAttribute("type") == "radio") {
+ suffix = v.value
+ while (newKey.substring(0, 8) == 'removeme')
+ newKey = newKey.substring(newKey.indexOf('_', 8) + 1);
+ } else if (v.getAttribute("suffix") != null) {
+ suffix = v.getAttribute("suffix")
+ } else {
+ if (i > 0)
+ suffix = i;
+ }
+ if (suffix == null) suffix = "";
+ else suffix = '[' + suffix + ']';
+
+ annotate(v, path + "/" + newKey + suffix);
+ }
+
+ if (v instanceof Array) {
+ var i = 0;
+ v.forEach(function (v) {
+ child(v, i++)
+ })
+ } else {
+ child(v, 0)
+ }
+ }
+
+ }
+
+ annotate(form, "");
+
+ // clean up
+ for (i = 0; i < doms.length; i++)
+ doms[i].formDom = null;
+
+ return true;
+ }
+
+ function applyAll() {
+ document.querySelectorAll("FORM").forEach(function (e) {
+ buildFormTree(e);
+ })
+ }
+
+ /* JavaScript sometimes re-arranges the DOM and doesn't call layout callback
+ * known cases: YUI buttons, CodeMirror.
+ * We run apply twice to work around this, once immediately so that most cases work and the tests don't need to wait,
+ * and once to catch the edge cases.
+ */
+ function hardenedApplyAll () {
+ applyAll();
+
+ setTimeout(function () {
+ applyAll();
+ }, 1000);
+ }
+
+ hardenedApplyAll();
+
+ layoutUpdateCallback.add(hardenedApplyAll)
+
+ // expose this globally so that Selenium can call it
+ window.recomputeFormElementPath = hardenedApplyAll;
+});
diff --git a/core/src/main/resources/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage/description.jelly b/core/src/main/resources/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage/description.jelly
new file mode 100644
index 000000000000..83f02a200bf1
--- /dev/null
+++ b/core/src/main/resources/jenkins/telemetry/impl/SlaveToMasterFileCallableUsage/description.jelly
@@ -0,0 +1,13 @@
+
+
+ Jenkins controllers construct a remote-procedure-call (RPC) channel to agents to instruct them to perform work.
+ This channel is bidirectional and in a handful of cases agents made requests of the controller.
+ This was always tricky to secure,
+ and recently
+ the category of usages which involved access to files was more tightly restricted than before;
+ Jenkins developers are considering disabling this kind of usage entirely.
+ Since it is difficult to determine via static analysis or even manual code inspection which plugins are using this system,
+ we are collecting information on how widely it is used.
+ The data includes names of Java classes mainly in Jenkins core and plugins as well as method names and line numbers.
+ It does not include the names of files being accessed or anything else not determined by versions of software components in use.
+
diff --git a/core/src/main/resources/lib/form/repeatable/repeatable.js b/core/src/main/resources/lib/form/repeatable/repeatable.js
index 1bd4efddf698..3260051d1795 100644
--- a/core/src/main/resources/lib/form/repeatable/repeatable.js
+++ b/core/src/main/resources/lib/form/repeatable/repeatable.js
@@ -121,8 +121,10 @@ var repeatableSupport = {
a.onComplete.subscribe(function() {
var p = n.parentNode;
p.removeChild(n);
- if (p.tag)
+ if (p.tag) {
p.tag.update();
+ }
+ layoutUpdateCallback.call();
});
a.animate();
},
@@ -145,6 +147,7 @@ var repeatableSupport = {
updateOptionalBlock(input, false);
}
}
+ layoutUpdateCallback.call();
}
};
@@ -185,7 +188,7 @@ Behaviour.specify("INPUT.repeatable-delete", 'repeatable', 0, function(e) {
e = be = null; // avoid memory leak
});
- // radio buttons in repeatable content
+// radio buttons in repeatable content
// Needs to run before the radioBlock behavior so that names are already unique.
Behaviour.specify("DIV.repeated-chunk", 'repeatable', -200, function(d) {
var inputs = d.getElementsByTagName('INPUT');
diff --git a/pom.xml b/pom.xml
index d5bd491675e0..231f7a20bad3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -70,7 +70,7 @@ THE SOFTWARE.
- 2.322
+ 2.323
-SNAPSHOT