Skip to content

Commit

Permalink
Merge branch 'master' into revamp-dropdowns
Browse files Browse the repository at this point in the history
  • Loading branch information
janfaracik committed Feb 10, 2024
2 parents 9865811 + a642354 commit 7270712
Show file tree
Hide file tree
Showing 66 changed files with 2,602 additions and 2,056 deletions.
2 changes: 1 addition & 1 deletion .github/dependabot.yml
Expand Up @@ -58,7 +58,7 @@ updates:
versions: [">=7.0.0"]
- package-ecosystem: "maven"
directory: "/"
target-branch: "stable-2.426"
target-branch: "stable-2.440"
labels:
- "into-lts"
- "needs-justification"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/changelog.yml
Expand Up @@ -24,7 +24,7 @@ jobs:
# Drafts your next Release notes as Pull Requests are merged into "master"
- name: Generate GitHub Release Draft
id: release-drafter
uses: release-drafter/release-drafter@v5
uses: release-drafter/release-drafter@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Generates a YAML changelog file using https://github.com/jenkinsci/jenkins-core-changelog-generator
Expand Down
2 changes: 1 addition & 1 deletion bom/pom.xml
Expand Up @@ -39,7 +39,7 @@ THE SOFTWARE.

<properties>
<asm.version>9.6</asm.version>
<slf4jVersion>2.0.11</slf4jVersion>
<slf4jVersion>2.0.12</slf4jVersion>
<stapler.version>1822.v120278426e1c</stapler.version>
<groovy.version>2.4.21</groovy.version>
</properties>
Expand Down
2 changes: 1 addition & 1 deletion cli/pom.xml
Expand Up @@ -65,7 +65,7 @@
<dependency>
<groupId>org.glassfish.tyrus.bundles</groupId>
<artifactId>tyrus-standalone-client-jdk</artifactId>
<version>2.1.4</version>
<version>2.1.5</version>
<optional>true</optional>
</dependency>
<dependency>
Expand Down
34 changes: 19 additions & 15 deletions cli/src/main/java/hudson/cli/CLI.java
Expand Up @@ -58,14 +58,13 @@
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import org.apache.commons.lang.StringUtils;
import org.glassfish.tyrus.client.ClientManager;
import org.glassfish.tyrus.client.ClientProperties;
import org.glassfish.tyrus.client.SslEngineConfigurator;
import org.glassfish.tyrus.container.jdk.client.JdkClientContainer;

