20

I am trying to set up a Jenkins master and a Jenkins slave node where the Jenkins Master is behind Nginx reverse proxy on a different server with SSL termination. The nginx configuration is as following:

upstream jenkins {
  server <server ip>:8080 fail_timeout=0;
}

server {
  listen 443 ssl;
  server_name jenkins.mydomain.com;
  ssl_certificate /etc/nginx/certs/mydomain.crt;
  ssl_certificate_key /etc/nginx/certs/mydomain.key;

  location / {
    proxy_set_header        Host $host:$server_port;
    proxy_set_header        X-Real-IP $remote_addr;
    proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header        X-Forwarded-Proto $scheme;
    proxy_redirect          http:// https://;
    proxy_pass              http://jenkins;
  }
}

server {
  listen 80;
  server_name jenkins.mydomain.com;
  return 301 https://$server_name$request_uri;
}

The TCP port for JNLP agents is set as 50000 in Jenkins master Global Security configuration. Port 50000 is set to be accessible from anywhere on the host machine.

The JNLP slave is launched with the following command:

java -jar slave.jar -jnlpUrl https://jenkins.mydomain.com/computer/slave-1/slave-agent.jnlp -secret <secret>

The JNLP slave fails to connect to the configured JNLP port on the master:

INFO: Connecting to jenkins.mydomain.com:50000 (retrying:4)
java.net.ConnectException: Connection timed out
        at java.net.PlainSocketImpl.socketConnect(Native Method)
        at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
        at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
        at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
        at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
        at java.net.Socket.connect(Socket.java:589)
        at java.net.Socket.connect(Socket.java:538)
        at hudson.remoting.Engine.connect(Engine.java:400)
        at hudson.remoting.Engine.run(Engine.java:298)

What is the configuration required for the JNLP slave to connect to the Jenkins master?

sajus
  • 357
  • 1
  • 2
  • 8

2 Answers2

30

The JNLP port seems to use a binary protocol, not a text-based HTTP protocol, so unfortunately it can't be reverse-proxied through NGINX like the normal Jenkins pages can be.

Instead, you should:

  1. Configure Global Security > Check "Enable security" and set a Fixed "TCP port for JNLP slave agents". This will cause all Jenkins pages to emit extra HTTP headers specifying this port: X-Hudson-CLI-Port, X-Jenkins-CLI-Port, X-Jenkins-CLI2-Port.

  2. Allow your fixed TCP JNLP port through any firewall(s) so CLI clients and JNLP agents can directly reach the Jenkins server on the backend.

  3. Set the system property hudson.TcpSlaveAgentListener.hostName to the hostname or IP address of your Jenkins server on the backend. This will cause all pages to emit an extra HTTP header (X-Jenkins-CLI-Host) containing this specified hostname. This tells CLI clients where to connect, but supposedly not JNLP agents.

  4. For each of your build slave machines in the list of nodes at jenkins.mydomain.com/computer/ that uses the Launch method "Launch slave agents via Java Web Start", click the computer, click Configure, click the Advanced... button on the right side under Launch method, and set the "Tunnel connection through" field appropriately. Read the question mark help. You probably just need the "HOST:" syntax, where HOST is the hostname or IP address of your Jenkins server on the backend.

References:

