Potential cipher mismatch when using reverse_proxy directive with old internal server

1. The problem I’m having:

Hello,

I’m trying to reverse proxy to an internal application that we can’t adjust because we no longer have the source code but I’d like to make it “more secure”.

I ran an sslscan against this internal admin application and got a list of supported ciphers:

PORT    STATE SERVICE
443/tcp open  https
| ssl-enum-ciphers:
|   TLSv1.2:
|     ciphers:
|       TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 2048) - A
|       TLS_RSA_WITH_AES_256_CBC_SHA256 (rsa 2048) - A
|     compressors:
|       NULL
|     cipher preference: server
|     warnings:
|       Forward Secrecy not supported by any cipher
|_  least strength: A

In addition this service is configured with a certificate that has a fair few years left on it, but no longer accurate for the DNS record routing to the service, so using tls_insecure_skip_verify. I have no capabilities to change this TLS configuration, ciphers nor common name so trying to use caddy to front it.

When I configure this upstream as a reverse_proxy target in caddy I get a handshake failure as seen in the log below:

2. Error messages and/or full log output:

2024/12/08 19:43:01.012 DEBUG   tls.handshake   default certificate selection results   {"identifier": "localhost", "subjects": ["localhost"], "managed": true, "issuer_key": "local", "hash": "c1d216bb54aa7e632da630b0921dc5ebc7f3a0c577da261791e5901c32b9442b"}
2024/12/08 19:43:01.012 DEBUG   tls.handshake   matched certificate in cache    {"remote_ip": "::1", "remote_port": "33260", "subjects": ["localhost"], "managed": true, "expiration": "2024/12/09 07:42:56.000", "hash": "c1d216bb54aa7e632da630b0921dc5ebc7f3a0c577da261791e5901c32b9442b"}
2024/12/08 19:43:01.017 DEBUG   http.handlers.reverse_proxy     selected upstream       {"dial": "admin.localdomain:443", "total_upstreams": 1}
2024/12/08 19:43:01.022 DEBUG   http.handlers.reverse_proxy     upstream roundtrip      {"upstream": "admin.localdomain:443", "duration": 0.005285108, "request": {"remote_ip": "::1", "remote_port": "33260", "client_ip": "::1", "proto": "HTTP/2.0", "method": "GET", "host": "localhost", "uri": "/", "headers": {"Accept": ["*/*"], "X-Forwarded-For": ["::1"], "X-Forwarded-Proto": ["https"], "X-Forwarded-Host": ["localhost"], "User-Agent": ["curl/8.5.0"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "localhost"}}, "error": "remote error: tls: handshake failure"}
2024/12/08 19:43:01.022 ERROR   http.log.error  remote error: tls: handshake failure    {"request": {"remote_ip": "::1", "remote_port": "33260", "client_ip": "::1", "proto": "HTTP/2.0", "method": "GET", "host": "localhost", "uri": "/", "headers": {"User-Agent": ["curl/8.5.0"], "Accept": ["*/*"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "localhost"}}, "duration": 0.005529867, "status": 502, "err_id": "kcjc1kqit", "err_trace": "reverseproxy.statusError (reverseproxy.go:1269)"}

I am guessing it’s a cipher mismatch but I can’t be sure!

3. Caddy version:

Caddy 2.8.4

4. How I installed and ran Caddy:

Official Docker image (caddy:2.8.4)

a. System environment:

Docker

b. Command:

docker run --rm --network=host docker:2.8.4

c. Service/unit/compose file:

n/a

d. My complete Caddy config:

{
	log {
		output stderr
		level DEBUG
		format console
	}
}

localhost:443 {
	tls internal
	reverse_proxy https://admin.localdomain {
		transport http {
			tls_insecure_skip_verify
		}
		header_up Host admin.localdomain
	}
}

5. Links to relevant resources:

tcpdump/wireshark view of the TLS ClientHello initiated by Caddy when a request lands on the frontend via curl.

Checked connecting to the upstream directly with curl and it works albeit needing the -k flag as you’d expect with the SNI mismatch.

Figured I’d check which client ciphers my curl client supports and got the following:

curl -s https://www.howsmyssl.com/a/check | jq .
{
  "given_cipher_suites": [
    "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_AES_256_GCM_SHA384",
    "TLS_AES_128_GCM_SHA256",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
    "TLS_GOST2012256-GOST89-GOST89",
    "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256",
    "TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA",
    "TLS_GOST2001-GOST89-GOST89",
    "TLS_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_RSA_WITH_AES_256_CBC_SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA",
    "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256",
    "TLS_RSA_WITH_CAMELLIA_256_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
    "TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA",
    "TLS_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA",
    "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256",
    "TLS_RSA_WITH_CAMELLIA_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
    "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
    "TLS_RSA_WITH_RC4_128_SHA",
    "TLS_RSA_WITH_RC4_128_MD5",
    "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
    "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
    "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
    "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
    "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"
  ],
  "ephemeral_keys_supported": true,
  "session_ticket_supported": false,
  "tls_compression_supported": false,
  "unknown_cipher_suite_supported": false,
  "beast_vuln": false,
  "able_to_detect_n_minus_one_splitting": false,
  "insecure_cipher_suites": {
    "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": [
      "uses RC4 which has insecure biases in its output"
    ],
    "TLS_ECDHE_RSA_WITH_RC4_128_SHA": [
      "uses RC4 which has insecure biases in its output"
    ],
    "TLS_RSA_WITH_RC4_128_MD5": [
      "uses RC4 which has insecure biases in its output"
    ],
    "TLS_RSA_WITH_RC4_128_SHA": [
      "uses RC4 which has insecure biases in its output"
    ]
  },
  "tls_version": "TLS 1.3",
  "rating": "Bad"
}

Notably, all 3 reported in the sslscan result are present in the curl request.

Setting the same cipher check as the upstream and checking what caddy offers up as ciphers:

curl https://localhost/a/check -s -k | jq .
{
  "given_cipher_suites": [
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
    "TLS_AES_128_GCM_SHA256",
    "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256"
  ],
  "ephemeral_keys_supported": true,
  "session_ticket_supported": false,
  "tls_compression_supported": false,
  "unknown_cipher_suite_supported": false,
  "beast_vuln": false,
  "able_to_detect_n_minus_one_splitting": false,
  "insecure_cipher_suites": {},
  "tls_version": "TLS 1.3",
  "rating": "Probably Okay"
}

So I guess my question is, can I configure Caddy to use insecure older ciphers or am I out of luck and need to use nginx?

Edit:
Finally trying nginx:1.27.3 via docker.

curl http://localhost/a/check -s | jq .
{
  "given_cipher_suites": [
    "TLS_AES_256_GCM_SHA384",
    "TLS_CHACHA20_POLY1305_SHA256",
    "TLS_AES_128_GCM_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
    "TLS_RSA_WITH_AES_256_GCM_SHA384",
    "TLS_RSA_WITH_AES_128_GCM_SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA256",
    "TLS_RSA_WITH_AES_128_CBC_SHA256",
    "TLS_RSA_WITH_AES_256_CBC_SHA",
    "TLS_RSA_WITH_AES_128_CBC_SHA",
    "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"
  ],
  "ephemeral_keys_supported": true,
  "session_ticket_supported": true,
  "tls_compression_supported": false,
  "unknown_cipher_suite_supported": false,
  "beast_vuln": false,
  "able_to_detect_n_minus_one_splitting": false,
  "insecure_cipher_suites": {},
  "tls_version": "TLS 1.3",
  "rating": "Probably Okay"
}

Sorry for the delay, was taking some time off from the forums.

No, Caddy does not allow you to use insecure ciphers. Only secure ciphers are supported.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.