/**
Expand Down Expand Up @@ -134,6 +133,7 @@ public static int _main(String[] _args) throws Exception {
String tokenEnv = System.getenv("JENKINS_API_TOKEN");

boolean strictHostKey = false;
boolean noCertificateCheck = false;

while (!args.isEmpty()) {
String head = args.get(0);
Expand Down Expand Up @@ -179,17 +179,7 @@ public static int _main(String[] _args) throws Exception {
}
if (head.equals("-noCertificateCheck")) {
LOGGER.info("Skipping HTTPS certificate checks altogether. Note that this is not secure at all.");
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[]{new NoCheckTrustManager()}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory());
// bypass host name check, too.
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
@SuppressFBWarnings(value = "WEAK_HOSTNAME_VERIFIER", justification = "User set parameter to skip verifier.")
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
noCertificateCheck = true;
args = args.subList(1, args.size());
continue;
}
Expand Down Expand Up @@ -305,7 +295,7 @@ public boolean verify(String s, SSLSession sslSession) {
LOGGER.warning("Warning: -user ignored unless using -ssh");
}

CLIConnectionFactory factory = new CLIConnectionFactory();
CLIConnectionFactory factory = new CLIConnectionFactory().noCertificateCheck(noCertificateCheck);
String userInfo = new URL(url).getUserInfo();
if (userInfo != null) {
factory = factory.basicAuth(userInfo);
Expand Down Expand Up @@ -361,6 +351,13 @@ public void beforeRequest(Map<String, List<String>> headers) {

ClientManager client = ClientManager.createClient(JdkClientContainer.class.getName()); // ~ ContainerProvider.getWebSocketContainer()
client.getProperties().put(ClientProperties.REDIRECT_ENABLED, true); // https://tyrus-project.github.io/documentation/1.13.1/index/tyrus-proprietary-config.html#d0e1775
if (factory.noCertificateCheck) {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] {new NoCheckTrustManager()}, new SecureRandom());
SslEngineConfigurator sslEngineConfigurator = new SslEngineConfigurator(sslContext);
sslEngineConfigurator.setHostnameVerifier((s, sslSession) -> true);
client.getProperties().put(ClientProperties.SSL_ENGINE_CONFIGURATOR, sslEngineConfigurator);
}
Session session = client.connectToServer(new CLIEndpoint(), ClientEndpointConfig.Builder.create().configurator(new Authenticator()).build(), URI.create(url.replaceFirst("^http", "ws") + "cli/ws"));
PlainCLIProtocol.Output out = new PlainCLIProtocol.Output() {
@Override
Expand All @@ -386,8 +383,15 @@ public void close() throws IOException {
}
}

private static int plainHttpConnection(String url, List<String> args, CLIConnectionFactory factory) throws IOException, InterruptedException {
private static int plainHttpConnection(String url, List<String> args, CLIConnectionFactory factory)
throws GeneralSecurityException, IOException, InterruptedException {
LOGGER.log(FINE, "Trying to connect to {0} via plain protocol over HTTP", url);
if (factory.noCertificateCheck) {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] {new NoCheckTrustManager()}, new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
HttpsURLConnection.setDefaultHostnameVerifier((s, sslSession) -> true);
}
FullDuplexHttpStream streams = new FullDuplexHttpStream(new URL(url), "cli?remoting=false", factory.authorization);
try (ClientSideImpl connection = new ClientSideImpl(new PlainCLIProtocol.FramedOutput(streams.getOutputStream()))) {
connection.start(args);
Expand Down
11 changes: 11 additions & 0 deletions cli/src/main/java/hudson/cli/CLIConnectionFactory.java
Expand Up @@ -10,6 +10,7 @@
*/
public class CLIConnectionFactory {
String authorization;
boolean noCertificateCheck;

/**
* For CLI connection that goes through HTTP, sometimes you need
Expand All @@ -21,6 +22,16 @@ public CLIConnectionFactory authorization(String value) {
return this;
}

/**
* Skip TLS certificate and hostname verification checks.
*
* @since TODO
*/
public CLIConnectionFactory noCertificateCheck(boolean value) {
this.noCertificateCheck = value;
return this;
}

/**
* Convenience method to call {@link #authorization} with the HTTP basic authentication.
* Currently unused.
Expand Down
18 changes: 0 additions & 18 deletions core/src/main/java/hudson/FilePath.java
Expand Up @@ -483,7 +483,6 @@ public int zip(OutputStream out, DirScanner scanner) throws IOException, Interru
* @return The number of files/directories archived.
* This is only really useful to check for a situation where nothing
*/
@Restricted(NoExternalUse.class)
public int zip(OutputStream out, DirScanner scanner, String verificationRoot, String prefix, OpenOption... openOptions) throws IOException, InterruptedException {
ArchiverFactory archiverFactory = prefix == null ? ArchiverFactory.ZIP : ArchiverFactory.createZipWithPrefix(prefix, openOptions);
return archive(archiverFactory, out, scanner, verificationRoot, openOptions);
Expand Down Expand Up @@ -515,7 +514,6 @@ public int archive(final ArchiverFactory factory, OutputStream os, final DirScan
* @return The number of files/directories archived.
* This is only really useful to check for a situation where nothing
*/
@Restricted(NoExternalUse.class)
public int archive(final ArchiverFactory factory, OutputStream os, final DirScanner scanner,
String verificationRoot, OpenOption... openOptions) throws IOException, InterruptedException {
final OutputStream out = channel != null ? new RemoteOutputStream(os) : os;
Expand Down Expand Up @@ -762,7 +760,6 @@ public String invoke(File f, VirtualChannel channel) throws IOException {
}
}

@Restricted(NoExternalUse.class)
public boolean hasSymlink(FilePath verificationRoot, OpenOption... openOptions) throws IOException, InterruptedException {
return act(new HasSymlink(verificationRoot == null ? null : verificationRoot.remote, openOptions));
}
Expand All @@ -783,7 +780,6 @@ public Boolean invoke(File f, VirtualChannel channel) throws IOException {
}
}

@Restricted(NoExternalUse.class)
public boolean containsSymlink(FilePath verificationRoot, OpenOption... openOptions) throws IOException, InterruptedException {
return !list(new SymlinkRetainingFileFilter(verificationRoot, openOptions)).isEmpty();
}
Expand Down Expand Up @@ -2057,7 +2053,6 @@ public List<FilePath> list() throws IOException, InterruptedException {
* @param openOptions the options to apply when opening.
* @return Direct children of this directory.
*/
@Restricted(NoExternalUse.class)
@NonNull
public List<FilePath> list(FilePath verificationRoot, OpenOption... openOptions) throws IOException, InterruptedException {
return list(new OptionalDiscardingFileFilter(verificationRoot, openOptions));
Expand Down Expand Up @@ -2222,7 +2217,6 @@ public InputStream read() throws IOException, InterruptedException {
return read(null, new OpenOption[0]);
}

@Restricted(NoExternalUse.class)
public InputStream read(FilePath rootPath, OpenOption... openOptions) throws IOException, InterruptedException {
String rootPathString = rootPath == null ? null : rootPath.remote;
if (channel == null) {
Expand All @@ -2237,7 +2231,6 @@ public InputStream read(FilePath rootPath, OpenOption... openOptions) throws IOE
return p.getIn();
}

@Restricted(NoExternalUse.class)
public static InputStream newInputStreamDenyingSymlinkAsNeeded(File file, String verificationRoot, OpenOption... openOptions) throws IOException {
InputStream inputStream = null;
try {
Expand All @@ -2254,7 +2247,6 @@ public static InputStream newInputStreamDenyingSymlinkAsNeeded(File file, String
return inputStream;
}

@Restricted(NoExternalUse.class)
public static InputStream openInputStream(File file, OpenOption[] openOptions) throws IOException {
return Files.newInputStream(fileToPath(file), stripLocalOptions(openOptions));
}
Expand Down Expand Up @@ -2290,7 +2282,6 @@ private static void denyTmpDir(File file, String root, OpenOption... openOptions
}
}

@Restricted(NoExternalUse.class)
public static boolean isSymlink(File file, String root, OpenOption... openOptions) {
if (isNoFollowLink(openOptions)) {
if (Util.isSymlink(file.toPath())) {
Expand All @@ -2306,7 +2297,6 @@ private static boolean isSymlink(VisitorInfo visitorInfo) {
return isSymlink(visitorInfo.f, visitorInfo.verificationRoot, visitorInfo.openOptions);
}

@Restricted(NoExternalUse.class)
public static boolean isTmpDir(File file, String root, OpenOption... openOptions) {
if (isIgnoreTmpDirs(openOptions)) {
if (isTmpDir(file)) {
Expand All @@ -2318,7 +2308,6 @@ public static boolean isTmpDir(File file, String root, OpenOption... openOptions
return false;
}

@Restricted(NoExternalUse.class)
public static boolean isTmpDir(String filename, OpenOption... openOptions) {
if (isIgnoreTmpDirs(openOptions)) {
return isTmpDir(filename);
Expand All @@ -2338,12 +2327,10 @@ private static boolean isTmpDir(String filename) {
return filename.length() > WorkspaceList.TMP_DIR_SUFFIX.length() && filename.endsWith(WorkspaceList.TMP_DIR_SUFFIX);
}

@Restricted(NoExternalUse.class)
public static boolean isNoFollowLink(OpenOption... openOptions) {
return Arrays.asList(openOptions).contains(LinkOption.NOFOLLOW_LINKS);
}

@Restricted(NoExternalUse.class)
public static boolean isIgnoreTmpDirs(OpenOption... openOptions) {
return Arrays.asList(openOptions).contains(DisplayOption.IGNORE_TMP_DIRS);
}
Expand Down Expand Up @@ -3713,7 +3700,6 @@ public ExplicitlySpecifiedDirScanner(Map<String, String> files) {
/**
* Wraps {@link FileVisitor} to ignore symlinks.
*/
@Restricted(NoExternalUse.class)
public static FileVisitor ignoringSymlinks(final FileVisitor v, String verificationRoot, OpenOption... openOptions) {
return validatingVisitor(FilePath::isNoFollowLink,
visitorInfo -> !isSymlink(visitorInfo),
Expand All @@ -3723,7 +3709,6 @@ public static FileVisitor ignoringSymlinks(final FileVisitor v, String verificat
/**
* Wraps {@link FileVisitor} to ignore tmp directories.
*/
@Restricted(NoExternalUse.class)
public static FileVisitor ignoringTmpDirs(final FileVisitor v, String verificationRoot, OpenOption... openOptions) {
return validatingVisitor(FilePath::isIgnoreTmpDirs,
visitorInfo -> !isTmpDir(visitorInfo),
Expand Down Expand Up @@ -3766,10 +3751,7 @@ private static File mkdirsE(File dir) throws IOException {

/**
* Check if the relative child is really a descendant after symlink resolution if any.
*
* TODO un-restrict it in a weekly after the patch
*/
@Restricted(NoExternalUse.class)
public boolean isDescendant(@NonNull String potentialChildRelativePath) throws IOException, InterruptedException {
return act(new IsDescendant(potentialChildRelativePath));
}
Expand Down
21 changes: 20 additions & 1 deletion core/src/main/java/hudson/cli/CLIAction.java
Expand Up @@ -49,8 +49,10 @@
import javax.servlet.http.HttpServletResponse;
import jenkins.model.Jenkins;
import jenkins.util.FullDuplexHttpService;
import jenkins.util.SystemProperties;
import jenkins.websocket.WebSocketSession;
import jenkins.websocket.WebSockets;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
Expand All @@ -73,6 +75,12 @@ public class CLIAction implements UnprotectedRootAction, StaplerProxy {

private static final Logger LOGGER = Logger.getLogger(CLIAction.class.getName());

/**
* Boolean values map to allowing/disallowing WS CLI endpoint always, {@code null} is the default of doing an {@code Origin} check.
* {@code true} is only advisable if anonymous users have no permissions, and Jenkins sends SameSite=Lax cookies (or browsers use that as the implicit default).
*/
/* package-private for testing */ static /* non-final for Script Console */ Boolean ALLOW_WEBSOCKET = SystemProperties.optBoolean(CLIAction.class.getName() + ".ALLOW_WEBSOCKET");

private final transient Map<UUID, FullDuplexHttpService> duplexServices = new HashMap<>();

@Override
Expand Down Expand Up @@ -114,10 +122,21 @@ public boolean isWebSocketSupported() {
/**
* WebSocket endpoint.
*/
public HttpResponse doWs() {
public HttpResponse doWs(StaplerRequest req) {
if (!WebSockets.isSupported()) {
return HttpResponses.notFound();
}
if (ALLOW_WEBSOCKET == null) {
final String actualOrigin = req.getHeader("Origin");
final String expectedOrigin = StringUtils.removeEnd(StringUtils.removeEnd(Jenkins.get().getRootUrlFromRequest(), "/"), req.getContextPath());

if (actualOrigin == null || !actualOrigin.equals(expectedOrigin)) {
LOGGER.log(Level.FINE, () -> "Rejecting origin: " + actualOrigin + "; expected was from request: " + expectedOrigin);
return HttpResponses.forbidden();
}
} else if (!ALLOW_WEBSOCKET) {
return HttpResponses.forbidden();
}
Authentication authentication = Jenkins.getAuthentication2();
return WebSockets.upgrade(new WebSocketSession() {
ServerSideImpl connection;
Expand Down
16 changes: 15 additions & 1 deletion core/src/main/java/hudson/cli/CLICommand.java
Expand Up @@ -26,6 +26,7 @@

import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.AbortException;
import hudson.Extension;
import hudson.ExtensionList;
Expand All @@ -51,6 +52,7 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
import org.apache.commons.discovery.ResourceClassIterator;
import org.apache.commons.discovery.ResourceNameIterator;
import org.apache.commons.discovery.resource.ClassLoaders;
Expand All @@ -62,6 +64,7 @@
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.ParserProperties;
import org.kohsuke.args4j.spi.OptionHandler;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
Expand Down Expand Up @@ -107,6 +110,16 @@
*/
@LegacyInstancesAreScopedToHudson
public abstract class CLICommand implements ExtensionPoint, Cloneable {

/**
* Boolean values to either allow or disallow parsing of @-prefixes.
* If a command line value starts with @, it is interpreted as being a file, loaded,
* and interpreted as if the file content would have been passed to the command line
*/
@SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Accessible via System Groovy Scripts")
@Restricted(NoExternalUse.class)
public static boolean ALLOW_AT_SYNTAX = SystemProperties.getBoolean(CLICommand.class.getName() + ".allowAtSyntax");

/**
* Connected to stdout and stderr of the CLI agent that initiated the session.
* IOW, if you write to these streams, the person who launched the CLI command
Expand Down Expand Up @@ -307,7 +320,8 @@ private void logAndPrintError(Throwable e, String errorMessage, String logMessag
* @since 1.538
*/
protected CmdLineParser getCmdLineParser() {
return new CmdLineParser(this);
ParserProperties properties = ParserProperties.defaults().withAtSyntax(ALLOW_AT_SYNTAX);
return new CmdLineParser(this, properties);
}

/**
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/java/hudson/cli/declarative/CLIRegisterer.java
Expand Up @@ -59,6 +59,7 @@
import org.jvnet.localizer.ResourceBundleHolder;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.ParserProperties;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
Expand Down Expand Up @@ -131,7 +132,8 @@ protected CmdLineParser getCmdLineParser() {
private CmdLineParser bindMethod(List<MethodBinder> binders) {

registerOptionHandlers();
CmdLineParser parser = new CmdLineParser(null);
ParserProperties properties = ParserProperties.defaults().withAtSyntax(ALLOW_AT_SYNTAX);
CmdLineParser parser = new CmdLineParser(null, properties);

// build up the call sequence
Stack<Method> chains = new Stack<>();
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/hudson/model/AbstractItem.java
Expand Up @@ -966,7 +966,7 @@ public void load() throws IOException {
Items.whileUpdatingByXml(new NotReallyRoleSensitiveCallable<Void, IOException>() {
@Override
public Void call() throws IOException {
onLoad(getParent(), getRootDir().getName());
onLoad(getParent(), getParent().getItemName(getRootDir(), AbstractItem.this));
return null;
}
});
Expand Down

0 comments on commit 7270712

Please sign in to comment.