From fee080dbf4def2d5a32c23fcaeb3d832361109d3 Mon Sep 17 00:00:00 2001 From: Michael Hoennig Date: Tue, 1 Jul 2025 11:58:20 +0200 Subject: [PATCH] feature/jenkins-proxy (#182) Co-authored-by: Michael Hoennig Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/182 Reviewed-by: Marc Sandlus --- Jenkins/.gitignore | 1 + Jenkins/Jenkinsfile | 2 +- Jenkins/Makefile | 157 +++++++++++++++--- Jenkins/README.md | 16 ++ Jenkins/{agent => jenkins-agent}/Dockerfile | 0 .../nginx-letsencrypt-renew.service | 8 + .../nginx-proxy/nginx-letsencrypt-renew.timer | 11 ++ Jenkins/nginx-proxy/nginx.conf | 40 +++++ Jenkins/nginx-proxy/options-ssl-nginx.conf | 7 + 9 files changed, 220 insertions(+), 22 deletions(-) create mode 100644 Jenkins/.gitignore create mode 100644 Jenkins/README.md rename Jenkins/{agent => jenkins-agent}/Dockerfile (100%) create mode 100644 Jenkins/nginx-proxy/nginx-letsencrypt-renew.service create mode 100644 Jenkins/nginx-proxy/nginx-letsencrypt-renew.timer create mode 100644 Jenkins/nginx-proxy/nginx.conf create mode 100644 Jenkins/nginx-proxy/options-ssl-nginx.conf diff --git a/Jenkins/.gitignore b/Jenkins/.gitignore new file mode 100644 index 00000000..5f1e4d07 --- /dev/null +++ b/Jenkins/.gitignore @@ -0,0 +1 @@ +.generated diff --git a/Jenkins/Jenkinsfile b/Jenkins/Jenkinsfile index 34332d46..167a6ba9 100644 --- a/Jenkins/Jenkinsfile +++ b/Jenkins/Jenkinsfile @@ -6,7 +6,7 @@ pipeline { } agent { dockerfile { - filename 'Jenkins/agent/Dockerfile' + filename 'Jenkins/jenkins-agent/Dockerfile' args """--user root --network ${params.AGENT_NETWORK} --volume /var/run/docker.sock:/var/run/docker.sock --memory=8g --cpus=${params.AGENT_CPUS}""" diff --git a/Jenkins/Makefile b/Jenkins/Makefile index 4beb5ebd..d891ac8a 100644 --- a/Jenkins/Makefile +++ b/Jenkins/Makefile @@ -1,54 +1,169 @@ -DOCKER := docker +include .env +export + SOCKET := /var/run/docker.sock VOLUME := jenkins_home -.PHONY: build run bash init-pw unprotected protected start stop rm purge +CERTBOT_CONF := $(PWD)/.generated/certbot/lib/conf +CERTBOT_WWW := $(PWD)/.generated/certbot/lib/www +CERTBOT_LOG := $(PWD)/.generated/certbot/log +NGINX_LOG := $(PWD)/.generated/certbot/nginx/log -# building the Jenkins image +.PHONY: provision \ + build run bash init-pw unprotected protected start stop rm purge \ + nginx-prepare nginx-proxy nginx-start nginx-letsencrypt-init nginx-letsencrypt-timer nginx-restart nginx-stop + +## lists all documented targets +help: + @awk '/^##/ {sub(/^## /, "", $$0); desc=$$0; next} /^[a-zA-Z0-9][^:]*:/ { \ + print "\033[1m" $$1 "\033[0m"; \ + print " " desc "\n" \ + }' $(MAKEFILE_LIST) + +## initially, run this once to provision te nginx +provision: nginx-prepare nginx-letsencrypt-init nginx-letsencrypt-timer nginx-start build start + +## removes all generated files +clean: nginx-stop stop + rm -rf .generated/ + +## builds the Jenkins image build: - $(DOCKER) build -t jenkins-docker . + docker build -t jenkins-docker . -# initially running the Jenkins container +## manually running the Jenkins container run: - $(DOCKER) run --detach \ + docker run --detach \ --dns 8.8.8.8 \ --network bridge \ - --publish 8080:8080 --publish 50000:50000 \ + --publish 8090:8080 --publish 50000:50000 \ --volume $(SOCKET):/var/run/docker.sock \ --volume $(VOLUME):/var/jenkins_home \ --restart unless-stopped \ --name jenkins jenkins-docker -# (re-) starts the Jenkins container +## manually starts the Jenkins container (again) start: - $(DOCKER) start jenkins + docker start jenkins -# opens a bash within the Jenkins container +## opens a bash within the Jenkins container bash: - $(DOCKER) exec -it jenkins bash + docker exec -it jenkins bash -# prints the inital password of a newly setup Jenkins +## prints the initial password of a newly setup Jenkins init-pw: - $(DOCKER) exec -it jenkins cat /var/jenkins_home/secrets/initialAdminPassword + docker exec -it jenkins sh -c '\ + while [ ! -f /var/jenkins_home/secrets/initialAdminPassword ]; do \ + sleep 1; \ + done; \ + cat /var/jenkins_home/secrets/initialAdminPassword \ + ' -# disables security for the Jenkins, allows login without credentials +## disables security for the Jenkins => allows login to Jenkins without credentials unprotected: docker exec -it jenkins sed -i 's|true|false|' /var/jenkins_home/config.xml docker exec -it jenkins grep useSecurity /var/jenkins_home/config.xml -# enables security for the Jenkins, requires login with credentials +## enables security for the Jenkins => Jenkins requires login with credentials protected: docker exec -it jenkins sed -i 's|true|true|' /var/jenkins_home/config.xml docker exec -it jenkins grep useSecurity /var/jenkins_home/config.xml -# stops the Jenkins container +## stops the Jenkins container stop: - $(DOCKER) stop jenkins + docker stop jenkins -# removes the Jenkins container +## removes the Jenkins container rm: stop - $(DOCKER) rm jenkins + docker rm jenkins -# purges the Jenkins volume (finally deletes the configuration) +## purges the Jenkins volume (finally deletes the configuration) purge: rm - $(DOCKER) volume rm $(VOLUME) + docker volume rm $(VOLUME) + +# (internal) generates the files for nginx-proxy and certbot +nginx-prepare: + mkdir -p $(CERTBOT_WWW) $(CERTBOT_LOG) $(CERTBOT_CONF)/live/$(SERVER_NAME) $(NGINX_LOG) + chmod 755 $(CERTBOT_WWW) $(CERTBOT_LOG) $(CERTBOT_CONF)/live/$(SERVER_NAME) $(NGINX_LOG) + sed -e 's/%SERVER_NAME/$(SERVER_NAME)/g' .generated/nginx.conf + cp nginx-proxy/options-ssl-nginx.conf $(CERTBOT_CONF)/options-ssl-nginx.conf + chmod 644 $(CERTBOT_CONF)/options-ssl-nginx.conf + test -f $(CERTBOT_CONF)/ssl-dhparams.pem || curl -o $(CERTBOT_CONF)/ssl-dhparams.pem \ + https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem + chmod 644 $(CERTBOT_CONF)/ssl-dhparams.pem + openssl req -x509 -nodes -newkey rsa:2048 \ + -keyout $(CERTBOT_CONF)/live/$(SERVER_NAME)/privkey.pem \ + -out /$(CERTBOT_CONF)/live/$(SERVER_NAME)/fullchain.pem \ + -subj "/CN=dummy" + +## opens a bash within the Nginx-proxy container +nginx-bash: + docker exec -it nginx bash + +# (internal) fetches an initial certificate from letsencrypt +nginx-letsencrypt-init: nginx-start + # wait for nginx actually running (could be improved) + @sleep 5 + # delete the previous (dummy) config to avoid file creation with suffix -0001 etc. + rm -rf $(CERTBOT_CONF)/etc/letsencrypt/live/$(SERVER_NAME) \ + $(CERTBOT_CONF)/etc/letsencrypt/archive/$(SERVER_NAME) \ + $(CERTBOT_CONF)/etc/letsencrypt/renewal/$(SERVER_NAME).conf + # request the certificate via letsencrypt + docker run --rm \ + -v $(CERTBOT_CONF):/etc/letsencrypt \ + -v $(CERTBOT_WWW):/var/www/certbot \ + -v $(CERTBOT_LOG):/var/log/letsencrypt \ + certbot/certbot \ + certonly --webroot --webroot-path /var/www/certbot \ + --email $(EMAIL) --cert-name $(SERVER_NAME) \ + -d $(SERVER_NAME) --rsa-key-size 4096 \ + --agree-tos --force-renewal + # restart nginx + docker stop nginx || true + docker start nginx + +## opens a shell in the letsencrypt certbot +nginx-letsencrypt-sh: + docker run -it --rm \ + -v $(CERTBOT_CONF):/etc/letsencrypt \ + -v $(CERTBOT_WWW):/var/www/certbot \ + -v $(CERTBOT_LOG):/var/log/letsencrypt \ + --entrypoint /bin/sh \ + certbot/certbot + +# (internal) installs the letsencrypt certbot timer for automatic renewal +nginx-letsencrypt-timer: + @mkdir -p $(HOME)/.config/systemd/user + @cp nginx-proxy/nginx-letsencrypt-renew.timer $(HOME)/.config/systemd/user/nginx-letsencrypt-renew.timer + @cp nginx-proxy/nginx-letsencrypt-renew.service $(HOME)/.config/systemd/user/nginx-letsencrypt-renew.service + systemctl --user daemon-reload + systemctl --user enable --now nginx-letsencrypt-renew.timer + +## renews the cert, if already renewable - this is also called from the timer +nginx-letsencrypt-renew: + docker run --rm \ + -v $(CERTBOT_CONF):/etc/letsencrypt \ + -v $(CERTBOT_WWW):/var/www/certbot \ + -v $(CERTBOT_LOG):/var/log/letsencrypt \ + certbot/certbot renew -q + +## starts the nginx proxy server +nginx-start: nginx-stop + docker run -d --name nginx \ + --publish 8080:80 \ + --publish 8443:443 \ + --network bridge \ + -v $(CERTBOT_CONF):/etc/letsencrypt \ + -v $(CERTBOT_WWW):/var/www/certbot \ + -v $(NGINX_LOG):/var/log/nginx \ + -v $(PWD)/.generated/nginx.conf:/etc/nginx/nginx.conf \ + nginx + +## restarts the nginx proxy server +nginx-restart: nginx-stop nginx-start + +## stops the nginx proxy server +nginx-stop: + docker stop nginx || true + docker rm nginx || true + diff --git a/Jenkins/README.md b/Jenkins/README.md new file mode 100644 index 00000000..9b5422c9 --- /dev/null +++ b/Jenkins/README.md @@ -0,0 +1,16 @@ +# Jenkins Build+Test-Pipeline with NGINX HTTPS-Proxy and Letsencrypt + +The scripts work in a Hostsharing Managed Docker environment. + +Requires a .env file like this in the current directory: + +``` +SERVER_NAME=jenkins.example.org +EMAIL=contact@example.org +``` + +Then run `make provision` to initialize everything. + +Run `make help` for more information. + +WARNING: Provisioning does not really work yet, needs some manual restarts. diff --git a/Jenkins/agent/Dockerfile b/Jenkins/jenkins-agent/Dockerfile similarity index 100% rename from Jenkins/agent/Dockerfile rename to Jenkins/jenkins-agent/Dockerfile diff --git a/Jenkins/nginx-proxy/nginx-letsencrypt-renew.service b/Jenkins/nginx-proxy/nginx-letsencrypt-renew.service new file mode 100644 index 00000000..a327a7c5 --- /dev/null +++ b/Jenkins/nginx-proxy/nginx-letsencrypt-renew.service @@ -0,0 +1,8 @@ +[Unit] +Description=Renew Let's Encrypt certs via Make + +[Service] +Type=oneshot +WorkingDirectory=%h/hs.hsadmin.ng/Jenkins +ExecStart=/usr/bin/make nginx-letsencrypt-renew + diff --git a/Jenkins/nginx-proxy/nginx-letsencrypt-renew.timer b/Jenkins/nginx-proxy/nginx-letsencrypt-renew.timer new file mode 100644 index 00000000..cc02c9bb --- /dev/null +++ b/Jenkins/nginx-proxy/nginx-letsencrypt-renew.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run cert renew Make target at ~5 AM daily + +[Timer] +OnCalendar=05:05 +RandomizedDelaySec=20m +Persistent=true + +[Install] +WantedBy=timers.target + diff --git a/Jenkins/nginx-proxy/nginx.conf b/Jenkins/nginx-proxy/nginx.conf new file mode 100644 index 00000000..bf0a6864 --- /dev/null +++ b/Jenkins/nginx-proxy/nginx.conf @@ -0,0 +1,40 @@ +events {} + +http { + server { + listen 80; + server_name %SERVER_NAME; + + # directly answer initial certbot request + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + # forward all other HTTP-requests to HTTPS + location / { + return 301 https://$host$request_uri; + } + } + + server { + listen 443 ssl; + server_name %SERVER_NAME; + + ssl_certificate /etc/letsencrypt/live/%SERVER_NAME/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/%SERVER_NAME/privkey.pem; + include /etc/letsencrypt/options-ssl-nginx.conf; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + proxy_pass http://%SERVER_NAME:8090; + proxy_set_header Host $host; + 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; + } + } +} + diff --git a/Jenkins/nginx-proxy/options-ssl-nginx.conf b/Jenkins/nginx-proxy/options-ssl-nginx.conf new file mode 100644 index 00000000..35296eea --- /dev/null +++ b/Jenkins/nginx-proxy/options-ssl-nginx.conf @@ -0,0 +1,7 @@ +ssl_session_cache shared:le_nginx_SSL:1m; +ssl_session_timeout 1440m; +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:..."; +ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;