无法获取域的 ACME 证书 - 尝试在 docker 上的虚拟本地主机中设置 HTTPS

Unable to obtain ACME certificate for domains - trying to setup HTTPS in virtual localhost on docker

我正在尝试为虚拟主机 local.example.com 上的本地 运行 React 应用设置证书。这必须只在 docker 设置上本地工作。在浏览了一些文章之后,我想出了这个 docker-compose.yml:

version: "3"
services:
  mongo:
    image: mongo
    restart: always
    ports:
      - "27017:27017"
    volumes:
      - mongodbdata:/data/db
    networks:
      - proxy
  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - "8081:8081"
    networks:
      - proxy
  react:
    build:
      context: ./client
      dockerfile: ./Dockerfile
    ports:
      - "3001:3001"
    stdin_open: true
    volumes:
      - ./client:/client
      - /client/node_modules
    labels:
      # this enables traefik for your service
      - "traefik.enable=true"
      # this defines the url, traefik will get the ssl certificate for
      - "traefik.http.routers.myapplication.rule=Host(`local.example.com`)"
      # this tells traefik to use https to access the website
      - "traefik.http.routers.myapplication.entrypoints=websecure"
      # this tells traefik to use the certresolver, that we defined above for resolving tls (in our case letsencrypt)
      - "traefik.http.routers.myapplication.tls.certresolver=myresolver"
      # this let's us forward the port we set above. Change this to the port you expose in your application (3000, 4000, ...) or remove the line, if your application already exposes port 80/443
      - "traefik.http.services.myapplication.loadbalancer.server.port=3000"
    depends_on:
      - "server"
    networks:
      - proxy
  server:
    build:
      context: ./server
      dockerfile: ./Dockerfile
    ports:
      - "5001:5001"
    volumes:
      - traefik.toml:/traefik.toml
      - acme.json:/acme.json
      - ./server:/server
      - /server/node_modules
    labels:
      # this enables traefik for your service
      - "traefik.enable=true"
      # this defines the url, traefik will get the ssl certificate for
      - "traefik.http.routers.myapplication.rule=Host(`local.example.com`)"
      # this tells traefik to use https to access the website
      - "traefik.http.routers.myapplication.entrypoints=websecure"
      # this tells traefik to use the certresolver, that we defined above for resolving tls (in our case letsencrypt)
      - "traefik.http.routers.myapplication.tls.certresolver=myresolver"
      # this let's us forward the port we set above. Change this to the port you expose in your application (3000, 4000, ...) or remove the line, if your application already exposes port 80/443
      - "traefik.http.services.myapplication.loadbalancer.server.port=5001"
    depends_on:
      - "mongo"
    networks:
      - proxy
  whoami:
    image: "containous/whoami"
    container_name: "myapplication"
    restart: unless-stopped
    ports:
      - "4000:4000"
    networks:
      - proxy

  traefik:
    image: "traefik:v2.2"
    container_name: "traefik"
    command:
      # this can be uncommented to get more information, in case something doesn't work
      - "--log.level=DEBUG"
      # set this to true to get access to the traefik web interface unter http://YOURIP:8080
      - "--api.insecure=false"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
        #- "--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" # uncomment this line to only test ssl generation first (to make sure you don't run into letsencrypt limits)
      - "--certificatesresolvers.myresolver.acme.email=kamlekar.venkatesh@gmail.com"
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080" # this is used for the web interface, that let's you check and monitor traefik and your configuration. It's very nice for debugging your config - only available if "api.insecure" above is set to true
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    networks:
      - proxy
    # The following is only necessary if you want to enforce https!
    # if you don't need that, you can just remove the labels here
    labels:
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"

networks:
  proxy:
    external: true

volumes:
  mongodbdata:
  traefik.toml:
  acme.json:

在执行 docker-compose up 时,我在 traefik 服务中看到以下错误。此外,虽然 React 应用程序服务在 docker 中是 运行 但我点击了 https://local.example.com:3001,但无法访问

