是否可以使用 Traefik 通过 SSL 代理 PostgreSQL?

jla*_*rcy 9 postgresql proxy tcp sni traefik

动机

尝试使用 Let's Encrypt 通过 SSL 使用 Traefik 代理 PostgreSQL 时,我遇到了一个问题。我做了一些研究,但没有很好的记录,我想确认我的观察并给每个面临这种情况的人留下记录。

配置

我使用最新版本的 PostgreSQL v12 和 Traefik v2。我想使用 Let's Encrypt从->通过 TLS构建纯 TCP 流tcp://example.com:5432tcp://postgresql:5432

Traefik 服务配置如下:

  version: "3.6"
    
    services:
    
      traefik:
        image: traefik:latest
        restart: unless-stopped
        volumes:
          - "/var/run/docker.sock:/var/run/docker.sock:ro"
          - "./configuration/traefik.toml:/etc/traefik/traefik.toml:ro"
          - "./configuration/dynamic_conf.toml:/etc/traefik/dynamic_conf.toml"
          - "./letsencrypt/acme.json:/acme.json"
    
        networks:
          - backend
        ports:
          - "80:80"
          - "443:443"
          - "5432:5432"
    
    networks:
      backend:
        external: true
Run Code Online (Sandbox Code Playgroud)

使用静态设置:


[entryPoints]
  [entryPoints.web]
    address = ":80"
    [entryPoints.web.http]
      [entryPoints.web.http.redirections.entryPoint]
        to = "websecure"
        scheme = "https"

  [entryPoints.websecure]
    address = ":443"
    [entryPoints.websecure.http]
      [entryPoints.websecure.http.tls]
        certresolver = "lets"

  [entryPoints.postgres]
    address = ":5432"
Run Code Online (Sandbox Code Playgroud)

PostgreSQL 服务配置如下:


[entryPoints]
  [entryPoints.web]
    address = ":80"
    [entryPoints.web.http]
      [entryPoints.web.http.redirections.entryPoint]
        to = "websecure"
        scheme = "https"

  [entryPoints.websecure]
    address = ":443"
    [entryPoints.websecure.http]
      [entryPoints.websecure.http.tls]
        certresolver = "lets"

  [entryPoints.postgres]
    address = ":5432"
Run Code Online (Sandbox Code Playgroud)

看来我的 Traefik 配置是正确的。日志中一切正常,仪表板中的所有部分都标记为成功(无警告,无错误)。所以我对上面的 Traefik 配置充满信心。完整的流程是关于:

EntryPoint(':5432') -> HostSNI(`example.com`) -> TcpRouter(`postgres`) -> Service(`postgres@docker`)
Run Code Online (Sandbox Code Playgroud)

但是,它在 PostgreSQL 方面可能有一个限制。

调试

问题是我无法连接 PostgreSQL 数据库。我总是收到超时错误

我已经检查过 PostgreSQL 是否正在正常监听(超时错误的主要原因):

# - Connection Settings -
listen_addresses = '*'
port = 5432
Run Code Online (Sandbox Code Playgroud)

我检查了我可以在主机上(容器外)连接 PostgreSQL:

version: "3.6"

services:

  postgresql:
    image: postgres:latest
    environment:
      - POSTGRES_PASSWORD=secret
    volumes:
      - ./configuration/trial_config.conf:/etc/postgresql/postgresql.conf:ro
      - ./configuration/trial_hba.conf:/etc/postgresql/pg_hba.conf:ro
      - ./configuration/initdb:/docker-entrypoint-initdb.d
      - postgresql-data:/var/lib/postgresql/data
    networks:
      - backend
    #ports:
    #  - 5432:5432
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=backend"
      - "traefik.tcp.routers.postgres.entrypoints=postgres"
      - "traefik.tcp.routers.postgres.rule=HostSNI(`example.com`)"
      - "traefic.tcp.routers.postgres.tls=true"
      - "traefik.tcp.routers.postgres.tls.certresolver=lets"
      - "traefik.tcp.services.postgres.loadBalancer.server.port=5432"

networks:
  backend:
    external: true

volumes:
  postgresql-data:
Run Code Online (Sandbox Code Playgroud)

因此,我知道 PostgreSQL 正在其容器外侦听,因此 Traefik 应该能够绑定流。我还检查了外部 traefik 可以访问服务器:

EntryPoint(':5432') -> HostSNI(`example.com`) -> TcpRouter(`postgres`) -> Service(`postgres@docker`)
Run Code Online (Sandbox Code Playgroud)

所以,我想知道为什么连接不能成功。Traefik 和 PostgreSQL 之间一定有问题。

