You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker容器内SSL证书生成与信任问题排查

问题描述

我在Docker容器中部署了一个监听8090端口的HTTPS Web应用。创建自签名证书并添加到容器内信任根后,容器内通过curl验证证书有效,但外部浏览器及curl访问时始终显示证书不受信任。推测是容器内可解析CA根证书,但外部无法解析。以下是相关文件及验证日志:


Dockerfile内容

FROM ubuntu

EXPOSE 8090
WORKDIR /app

#install tools
RUN apt-get -y update; apt-get -y install sudo curl wget libicu-dev apt-transport-https ca-certificates

# create SSL certificate
RUN openssl req -x509 -noenc -newkey rsa:4096 -sha256 -keyout /app/Certificates/test.key -out /app/Certificates/test.crt -passin "pass:password" -subj "/O=Test/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1" -days 3650

# export certificate
RUN openssl pkcs12 -export -password "pass:password" -inkey /app/Certificates/test.key -in /app/Certificates/test.crt -out /app/Certificates/test.pfx


# trust certificate
RUN cp /app/Certificates/test.crt /usr/local/share/ca-certificates/test.crt && update-ca-certificates

RUN useradd app
USER app
ENTRYPOINT ["./WebApplication"]

容器内验证证书(正常)

$ curl -vv https://localhost:8090
* Host localhost:8090 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8090...
* connect to ::1 port 8090 from ::1 port 59964 failed: Connection refused
*   Trying 127.0.0.1:8090...
* Connected to localhost (127.0.0.1) port 8090
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS
* ALPN: server accepted h2
* Server certificate:
*  subject: O=Test; CN=localhost
*  start date: Mar 17 01:37:17 2025 GMT
*  expire date: Mar 15 01:37:17 2035 GMT
*  subjectAltName: host "localhost" matched cert's "localhost"
*  issuer: O=Test; CN=localhost
*  SSL certificate verify ok.
*   Certificate level 0: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://localhost:8090/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: localhost:8090]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.5.0]
* [HTTP/2] [1] [accept: */*]
> GET / HTTP/2
> Host: localhost:8090
> User-Agent: curl/8.5.0
> Accept: */*
> 
< HTTP/2 200 
< content-type: text/html
< date: Mon, 17 Mar 2025 01:45:32 GMT
< server: Kestrel
< accept-ranges: bytes
< etag: "1db9629b316d3e9"
< last-modified: Sun, 16 Mar 2025 04:12:58 GMT
< content-length: 4841
< x-version: 0.0.0
< 
<!DOCTYPE html>
<html lang="en">
  <!-- ... HTML CONTENTS ... -->
</html>
* Connection #0 to host localhost left intact

容器外验证证书(失败)

$ curl -vv https://localhost:8090
* Host localhost:8090 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:8090...
* Connected to localhost (::1) port 8090
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - The certificate chain was issued by an authority that is not trusted.
* closing connection #0
curl: (60) schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - The certificate chain was issued by an authority that is not trusted.
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the webpage mentioned above.

OpenSSL证书链信息

$ openssl s_client -servername localhost -connect localhost:8090 -CApath /app/Certificates
CONNECTED(00000003)
depth=0 O = Test, CN = localhost
verify error:num=18:self-signed certificate
verify return:1
depth=0 O = Binner, CN = localhost
verify return:1
---
Certificate chain
 0 s:O = Test, CN = localhost
   i:O = Test, CN = localhost
   a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256
   v:NotBefore: Mar 16 15:39:02 2025 GMT; NotAfter: Mar 14 15:39:02 2035 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=O = Test, CN = localhost
issuer=O = Test, CN = localhost
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: RSA-PSS
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2194 bytes and written 391 bytes
Verification error: self-signed certificate
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 4096 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 18 (self-signed certificate)
---
---
Post-Handshake New Session Ticket arrived:
SSL-Session:
    Protocol  : TLSv1.3
    Cipher    : TLS_AES_256_GCM_SHA384
    Session-ID: D35F37B1E33D77323D56FA4BF418459FF7C4B752F4318F5DE0CFA15BB53FEC37
    Session-ID-ctx: 
    Resumption PSK: ...
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    TLS session ticket lifetime hint: 7200 (seconds)
    TLS session ticket:
    ...

    Start Time: 1742174452
    Timeout   : 7200 (sec)
    Verify return code: 18 (self-signed certificate)
    Extended master secret: no
    Max Early Data: 0
---

问题原因与解决方案

核心原因

  1. 容器与外部主机的信任根完全独立:你在容器内执行update-ca-certificates只是把自签名证书添加到容器的CA信任库中,外部主机(本地curl、浏览器)的CA信任链和容器没有任何关联,根本不识别这个自签名证书。
  2. 证书本身是自签名的:从OpenSSL日志可以看到,证书的颁发者(issuer)和主体(subject)完全一致,属于自签名证书,不在任何公共或本地受信任的CA链中,外部必须手动信任该证书才能通过验证。

解决方案

方案1:将自签名证书导入外部主机的信任根

这是最直接的解决方法,让外部主机信任这个证书:

  • 第一步:从容器中导出证书到本地主机
    docker cp <你的容器ID>:/app/Certificates/test.crt ./local-test.crt
    
  • 第二步:根据操作系统添加信任:
    • Windows:双击导出的local-test.crt,选择「安装证书」→「本地计算机」→「受信任的根证书颁发机构」,完成导入。
    • macOS:打开「钥匙串访问」,导入证书,找到该证书后右键选择「显示简介」,在「信任」选项卡中设置「使用此证书时」为「始终信任」。
    • Linux:将证书复制到系统CA目录,更新信任链:
      sudo cp local-test.crt /usr/local/share/ca-certificates/
      sudo update-ca-certificates
      

方案2:创建私有CA签发服务器证书(更规范)

如果后续有多个服务需要使用自签名证书,建议先创建自己的私有CA,再用CA签发服务器证书:

  1. 生成CA密钥和证书:
    openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -keyout ca.key -out ca.crt -subj "/O=Test CA/CN=Test Root CA" -noenc
    
  2. 生成服务器证书签名请求(CSR):
    openssl req -newkey rsa:4096 -sha256 -keyout test.key -out test.csr -subj "/O=Test/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1" -noenc
    
  3. 用CA签发服务器证书:
    openssl x509 -req -in test.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out test.crt -days 3650 -sha256 -extfile <(printf "subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1")
    
  4. 只需要把ca.crt导入外部主机的信任根,所有由该CA签发的证书都会被信任,容器内也只需要信任ca.crt即可。

方案3:临时跳过验证(仅用于测试)

如果只是临时测试,不需要长期信任,可以用curl跳过证书验证:

curl -k https://localhost:8090

注意:浏览器无法直接跳过验证,只能临时添加安全例外,但不推荐用于生产环境。


内容的提问来源于stack exchange,提问作者Michael Brown

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 21:12:02