Traefik failing TLS handshake with Let's Encrypt Certificate - docker

I am attempting to have Traefik serve as a reverse proxy for services running in Docker containers. I've been following the documentation that Traefik provides and have a small docker environment configured via docker compose that successfully serves data via HTTP. Traefik sits behind HAProxy running in TCP mode forwarding packets received from the Internet to Traefik.
However when I tried to add a new router for serving the same content via HTTPS, I receive the following esoteric (to me) error when I run a curl directed to https://my.domain.tld/: error:1408F10B:SSL routines:ssl3_get_record:wrong version number
Full curl output:
curl -v https://my.domain.tld/
* Trying <IP Address of Domain>...
* TCP_NODELAY set
* Connected to my.domain.tld (<IP Address of Domain>) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* error:1408F10B:SSL routines:ssl3_get_record:wrong version number
* Closing connection 0
When I attempt to browse to the site via Firefox (web browser) I receive an error code of SSL_ERROR_RX_RECORD_TOO_LONG. When googling this error I was unable to find a post that seemed to have my specific issue.
Below is the docker-compose for the setup I am using to configure the applications
version: "3.9"
secrets:
cloudflare_dns_token:
file: ./secrets/cf_dns_api_token.txt
networks:
socket_proxy:
name: socket_proxy
driver: bridge
ipam:
config:
- subnet: 192.168.0.0/24
container_bridge:
name: container_bridge
driver: bridge
ipam:
config:
- subnet: 192.168.1.0/24
services:
socket-proxy:
image: tecnativa/docker-socket-proxy
container_name: socket-proxy
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
socket_proxy:
ipv4_address: 192.168.0.2 # Static IP address
environment:
EVENTS: 1
PING: 1
VERSION: 1
CONTAINERS: 1
NETWORKS: 1
traefik:
# The official v2 Traefik docker image
image: traefik:v2.8.1
container_name: traefik-proxy
command:
# Log Level for Traefik
- "--log.level=DEBUG"
# Enables the web UI
- "--api.insecure=true"
# Traefik enables docker as the provider to look for services
- "--providers.docker=true"
# Traefik will use the Docker Socket proxy to communicate with the docker socket
- "--providers.docker.endpoint=tcp://192.168.0.2:2375"
# Traefik will not expose services if they aren't labled for export
- "--providers.docker.exposedByDefault=false"
# Port where Traefik will listen for web (http) traffic for routing
- "--entrypoints.web.address=:80"
# Port where Traefik will listen for web secure (https) traffic for routing
- "--entrypoints.websecure.address=:443"
# Trust Proxy Protocol Packets from only the listed IP address
- "--entryPoints.web.proxyProtocol.trustedIPs=10.0.8.1/32"
# Trust Proxy Protocol Packets from only the listed IP address
- "--entryPoints.websecure.proxyProtocol.trustedIPs=10.0.8.1/32"
# Enable a ACME DNS challenge named "letsencrypt"
- "--certificatesresolvers.letsencrypt.acme.dnschallenge=true"
# Tell Traefik which provider to use for DNS Challenge
- "--certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare"
# Staging environment for let's encrypt for testing
- "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
# Email to provide to let's encrypt
- "--certificatesresolvers.letsencrypt.acme.email=${EMAIL}"
# Tell Traefik to store the certificate on a path under our volume
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
networks:
# Tells Traefik to connect to both the socket proxy network and the container bridge network where the other containers will be connected
socket_proxy:
container_bridge:
ports:
# The HTTP port
- "80:80"
# The HTTPS port
- "443:443"
# The Web UI (enabled by --api.insecure=true)
- "8080:8080"
secrets:
- "cloudflare_dns_token"
environment:
# To Be Removed Need Secret Working Properly
- "CF_DNS_API_TOKEN=${CF_DNS_TOKEN}"
#- "CF_DNS_API_TOKEN=/run/secrets/cloudflare_dns_token"
volumes:
# Create a letsencrypt dir within the folder where the docker-compose file is
- "./letsencrypt:/letsencrypt"
whoami:
image: traefik/whoami
container_name: whoami-server
networks:
container_bridge:
labels:
# Tells Traefik to proxy to the service (container)
- "traefik.enable=true"
#####################################################################
#
# Labels for HTTPS Proxying
#
#####################################################################
# Explicitly stating 'whoami-secure' route is HTTPS
- "traefik.http.routers.whoami-secure.tls=true"
# Rule for determing when to route requests to this service for the secure http router
- "traefik.http.routers.whoami-secure.rule=Host(`whoami.${FQDN}`)"
# Entry point for requests to this service for the secure http router
- "traefik.http.routers.whoami-secure.entrypoints=websecure"
# Uses the Host rule to define which certificate to issue
- "traefik.http.routers.whoami-secure.tls.certresolver=letsencrypt"
#####################################################################
#
# Labels for HTTP Proxying
#
#####################################################################
# Rule for determing when to route requests to this service for the unsecure http router
- "traefik.http.routers.whoami.rule=Host(`whoami.${FQDN}`)"
# Entry point for requests to this service for the unsecure http router
- "traefik.http.routers.whoami.entrypoints=web"
My expectation is that Traefik would gracefully handle the request via HTTPS and manage the TLS handshake without issue. I can confirm that Traefik is able to successfully generate a certificate via Let's Encrypt DNS Challenge for Cloudflare. I am using the Let's Encrypt staging environment at the moment so I did expect an error about the certificate being served as invalid, but it seems that TLS handshake errors out before a determination of validity.
EDIT #1: Running OpenSSL and Wireshark
OpenSSL returns the following when run ```openssl s_client -connect my.domain.tld:443``
CONNECTED(00000003)
140330304906560:error:1408F10B:SSL routines:ssl3_get_record:wrong version number:../ssl/record/ssl3_record.c:331:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 5 bytes and written 315 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
Wireshark logs show the following:
3 TCP packets preceding the first TLSv1 Client Hello
The Client Hello is acknowledged by the server
Then the server returns a HTTP 400 error - Bad Request
Wireshark dump of error
Transmission Control Protocol, Src Port: 443, Dst Port: 50085, Seq: 1, Ack: 518, Len: 207
Source Port: 443
Destination Port: 50085
[Stream index: 0]
[Conversation completeness: Complete, WITH_DATA (63)]
[TCP Segment Len: 207]
Sequence Number: 1 (relative sequence number)
Sequence Number (raw): 1488204866
[Next Sequence Number: 208 (relative sequence number)]
Acknowledgment Number: 518 (relative ack number)
Acknowledgment number (raw): 500352812
0101 .... = Header Length: 20 bytes (5)
Flags: 0x018 (PSH, ACK)
000. .... .... = Reserved: Not set
...0 .... .... = Nonce: Not set
.... 0... .... = Congestion Window Reduced (CWR): Not set
.... .0.. .... = ECN-Echo: Not set
.... ..0. .... = Urgent: Not set
.... ...1 .... = Acknowledgment: Set
.... .... 1... = Push: Set
.... .... .0.. = Reset: Not set
.... .... ..0. = Syn: Not set
.... .... ...0 = Fin: Not set
[TCP Flags: ·······AP···]
Window: 501
[Calculated window size: 64128]
[Window size scaling factor: 128]
Checksum: 0x1155 [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
[Timestamps]
[SEQ/ACK analysis]
TCP payload (207 bytes)
Hypertext Transfer Protocol
[Expert Info (Warning/Security): Unencrypted HTTP protocol detected over encrypted port, could indicate a dangerous misconfiguration.]
[Unencrypted HTTP protocol detected over encrypted port, could indicate a dangerous misconfiguration.]
[Severity level: Warning]
[Group: Security]
HTTP/1.1 400 Bad request\r\n
[Expert Info (Chat/Sequence): HTTP/1.1 400 Bad request\r\n]
[HTTP/1.1 400 Bad request\r\n]
[Severity level: Chat]
[Group: Sequence]
Response Version: HTTP/1.1
Status Code: 400
[Status Code Description: Bad Request]
Response Phrase: Bad request
Content-length: 90\r\n
Cache-Control: no-cache\r\n
Connection: close\r\n
Content-Type: text/html\r\n
\r\n
[HTTP response 1/1]
File Data: 90 bytes
5 packets later the connection is reset
Wireguard dump of connection reset
Transmission Control Protocol, Src Port: 443, Dst Port: 50085, Seq: 208, Len: 0
Source Port: 443
Destination Port: 50085
[Stream index: 0]
[Conversation completeness: Complete, WITH_DATA (63)]
[TCP Segment Len: 0]
Sequence Number: 208 (relative sequence number)
Sequence Number (raw): 1488205073
[Next Sequence Number: 208 (relative sequence number)]
Acknowledgment Number: 0
Acknowledgment number (raw): 0
0101 .... = Header Length: 20 bytes (5)
Flags: 0x004 (RST)
000. .... .... = Reserved: Not set
...0 .... .... = Nonce: Not set
.... 0... .... = Congestion Window Reduced (CWR): Not set
.... .0.. .... = ECN-Echo: Not set
.... ..0. .... = Urgent: Not set
.... ...0 .... = Acknowledgment: Not set
.... .... 0... = Push: Not set
.... .... .1.. = Reset: Set
[Expert Info (Warning/Sequence): Connection reset (RST)]
[Connection reset (RST)]
[Severity level: Warning]
[Group: Sequence]
.... .... ..0. = Syn: Not set
.... .... ...0 = Fin: Not set
[TCP Flags: ·········R··]
Window: 0
[Calculated window size: 0]
[Window size scaling factor: 128]
Checksum: 0x6dc9 [unverified]
[Checksum Status: Unverified]
Urgent Pointer: 0
[Timestamps]

Related

TLS with Rabbitmq Docker-Image: handshake_failure

I am running rabbitmq:3.10.7-management for AMQPS on a VM and I am using chained TLS-Certificates.
When I configure Rabbitmq according to the How-To-TLS website from Rabbitmq I get no errors starting the container but I can't connect via TLS and get an immediate Connection-Error. Depending on which Tool I use or where I use it I get a connection reset by peer. But it seems like the handshake does not work at all.
When I enable TCP I can just connect fine with a non-TLS-client.
The Rabbitmq part of the Docker-Compose looks like this:
rabbitmq:
restart: unless-stopped
hostname: rabbitmq
image: rabbitmq:3.10.7-management
networks:
- traefik
ports:
- "5672:5672"
- "5671:5671"
logging:
options:
max-size: "10m"
max-file: "3"
volumes:
- ./rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf
- ./certs:/certs
- /data/rabbitmq_data:/var/lib/rabbitmq
labels:
- "traefik.enable=true"
- "traefik.http.routers.rabbitmq-secure.priority=150"
- "traefik.http.services.rabbitmq-secure.loadbalancer.server.port=15672"
- "traefik.http.routers.rabbitmq-secure.rule=Host(`<myDomain>`) && PathPrefix(`/`)"
- "traefik.http.routers.rabbitmq-secure.entrypoints=web-secure"
- "traefik.http.routers.rabbitmq-secure.tls=true"
- "traefik.http.routers.rabbitmq-secure.tls.options=myTLSOptions#file"
- "traefik.http.routers.rabbitmq.rule=Host(`<myDomain>`) && PathPrefix(`/`)"
- "traefik.http.routers.rabbitmq.entrypoints=web"
I am using the same certs to serve the management frontend with no problems.
ca_certificate.pem ->
CN=T-TeleSec GlobalRoot Class 2, OU=T-Systems Trust Center, O=T-Systems Enterprise Services GmbH, C=DE
Chain-Cert 1
Chain-Cert 2
server_certificate.pem ->
Wild-Card-Cert
server_key.pem ->
Key for Wild-Card-Cert
I think I tried almost every other configuration as well, for which part goes where.
RabbitMQ-Conf:
log.console.level = debug
listeners.ssl.default = 5671
listeners.tcp = none
ssl_options.cacertfile = /certs/ca_certificate.pem
ssl_options.certfile = /certs/server_certificate.pem
ssl_options.keyfile = /certs/server_key.pem
ssl_options.verify = verify_none
ssl_options.fail_if_no_peer_cert = false
ssl_options.versions.1 = tlsv1.3
ssl_options.versions.2 = tlsv1.2
ssl_options.honor_cipher_order = false
ssl_options.honor_ecc_order = false
ssl_handshake_timeout = 10000
ssl_options.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384
ssl_options.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384
ssl_options.ciphers.3 = ECDHE-ECDSA-AES128-GCM-SHA256
ssl_options.ciphers.4 = ECDHE-RSA-AES128-GCM-SHA256
ssl_options.ciphers.5 = ECDHE-ECDSA-CHACHA20-POLY1305
ssl_options.ciphers.6 = ECDHE-RSA-CHACHA20-POLY1305
ssl_options.ciphers.7 = DHE-RSA-AES128-GCM-SHA256
ssl_options.ciphers.8 = DHE-RSA-AES256-GCM-SHA384
Logs seem to be OK:
...
2022-09-06 14:42:22.024539+00:00 [info] <0.615.0> started TLS (SSL) listener on [::]:5671
...
Going through "Troubleshooting TLS-enabled Connections" I can check all the boxes I can find up until connecting with TLS:
Check Listeners:
rabbitmq#rabbitmq:/$ rabbitmq-diagnostics listeners
Asking node rabbit#rabbitmq to report its protocol listeners ...
Interface: [::], port: 15672, protocol: http, purpose: HTTP API
Interface: [::], port: 15692, protocol: http/prometheus, purpose: Prometheus exporter API over HTTP
Interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Interface: [::], port: 5671, protocol: amqp/ssl, purpose: AMQP 0-9-1 and AMQP 1.0 over TLS
Check permissions:
rabbitmq#rabbitmq:/certs$ ls -ll
total 20
-rw-r--r-- 1 rabbitmq rabbitmq 1366 Sep 6 14:39 ca_certificate.pem
-rw-r--r-- 1 rabbitmq rabbitmq 6943 Sep 6 14:39 server_certificate.pem
-rw-r--r-- 1 rabbitmq rabbitmq 3275 Sep 5 13:20 server_key.pem
Check TLS-support in erlang:
rabbitmq#rabbitmq:/certs$ rabbitmq-diagnostics --silent tls_versions
tlsv1.3
tlsv1.2
tlsv1.1
tlsv1
Attempt TLS-Connection with openssl
rabbitmq#rabbitmq:/certs$ openssl s_client -connect localhost:5671
CONNECTED(00000003)
write:errno=104
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 0 bytes and written 293 bytes
Verification: OK
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
The debug logs don't even show any error when trying to connect.
Trying to connect with TLS to TCP throws an error, though. Somehow the listener does not accept connections at all.
Maybe someone experienced something similar already or I am just dumb overlooking the obvious. But for everything else the VM and the certs work just fine (e.g. Mosquitto-MQTTS).
Help would be really appreciated.

SSH not working using Gitea on docker with traefik

I have installed gitea on docker (docker-compose) with traefik (v2.3) as reverse proxy. I'm trying to set up ssh but it's failing, both to SSH into and (mainly) to perform git clone and push.
I get
<user>#<domain>: Permission denied (publickey).
I have tried specifying ports in the docker-compose file for the traefik container
ports:
- "22:22"
and in the docker-compose for gitea I have the following labels:
# SSH
- "traefik.http.routers.gitea_ssh.rule=HOST(`gitea.localhost`)"
- "traefik.http.routers.gitea_ssh.entrypoints=ssh"
- "traefik.http.routers.gitea_ssh.service=gitea_ssh"
# Services
- "traefik.http.services.gitea_ssh.loadbalancer.server.port=22"
I have similar setup for http and https, http is redirected to https.
Entrypoints is defined in traefik.yml as ":22"
This however doesn't work. I figured signal flow would go like this:
ssh request -> server port 1234 -> docker port 22 -> traefik redirects -> gitea container port 22
I have uploaded public key to
The response I get with this setup for ssh connection request is:
<login on computer>:/ <user>$ ssh -v <address to gitea>
OpenSSH_8.1p1, LibreSSL 2.7.3
debug1: Reading configuration data /<Path to config>/config
debug1: /<Path to config>/config line 12: Applying options for <address to gitea>
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 47: Applying options for *
debug1: Connecting to <address to gitea> port 1234.
debug1: Connection established.
debug1: identity file /<Path to private key>/private-key type 0
debug1: identity file /<Path to private key>/private-key-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_8.1
debug1: kex_exchange_identification: banner line 0: HTTP/1.1 400 Bad Request
debug1: kex_exchange_identification: banner line 1: Content-Type: text/plain; charset=utf-8
debug1: kex_exchange_identification: banner line 2: Connection: close
debug1: kex_exchange_identification: banner line 3:
kex_exchange_identification: Connection closed by remote host
And when I try to access git clone:
<login on computer>:/ <user>$ git clone git#<address to gitea>:<path to repo>.git
Cloning into 'some-repo'...
kex_exchange_identification: Connection closed by remote host
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
Example for gitea ssh available on port 222 because port 22 is used for host ssh.
Create a entrypoint for traefik called ssh for port 222
Add port config to traefik container 222:222/tcp (udp, sctp not needed)
for gitea you dont need to expose ports
configure gitea, add environment variables:
# details: https://docs.gitea.io/en-us/config-cheat-sheet/#admin-admin
# start gitea only ssh server, default use system which didn't work for me in any way
GITEA__server__START_SSH_SERVER: "true"
# this port is used in git clone, if not 22 this will add ssh:// to clone url which... i needed to remove everytime
GITEA__server__SSH_PORT: 22
# gitea ssh listen port
GITEA__server__SSH_LISTEN_PORT: 222
configure traefik for gitea with labels:
# host resolving doesn't work for ssh, so you can only use "*"
- "traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)"
- "traefik.tcp.routers.gitea-ssh.entrypoints=ssh"
- "traefik.tcp.routers.gitea-ssh.service=gitea-ssh-svc"
- "traefik.tcp.services.gitea-ssh-svc.loadbalancer.server.port=222"
add your ssh key to your account, check if its a valid type, minimum bit aso. https://docs.gitea.io/en-us/config-cheat-sheet/#ssh-minimum-key-sizes-sshminimum_key_sizes
create a simple repository named Example
update on client side ~/.ssh/config:
Host MyGiteaInstance
HostName git.example.de
IdentityFile ~/.ssh/gitea
User username
Port 222
clone git#git.example.de:user/Example.git, for me it failed if there is port information in the url or if there is the protocol prefix like ssh://
Debugging tipps:
check traefik logs if there is an error like entrypoint not found
do a clone with verbose ssh: GIT_SSH_COMMAND="ssh -v" git clone git#git.example.de:user/Example.git
if the host is unreachable -> wrong ssh port on your side or entrypoint not set in traefik
host authenticity request = traefik is fine and client port config is maybe fine (multiple ssh server)
permission denied -> ssh server in gitea started, correct port set, check gitea logs or ssh key not added to user
Complete not minimal example, if I missed something above:
networks:
proxy:
external: false
internal: true
web:
external: false
gitea:
external: false
internal: true
volumes:
gitea:
gitea_db:
services:
traefik:
image: traefik:v2.5
command: --api.insecure=true --providers.docker
ports:
# entrypoint http
- "80:80"
# entrypoint https
- "443:443"
# entrypoint ssh
- "222:222/tcp"
# todo maybe behind vpn?
# - "8080:8080"
# todo create docker.sock proxy
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./traefik:/etc/traefik:ro
- ./acme.json:/certs/acme.json
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
networks:
- proxy
- web
watchtower:
image: containrrr/watchtower:latest
environment:
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_REVIVE_STOPPED: "true"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
# disable for container?
# LABEL com.centurylinklabs.watchtower.enable="false"
gitea_db:
image: postgres:14
restart: unless-stopped
environment:
POSTGRES_USER: gitea
POSTGRES_PASSWORD: ${GITEA_DB_PASSWD}
POSTGRES_DB: gitea
networks:
- gitea
volumes:
- gitea_db:/var/lib/postgresql/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
gitea:
image: gitea/gitea:1.15
depends_on:
- gitea_db
# https://docs.gitea.io/en-us/config-cheat-sheet/
environment:
USER_UID: 1000
USER_GID: 1000
# configuration:
# some values are created from the documentation but untested, therefore uncommented
# https://docs.gitea.io/en-us/config-cheat-sheet/#repository---local-repositorylocal
## default
GITEA__default__RUN_MODE: prod
GITEA__default__APP_NAME: "Gitea: Git with a cup of tea"
## database
GITEA__database__DB_TYPE: postgres
GITEA__database__HOST: gitea_db:5432
GITEA__database__NAME: gitea
GITEA__database__USER: gitea
GITEA__database__PASSWD: ${GITEA_DB_PASSWD}
## server
GITEA__server__DOMAIN: ${GITEA_DOMAIN}
# ensuring unneccessary port isnt added
GITEA__server__ROOT_URL: "https://%(DOMAIN)s"
GITEA__server__HTTP_PORT: 80
# if true SSH_LISTEN_PORT needs to be != 22
GITEA__server__START_SSH_SERVER: "true"
# SSH port in clone URL (needs to be 22 to remove ssh:// from clone url)
GITEA__server__SSH_PORT: 22
# disable forced ssh:// prefix of clone url
GITEA__repository__USE_COMPAT_SSH_URI: "false"
# SSH port for built-in SSH server (e.g. docker run ... -p SSH_PORT:LISTEN_PORT)
GITEA__server__SSH_LISTEN_PORT: 222
# mailer
GITEA__mailer__ENABLED: "true"
GITEA__mailer__HOST: "${GITEA_MAIL_HOST}"
GITEA__mailer__FROM: "${GITEA_MAIL}"
GITEA__mailer__USER: "${GITEA_MAIL}"
# require email confirmation to register, enable email notifications
# no effect: GITEA__server__REGISTER_EMAIL_CONFIRM: "true"
# allows mails as notifications on updates
# no effect: GITEA__server__ENABLE_NOTIFY_MAIL: "true"
#
# open-id sign
GITEA__openid__ENABLE_OPENID_SIGNIN: "true"
# disable self-registration
GITEA__service__DISABLE_REGISTRATION: "true"
# require sign in to view pages
GITEA__service__REQUIRE_SIGNIN_VIEW: "true"
# password hash argon2
GITEA__security__PASSWORD_HASH_ALGO: "argon2"
#
# change default branch from master to x
GITEA__repository__DEFAULT_BRANCH: "main"
#
# set manually during install, keys don't work for unknown reason:
# Email Settings:
# SMTP Password:
# GITEA__mailer__PASSWD: "${GITEA_MAIL_PASSWORD}"
# [x] Require Email Confirmation to Register
# [x] Enable Email Notifications (to watch repos / issues aso.)
#
# set manually, no config option found:
# Server and Third-Party Service Settings:
# [x] Enable Local Mode (disable all third party content)
#
# Admin Account:
# admin: ${GITEA_ADMIN_NAME}
# pw: "${GITEA_MAIL_PASSWORD}"
# mail: ${GITEA_MAIL}
#
# check configuration here:
# https://${GITEA_DOMAIN}/admin/config
#
# remember correct port isn't part of ssh clone url, but works with ~/.ssh/config
restart: unless-stopped
networks:
- gitea
- proxy
volumes:
- gitea:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
labels:
- "traefik.enable=true"
- "traefik.http.services.gitea.loadbalancer.server.port=80"
- "traefik.http.routers.gitea_insecure.rule=Host(`${GITEA_DOMAIN}`)"
- "traefik.http.routers.gitea_insecure.entrypoints=http"
- "traefik.http.routers.gitea_insecure.middlewares=https-redirect#file"
- "traefik.http.routers.gitea.rule=Host(`${GITEA_DOMAIN}`)"
- "traefik.http.routers.gitea.entrypoints=https"
- "traefik.http.routers.gitea.tls.certresolver=tlsChallenge"
# https://community.traefik.io/t/routing-ssh-traffic-with-traefik-v2/717
# ssh cant be set on specific domain
- "traefik.tcp.routers.gitea-ssh.rule=HostSNI(`*`)"
- "traefik.tcp.routers.gitea-ssh.entrypoints=ssh"
- "traefik.tcp.routers.gitea-ssh.service=gitea-ssh-svc"
- "traefik.tcp.services.gitea-ssh-svc.loadbalancer.server.port=222"
.env file:
GITEA_DOMAIN=git.example.de
GITEA_DB_PASSWD=securePassword
GITEA_ADMIN_NAME=admin name
GITEA_MAIL_PASSWORD=securePassword
GITEA_MAIL_HOST=smtp.mymail.de:465
GITEA_MAIL=yourEmail
./traefik/dynamic.yml
http:
middlewares:
https-redirect:
redirectScheme:
scheme: https
./traefik/traefik.yml
global:
sendAnonymousUsage: false
log:
level: INFO
entryPoints:
http:
address: :80
https:
address: :443
ssh:
address: :222
defaultEntryPoints:
- https
api:
insecure: true
dashboard: true
providers:
docker:
endpoint: unix:///var/run/docker.sock
watch: true
exposedByDefault: false
network: shared_proxy
file:
filename: /etc/traefik/dynamic.yml
watch: true
certificatesResolvers:
tlsChallenge:
acme:
email: MyEmailAdress
storage: /certs/acme.json
tlsChallenge: {}
# https://doc.traefik.io/traefik/https/acme/#caserver
# caServer: https://acme-staging-v02.api.letsencrypt.org/directory # For test certificates
I don't have first hand experience with proxying ssh via traefik but i've looked at this article before:
https://www.georglutz.de/blog/2020/06/20/homeassistant-with-traefik-and-ssh/
and makes sense to recommend it, since it's the same setup.
and i would point out that you're wrongly using the http router, you need the tcp one.
I use Gitea and I would advise just exposing SSH, on a different port; Traefik will only give you overhead.

Forwarding TCP traffic from Traefik to a Docker container

I currently have a Traefik instance that's being run using the following. It works fine forwarding HTTP connections to the appropriate backends.
services_lb:
image: traefik:v2.2
cmd: |
--entrypoints.web.address=:80
--entrypoints.websecure.address=:443
--entrypoints.web.http.redirections.entryPoint.to=websecure
--entrypoints.web.http.redirections.entryPoint.scheme=https
--entrypoints.web.http.redirections.entrypoint.permanent=true
--entrypoints.matrixfederation.address=:8448
--entrypoints.prosodyc2s.address=:5222
--entrypoints.prosodys2s.address=:5269
--providers.docker
--providers.docker.constraints=Label(`lb.net`,`services`)
--providers.docker.network=am-services
--certificatesresolvers.lec.acme.email=notify#battlepenguin.com
--certificatesresolvers.lec.acme.storage=/letsencrypt/acme.json
--certificatesresolvers.lec.acme.tlschallenge=true
--entryPoints.web.forwardedHeaders.trustedIPs=172.50.0.1/24
ports:
- 80
- 443
# Matrix
- 8448
# XMPP
- 5222
- 5269
My web and Matrix federation connections work fine as they're all HTTP. But for Prosody (XMPP) I need to forward 5222 and 5269 directly without any HTTP routing. I configured the container like so:
xmpp:
image: prosody/prosody:0.11
network:
- services
- database
labels:
lb.net: services
traefik.tcp.services.prosodyc2s.loadbalancer.server.port: "5222"
traefik.tcp.services.prosodys2s.loadbalancer.server.port: "5269"
traefik.http.routers.am-app-xmpp.entrypoints: "websecure"
traefik.http.routers.am-app-xmpp.rule: "Host(`xmpp.example.com`)"
traefik.http.routers.am-app-xmpp.tls.certresolver: "lec"
traefik.http.services.am-app-xmpp.loadbalancer.server.port: "5280"
volumes:
- prosody-config:/etc/prosody:rw
- services_certs:/certs:ro
- prosody-logs:/var/log/prosody:rw
- prosody-modules:/usr/lib/prosody-modules:rw
With the tcp services, I still can't get Traefik to forward the raw TCP connections to this container. I've tried removing the --entrypoints from the Traefik instance and of course, Traefik stopped listening on those ports. I assumed the traefik.tcp.service definition would cause that entrypoint to switch to a TCP passthrough mode, but that isn't the case. I couldn't see anything in the Traefik documentation on putting the entrypoint itself into TCP mode instead of HTTP mode.
How do I pass the raw TCP connection from Traefik to this particular container using labels on the container and CLI options for Traefik?
I figured it out. You can't use any standard Traefik TLS offloading due to the differences in how Traefik and Prosidy handle TLS. I had to disable TLS entirely and use the special HostSNI(*) rule below to allow straight pass throughts. I was also missing the routers that connect the Traefik entrypoints to the TCP services.
labels:
lb.net: services
# client to server
traefik.tcp.routers.prosodyc2s.entrypoints: prosodyc2s
traefik.tcp.routers.prosodyc2s.rule: HostSNI(`*`)
traefik.tcp.routers.prosodyc2s.tls: "false"
traefik.tcp.services.prosodyc2s.loadbalancer.server.port: "5222"
traefik.tcp.routers.prosodyc2s.service: prosodyc2s
# server to server
traefik.tcp.routers.prosodys2s.entrypoints: prosodys2s
traefik.tcp.routers.prosodys2s.rule: HostSNI(`*`)
traefik.tcp.routers.prosodys2s.tls: "false"
traefik.tcp.services.prosodys2s.loadbalancer.server.port: "5269"
traefik.tcp.routers.prosodys2s.service: prosodys2s
# web
traefik.http.routers.am-app-xmpp.entrypoints: "websecure"
traefik.http.routers.am-app-xmpp.rule: "Host(`xmpp.example.com`)"
traefik.http.routers.am-app-xmpp.tls.certresolver: "lec"
traefik.http.services.am-app-xmpp.loadbalancer.server.port: "5280"

Traefik v2 reverse proxy to a local server outside Docker

I have a simple server written in Python that listens on port 8000 inside a private network (HTTP communication). There is now a requirement to switch to HTTPS communications and every client that sends a request to the server should get authenticated with his own cert/key pair.
I have decided to use Traefik v2 for this job. Please see the block diagram.
Traefik runs as a Docker image on a host that has IP 192.168.56.101. First I wanted to simply forward a HTTP request from a client to Traefik and then to the Python server running outside Docker on port 8000. I would add the TLS functionality when the forwarding is running properly.
However, I can not figure out how to configure Traefik to reverse proxy from i.e. 192.168.56.101/notify?wrn=1 to the Python server 127.0.0.1:8000/notify?wrn=1.
When I try to send the above mentioned request to the server (curl "192.168.56.101/notify?wrn=1") I get "Bad Gateway" as an answer. What am I missing here? This is the first time that I am in contact with Docker and reverse proxy/Traefik. I believe it has something to do with ports but I can not figure it out.
Here is my Traefik configuration:
docker-compose.yml
version: "3.3"
services:
traefik:
image: "traefik:v2.1"
container_name: "traefik"
hostname: "traefik"
ports:
- "80:80"
- "8080:8080"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./traefik.yml:/traefik.yml:ro"
traefik.yml
## STATIC CONFIGURATION
log:
level: INFO
api:
insecure: true
dashboard: true
entryPoints:
web:
address: ":80"
providers:
docker:
watch: true
endpoint: "unix:///var/run/docker.sock"
file:
filename: "traefik.yml"
## DYNAMIC CONFIGURATION
http:
routers:
to-local-ip:
rule: "Host(`192.168.56.101`)"
service: to-local-ip
entryPoints:
- web
services:
to-local-ip:
loadBalancer:
servers:
- url: "http://127.0.0.1:8000"
First, 127.0.0.1 will resolve to the traefik container and not to the docker host. You need to provide a private IP of the node and it needs to be accessible form the traefik container.
There is some workaround to make proxy to localhost:
change 127.0.0.1 to IP of docker0 interface
It should be 172.17.0.1
and then try to listen your python server on all interfaces (0.0.0.0)
if you use simple python http server nothing change... on default it listen on all interfaces

nginx reverse proxy upstream fails in docker-compose with connection refused message

I have a docker-compose.yaml similar to this (shortened for simplicity):
# ...
services:
my-ui:
# ...
ports:
- 5402:8080
networks:
- my-net
networks:
my-net:
external:
name: my-net
and I'm trying to set up nginx as a reverse proxy with this configuration:
upstream client {
server my-ui:5402;
}
server {
listen 80;
location / {
proxy_pass http://client;
}
}
and this is the docker-compose.yaml I have for nginx:
# ...
services:
reverse-proxy:
# ...
ports:
- 5500:80
networks:
- my-net
networks:
my-net:
external:
name: my-net
What happens now is that when I run my-ui and reverse-proxy (each using its own docker-compose up), and I go to http://localhost:5500, I get a Bad Gateway message, and my nginx logs says this:
connect() failed (111: Connection refused) while connecting to
upstream, client: 172.19.0.1, server: , request: "GET / HTTP/1.1",
upstream: "http://172.19.0.5:5402/", host: "localhost:5500"
If I exec into my nginx container and use ping:
ping my-ui
ping 172.19.0.5
Both are successful, but if I want to, for example, curl:
curl -L http://my-ui
curl -L http://my-ui:5402
curl -L http://172.19.0.1
All of them fail with connection refused message. What am I missing here?
PS: I'm not sure, but it might be useful to add that my-ui is a basic vuejs application, running on Webpack dev server.
PS2: I also tried passing host headers etc. but same result
The name of the container (my-ui) resolves to the IP of the container. Therefor you have to provide in upstream the port of the container and not the port you have mapped to the host.
upstream client {
server my-ui:8080;
}
server {
listen 80;
location / {
proxy_pass http://client;
}
}
You could also configure your upstream with the name of your host machine and use the mapped port. (server <name of host>:5402) But this could get quite messy and you would lose the advantage of isolating services with docker networks.
Furthermore you could also remove the port mapping unless you need to access the webservice without reverse proxy:
# ...
services:
reverse-proxy:
# ...
# ports:
# - 5500:80

Resources