Skip to content

Commit 66d32b3

Browse files
authored
Flag to disable TLS handling by proxy (#18)
1 parent 2d9cb2a commit 66d32b3

4 files changed

Lines changed: 159 additions & 19 deletions

File tree

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,14 @@ RUN apk del tar && \
8888
rm -f /var/cache/apk/*
8989

9090
ADD haproxy.cfg /etc/haproxy/haproxy.cfg
91+
ADD haproxy-edge-terminated-tls.cfg /etc/haproxy/haproxy-edge-terminated-tls.cfg
9192
ADD certs /etc/haproxy/certs
9293

9394
ADD cli.ini /root/.config/letsencrypt/
9495
ADD entrypoint.sh /
9596
RUN chmod +x /entrypoint.sh
9697

97-
HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=10 CMD curl --fail --silent http://127.0.0.1/docker-health || exit 1
98+
HEALTHCHECK --interval=5s --timeout=3s --start-period=5s --retries=10 CMD curl --fail --silent "http://127.0.0.1:${HTTP_PORT}/docker-health" || exit 1
9899

99100
RUN chown -R haproxy:haproxy /etc/haproxy
100101

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ requested (this is a multi-value alternative to DOMAINNAME)
2121
* `PROXY_LOGLEVEL` - Log level for HAProxy (default: `notice`)
2222
* `HTTP_PORT` - The container binds to this port for handling HTTP requests (default: `80`)
2323
* `HTTPS_PORT` - The container binds to this port for handling HTTPS requests (default: `443`)
24-
* `HTTPS_FORWARDED_PORT` - The port set in the `X-Forwarded-Port` header of requests send to the Manager/Keycloak (default: `%[dst_port]` this is the HAProxy port)
24+
* `HTTPS_FORWARDED_PORT` - The port set in the `X-Forwarded-Port` header of requests sent to the Manager/Keycloak (default: `%[dst_port]` this is the HAProxy port)
2525
* `NAMESERVER` - The nameserver hostname and port used for resolving the Manager/Keycloak hosts (default: `127.0.0.11:53`)
2626
* `MANAGER_HOST` - Hostname of OpenRemote Manager (default: `manager`)
2727
* `MANAGER_WEB_PORT` - Web server port of OpenRemote Manager (default `8080`)
@@ -33,6 +33,7 @@ requested (this is a multi-value alternative to DOMAINNAME)
3333
* `LOGFILE` - Location of log file for entrypoint script to write to in addition to stdout (default `none`)
3434
* `AWS_ROUTE53_ROLE` - AWS Route53 Role ARN to be assumed when trying to generate wildcard certificates using Route53 DNS zone, specifically for cross account updates (default `none`)
3535
* `LE_EXTRA_ARGS` - Can be used to add additional arguments to the certbot command (default `none`)
36+
* `DISABLE_ACME` - Disable certbot/ACME initialization and renewal logic in the entrypoint; useful when TLS is terminated externally such as with ACM on an AWS load balancer (accepted true values: `1`, `true`, `yes`, `on`)
3637
* `SISH_HOST` - Defines the destination hostname for forwarding requests that begin with `gw-` used in combination with `SISH_PORT`
3738
* `SISH_PORT` - Defined the destination port for forwarding requests tha begin with `gw-` used in combination with `SISH_HOST`
3839
* `MQTT_RATE_LIMIT` - Enable rate limiting for MQTT connections (connections/s)
@@ -57,3 +58,21 @@ If you use an Ingress, reconfigure the `HTTPS_FORWARDED_PORT` to the HTTPS port
5758

5859
You will also need to set the `NAMESERVER` environment variable to the cluster DNS (usually 10.96.0.10:53).
5960
The cluster DNS typically only resolves fully qualified hostnames, so make sure to set these using the `MANAGER_HOST` and `KEYCLOAK_HOST` environment variables (e.g. `manager.default.svc.cluster.local`).
61+
62+
## Edge-Terminated TLS
63+
64+
If TLS is terminated upstream before traffic reaches this pod, for example by an AWS NLB with ACM, an ALB, an ingress controller, or another reverse proxy, then:
65+
66+
* Set `DISABLE_ACME=true` to disable certbot initialization and renewal in the container
67+
* Use `HAPROXY_CONFIG=/etc/haproxy/haproxy-edge-terminated-tls.cfg`
68+
* Set `HTTP_PORT` to a non-privileged container port such as `8080`
69+
* Set `HTTPS_FORWARDED_PORT=443` so upstream services see the original external HTTPS port
70+
* Configure the upstream load balancer or proxy to forward decrypted HTTP traffic to the pod `HTTP_PORT`
71+
72+
For MQTT in the same setup, if MQTT TLS is also terminated upstream:
73+
74+
* Terminate TLS on the upstream listener (for example external port `8883`)
75+
* Forward plaintext TCP traffic from that listener to the pod's MQTT port
76+
* The provided `haproxy-edge-terminated-tls.cfg` listens for MQTT on `MANAGER_MQTT_PORT` and forwards it to the configured manager MQTT backend
77+
78+
The `haproxy-edge-terminated-tls.cfg` file removes local TLS certificate usage from the pod and preserves the usual `X-Forwarded-*` HTTP headers for upstream applications. Do not use this config if HTTPS or MQTT TLS is still passed through to the pod.

entrypoint.sh

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ fi
1515

1616
LE_CMD="certbot certonly -n --logs-dir - -w ${CHROOT_DIR} ${LE_EXTRA_ARGS}"
1717

18+
acme_enabled() {
19+
case "${DISABLE_ACME}" in
20+
1|true|TRUE|yes|YES|on|ON)
21+
return 1
22+
;;
23+
*)
24+
return 0
25+
;;
26+
esac
27+
}
28+
1829
# Configure haproxy
1930
HAPROXY_CMD="haproxy -W -db -f ${HAPROXY_CONFIG} ${HAPROXY_USER_PARAMS}"
2031
HAPROXY_RESTART_CMD="kill -s HUP 1"
@@ -73,21 +84,26 @@ run_proxy() {
7384
log_info "AWS_ROUTE53_ROLE: ${AWS_ROUTE53_ROLE}"
7485

7586
if check_proxy; then
76-
77-
log_info "Starting crond"
78-
crond
79-
80-
if [ -n "${AWS_ROUTE53_ROLE}" ]; then
81-
log_info "Creating AWS CLI config file"
82-
mkdir ~/.aws
83-
rm -f ~/.aws/config 2> /dev/null
84-
echo "[default]" >> ~/.aws/config
85-
echo "role_arn = ${AWS_ROUTE53_ROLE}" >> ~/.aws/config
86-
echo "credential_source = Ec2InstanceMetadata" >> ~/.aws/config
87-
echo "" >> ~/.aws/config
87+
start_monitor
88+
89+
if acme_enabled; then
90+
log_info "Starting crond"
91+
crond
92+
93+
if [ -n "${AWS_ROUTE53_ROLE}" ]; then
94+
log_info "Creating AWS CLI config file"
95+
mkdir ~/.aws
96+
rm -f ~/.aws/config 2> /dev/null
97+
echo "[default]" >> ~/.aws/config
98+
echo "role_arn = ${AWS_ROUTE53_ROLE}" >> ~/.aws/config
99+
echo "credential_source = Ec2InstanceMetadata" >> ~/.aws/config
100+
echo "" >> ~/.aws/config
101+
fi
102+
103+
cert_init&
104+
else
105+
log_info "ACME is disabled; skipping certbot initialization and renewal"
88106
fi
89-
90-
cert_init&
91107

92108
log_info "HAProxy starting"
93109
exec su haproxy -s /bin/sh -c "$HAPROXY_CMD $HAPROXY_START_OPTIONS"
@@ -129,6 +145,14 @@ restart() {
129145
fi
130146
}
131147

148+
start_monitor() {
149+
log_info "Starting monitoring process"
150+
(
151+
sleep 5
152+
monitor
153+
)&
154+
}
155+
132156
add() {
133157
if [ $# -lt 1 ]
134158
then
@@ -234,6 +258,10 @@ renew() {
234258
}
235259

236260
auto_renew() {
261+
if ! acme_enabled; then
262+
log_info "ACME is disabled; skipping auto renew"
263+
return 0
264+
fi
237265
log_info "Executing auto renew at $(date -R)"
238266
certbot renew --deploy-hook "/entrypoint.sh sync-haproxy"
239267
}
@@ -390,9 +418,6 @@ cert_init() {
390418
log_info "HAProxy certs have been modified so restarting"
391419
restart
392420
fi
393-
394-
log_info "Starting monitoring process"
395-
monitor
396421
}
397422

398423
sync_haproxy() {

haproxy-edge-terminated-tls.cfg

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#
2+
# Use this config when TLS is terminated externally before traffic reaches
3+
# this pod. HAProxy receives plain HTTP and plain TCP MQTT.
4+
#
5+
global
6+
log stdout format raw local0 "${PROXY_LOGLEVEL}"
7+
8+
tune.ssl.default-dh-param 4096
9+
# Works around breaking change in docker 23+ - just uses the old docker default value
10+
fd-hard-limit 1048576
11+
12+
defaults
13+
log global
14+
mode http
15+
log-format "%T %ft %ci:%cp %s %TR/%Tw/%Tc/%Tr/%Ta %{+Q}r %ST %ac/%fc/%bc/%sc/%rc %sq/%bq"
16+
timeout connect 30s
17+
timeout client 60s
18+
timeout server 60s
19+
timeout tunnel 720m
20+
# never fail on address resolution
21+
default-server init-addr none
22+
23+
resolvers docker_resolver
24+
nameserver dns "${NAMESERVER}"
25+
26+
frontend stats
27+
bind *:8404
28+
http-request use-service prometheus-exporter if { path /metrics }
29+
stats enable
30+
stats uri /stats
31+
stats refresh 10s
32+
33+
frontend http
34+
bind *:"${HTTP_PORT}"
35+
36+
# Static health endpoint for docker healthcheck (don't log it)
37+
acl url_docker_health path /docker-health
38+
http-request set-log-level silent if url_docker_health
39+
http-request return status 200 if url_docker_health
40+
41+
# TLS was already terminated upstream, so propagate the original scheme.
42+
option forwardfor
43+
http-request add-header X-Forwarded-Proto https
44+
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
45+
http-request add-header X-Forwarded-Port "${HTTPS_FORWARDED_PORT}"
46+
http-response add-header Strict-Transport-Security max-age=15768000
47+
http-response add-header X-Robots-Tag noindex
48+
49+
# Gateway tunnelling config
50+
.if defined(SISH_HOST) && defined(SISH_PORT)
51+
acl gateway_sub_domain hdr_beg(host) gw-
52+
use_backend sish if gateway_sub_domain
53+
.endif
54+
55+
acl auth path_beg "${KEYCLOAK_PATH_PREFIX}/auth"
56+
use_backend keycloak_backend if auth
57+
58+
use_backend manager_backend
59+
60+
listen mqtt
61+
bind *:"${MANAGER_MQTT_PORT}"
62+
mode tcp
63+
64+
.if defined(MQTT_RATE_LIMIT)
65+
# Rate limiting
66+
acl too_fast fe_sess_rate ge "${MQTT_RATE_LIMIT}"
67+
tcp-request connection reject if too_fast
68+
.endif
69+
70+
option clitcpka
71+
timeout client 3h
72+
timeout server 3h
73+
option logasap
74+
log-format "%T %ft CLIENT=%ci:%cp BACKEND=%bi:%bp %ts %ac/%fc/%bc/%sc/%rc %sq/%bq"
75+
balance leastconn
76+
77+
server manager "${MANAGER_HOST}":"${MANAGER_MQTT_PORT}" resolvers docker_resolver
78+
79+
backend manager_backend
80+
compression algo gzip deflate
81+
compression type text/html text/css application/javascript application/json image/svg+xml
82+
compression offload
83+
server manager "${MANAGER_HOST}":"${MANAGER_WEB_PORT}" resolvers docker_resolver
84+
.if defined(MANAGER_PATH_PREFIX)
85+
http-request replace-path ^"${MANAGER_PATH_PREFIX}"(/.*)?$ \1
86+
.endif
87+
88+
backend keycloak_backend
89+
server keycloak "${KEYCLOAK_HOST}":"${KEYCLOAK_PORT}" resolvers docker_resolver
90+
91+
# Gateway tunnelling config
92+
.if defined(SISH_HOST) && defined(SISH_PORT)
93+
backend sish
94+
server sish "${SISH_HOST}":"${SISH_PORT}" resolvers docker_resolver
95+
.endif

0 commit comments

Comments
 (0)