PolyTekPatrick
  • 3,122
  • 1
  • 25
  • 19
  • I'm using dockerized Jenkins, running with dockerized slaves. Jenkins is behind a reverse proxy (using registrator & consul-template). Currently it works with hardcoding the slave port, but we would like to make it all dynamic. Do you have any advice for configuring Jenkins when we don't know the slave port before Jenkins is running? – Brandon Jan 31 '17 at 00:22
  • @Brandon I'm not familiar with registrator & consul-template. I would think you would need to decide a fixed slave port first though so you can open the pinhole in your firewall. If you have to do it dynamically, probably locate the jenkins settings xml files that store it (both the global setting and the per-slave setting) and come up with hacks to edit those files, then reload the jenkins config (or just restart jenkins) so it takes effect. – PolyTekPatrick Jan 31 '17 at 05:05
  • 9
    If like me you have no idea where to set step 3 "Set the system property hudson.TcpSlaveAgentListener.hostName", this is where and how it's done: https://wiki.jenkins.io/display/JENKINS/Features+controlled+by+system+properties And when you need to do that with Jenkins master running in Docker https://github.com/jenkinsci/docker/blob/master/README.md#passing-jvm-parameters An additional titbit is _not_ to include a port on this server value (just the server lan ip or resolvable hostname). – Eoan Nov 15 '17 at 04:35
  • 1
    This is the precise answer! I'm running jenkins-master at AWS behind ALB, so 4th step is the only way how you can bypass traffic through the binary JNLP protocol (ALB & NLB support only TCP/TLS protocols). – Sasha Miroshnychenko Jan 29 '19 at 10:51
  • Worth mentioning, that I additionally use nGinx proxy. It forbids some of the dangerous routes. It also passes traffic to the jenkins process which is imposed within the internal (127.0.0.1) interface and unavailable from the internet. So, in this case, nGinx config should also have an appropriate _server_ to route traffic to jenkins-master on your configured JNLP port which should be _fixed_ at the /configureSecurity/ page – Sasha Miroshnychenko Jan 29 '19 at 11:12
  • And yet another _fun fact_ :) I've noticed that _java -jar agent.jar -help_ has `-connectTo HOST:PORT` – _make a TCP connection to the given host and port, then start communication_ – which is pretty similar to "Tunnel connection through" directive. But I wasn't able to make it work in any way... – Sasha Miroshnychenko Jan 29 '19 at 12:22
  • The step 4 worked for us. In our case the jenkins was running behind a Load Balancer and the connection was getting refused on the TCP port. The option to enable tunneling is on the agent config page (by expanding the advanced properties). Thanks a ton @PolyTekPatrick – durgasunil Apr 23 '19 at 15:21
  • I've not managed to get this working with an ALB target grouped to ECS master container's 50000 port with ECS slaves configured via the EC2 ECS slave plugin with tunnel connection through nor with specifying the hostname via JAVA_OPTS; logs from the jnlp slave containers indicate `WARNING: [JNLP4-connect connection to /:50000] Incorrect acknowledgement sequence` - presumably due to ALB HTTP/HTTPS only, but from what @SashaMiroshnychenko indicates maybe this isn't a blocker? Any ideas? Is Classic ELB > ALB in this case? – kian Dec 29 '19 at 14:30
9

It's been almost 4 years since OP has asked this question, nevertheless, if you reached this page and looking for a proper solution, well, it's now possible.

I use Traefik as reverse proxy to Jenkins. TCP port inbound completely disabled now. enter image description here

The only thing you need to make sure is your agent/slave is trusting Jenkins server certificate (as webSocket cannot be used with -disableHttpsCertValidation or -noCertificateCheck

If this is a Windows agent, use:

C:\Program Files (x86)\Java\jre1.8.0_251\bin\keytool.exe -import -storepass "changeit" -keystore "C:\Program Files (x86)\Java\jre1.8.0_251\lib\security\cacerts" -alias <cert_alias> -file "<path_to_cert>"

(Change path accordingly to your java version)

Danny Rehelis
  • 310
  • 4
  • 8
  • This same option works even when you using the Kubernetes plugin – buzypi Feb 02 '21 at 10:20
  • 1
    Does reverse proxy itself need any additional configurations to allow websocket connections? I'm using Jenkins behind Nginx and I receive "HandshakeException: Response code was not 101: 400" on all attempts to establish a websocket connection with it. – MaroonedMind May 04 '22 at 16:40
  • Update: You do need to change your reverse proxy configs for this to work, at least with Nginx. I used the configs described in the following doc to fix my error: https://www.jenkins.io/doc/book/system-administration/reverse-proxy-configuration-nginx/ – MaroonedMind May 04 '22 at 19:58