SNI不兼容?

即使我去掉了TLS配置,问题依然存在,所以我不指望TLS是这个问题的根源。

然后我搜索,发现很少有关于类似问题的帖子:

据我了解,PostgreSQL 的 SSL 协议是自定义协议,暂时不支持SNI,可能永远不会支持。如果正确,将确认 Traefik 暂时无法代理 PostgreSQL,这是一个限制。

通过写这篇文章,我想确认我的观察,同时在 Stack Overflow 上留下一个可见的记录给任何面临同样问题并寻求帮助的人。那么我的问题是:是否可以使用 Traefik 来代理 PostgreSQL?

更新

有趣的观察,如果使用HostSNI('*')和让我们加密:

# - Connection Settings -
listen_addresses = '*'
port = 5432
Run Code Online (Sandbox Code Playgroud)

一切都在仪表板中标记为成功,但当然 Let's Encrypt 无法执行通配符的 DNS 挑战*,它在日志中抱怨:

psql --host 172.19.0.4 -U postgres
Password for user postgres:
psql (12.2 (Ubuntu 12.2-4), server 12.3 (Debian 12.3-1.pgdg100+1))
Type "help" for help.

postgres=#
Run Code Online (Sandbox Code Playgroud)

当我尝试以下配置时:

sudo tcpdump -i ens3 port 5432
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens3, link-type EN10MB (Ethernet), capture size 262144 bytes
09:02:37.878614 IP x.y-z-w.isp.com.61229 > example.com.postgresql: Flags [S], seq 1027429527, win 64240, options [mss 1452,nop,wscale 8,nop,nop,sackOK], length 0
09:02:37.879858 IP example.com.postgresql > x.y-z-w.isp.com.61229: Flags [S.], seq 3545496818, ack 1027429528, win 64240, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
09:02:37.922591 IP x.y-z-w.isp.com.61229 > example.com.postgresql: Flags [.], ack 1, win 516, length 0
09:02:37.922718 IP x.y-z-w.isp.com.61229 > example.com.postgresql: Flags [P.], seq 1:9, ack 1, win 516, length 8
09:02:37.922750 IP example.com.postgresql > x.y-z-w.isp.com.61229: Flags [.], ack 9, win 502, length 0
09:02:47.908808 IP x.y-z-w.isp.com.61229 > example.com.postgresql: Flags [F.], seq 9, ack 1, win 516, length 0
09:02:47.909578 IP example.com.postgresql > x.y-z-w.isp.com.61229: Flags [P.], seq 1:104, ack 10, win 502, length 103
09:02:47.909754 IP example.com.postgresql > x.y-z-w.isp.com.61229: Flags [F.], seq 104, ack 10, win 502, length 0
09:02:47.961826 IP x.y-z-w.isp.com.61229 > example.com.postgresql: Flags [R.], seq 10, ack 104, win 0, length 0
Run Code Online (Sandbox Code Playgroud)

错误从日志中消失,在两种设置中,仪表板似乎都可以,但流量未路由到 PostgreSQL(超时)。无论如何,从配置中删除 SSL 会使流程完整(并且不安全):

    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=backend"
      - "traefik.tcp.routers.postgres.entrypoints=postgres"
      - "traefik.tcp.routers.postgres.rule=HostSNI(`*`)"
      - "traefik.tcp.routers.postgres.tls=true"
      - "traefik.tcp.routers.postgres.tls.certresolver=lets"
      - "traefik.tcp.services.postgres.loadBalancer.server.port=5432"
Run Code Online (Sandbox Code Playgroud)

然后就可以连接PostgreSQL数据库了:

time="2020-08-12T10:25:22Z" level=error msg="Unable to obtain ACME certificate for domains \"*\": unable to generate a wildcard certificate in ACME provider for domain \"*\" : ACME needs a DNSChallenge" providerName=lets.acme routerName=postgres@docker rule="HostSNI(`*`)"
Run Code Online (Sandbox Code Playgroud)

小智 5

在此PR中,已将带有 STARTTLS 的 postgres 的 SNI 路由添加到 Traefik 中。现在 Treafik 将监听 postgres 发送的初始字节,如果要发起 TLS 握手(请注意,postgres TLS 请求首先创建为非 TLS,然后升级为 TLS 请求),Treafik 将处理握手,然后能够从 postgres 接收 TLS 标头,其中包含正确路由请求所需的 SNI 信息。这意味着您可以HostSNI("example.com")与 tls 一起使用来公开不同子域下的 postgres 数据库。

在写这个答案时,我能够使用图像v3.0.0-beta2参考