From 800fcae2f72b2e5d3dbe77458c038b9b4884e36d Mon Sep 17 00:00:00 2001 From: mhoennig Date: Mon, 10 Aug 2026 15:34:20 +0200 Subject: [PATCH] Bundle certbot's DH parameters as a resource instead of downloading them certbot removed ssl-dhparams.pem from its repository, so the managed-nginx startup failed with HTTP 404 on fresh installations (the legacy script only kept working through its year-old state-dir cache). The RFC 7919 ffdhe2048 parameters are now a classpath resource; the download seam and NginxConfigFiles.DH_PARAMS_URL are gone. Co-Authored-By: Claude Fable 5 --- .../gittally/server/NginxConfigFiles.kt | 3 -- .../gittally/server/NginxProxyManager.kt | 33 +++++++------------ src/main/resources/nginx/ssl-dhparams.pem | 8 +++++ .../gittally/server/NginxProxyManagerTest.kt | 4 +-- 4 files changed, 21 insertions(+), 27 deletions(-) create mode 100644 src/main/resources/nginx/ssl-dhparams.pem diff --git a/src/main/kotlin/de/hoennig/gittally/server/NginxConfigFiles.kt b/src/main/kotlin/de/hoennig/gittally/server/NginxConfigFiles.kt index 88ca544..dc63a19 100644 --- a/src/main/kotlin/de/hoennig/gittally/server/NginxConfigFiles.kt +++ b/src/main/kotlin/de/hoennig/gittally/server/NginxConfigFiles.kt @@ -77,7 +77,4 @@ object NginxConfigFiles { "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:" + "DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384\";\n" + "ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;\n" - - /** Certbot's pinned DH parameters, downloaded once into the state dir (like legacy). */ - const val DH_PARAMS_URL: String = "https://raw.githubusercontent.com/certbot/certbot/master/certbot/certbot/ssl-dhparams.pem" } diff --git a/src/main/kotlin/de/hoennig/gittally/server/NginxProxyManager.kt b/src/main/kotlin/de/hoennig/gittally/server/NginxProxyManager.kt index f106b95..585cbae 100644 --- a/src/main/kotlin/de/hoennig/gittally/server/NginxProxyManager.kt +++ b/src/main/kotlin/de/hoennig/gittally/server/NginxProxyManager.kt @@ -6,14 +6,9 @@ import de.hoennig.gittally.config.ConfigLoader import de.hoennig.gittally.git.GitCommandRunner import org.slf4j.LoggerFactory import org.springframework.stereotype.Component -import java.net.URI -import java.net.http.HttpClient -import java.net.http.HttpRequest -import java.net.http.HttpResponse import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths -import java.time.Duration /** * Manages the opt-in nginx+certbot Docker container that serves GitTally over @@ -36,9 +31,6 @@ class NginxProxyManager( var workingDir: Path = Paths.get(".") - /** Replaceable for tests: fetches certbot's pinned DH parameters into [target]; throws on failure. */ - internal var dhParamsDownloader: (target: Path) -> Unit = ::downloadDhParams - /** Replaceable for tests: the wait between port-conflict re-checks. */ internal var sleeper: (millis: Long) -> Unit = Thread::sleep @@ -188,7 +180,7 @@ class NginxProxyManager( Files.writeString(settings.certbotConf.resolve("options-ssl-nginx.conf"), NginxConfigFiles.SSL_OPTIONS) val dhParams = settings.certbotConf.resolve("ssl-dhparams.pem") if (!Files.exists(dhParams)) { - dhParamsDownloader(dhParams) + writeBundledDhParams(dhParams) } } @@ -424,19 +416,16 @@ class NginxProxyManager( fun defaultContainerName(repoDir: Path): String = "gittally-nginx-" + repoDir.fileName.toString().replace(Regex("[^A-Za-z0-9_.-]"), "-") - private fun downloadDhParams(target: Path) { - val response = - HttpClient - .newBuilder() - .connectTimeout(Duration.ofSeconds(10)) - .followRedirects(HttpClient.Redirect.NORMAL) - .build() - .send( - HttpRequest.newBuilder(URI.create(NginxConfigFiles.DH_PARAMS_URL)).GET().build(), - HttpResponse.BodyHandlers.ofString(), - ) - check(response.statusCode() == 200) { "downloading ssl-dhparams.pem failed with HTTP ${response.statusCode()}" } - Files.writeString(target, response.body()) + /** + * Certbot's pinned DH parameters (RFC 7919 ffdhe2048), bundled as a resource: + * certbot removed the file from its repository, so it can no longer be downloaded. + */ + private fun writeBundledDhParams(target: Path) { + val resource = + checkNotNull(NginxProxyManager::class.java.getResourceAsStream("/nginx/ssl-dhparams.pem")) { + "bundled nginx/ssl-dhparams.pem resource is missing" + } + resource.use { Files.copy(it, target) } } } } diff --git a/src/main/resources/nginx/ssl-dhparams.pem b/src/main/resources/nginx/ssl-dhparams.pem new file mode 100644 index 0000000..9b182b7 --- /dev/null +++ b/src/main/resources/nginx/ssl-dhparams.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- diff --git a/src/test/kotlin/de/hoennig/gittally/server/NginxProxyManagerTest.kt b/src/test/kotlin/de/hoennig/gittally/server/NginxProxyManagerTest.kt index 256df7d..5820325 100644 --- a/src/test/kotlin/de/hoennig/gittally/server/NginxProxyManagerTest.kt +++ b/src/test/kotlin/de/hoennig/gittally/server/NginxProxyManagerTest.kt @@ -126,7 +126,6 @@ class NginxProxyManagerTest : FunSpec() { } manager = NginxProxyManager(commandRunner, configLoader) manager.workingDir = repoDir - manager.dhParamsDownloader = { Files.writeString(it, "dh-params") } manager.sleeper = { sleepCount++ } } @@ -198,7 +197,8 @@ class NginxProxyManagerTest : FunSpec() { "--register-unsafely-without-email", ) Files.readString(stateDir.resolve("certbot/conf/options-ssl-nginx.conf")) shouldBe NginxConfigFiles.SSL_OPTIONS - Files.readString(stateDir.resolve("certbot/conf/ssl-dhparams.pem")) shouldBe "dh-params" + // the bundled RFC 7919 ffdhe2048 parameters, vendored because certbot removed the download + Files.readString(stateDir.resolve("certbot/conf/ssl-dhparams.pem")) shouldContain "BEGIN DH PARAMETERS" } test("start with an existing certificate uses the full config immediately and renews") {