time="2022-01-23T08:30:52Z" level=error msg="Unable to obtain ACME certificate for domains "local.example.com": unable to generate a certificate for the domains [local.example.com]: error: one or more domains had a problem:\n[local.example.com] acme: error: 400 :: urn:ietf:params:acme:error:dns :: DNS problem: NXDOMAIN looking up A for local.example.com - check that a DNS record exists for this domain; DNS problem: NXDOMAIN looking up AAAA for local.example.com - check that a DNS record exists for this domain, url: \n" providerName=myresolver.acme routerName=myapplication@docker rule="Host(local.example.com)"

这是我目前尝试的research notes(这些笔记仅供个人理解,可能不详)

你可以从这个fiddle开始:https://github.com/kamlekar/react-docker-ssl-virtualhost

您需要使用 TLS 进行本地设置。您需要证书的主机是 local.example.com。无法从 Letsencrypt 获得此名称的证书,因为您没有控制 example.com 域。 Letsencrypt 创建证书的方法之一是一个挑战 - 您可以通过创建 TXT DNS record 来证明您拥有该域。如果你拥有一个域,你可以这样做,但你的情况不同,因为你只需要这个用于本地开发。

但是,您可以只使用 openssl 为您想要的任何域名生成自签名证书。 This is a good reference on how to do this. 生成的证书可以使用local.example.com域名。如果你成功了,你最终会得到证书和它的私钥。请注意保存这些文件的位置,因为您将需要它们。请记住证书是 self-signed,因此您的浏览器会给您一个警告,除非您将此证书添加到操作系统的信任库中。

您案例的下一步是让 Traefik 在从您的应用程序提供内容时使用这些自签名证书。我认为 this answer 有一个很好的例子。

有了这个之后,你只需要 edit your hosts file 并将你的 localhost:8080 (你的 Traefik 为你的应用程序提供服务的端口)重定向到 local.example.com.

此外,Traefik 并不是您的案例的唯一解决方案。例如,您也可以使用 Nginx 实现相同的目的。选择满足您的用例的一个。我的建议是使用最容易配置的那个,因为它用于本地开发。 Here's the first result I got 在搜索 nginx docker-compose self-signed certificate.

更新
这是我上面描述的一个简单示例。
首先生成证书:

openssl req -x509 -newkey rsa:4096 -nodes -keyout key.pem -out cert.pem -sha256 -days 1000 -subj '/CN=local.example.com'

您将在当前目录中得到两个文件(key.pemcert.pem) .现在创建 nginx.conf:

worker_processes 1;

events {
    worker_connections 1024;
}

http {
    server {
        listen       443 ssl;
        server_name  local.example.com;

        ssl_certificate      cert.pem;
        ssl_certificate_key  key.pem;

        ssl_session_timeout  5m;

        location / {
          proxy_pass               http://myapp:8080;
          proxy_set_header         Host     $host;
          proxy_read_timeout       1800;
          proxy_connect_timeout    1800;
        }
    }
}

现在 docker-compose.yaml 文件:

version: "3"

services:   
  nginx:
    image: nginx
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./cert.pem:/etc/nginx/cert.pem
      - ./key.pem:/etc/nginx/key.pem
    ports:
      - "443:443"
    networks:
      - local-dev-01

  myapp:
    image: your-react-app-image
    command: "command-that-starts-your-app"
    networks:
      - local-dev-01

networks:   
  local-dev-01:

Docker 为您创建了一个名为 local-dev-01 的网络,它允许两个服务能够通过它们的名称相互解析。这就是为什么我们在 nginx.conf 中有 myapp:8080 的原因。我们还为 local.example.com.
安装配置和生成的证书和密钥 最后一步是编辑 hosts 文件并添加以下行:

127.0.0.1      local.example.com

之后,您应该可以轻松地在您的计算机上访问 https://local.example.com 上的应用程序。请记住,您的浏览器会不断警告您证书是 self-signed,因此您应该将其添加为例外。