From 2232d9d9beb25ac293aa6bb2eb67ab097c87d173 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Mon, 16 Oct 2023 10:46:27 -0700 Subject: [PATCH 1/6] Unify two entrypoints --- docs/inbound-agent.md | 4 +- src/main/java/hudson/remoting/Engine.java | 14 +- src/main/java/hudson/remoting/Launcher.java | 475 +++++++++++++----- src/main/java/hudson/remoting/Util.java | 66 --- src/main/java/hudson/remoting/jnlp/Main.java | 412 +-------------- .../engine/JnlpAgentEndpointResolver.java | 7 +- 6 files changed, 373 insertions(+), 605 deletions(-) diff --git a/docs/inbound-agent.md b/docs/inbound-agent.md index 7bed1752e..f2f38dc66 100644 --- a/docs/inbound-agent.md +++ b/docs/inbound-agent.md @@ -78,7 +78,7 @@ This mechanism requires a download of the `agent.jar`, as described for "Downloa Once all the prerequisite files and data have been obtained, the agent can be launched with a command like this ``` -java -cp agent.jar hudson.remoting.jnlp.Main \ +java -jar agent.jar \ -workDir \ -direct \ -protocols JNLP4-connect \ @@ -102,7 +102,7 @@ Additional descriptions of configuring this mechanism are located at [Installing There are a number of different launch parameters that control how the agent connects and behaves. The parameters available and the default behavior may vary depending upon the entry point. -You can obtain usage information by executing `java -cp agent.jar hudson.remoting.jnlp.Main` or `java -jar agent.jar --help`. +You can obtain usage information by executing `java -jar agent.jar --help`. Not all parameters work together and some parameters require the use of others. There are also system or environment variables that control some advanced behaviors documented at [Remoting Configuration](https://github.com/jenkinsci/remoting/blob/master/docs/configuration.md). diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index bb266028b..0c095ae3d 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -94,6 +94,8 @@ import org.jenkinsci.remoting.protocol.impl.ConnectionRefusalException; import org.jenkinsci.remoting.util.KeyUtils; import org.jenkinsci.remoting.util.VersionNumber; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; /** * Agent engine that proactively connects to Jenkins controller. @@ -169,7 +171,7 @@ public Thread newThread(@NonNull final Runnable r) { private String proxyCredentials = System.getProperty("proxyCredentials"); /** - * See {@link hudson.remoting.jnlp.Main#tunnel} for the documentation. + * See {@link Launcher#tunnel} for the documentation. */ @CheckForNull private String tunnel; @@ -885,7 +887,7 @@ private JnlpEndpointResolver createEndpointResolver(List jenkinsUrls) { if (directConnection == null) { SSLSocketFactory sslSocketFactory = null; try { - sslSocketFactory = getSSLSocketFactory(); + sslSocketFactory = getSSLSocketFactory(candidateCertificates); } catch (Exception e) { events.error(e); } @@ -1034,16 +1036,18 @@ private static FileInputStream getFileInputStream(final File file) throws Privil }); } - private SSLSocketFactory getSSLSocketFactory() + @CheckForNull + @Restricted(NoExternalUse.class) + static SSLSocketFactory getSSLSocketFactory(List x509Certificates) throws PrivilegedActionException, KeyStoreException, NoSuchProviderException, CertificateException, NoSuchAlgorithmException, IOException, KeyManagementException { SSLSocketFactory sslSocketFactory = null; - if (candidateCertificates != null && !candidateCertificates.isEmpty()) { + if (x509Certificates != null && !x509Certificates.isEmpty()) { KeyStore keyStore = getCacertsKeyStore(); // load the keystore keyStore.load(null, null); int i = 0; - for (X509Certificate c : candidateCertificates) { + for (X509Certificate c : x509Certificates) { keyStore.setCertificateEntry(String.format("alias-%d", i++), c); } // prepare the trust manager diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index 05360dbeb..ba4cb26b9 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -27,10 +27,10 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.remoting.Channel.Mode; +import org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver; import org.jenkinsci.remoting.engine.WorkDirManager; import org.jenkinsci.remoting.util.PathUtils; -import org.jenkinsci.remoting.util.https.NoCheckHostnameVerifier; -import org.jenkinsci.remoting.util.https.NoCheckTrustManager; +import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.CmdLineParser; import org.kohsuke.args4j.Option; @@ -41,12 +41,8 @@ import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -62,10 +58,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Method; -import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.InetSocketAddress; -import java.net.PasswordAuthentication; import java.net.ServerSocket; import java.net.Socket; import java.net.URL; @@ -75,18 +69,17 @@ import java.nio.file.Path; import java.security.GeneralSecurityException; import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; import java.security.PrivilegedActionException; import java.security.SecureRandom; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Base64; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Properties; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -115,6 +108,7 @@ public class Launcher { * Specifies a destination for error logs. * If specified, this option overrides the default destination within {@link #workDir}. * If both this options and {@link #workDir} is not set, the log will not be generated. + * @since 3.8 */ @Option(name="-agentLog", usage="Local agent error log destination (overrides workDir)") @CheckForNull @@ -127,17 +121,25 @@ public void setTextMode(boolean b) { System.out.println("Running in "+mode.name().toLowerCase(Locale.ENGLISH)+" mode"); } + /** + * @deprecated use {@link #secret}, {@link #name}, {@link #urls}, {@link #webSocket}, {@link #tunnel}, + * {@link #workDir}, {@link #internalDir}, and/or {@link #failIfWorkDirIsMissing} directly. + */ @Option(name="-jnlpUrl",usage="instead of talking to the controller via stdin/stdout, " + "emulate a JNLP client by making a TCP connection to the controller. " + - "Connection parameters are obtained by parsing the JNLP file.") + "Connection parameters are obtained by parsing the JNLP file.", forbids = {"-direct", "-name", "-tunnel", "-url", "-webSocket"}) + @Deprecated public URL agentJnlpURL = null; - @Option(name="-jnlpCredentials",metaVar="USER:PASSWORD",usage="HTTP BASIC AUTH header to pass in for making HTTP requests.") + @Option(name="-credentials",metaVar="USER:PASSWORD",aliases="-jnlpCredentials",usage="HTTP BASIC AUTH header to pass in for making HTTP requests.") public String agentJnlpCredentials = null; @Option(name="-secret", metaVar="HEX_SECRET", usage="Agent connection secret to use instead of -jnlpCredentials.") public String secret; + @Option(name="-name", usage="Name of the agent.") + public String name; + @Option(name="-proxyCredentials",metaVar="USER:PASSWORD",usage="HTTP BASIC AUTH header to pass in for making HTTP authenticated proxy requests.") public String proxyCredentials = null; @@ -163,8 +165,11 @@ public void addClasspath(String pathList) throws Exception { "then wait for the controller to connect to that port.") public File tcpPortFile=null; - - @Option(name="-auth",metaVar="user:pass",usage="If your Jenkins is security-enabled, specify a valid user name and password.") + /** + * @deprecated use {@link #agentJnlpCredentials} or {@link #proxyCredentials} + */ + @Deprecated + @Option(name="-auth",metaVar="user:pass",usage="(deprecated) unused; use -credentials or -proxyCredentials") public String auth = null; /** @@ -188,10 +193,15 @@ public void addClasspath(String pathList) throws Exception { "certificate file to read.", forbids = "-noCertificateCheck") public List candidateCertificates; + private List x509Certificates; + + private SSLSocketFactory sslSocketFactory; + /** * Disables HTTPs Certificate validation of the server when using {@link org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver}. * This option is managed by the {@code -noCertificateCheck} option. */ + @Option(name="-noCertificateCheck", aliases = "-disableHttpsCertValidation", forbids = "-cert", usage="Ignore SSL validation errors - use as a last resort only.") private boolean noCertificateCheck = false; public InetSocketAddress connectionTarget = null; @@ -211,20 +221,16 @@ public void setConnectTo(String target) { * * @param ignored * This is ignored. + * @deprecated {use {@link #noCertificateCheck} */ - @Option(name="-noCertificateCheck", forbids = "-cert") + @Deprecated public void setNoCertificateCheck(boolean ignored) throws NoSuchAlgorithmException, KeyManagementException { System.out.println("Skipping HTTPS certificate checks altogether. Note that this is not secure at all."); this.noCertificateCheck = true; - SSLContext context = SSLContext.getInstance("TLS"); - context.init(null, new TrustManager[]{new NoCheckTrustManager()}, new java.security.SecureRandom()); - HttpsURLConnection.setDefaultSSLSocketFactory(context.getSocketFactory()); - // bypass host name check, too. - HttpsURLConnection.setDefaultHostnameVerifier(new NoCheckHostnameVerifier()); } - @Option(name="-noReconnect",usage="Doesn't try to reconnect when a communication fail, and exit instead") + @Option(name="-noReconnect",aliases="-noreconnect",usage="Doesn't try to reconnect when a communication fail, and exit instead") public boolean noReconnect = false; @Option(name = "-noKeepAlive", @@ -269,6 +275,73 @@ public void setNoCertificateCheck(boolean ignored) throws NoSuchAlgorithmExcepti depends = "-workDir") public boolean failIfWorkDirIsMissing = WorkDirManager.DEFAULT_FAIL_IF_WORKDIR_IS_MISSING; + @Option(name = "-tunnel", + metaVar = "HOST:PORT", + usage = "Connect to the specified host and port, instead of connecting directly to Jenkins. " + + "Useful when connection to Jenkins needs to be tunneled. Can be also HOST: or :PORT, " + + "in which case the missing portion will be auto-configured like the default behavior.") + public String tunnel; + + @Deprecated + @Option(name = "-headless", usage = "(deprecated; now always headless)") + public boolean headlessMode; + + @Option(name = "-url", usage = "Specify the Jenkins root URLs to connect to.") + public List urls = new ArrayList<>(); + + @Option(name = "-webSocket", + usage = "Make a WebSocket connection to Jenkins rather than using the TCP port.", + depends = "-url", + forbids = { + "-direct", + "-tunnel", + "-credentials", + "-proxyCredentials", + "-cert", + "-noCertificateCheck", + "-noKeepAlive" + }) + public boolean webSocket; + + @Option(name = "-webSocketHeader", + usage = "Additional WebSocket header to set, eg for authenticating with reverse proxies. To specify multiple headers, call this flag multiple times, one with each header", + metaVar = "NAME=VALUE", + depends = "-webSocket") + public Map webSocketHeaders; + + /** + * Connect directly to the TCP port specified, skipping the HTTP(S) connection parameter download. + * @since 3.34 + */ + @Option(name = "-direct", + metaVar = "HOST:PORT", + aliases = "-directConnection", + depends = "-instanceIdentity", + forbids = {"-jnlpUrl", "-url", "-tunnel"}, + usage = "Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. For example, \"myjenkins:50000\".") + public String directConnection; + + /** + * The controller's instance identity. + * @see Instance Identity + * @since 3.34 + */ + @Option(name = "-instanceIdentity", + depends = "-direct", + usage = "The base64 encoded InstanceIdentity byte array of the Jenkins controller. When this is set, the agent skips connecting to an HTTP(S) port for connection info.") + public String instanceIdentity; + + /** + * When {@link #instanceIdentity} is set, the agent skips connecting via http(s) where it normally + * obtains the configured protocols. When no protocols are given the agent tries all protocols + * it knows. Use this to limit the protocol list. + * @since 3.34 + */ + @Option(name = "-protocols", + depends = {"-direct"}, + usage = "Specify the remoting protocols to attempt when instanceIdentity is provided.") + public List protocols = new ArrayList<>(); + /** * Shows help message and then exits * @since 3.36 @@ -283,12 +356,20 @@ public void setNoCertificateCheck(boolean ignored) throws NoSuchAlgorithmExcepti @Option(name="-version",usage="Shows the version of the remoting jar and then exits") public boolean showVersion = false; + /** + * The original calling convention takes two positional arguments: secret key and agent name. + * @deprecated use {@link #secret} and {@link #name} + */ + @Argument + @Deprecated + public List args = new ArrayList<>(); - public static void main(String... args) throws Exception { + public static void main(String... args) throws IOException, InterruptedException { Launcher launcher = new Launcher(); CmdLineParser parser = new CmdLineParser(launcher); try { parser.parseArgument(args); + normalizeArguments(launcher); if (launcher.showHelp && !launcher.showVersion) { parser.printUsage(System.out); return; @@ -303,7 +384,7 @@ public static void main(String... args) throws Exception { } @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "log file, just like console output, should be in platform default encoding") - public void run() throws Exception { + public void run() throws CmdLineException, IOException, InterruptedException { if (showVersion) { String version = Util.getVersion(); if(version != null) { @@ -326,84 +407,30 @@ public void run() throws Exception { } workDirManager.setupLogging(internalDirPath, agentLog != null ? PathUtils.fileToPath(agentLog) : null); - if(auth!=null) { - final int idx = auth.indexOf(':'); - if(idx<0) throw new CmdLineException(null, "No ':' in the -auth option", null); - Authenticator.setDefault(new Authenticator() { - @Override public PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(auth.substring(0,idx), auth.substring(idx+1).toCharArray()); - } - }); - } - if (candidateCertificates != null && !candidateCertificates.isEmpty()) { - HttpsURLConnection.setDefaultSSLSocketFactory(getSSLSocketFactory()); + createX509Certificates(); + try { + sslSocketFactory = Engine.getSSLSocketFactory(x509Certificates); + } catch (GeneralSecurityException | PrivilegedActionException e) { + throw new RuntimeException(e); } if(connectionTarget!=null) { runAsTcpClient(); } else - if(agentJnlpURL !=null) { - List jnlpArgs = parseJnlpArguments(); - if (jarCache != null) { - jnlpArgs.add("-jar-cache"); - jnlpArgs.add(jarCache.getPath()); - } - if (this.noReconnect) { - jnlpArgs.add("-noreconnect"); - } - if (this.noKeepAlive) { - jnlpArgs.add("-noKeepAlive"); - } - if (agentLog != null) { - jnlpArgs.add("-agentLog"); - jnlpArgs.add(agentLog.getPath()); - } - if (loggingConfigFilePath != null) { - jnlpArgs.add("-loggingConfig"); - jnlpArgs.add(loggingConfigFilePath.getAbsolutePath()); - } - if (this.workDir != null) { - jnlpArgs.add("-workDir"); - jnlpArgs.add(workDir.getAbsolutePath()); - jnlpArgs.add("-internalDir"); - jnlpArgs.add(internalDir); - if (failIfWorkDirIsMissing) { - jnlpArgs.add("-failIfWorkDirIsMissing"); - } - } - if (candidateCertificates != null && !candidateCertificates.isEmpty()) { - for (String c: candidateCertificates) { - jnlpArgs.add("-cert"); - jnlpArgs.add(c); - } - } - if (noCertificateCheck) { - // Generally it is not required since the default settings have been changed anyway. - // But we set it up just in case there are overrides somewhere in the logic - jnlpArgs.add("-disableHttpsCertValidation"); - } - try { - hudson.remoting.jnlp.Main._main(jnlpArgs.toArray(new String[0])); - } catch (CmdLineException e) { - System.err.println("JNLP file "+ agentJnlpURL +" has invalid arguments: "+jnlpArgs); - System.err.println("Most likely a configuration error in the controller"); - System.err.println(e.getMessage()); - System.exit(1); + if (agentJnlpURL != null || !urls.isEmpty()) { + if (agentJnlpURL != null) { + bootstrapInboundAgent(); } + runAsInboundAgent(); } else if(tcpPortFile!=null) { runAsTcpServer(); } else { runWithStdinStdout(); } - System.exit(0); } - @CheckForNull @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Parameter supplied by user / administrator.") - private SSLSocketFactory getSSLSocketFactory() - throws PrivilegedActionException, KeyStoreException, NoSuchProviderException, CertificateException, - NoSuchAlgorithmException, IOException, KeyManagementException { - SSLSocketFactory sslSocketFactory = null; + private void createX509Certificates() { if (candidateCertificates != null && !candidateCertificates.isEmpty()) { CertificateFactory factory; try { @@ -411,10 +438,7 @@ private SSLSocketFactory getSSLSocketFactory() } catch (CertificateException e) { throw new IllegalStateException("Java platform specification mandates support for X.509", e); } - KeyStore keyStore = Engine.getCacertsKeyStore(); - // load the keystore - keyStore.load(null, null); - int i = 0; + x509Certificates = new ArrayList<>(); for (String certOrAtFilename : candidateCertificates) { certOrAtFilename = certOrAtFilename.trim(); byte[] cert; @@ -456,25 +480,146 @@ private SSLSocketFactory getSSLSocketFactory() cert = certOrAtFilename.getBytes(StandardCharsets.US_ASCII); } try { - keyStore.setCertificateEntry(String.format("alias-%d", i++), - factory.generateCertificate(new ByteArrayInputStream(cert))); + x509Certificates.add((X509Certificate) factory.generateCertificate(new ByteArrayInputStream(cert))); } catch (ClassCastException e) { LOGGER.log(Level.WARNING, "Expected X.509 certificate from " + certOrAtFilename, e); } catch (CertificateException e) { LOGGER.log(Level.WARNING, "Could not parse X.509 certificate from " + certOrAtFilename, e); } } - // prepare the trust manager - TrustManagerFactory trustManagerFactory = - TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(keyStore); - // prepare the SSL context - SSLContext ctx = SSLContext.getInstance("TLS"); - ctx.init(null, trustManagerFactory.getTrustManagers(), null); - // now we have our custom socket factory - sslSocketFactory = ctx.getSocketFactory(); - } - return sslSocketFactory; + } + } + + private void bootstrapInboundAgent() throws CmdLineException, IOException, InterruptedException { + List jnlpArgs; + try { + jnlpArgs = parseJnlpArguments(); + } catch (ParserConfigurationException | SAXException e) { + throw new RuntimeException(e); + } + + // Set any arguments that are needed for validation of the server's values. + if (directConnection != null) { + jnlpArgs.add("-direct"); + jnlpArgs.add(directConnection); + } + if (tunnel != null) { + jnlpArgs.add("-tunnel"); + jnlpArgs.add(tunnel); + } + if (agentJnlpCredentials != null) { + jnlpArgs.add("-credentials"); + jnlpArgs.add(agentJnlpCredentials); + } + if (proxyCredentials != null) { + jnlpArgs.add("-proxyCredentials"); + jnlpArgs.add(proxyCredentials); + } + if (noKeepAlive) { + jnlpArgs.add("-noKeepAlive"); + } + if (workDir != null) { + jnlpArgs.add("-workDir"); + jnlpArgs.add(workDir.getAbsolutePath()); + jnlpArgs.add("-internalDir"); + jnlpArgs.add(internalDir); + if (failIfWorkDirIsMissing) { + jnlpArgs.add("-failIfWorkDirIsMissing"); + } + } + if (candidateCertificates != null && !candidateCertificates.isEmpty()) { + for (String c : candidateCertificates) { + jnlpArgs.add("-cert"); + jnlpArgs.add(c); + } + } + if (noCertificateCheck) { + jnlpArgs.add("-noCertificateCheck"); + } + + // Parse the server's pseudo-JNLP output + Launcher bootstrap = new Launcher(); + CmdLineParser parser = new CmdLineParser(bootstrap); + parser.parseArgument(jnlpArgs.toArray(new String[0])); + normalizeArguments(bootstrap); + validateInboundAgentArgs(bootstrap); + + // Apply the results + assert urls.isEmpty(); + urls.addAll(bootstrap.urls); + if (bootstrap.secret != null) { + secret = bootstrap.secret; + } + if (bootstrap.name != null) { + name = bootstrap.name; + } + if (bootstrap.webSocket) { + webSocket = true; + } + if (bootstrap.tunnel != null) { + tunnel = bootstrap.tunnel; + } + if (bootstrap.workDir != null) { + workDir = bootstrap.workDir; + } + if (!WorkDirManager.DirType.INTERNAL_DIR.getDefaultLocation().equals(bootstrap.internalDir)) { + internalDir = bootstrap.internalDir; + } + if (bootstrap.failIfWorkDirIsMissing != WorkDirManager.DEFAULT_FAIL_IF_WORKDIR_IS_MISSING) { + failIfWorkDirIsMissing = bootstrap.failIfWorkDirIsMissing; + } + } + + private static void normalizeArguments(Launcher launcher) throws CmdLineException { + if (!launcher.args.isEmpty()) { + if (launcher.args.size() != 2) { + throw new CmdLineException(null, "Two arguments required, but got " + launcher.args); + } + if (launcher.secret == null) { + launcher.secret = launcher.args.get(0); + } else { + throw new CmdLineException(null, "Cannot provide secret via both named and positional arguments"); + } + if (launcher.name == null) { + launcher.name = launcher.args.get(1); + } else { + throw new CmdLineException(null, "Cannot provide name via both named and positional arguments"); + } + launcher.args.clear(); + } + } + + private static void validateInboundAgentArgs(Launcher launcher) throws CmdLineException { + assert launcher.args.isEmpty() : "should have been normalized previously"; + if (launcher.secret == null) { + throw new CmdLineException(null, "Secret is required for inbound agents"); + } + if (launcher.name == null) { + throw new CmdLineException(null, "Name is required for inbound agents"); + } + if (launcher.urls.isEmpty() && launcher.directConnection == null) { + throw new CmdLineException(null, "At least one URL is required for inbound agents"); + } + if (launcher.webSocket) { + assert !launcher.urls.isEmpty(); // depends = "-url" + if (launcher.urls.size() > 1) { + throw new CmdLineException(null, "Only a single URL is supported for WebSocket agents"); + } + } + } + + private void runAsInboundAgent() throws CmdLineException, IOException, InterruptedException { + validateInboundAgentArgs(this); + Engine engine = createEngine(); + engine.startEngine(); + try { + engine.join(); + LOGGER.fine("Engine has died"); + } finally { + // if we are programmatically driven by other code, allow them to interrupt our blocking main thread to kill + // the on-going connection to Jenkins + engine.interrupt(); + } } /** @@ -491,19 +636,7 @@ public List parseJnlpArguments() throws ParserConfigurationException, SA while (true) { URLConnection con = null; try { - con = Util.openURLConnection(agentJnlpURL); - if (con instanceof HttpURLConnection) { - HttpURLConnection http = (HttpURLConnection) con; - if (agentJnlpCredentials != null) { - String userPassword = agentJnlpCredentials; - String encoding = Base64.getEncoder().encodeToString(userPassword.getBytes(StandardCharsets.UTF_8)); - http.setRequestProperty("Authorization", "Basic " + encoding); - } - if (System.getProperty("proxyCredentials", proxyCredentials) != null) { - String encoding = Base64.getEncoder().encodeToString(System.getProperty("proxyCredentials", proxyCredentials).getBytes(StandardCharsets.UTF_8)); - http.setRequestProperty("Proxy-Authorization", "Basic " + encoding); - } - } + con = JnlpAgentEndpointResolver.openURLConnection(agentJnlpURL, agentJnlpCredentials, proxyCredentials, sslSocketFactory, noCertificateCheck); con.connect(); if (con instanceof HttpURLConnection) { @@ -550,10 +683,6 @@ public List parseJnlpArguments() throws ParserConfigurationException, SA List jnlpArgs = new ArrayList<>(); for( int i=0; i(protocols)); + engine.setWebSocket(webSocket); + if (webSocketHeaders != null) { + engine.setWebSocketHeaders(webSocketHeaders); + } + if (tunnel != null) { + engine.setTunnel(tunnel); + } + if (agentJnlpCredentials != null) { + engine.setCredentials(agentJnlpCredentials); + } + if (proxyCredentials != null) { + engine.setProxyCredentials(proxyCredentials); + } + if (jarCache != null) { + engine.setJarCache(new FileSystemJarCache(jarCache, true)); + } + engine.setNoReconnect(noReconnect); + engine.setKeepAlive(!noKeepAlive); + + if (noCertificateCheck) { + LOGGER.log(Level.WARNING, "Certificate validation for HTTPs endpoints is disabled"); + } + engine.setDisableHttpsCertValidation(noCertificateCheck); + + // TODO: ideally logging should be initialized before the "Setting up agent" entry + if (agentLog != null) { + try { + engine.setAgentLog(PathUtils.fileToPath(agentLog)); + } catch (IOException ex) { + throw new IllegalStateException("Cannot retrieve custom log destination", ex); + } + } + if (loggingConfigFilePath != null) { + try { + engine.setLoggingConfigFile(PathUtils.fileToPath(loggingConfigFilePath)); + } catch (IOException ex) { + throw new IllegalStateException("Logging config file is invalid", ex); + } + } + + if (x509Certificates != null && !x509Certificates.isEmpty()) { + engine.setCandidateCertificates(x509Certificates); + } + + // Working directory settings + if (workDir != null) { + try { + engine.setWorkDir(PathUtils.fileToPath(workDir)); + } catch (IOException ex) { + throw new IllegalStateException("Work directory path is invalid", ex); + } + } + engine.setInternalDir(internalDir); + engine.setFailIfWorkDirIsMissing(failIfWorkDirIsMissing); + + return engine; + } + + /** + * {@link EngineListener} implementation that sends output to {@link Logger}. + */ + private static final class CuiListener implements EngineListener { + @Override + public void status(String msg, Throwable t) { + LOGGER.log(Level.INFO, msg, t); + } + + @Override + public void status(String msg) { + status(msg, null); + } + + @Override + @SuppressFBWarnings( + value = "DM_EXIT", + justification = "Yes, we really want to exit in the case of severe error") + public void error(Throwable t) { + LOGGER.log(Level.SEVERE, t.getMessage(), t); + System.exit(-1); + } + + @Override + public void onDisconnect() {} + + @Override + public void onReconnect() {} + } + private static String communicationProtocolName; /** diff --git a/src/main/java/hudson/remoting/Util.java b/src/main/java/hudson/remoting/Util.java index 1cfa872f8..4bbc51dab 100644 --- a/src/main/java/hudson/remoting/Util.java +++ b/src/main/java/hudson/remoting/Util.java @@ -6,24 +6,15 @@ import org.kohsuke.accmod.Restricted; import org.kohsuke.accmod.restrictions.NoExternalUse; -import javax.net.ssl.HttpsURLConnection; -import javax.net.ssl.SSLSocketFactory; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.Proxy; -import java.net.SocketAddress; import java.net.URL; -import java.net.URLConnection; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Base64; import java.util.Enumeration; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -100,63 +91,6 @@ static String indent(String s) { return " " + s.trim().replace("\n", "\n "); } - /** - * Gets URL connection. - * If http_proxy environment variable exists, the connection uses the proxy. - * Credentials can be passed e.g. to support running Jenkins behind a (reverse) proxy requiring authorization - */ - @SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "Used for retrieving the connection info from the server. We should cleanup the other, unused references.") - static URLConnection openURLConnection(URL url, String credentials, String proxyCredentials, SSLSocketFactory sslSocketFactory) throws IOException { - String httpProxy = null; - // If http.proxyHost property exists, openConnection() uses it. - if (System.getProperty("http.proxyHost") == null) { - httpProxy = System.getenv("http_proxy"); - } - URLConnection con; - if (httpProxy != null && "http".equals(url.getProtocol()) && NoProxyEvaluator.shouldProxy(url.getHost())) { - try { - URL proxyUrl = new URL(httpProxy); - SocketAddress addr = new InetSocketAddress(proxyUrl.getHost(), proxyUrl.getPort()); - Proxy proxy = new Proxy(Proxy.Type.HTTP, addr); - con = url.openConnection(proxy); - } catch (MalformedURLException e) { - System.err.println("Not use http_proxy property or environment variable which is invalid: "+e.getMessage()); - con = url.openConnection(); - } - } else { - con = url.openConnection(); - } - if (credentials != null) { - String encoding = Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); - con.setRequestProperty("Authorization", "Basic " + encoding); - } - if (proxyCredentials != null) { - String encoding = Base64.getEncoder().encodeToString(proxyCredentials.getBytes(StandardCharsets.UTF_8)); - con.setRequestProperty("Proxy-Authorization", "Basic " + encoding); - } - if (con instanceof HttpsURLConnection && sslSocketFactory != null) { - ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); - } - return con; - } - - /** - * Gets URL connection. - * If http_proxy environment variable exists, the connection uses the proxy. - * Credentials can be passed e.g. to support running Jenkins behind a (reverse) proxy requiring authorization - */ - static URLConnection openURLConnection(URL url, String credentials, String proxyCredentials) throws IOException { - return openURLConnection(url, credentials, proxyCredentials, null); - } - - /** - * Gets URL connection. - * If http_proxy environment variable exists, the connection uses the proxy. - */ - static URLConnection openURLConnection(URL url) throws IOException { - return openURLConnection(url, null, null, null); - } - /** * @deprecated Use {@link Files#createDirectories(java.nio.file.Path, java.nio.file.attribute.FileAttribute...)} instead. */ diff --git a/src/main/java/hudson/remoting/jnlp/Main.java b/src/main/java/hudson/remoting/jnlp/Main.java index e7a0e3a40..51001cf0c 100644 --- a/src/main/java/hudson/remoting/jnlp/Main.java +++ b/src/main/java/hudson/remoting/jnlp/Main.java @@ -23,415 +23,15 @@ */ package hudson.remoting.jnlp; -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import hudson.remoting.Engine; -import hudson.remoting.EngineListener; -import hudson.remoting.FileSystemJarCache; -import hudson.remoting.Util; -import java.util.Map; -import org.jenkinsci.remoting.engine.WorkDirManager; -import org.jenkinsci.remoting.util.PathUtils; -import org.kohsuke.args4j.Argument; -import org.kohsuke.args4j.CmdLineException; -import org.kohsuke.args4j.CmdLineParser; -import org.kohsuke.args4j.Option; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static java.util.logging.Level.INFO; -import static java.util.logging.Level.WARNING; +import hudson.remoting.Launcher; /** - * Entry point to pseudo-JNLP agent. + * Previous entry point to pseudo-JNLP agent. * - *

- * See also {@code jenkins-agent.jnlp.jelly} in the core. + *

See also {@code jenkins-agent.jnlp.jelly} in the core. * * @author Kohsuke Kawaguchi + * @deprecated use {@link Launcher} */ -public class Main { - - @Option(name="-tunnel",metaVar="HOST:PORT", - usage="Connect to the specified host and port, instead of connecting directly to Jenkins. " + - "Useful when connection to Jenkins needs to be tunneled. Can be also HOST: or :PORT, " + - "in which case the missing portion will be auto-configured like the default behavior") - public String tunnel; - - @Deprecated - @Option(name="-headless", - usage="(deprecated; now always headless)") - public boolean headlessMode; - - @Option(name="-url", - usage="Specify the Jenkins root URLs to connect to.") - public List urls = new ArrayList<>(); - - @Option(name="-webSocket", - usage="Make a WebSocket connection to Jenkins rather than using the TCP port.", - depends="-url", - forbids={"-direct", "-tunnel", "-credentials", "-proxyCredentials", "-cert", "-disableHttpsCertValidation", "-noKeepAlive"}) - public boolean webSocket; - - @Option(name="-webSocketHeader", - usage="Additional WebSocket header to set, eg for authenticating with reverse proxies. To specify multiple headers, call this flag multiple times, one with each header", - metaVar = "NAME=VALUE", - depends="-webSocket") - public Map webSocketHeaders; - - @Option(name="-credentials",metaVar="USER:PASSWORD", - usage="HTTP BASIC AUTH header to pass in for making HTTP requests.") - public String credentials; - - @Option(name="-proxyCredentials",metaVar="USER:PASSWORD",usage="HTTP BASIC AUTH header to pass in for making HTTP authenticated proxy requests.") - public String proxyCredentials = null; - - @Option(name="-noreconnect", - usage="If the connection ends, don't retry and just exit.") - public boolean noReconnect = false; - - @Option(name="-noKeepAlive", - usage="Disable TCP socket keep alive on connection to the controller.") - public boolean noKeepAlive = false; - - @Option(name = "-cert", - usage = "Specify additional X.509 encoded PEM certificates to trust when connecting to Jenkins " + - "root URLs. If starting with @ then the remainder is assumed to be the name of the " + - "certificate file to read.") - public List candidateCertificates; - - /** - * Disables HTTPs Certificate validation of the server when using {@link org.jenkinsci.remoting.engine.JnlpAgentEndpointResolver}. - * - * This option is not recommended for production use. - */ - @Option(name="-disableHttpsCertValidation", - usage="Ignore SSL validation errors - use as a last resort only.") - public boolean disableHttpsCertValidation = false; - - /** - * Specifies a destination for error logs. - * If specified, this option overrides the default destination within {@link #workDir}. - * If both this options and {@link #workDir} is not set, the log will not be generated. - * @since 3.8 - */ - @Option(name="-agentLog", usage="Local agent error log destination (overrides workDir)") - @CheckForNull - public File agentLog = null; - - /** - * Specified location of the property file with JUL settings. - * @since 3.8 - */ - @CheckForNull - @Option(name="-loggingConfig",usage="Path to the property file with java.util.logging settings") - public File loggingConfigFile = null; - - /** - * Specifies a default working directory of the remoting instance. - * If specified, this directory will be used to store logs, JAR cache, etc. - *

- * In order to retain compatibility, the option is disabled by default. - *

- * Jenkins specifics: This working directory is expected to be equal to the agent root specified in Jenkins configuration. - * @since 3.8 - */ - @Option(name = "-workDir", - usage = "Declares the working directory of the remoting instance (stores cache and logs by default)") - @CheckForNull - public File workDir = null; - - /** - * Specifies a directory within {@link #workDir}, which stores all the remoting-internal files. - *

- * This option is not expected to be used frequently, but it allows remoting users to specify a custom - * storage directory if the default {@code remoting} directory is consumed by other stuff. - * @since 3.8 - */ - @Option(name = "-internalDir", - usage = "Specifies a name of the internal files within a working directory ('remoting' by default)", - depends = "-workDir") - @NonNull - public String internalDir = WorkDirManager.DirType.INTERNAL_DIR.getDefaultLocation(); - - /** - * Fail the initialization if the workDir or internalDir are missing. - * This option presumes that the workspace structure gets initialized previously in order to ensure that we do not start up with a borked instance - * (e.g. if a filesystem mount gets disconnected). - * @since 3.8 - */ - @Option(name = "-failIfWorkDirIsMissing", - usage = "Fails the initialization if the requested workDir or internalDir are missing ('false' by default)", - depends = "-workDir") - public boolean failIfWorkDirIsMissing = WorkDirManager.DEFAULT_FAIL_IF_WORKDIR_IS_MISSING; - - /** - * @since 2.24 - */ - @Option(name="-jar-cache",metaVar="DIR",usage="Cache directory that stores jar files sent from the controller") - public File jarCache = null; - - /** - * Connect directly to the TCP port specified, skipping the HTTP(S) connection parameter download. - * @since 3.34 - */ - @Option(name="-direct", metaVar="HOST:PORT", aliases = "-directConnection", depends = {"-instanceIdentity"}, forbids = {"-url", "-tunnel"}, - usage="Connect directly to this TCP agent port, skipping the HTTP(S) connection parameter download. For example, \"myjenkins:50000\".") - public String directConnection; - - /** - * The controller's instance identity. - * @see Instance Identity - * @since 3.34 - */ - @Option(name="-instanceIdentity", depends = {"-direct"}, - usage="The base64 encoded InstanceIdentity byte array of the Jenkins controller. When this is set, the agent skips connecting to an HTTP(S) port for connection info.") - public String instanceIdentity; - - /** - * When instanceIdentity is set, the agent skips connecting via http(s) where it normally - * obtains the configured protocols. When no protocols are given the agent tries all protocols - * it knows. Use this to limit the protocol list. - * @since 3.34 - */ - @Option(name="-protocols", depends = {"-direct"}, - usage="Specify the remoting protocols to attempt when instanceIdentity is provided.") - public List protocols = new ArrayList<>(); - - /** - * Shows help message and then exits - * @since 3.36 - */ - @Option(name="-help",usage="Show this help message") - public boolean showHelp = false; - - /** - * Shows version information and then exits - * @since 3.36 - */ - @Option(name="-version",usage="Shows the version of the remoting jar and then exits") - public boolean showVersion = false; - - /** - * Two mandatory parameters: secret key, and agent name. - */ - @Argument - public List args = new ArrayList<>(); - - public static void main(String[] args) throws IOException, InterruptedException { - try { - _main(args); - } catch (CmdLineException e) { - System.err.println(e.getMessage()); - System.err.println("java -jar agent.jar [options...] "); - new CmdLineParser(new Main()).printUsage(System.err); - } - } - - /** - * Main without the argument handling. - */ - public static void _main(String[] args) throws IOException, InterruptedException, CmdLineException { - Main m = new Main(); - CmdLineParser p = new CmdLineParser(m); - p.parseArgument(args); - if (m.showHelp && !m.showVersion) { - p.printUsage(System.out); - return; - } else if(m.showVersion) { - System.out.println(Util.getVersion()); - return; - } - if(m.args.size()!=2) { - throw new CmdLineException(p, "two arguments required, but got " + m.args, null); - } - if(m.urls.isEmpty() && m.directConnection == null) { - throw new CmdLineException(p, "At least one -url option is required.", null); - } - if (m.webSocket) { - assert !m.urls.isEmpty(); // depends="-url" - if (m.urls.size() > 1) { - throw new CmdLineException(p, "-webSocket supports only a single -url", null); - } - } - m.main(); - } - - public void main() throws IOException, InterruptedException { - Engine engine = createEngine(); - engine.startEngine(); - try { - engine.join(); - LOGGER.fine("Engine has died"); - } finally { - // if we are programmatically driven by other code, - // allow them to interrupt our blocking main thread - // to kill the on-going connection to Jenkins - engine.interrupt(); - } - } - - @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "Parameter supplied by user / administrator.") - public Engine createEngine() { - String agentName = args.get(1); - LOGGER.log(INFO, "Setting up agent: {0}", agentName); - Engine engine = new Engine( - new CuiListener(), - urls, args.get(0), agentName, directConnection, instanceIdentity, new HashSet<>(protocols)); - engine.setWebSocket(webSocket); - if(webSocketHeaders!=null) - engine.setWebSocketHeaders(webSocketHeaders); - if(tunnel!=null) - engine.setTunnel(tunnel); - if(credentials!=null) - engine.setCredentials(credentials); - if(proxyCredentials!=null) - engine.setProxyCredentials(proxyCredentials); - if(jarCache!=null) - engine.setJarCache(new FileSystemJarCache(jarCache,true)); - engine.setNoReconnect(noReconnect); - engine.setKeepAlive(!noKeepAlive); - - if (disableHttpsCertValidation) { - LOGGER.log(WARNING, "Certificate validation for HTTPs endpoints is disabled"); - } - engine.setDisableHttpsCertValidation(disableHttpsCertValidation); - - - // TODO: ideally logging should be initialized before the "Setting up agent" entry - if (agentLog != null) { - try { - engine.setAgentLog(PathUtils.fileToPath(agentLog)); - } catch (IOException ex) { - throw new IllegalStateException("Cannot retrieve custom log destination", ex); - } - } - if (loggingConfigFile != null) { - try { - engine.setLoggingConfigFile(PathUtils.fileToPath(loggingConfigFile)); - } catch (IOException ex) { - throw new IllegalStateException("Logging config file is invalid", ex); - } - } - - if (candidateCertificates != null && !candidateCertificates.isEmpty()) { - CertificateFactory factory; - try { - factory = CertificateFactory.getInstance("X.509"); - } catch (CertificateException e) { - throw new IllegalStateException("Java platform specification mandates support for X.509", e); - } - List certificates = new ArrayList<>(candidateCertificates.size()); - for (String certOrAtFilename : candidateCertificates) { - certOrAtFilename = certOrAtFilename.trim(); - byte[] cert; - if (certOrAtFilename.startsWith("@")) { - File file = new File(certOrAtFilename.substring(1)); - long length; - if (file.isFile() - && (length = file.length()) < 65536 - && length > "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----".length()) { - try { - // we do basic size validation, if there are x509 certificates that have a PEM encoding - // larger - // than 64kb we can revisit the upper bound. - cert = new byte[(int) length]; - final int read; - try (FileInputStream fis = new FileInputStream(file)) { - read = fis.read(cert); - } - if (cert.length != read) { - LOGGER.log(Level.WARNING, "Only read {0} bytes from {1}, expected to read {2}", - new Object[]{read, file, cert.length}); - // skip it - continue; - } - } catch (IOException e) { - LOGGER.log(Level.WARNING, e, () -> "Could not read certificate from " + file); - continue; - } - } else { - if (file.isFile()) { - LOGGER.log(Level.WARNING, "Could not read certificate from {0}. File size is not within " + - "the expected range for a PEM encoded X.509 certificate", file.getAbsolutePath()); - } else { - LOGGER.log(Level.WARNING, "Could not read certificate from {0}. File not found", - file.getAbsolutePath()); - } - continue; - } - } else { - cert = certOrAtFilename.getBytes(StandardCharsets.US_ASCII); - } - try { - certificates.add((X509Certificate) factory.generateCertificate(new ByteArrayInputStream(cert))); - } catch (ClassCastException e) { - LOGGER.log(Level.WARNING, "Expected X.509 certificate from " + certOrAtFilename, e); - } catch (CertificateException e) { - LOGGER.log(Level.WARNING, "Could not parse X.509 certificate from " + certOrAtFilename, e); - } - } - engine.setCandidateCertificates(certificates); - } - - // Working directory settings - if (workDir != null) { - try { - engine.setWorkDir(PathUtils.fileToPath(workDir)); - } catch (IOException ex) { - throw new IllegalStateException("Work directory path is invalid", ex); - } - } - engine.setInternalDir(internalDir); - engine.setFailIfWorkDirIsMissing(failIfWorkDirIsMissing); - - return engine; - } - - /** - * {@link EngineListener} implementation that sends output to {@link Logger}. - */ - private static final class CuiListener implements EngineListener { - @Override - public void status(String msg, Throwable t) { - LOGGER.log(INFO,msg,t); - } - - @Override - public void status(String msg) { - status(msg,null); - } - - @Override - @SuppressFBWarnings(value = "DM_EXIT", - justification = "Yes, we really want to exit in the case of severe error") - public void error(Throwable t) { - LOGGER.log(Level.SEVERE, t.getMessage(), t); - System.exit(-1); - } - - @Override - public void onDisconnect() { - } - - @Override - public void onReconnect() { - } - } - - private static final Logger LOGGER = Logger.getLogger(Main.class.getName()); -} +@Deprecated +public class Main extends Launcher {} diff --git a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java index 927a840d4..beaf9feb9 100644 --- a/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java +++ b/src/main/java/org/jenkinsci/remoting/engine/JnlpAgentEndpointResolver.java @@ -32,6 +32,8 @@ import org.jenkinsci.remoting.util.VersionNumber; import org.jenkinsci.remoting.util.https.NoCheckHostnameVerifier; import org.jenkinsci.remoting.util.https.NoCheckTrustManager; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; @@ -499,8 +501,9 @@ else if(entry.endsWith("*")) * Credentials can be passed e.g. to support running Jenkins behind a (reverse) proxy requiring authorization * FIXME: similar to hudson.remoting.Util.openURLConnection which is still used in hudson.remoting.Launcher */ + @Restricted(NoExternalUse.class) @SuppressFBWarnings(value = "URLCONNECTION_SSRF_FD", justification = "Used by the agent for retrieving connection info from the server.") - static URLConnection openURLConnection(URL url, String credentials, String proxyCredentials, + public static URLConnection openURLConnection(URL url, String credentials, String proxyCredentials, SSLSocketFactory sslSocketFactory, boolean disableHttpsCertValidation) throws IOException { String httpProxy = null; // If http.proxyHost property exists, openConnection() uses it. @@ -550,8 +553,6 @@ static URLConnection openURLConnection(URL url, String credentials, String proxy } else if (sslSocketFactory != null) { httpsConnection.setSSLSocketFactory(sslSocketFactory); - //FIXME: Is it really required in this path? Seems like a bug - httpsConnection.setHostnameVerifier(new NoCheckHostnameVerifier()); } } return con; From 12eabf0b8deada4755ad2661fbd915d49b2080c0 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 19 Oct 2023 12:52:16 -0700 Subject: [PATCH 2/6] Update src/main/java/hudson/remoting/Launcher.java Co-authored-by: Jesse Glick --- src/main/java/hudson/remoting/Launcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index ba4cb26b9..ac0cc15aa 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -221,7 +221,7 @@ public void setConnectTo(String target) { * * @param ignored * This is ignored. - * @deprecated {use {@link #noCertificateCheck} + * @deprecated use {@link #noCertificateCheck} */ @Deprecated public void setNoCertificateCheck(boolean ignored) throws NoSuchAlgorithmException, KeyManagementException { From d4908951a3a4517d8096b316ad2d700ad966f012 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 19 Oct 2023 12:52:29 -0700 Subject: [PATCH 3/6] Update src/main/java/hudson/remoting/Launcher.java Co-authored-by: Jesse Glick --- src/main/java/hudson/remoting/Launcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index ac0cc15aa..2f1cea852 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -323,7 +323,7 @@ public void setNoCertificateCheck(boolean ignored) throws NoSuchAlgorithmExcepti /** * The controller's instance identity. - * @see Instance Identity + * @see Instance Identity * @since 3.34 */ @Option(name = "-instanceIdentity", From 7875c0d0e3feabcadfb47fc2fed678e3b4d4c9b3 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 19 Oct 2023 12:54:32 -0700 Subject: [PATCH 4/6] Stop using deprecated args --- docs/inbound-agent.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/inbound-agent.md b/docs/inbound-agent.md index f2f38dc66..6015abff8 100644 --- a/docs/inbound-agent.md +++ b/docs/inbound-agent.md @@ -83,7 +83,8 @@ java -jar agent.jar \ -direct \ -protocols JNLP4-connect \ -instanceIdentity \ - + -secret \ + -name ``` The "-protocols" parameter is optional, but is useful to limit the agent to protocols the server supports. The only currently supported and recommended protocol is "JNLP4-connect". From 01fee0fd0773ff00e410cac8fc9ab25418ec8f60 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 19 Oct 2023 12:55:44 -0700 Subject: [PATCH 5/6] Update src/main/java/hudson/remoting/Launcher.java Co-authored-by: Jesse Glick --- src/main/java/hudson/remoting/Launcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index 2f1cea852..ec4534021 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -134,7 +134,7 @@ public void setTextMode(boolean b) { @Option(name="-credentials",metaVar="USER:PASSWORD",aliases="-jnlpCredentials",usage="HTTP BASIC AUTH header to pass in for making HTTP requests.") public String agentJnlpCredentials = null; - @Option(name="-secret", metaVar="HEX_SECRET", usage="Agent connection secret to use instead of -jnlpCredentials.") + @Option(name="-secret", metaVar="HEX_SECRET", usage="Agent connection secret.") public String secret; @Option(name="-name", usage="Name of the agent.") From e4c97287dd55509a4f191945ed16d693dd643e45 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Thu, 19 Oct 2023 13:04:58 -0700 Subject: [PATCH 6/6] Continue to honor `proxyCredentials` Java system property --- src/main/java/hudson/remoting/Engine.java | 2 +- src/main/java/hudson/remoting/Launcher.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/hudson/remoting/Engine.java b/src/main/java/hudson/remoting/Engine.java index 0c095ae3d..c53e20cac 100644 --- a/src/main/java/hudson/remoting/Engine.java +++ b/src/main/java/hudson/remoting/Engine.java @@ -168,7 +168,7 @@ public Thread newThread(@NonNull final Runnable r) { private Map webSocketHeaders; private String credentials; private String protocolName; - private String proxyCredentials = System.getProperty("proxyCredentials"); + private String proxyCredentials; /** * See {@link Launcher#tunnel} for the documentation. diff --git a/src/main/java/hudson/remoting/Launcher.java b/src/main/java/hudson/remoting/Launcher.java index ec4534021..061e97cda 100644 --- a/src/main/java/hudson/remoting/Launcher.java +++ b/src/main/java/hudson/remoting/Launcher.java @@ -141,7 +141,7 @@ public void setTextMode(boolean b) { public String name; @Option(name="-proxyCredentials",metaVar="USER:PASSWORD",usage="HTTP BASIC AUTH header to pass in for making HTTP authenticated proxy requests.") - public String proxyCredentials = null; + public String proxyCredentials = System.getProperty("proxyCredentials"); @Option(name="-cp",aliases="-classpath",metaVar="PATH", usage="add the given classpath elements to the system classloader. (DEPRECATED)")