APISIX API配置HTTPS访问的踩坑实录

前言

        为了实现APISIX的生产级部署,需要在多个方面进行安全加固,其中HTTP改造为HTTPS是一个基本要求。在实施HTTPS改造过程中,本人遇到了一系列问题,并将这些问题的解决过程记录下来整理成文档。值得注意的是,即使在GPT的加持下,这些问题的解决之路依然坎坷,这似乎也意味着:由于技术与产品的不断迭代,系统集成工程师在短时间内被AI取代依然是一种奢望。

1 基础环境介绍

● 工程源码:https://github.com/apache/apisix-docker.git

● 分支详情:* master e0a2c51 chore: release APISIX 3.12.0 (#585)

● docker版本:24.0.9

● docker-compose版本:v2.26.1

● 操作系统:BigCloud Enterprise Linux release 8.2.2107 (Core)

● 体系结构:x86_64

● 主机环境:

主机业务网口IP

虚地址

虚地址实现方式

192.168.61.23

192.168.61.26

辅助IP

192.168.61.24

192.168.61.27

192.168.61.25

192.168.61.28

● 路径结构(以apisix-docker/example为相对路径):

.

│── apisix_conf

│   └── config.yaml

│──docker-compose.yml

└──startup.sh

● 证书配置情况说明:APISIX整体上有四个组件需要配置HTTPS,分别是APISIX(服务端口/9443)、APISIX(管理端口/9180)、ETCD和APISIX Dashboard,其中:APISIX管理端口9180只能使用mTLS模式,其他组件可使用TLS模式。本文主要介绍APISIX管理端口的mTLS加密方式。

【注意】mTLS(双向TLS认证)就像网络世界的"双向安检":不仅服务器需要出示身份证,客户端也得亮明真身。双方会互相检查对方是否持有匹配的密码钥匙,再通过数字证书里的信息双重确认身份,确保连接的确实是"真人真设备"。

        这种技术是零信任安全体系的核心守卫——在这个框架下,系统默认不信任任何人,即使你身处内部网络。无论是员工登录设备、服务器之间传输数据,还是调用API接口,mTLS都会让通信双方完成"你证我、我证你"的身份核验。就像进出机密实验室需要双向刷卡,这种机制能有效拦截伪造身份、数据窃听等风险,为数据传输装上双保险。

2 配置文件介绍

        为便于讲述,以下配置文件、脚本中只展示和APISIX组件相关的配置。

2.1 docker-compose.yml

        以下为最终版本。

services:

  apisix:

    image: apache/apisix:${APISIX_IMAGE_TAG:-3.12.0-debian}

    container_name: apisix

    restart: always

    volumes:

      - /data/labs/certs:/usr/local/apisix/certs:ro

      - /data/labs/mTLS_certs:/usr/local/apisix/mTLS_certs:ro

      - ./apisix_conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro

    depends_on:

      - etcd

    ports:

      - "${ALIAS}:8443:8443/tcp"

      - "${ALIAS}:9180:9180/tcp"

      - "${ALIAS}:9080:9080/tcp"

      - "${ALIAS}:9091:9091/tcp"

      - "${ALIAS}:9443:9443/tcp"

      - "${ALIAS}:9092:9092/tcp"

    networks:

      - apisix

【解释】

    APISIX容器挂载的卷分别为:/data/labs/certs(用于服务端口的TLS证书路径)、/data/labs/mTLS_certs(APISIX 管理端口专用的mTLS证书路径)、./apisix_conf/config.yaml(APISIX配置文件路径)。

        为确保容器暴露的端口尽量与其内部进程开放的端口一致(便于管理),同时又避免与主机上的其他服务端口冲突,将端口的暴露面进行收敛,如${ALIAS}:9180:9180/tcp,即暴露在虚拟IP上。这里采用辅助IP方式创建虚地址(具体参见2.2节),而采用其他方式如虚拟网络接口dummy会在本地创建默认路由,干扰正常的数据包转发。

2.2 创建虚地址

        本生产部署方案力争三个节点的配置文件和脚本完全一致,即同质化部署,因此做了一些特殊处理,如下:

# create-if-alias.sh

#!/bin/bash

# 网络适配器名称

IF_NAME=bond0

# 已知三个节点所属的C类地址

CLASS_C_ADDR="192.168.61."

# 根据已知的IP地址范围192.168.61.识别到当前节点IP

NODE_IP=$(hostname -I | tr ' ' '\n' | grep -oE "${CLASS_C_ADDR}23|${CLASS_C_ADDR}24|${CLASS_C_ADDR}25")

# 从节点IP中提取主机号

HOST_NO=$(echo ${NODE_IP} | sed "s#${CLASS_C_ADDR}##")

# 提取节点IP的掩码

MASK=$(ip addr show dev ${IF_NAME} scope global permanent | grep -oE "${NODE_IP}/[0-9]+" | sed "s#${NODE_IP}/##")

# 根据当前节点IP计算出对应的虚地址IP(+偏移)

ALIAS=${CLASS_C_ADDR}$(expr ${HOST_NO} + 3)

# 一个简单的校验

if [ -z $NODE_IP ]; then

   echo '[Warn] The current node does not belong to the cluster.'

   exit 1

fi

# 避免重复添加虚地址

if [ $(ip addr show dev bond0 scope global permanent | grep "${ALIAS}" | wc -l) -ne 0 ]; then

   echo '[Warn] The alias has been created.'

   exit 1

fi

# 添加虚地址

ip addr add ${ALIAS}/${MASK} dev bond0

# 显示创建好的虚地址

ip addr show dev ${IF_NAME} scope global permanent

2.3 startup.sh

该文件为容器运行脚本。

#!/bin/bash

# 节点IP地址的网络号部分

CLASS_C_ADDR="192.168.61."

# ETCD节点名称前缀

NODE_PREF="etcd_61_"

# 根据已知的IP地址范围192.168.61.识别到当前节点IP

NODE_IP=$(hostname -I | tr ' ' '\n' | grep -oE "${CLASS_C_ADDR}23|${CLASS_C_ADDR}24|${CLASS_C_ADDR}25")

# 从节点IP中提取主机号

HOST_NO=$(echo ${NODE_IP} | sed "s#${CLASS_C_ADDR}##")

# 组装ETCD节点名称,如etcd_61_23。

# 【注意】etcd_61_23不能做为访问ETCD的HOSTNAME,根据RFC 1123规范,域名和主机名不能包含下划线,需要将_换成-

NODE_NAME=${NODE_PREF}${HOST_NO}

# 根据当前节点IP计算出对应的虚地址IP(+偏移)

ALIAS=${CLASS_C_ADDR}$(expr ${HOST_NO} + 3)

if [ -z $NODE_IP ]; then

   echo '[Warn] The current node does not belong to the etcd cluster.'

   exit 1

fi

# ETCD加密套件

ETCD_CIPHER_SUITES="TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_GCM_SHA256"

# ETCD集群信息

ETCD_INITIAL_CLUSTER="${NODE_PREF}23=https://${CLASS_C_ADDR}26:2380,${NODE_PREF}24=https://${CLASS_C_ADDR}27:2380,${NODE_PREF}25=https://${CLASS_C_ADDR}28:2380"

# ETCD集群Token,使用uuidgen生成

ETCD_INITIAL_CLUSTER_TOKEN="fd82bf3a-3eef-4e81-803e-0033fd8d7404"

# Docker运行时环境根路径

DOCKER_ROOT=/data/labs/runtimes/docker-24

# 启动容器

CLASS_C_ADDR=${CLASS_C_ADDR} NODE_NAME=${NODE_NAME} ALIAS=${ALIAS} ETCD_CIPHER_SUITES=${ETCD_CIPHER_SUITES} ETCD_INITIAL_CLUSTER=${ETCD_INITIAL_CLUSTER} ETCD_INITIAL_CLUSTER_TOKEN=${ETCD_INITIAL_CLUSTER_TOKEN} ${DOCKER_ROOT}/docker-compose -H unix://${DOCKER_ROOT}/docker.sock -f docker-compose.yml up $1 -d

# 注意:运行示例 ./startup.sh apisix,不带参数为启动全部组件。

2.4 apisix配置文件

# 这是最终版本的apisix配置文件

apisix:

  node_listen: 9080 # APISIX 服务监听端口

  enable_ipv6: false # 启用IPv4/6双栈

  enable_control: true # 启用Control API

  control:

    ip: "0.0.0.0" # 容器内可监听在0.0.0.0:9092上

    port: 9092

  enable_http2: true # 启用 HTTP/2 协议支持

  ssl:

    enable: true # 同时支持HTTP/HTTPS访问

    listen:

      - ip: 0.0.0.0

        port: 9443

    ssl_protocols: "TLSv1.2 TLSv1.3" # 限制指定协议的TLS版本

    ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA" # 指定安全的加密套件

    # 【关键配置】指定包含可信CA证书的文件路径

ssl_trusted_certificate: /usr/local/apisix/mTLS_certs/apisix.ca-bundle

# 【注意】此二项为无效配置

    # ssl_cert: /usr/local/apisix/certs/tls.crt

    # ssl_cert_key: /usr/local/apisix/certs/tls.key

deployment:

  admin:

    allow_admin: # https://nginx.org/en/docs/http/ngx_http_access_module.html#allow

      - 0.0.0.0/0 # We need to restrict ip access rules for security. 0.0.0.0/0 is for test.

    admin_key_required: true

    admin_key:

      - name: "admin" # Use command "openssl rand -hex 16" to generate new key

        key: 53962cdfe4782e146bc18fd02d35a3b6

        role: admin # admin: manage all configuration data

      - name: "viewer"

        key: b93a4d8b774b2658a137951e79959f13

        role: viewer

    enable_admin_cors: false # 允许跨域资源共享

    admin_listen: # APISIX 管理监听地址与端口

      ip: 0.0.0.0

      port: 9180

    https_admin: true

    admin_api_mtls: # mTLS证书配置

      admin_ssl_ca_cert: /usr/local/apisix/mTLS_certs/apisix.ca-bundle

      admin_ssl_cert: /usr/local/apisix/mTLS_certs/server.crt

      admin_ssl_cert_key: /usr/local/apisix/mTLS_certs/server.key

    admin_api_version: v3

  etcd:

    host:                           # it's possible to define multiple etcd hosts addresses of the same etcd cluster.

      - "https://etcd:1159"         # multiple etcd address

      - "https://192.168.61.26:1159"

      - "https://192.168.61.27:1159"

      - "https://192.168.61.28:1159"

    prefix: "/apisix"               # apisix configurations prefix

    timeout: 30                     # 30 seconds

    tls:

      cert: /usr/local/apisix/certs/tls.crt

      key: /usr/local/apisix/certs/tls.key

      ca_file: /usr/local/apisix/certs/ca.crt

      verify: false

plugin_attr:

  prometheus:

    export_addr:

      ip: "0.0.0.0"

      port: 9091

nginx_config:

  error_log_level: info

3 问题诊断和解决

3.1 APISIX的服务端口HTTPS配置

        业务服务端口不存在一个固定和统一的证书配置,即以下配置为GPT幻觉产生,APISIX并不存在下述两个配置项,但启动服务时也不会有任何错误提示:

apisix:

  ssl:

    ssl_cert: /usr/local/apisix/certs/tls.crt

    ssl_cert_key: /usr/local/apisix/certs/tls.key

        若请求服务端口https://apisix.dev:9443/,则出现“找不到SNI”的错误提示。

apisix  | 2025/04/15 01:40:17 [error] 141#141: *11915 [lua] init.lua:185: http_ssl_client_hello_phase(): failed to find SNI: please check if the client requests via IP or uses an outdated protocol. If you need to report an issue, provide a packet capture file of the TLS handshake., context: ssl_client_hello_by_lua*, client: 192.168.4.5, server: 0.0.0.0:9443

        通过官方文档可知apisix.ssl下只能配置一个证书路径:ssl_trusted_certificate(受信任的根证书):

apisix:

  ssl:

    ssl_trusted_certificate: /usr/local/apisix/certs/ca.crt

        APISIX的服务端口证书是与具体的路由策略绑定的,即只能通过动态路由配置证书,而无法在配置文件中指定。因此,正确的配置方法如下:

        首先,配置一个证书策略,证书与具体的SNI捆绑。

# add-ssl.sh

#!/bin/bash

admin_key=$(yq r apisix_conf/config.yaml deployment.admin.admin_key[name=="admin"].key)

curl http://apisix.dev:9180/apisix/admin/ssls/1 \

   -H "X-API-KEY: $admin_key" \

   -X PUT -k \

   -d '{

   "cert" : "'"$(cat /data/labs/certs/tls.crt)"'",

   "key": "'"$(cat /data/labs/certs/tls.key)"'",

   "snis": ["apisix.dev"]

}'

        再配置路由策略:

#!/bin/bash

admin_key=$(yq r apisix_conf/config.yaml deployment.admin.admin_key[name=="admin"].key)

curl http://apisix.dev:9180/apisix/admin/routes/1 \

   -H "X-API-KEY: $admin_key" \

   -X PUT -i -d {'

    "name": "apisix",

    "uri": "/protected",

    "methods": ["GET"],

    "hosts": ["apisix.dev"],

    "upstream": {

        "type": "roundrobin",

        "nodes": {

            "192.168.61.26:9081": 1

        }

    }

}'

        测试 https://apisix.dev:9443/protected 工作正常!

3.2 APISIX管理端口HTTPS配置

        在3.2.1 - 3.2.10中笔者反复进行了验证,最终问题得以解决。

        起初,管理端口与业务端口配置相同的证书:

deployment:

  admin:

    https_admin: true

    admin_api_mtls:

      admin_ssl_ca_cert: /usr/local/apisix/certs/ca.crt

      admin_ssl_cert: /usr/local/apisix/certs/tls.crt

      admin_ssl_cert_key: /usr/local/apisix/certs/tls.key

        请求9180端口时报错:

curl https://apisix.dev:9180/apisix/admin/routes \

   --cert /data/labs/certs/tls.crt \

   --key /data/labs/certs/tls.key \

   --cacert /data/labs/certs/tls.key \

   -H "X-API-KEY: ${admin_key}" \

   --insecure \

   --tlsv1.3

        APISIX报错日志如下:

apisix  | 192.168.208.1 - - [16/Apr/2025:01:12:40 +0000] apisix.dev:9180 "GET /apisix/admin/routes HTTP/1.1" 400 287 0.000 "-" "curl/7.61.1" - - - "://"

        从日志看信息量非常少,因此考虑调整日志级别。

3.2.1 调整日志级别分析原因(必要手段)

        修改APISIX的配置文件,增加一个配置,可放开日志级别:

nginx_config:

  error_log_level: info

        放开后,每次请求https://apisix.dev:9018会出现如下一条异常日志:

apisix  | 2025/04/15 06:59:46 [info] 50#50: *39905 client SSL certificate verify error: (18:self-signed certificate) while reading client request headers, client: 192.168.192.1, server: , request: "GET /apisix/admin/routes HTTP/1.1", host: "apisix.dev:9180"

        从字面意义上看,客户端证书校验错误(自签名证书),“自签”可能导致了证书验证失败,但我们前期已经配置了ssl_trusted_certificate,这让人感到非常困惑。

        此外,在info级别日志量已经非常大了,动作足够快才能捕获到。尝试将日志级别调整为debug后发现,除了这条日志外也没有其他更有价值的信息了。

3.2.2 证书校验通用名而非备用名(必要但非根因)

        官方文档中提到:admin.apisix.dev | 域名 | 签发 `foo_server.crt` 证书时使用的 Common Name,客户端通过该域名访问 APISIX Admin API。

        这与subjectAltName(SAN)优先的证书校验规则不同,需要特别注意。通常情况下:服务器证书校验应优先检查SAN扩展字段,若证书中存在SAN,校验方甚至会完全忽略Common Name,而仅当证书中未配置SAN时,才会回退到Common Name,即只起到兜底作用。此外,从标准演进上:RFC 6125(2011)已明确将 SAN 作为域名校验的‌唯一标准字段‌,Common Name仅保留向后兼容性‌。

        按照官方文档的要求重新签发了证书,将Common Name调整正确,发现问题依旧。

3.2.3 在操作系统层面添加自签信任(无效)

        由于添加ssl_trusted_certificate信任无效,因此也怀疑是否需要在操作系统层面添加信任。CentOS 8的添加方式如下:

# 安装依赖工具包

yum install -y ca-certificates

# 将CA证书添加到信任锚点

cp -f /data/labs/certs/ca.crt /etc/pki/ca-trust/source/anchors/

# 触发对受信任的CA证书集合的更新

update-ca-trust

# 重启服务

        由于APISIX的基础镜像为Debian GNU/Linux 11 (bullseye),其CA授信过程略有不同

# 进入APISIX容器(略)

# 安装依赖工具包

apt update && apt install ca-certificates -y

# 拷贝CA证书到相应目录下

cp /usr/local/apisix/certs/ca.crt /usr/local/share/ca-certificates/

# 执行更新

update-ca-certificates

# 为确保CA证书信任永久生效,需提交一个新的镜像并更新docker-compose.yml

docker commit apisix apache/apisix:3.12.0-debian-patched

        重新运行,测试无效!

3.2.4 签发mTLS证书(无效)

        根据mTLS的要求,从签发一套证书改为分别为客户端、服务端签发一套证书,同时在签发过程中应显式注明extendedKeyUsage为serverAuth和clientAuth,具体如下:

# 签发CA证书密钥

openssl genpkey -algorithm RSA -out ca.key -pkeyopt rsa_keygen_bits:4096

openssl req -x509 -new -nodes -key ca.key -sha512 -days 3650 -out ca.crt \

  -subj "/C=***/ST=***/L=***/O=***/CN=***" \

  -addext "basicConstraints=critical,CA:TRUE" \

  -addext "keyUsage=critical,keyCertSign,cRLSign"

# 签发服务端证书密钥

openssl genpkey -algorithm RSA -out server.key -pkeyopt rsa_keygen_bits:4096

openssl req -new -key server.key -out server.csr \

  -subj "/CN=***" \

  -addext "subjectAltName=DNS:*.dev,IP:127.0.0.1" \

  -addext "extendedKeyUsage=serverAuth"

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \

  -out server.crt -days 3650 -sha512 \

  -extfile <(printf "extendedKeyUsage=serverAuth\nsubjectAltName=DNS:*.dev,IP:127.0.0.1")

# 签发客户端证书密钥

openssl genpkey -algorithm RSA -out client.key -pkeyopt rsa_keygen_bits:4096

openssl req -new -key client.key -out client.csr \

  -subj "/CN=***" \

  -addext "subjectAltName=DNS:*.dev,IP:127.0.0.1" \

  -addext "extendedKeyUsage=clientAuth"

openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \

  -out client.crt -days 3650 -sha512 \

  -extfile <(printf "extendedKeyUsage=clientAuth\nsubjectAltName=DNS:*.dev,IP:127.0.0.1")

# 签发新的证书后出现了新的错误:

apisix  | 2025/04/16 01:12:40 [alert] 51#51: *201122 ignoring stale global SSL error (SSL: error:1100009E:X509 V3 routines::invalid certificate error:1100009E:X509 V3 routines::invalid certificate) while waiting for request, client: 192.168.208.1, server: 0.0.0.0:9180

apisix  | 192.168.208.1 - - [16/Apr/2025:01:12:40 +0000] apisix.dev:9180 "GET /apisix/admin/routes HTTP/1.1" 400 287 0.000 "-" "curl/7.61.1" - - - "://"

# 搜索到一个高度相关的帖子:
https://lists.gnupg.org/pipermail/gnutls-devel/2025-January/026825.html

        大致意思是:GnuTLS和OpenSSL在验证证书时的行为不一致。根据 RFC5280 的规定,当证书中包含 keyUsage 扩展时,至少有一个位必须设置为 1,这是为了确保证书的用途明确且符合预期。然而,在测试用例中,keyUsage 的值为空,没有任何位被设置为1。

# 因此考虑修改证书签发的keyUsage选项(未进行验证)

# 1. 生成私钥

openssl genpkey -algorithm RSA -out server.key

# 2. 生成证书签名请求 (CSR)

openssl req -new -key server.key -out server.csr

# 3. 创建一个配置文件,指定 keyUsage 扩展

cat <<EOF > v3.ext

[ v3_ca ]

keyUsage = digitalSignature,keyEncipherment

extendedKeyUsage = serverAuth,clientAuth

EOF

# 4.签发证书

openssl x509 -req -in server.csr -CA RootCA.pem -CAkey RootCA.key -CAcreateserial -out server.crt -days 365 -extfile v3.ext -extensions v3_ca

        由于时间关系,使用带有扩展keyUsage选项的证书签发方式没有继续验证,而在3.2.8节使用了官方的证书签发流程进行签发。

3.2.5 验证mTLS握手(必要)

# 使用 OpenSSL 的 s_client 工具,以客户端的身份连接到指定的服务器,并进行 SSL/TLS 握手,用于测试 SSL/TLS 服务是否正常工作,以及检查服务器返回的证书链是否正确。

openssl s_client -connect 192.168.61.26:9180 -servername apisix.dev -showcerts

# 提示无法验证第一证书,需要补充客户端证书路径。

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: 21 (unable to verify the first certificate)

# 重新测试:

openssl s_client -connect 192.168.61.26:9180 -servername apisix.dev -showcerts --cert /data/labs/mTLS_certs/client.crt --key /data/labs/mTLS_certs/client.key -CAfile /data/labs/mTLS_certs/ca.crt

# 验证mTLS握手成功:

Verify return code: 0 (ok)

SSL handshake has read 2492 bytes and written 4023 bytes

Verification: OK

至此,mTLS握手验证成功,但400问题持续,推测问题的根因可能出现在APISIX集成的openresty层面。

3.2.6 排除插件开启原因

        查阅GITHUB上apache/apisix项目相关资料,及相关组件的代码修改情况。

        最终确认APISIX 3.12版本无需进行插件配置

3.2.7 生成mTLS证书后需做重新初始化(必要)

        根据官方文档指示,配置完admin_api_mtls后,需要进行重新初始化

1、生成自签证书对,包括 CA、server、client 证书对。

2、修改 `conf/config.yaml` 中的配置项:

```yaml title="conf/config.yaml"

  admin_listen:

    ip: 127.0.0.1

    port: 9180

  https_admin: true

  admin_api_mtls:

    admin_ssl_ca_cert: "/data/certs/mtls_ca.crt"              # Path of your self-signed ca cert.

    admin_ssl_cert: "/data/certs/mtls_server.crt"             # Path of your self-signed server side cert.

    admin_ssl_cert_key: "/data/certs/mtls_server.key"         # Path of your self-signed server side key.

```

3. 执行命令,使配置生效:

apisix init

apisix reload

因此在启动APISIX容器前,需要删除卷以触发重新初始化:

docker volume rm example_etcd_data

3.2.8 生成mTLS的官方流程

        参考官方流程签发mTLS证书:https://github.com/apache/apisix/blob/master/docs/en/latest/tutorials/client-to-apisix-mtls.md#generate-certificates

        部署后3.2.4出现的错误消失,恢复为400错误:

apisix  | 172.20.0.1 - - [16/Apr/2025:06:46:36 +0000] apisix.dev:9180 "PUT /apisix/admin/ssls/1 HTTP/1.1" 400 287 0.001 "-" "curl/7.61.1" - - - "://"

        在curl时开启详细TLS握手日志:curl -vvv,确认TLS握手完全正常。

> GET /apisix/admin/routes HTTP/1.1

> Host: apisix.dev:9180

> User-Agent: curl/7.61.1

> Accept: */*

> X-API-KEY: 53962cdfe4782e146bc18fd02d35a3b6

>

* TLSv1.3 (IN), TLS handshake, [no content] (0):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS handshake, [no content] (0):

* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):

* TLSv1.3 (IN), TLS app data, [no content] (0):

< HTTP/1.1 400 Bad Request

< Server: openresty

< Date: Wed, 16 Apr 2025 06:48:43 GMT

< Content-Type: text/html; charset=utf-8

< Content-Length: 287

< Connection: close

<

<html>

<head><title>400 The SSL certificate error</title></head>

<body>

<center><h1>400 Bad Request</h1></center>

<center>The SSL certificate error</center>

<hr><center>openresty</center>

<p><em>Powered by <a href=" ">APISIX</a >.</em></p ></body>

</html>

* Closing connection 0

* TLSv1.3 (OUT), TLS alert, [no content] (0):

* TLSv1.3 (OUT), TLS alert, close notify (256):

3.2.9 证书打包添加信任(必要但无效)

        需要注意的是:虽然可以为APISIX的服务端口、APISIX管理端口配置不同的CA证书,但两者的CA证书授信均在:

apisix:

  ssl:

    ssl_trusted_certificate: <The path of certificate bundle>

        因此需要对两个CA证书进行合并:

cat /data/labs/certs/ca.crt /data/labs/mTLS_certs/ca.crt > apisix.ca-bundle,配置在deployment.admin.admin_api_mtls.admin_ssl_ca_cert、apisix.ssl.ssl_trusted_certificate。

3.2.10 发现根本原因

        将报错的这段日志"client SSL certificate verify error: (18:self signed certificate) while reading client request headers"在GITHUB上APISIX代码工程中检索,没有找到任何记录,在openresty中也未找到,但在stackoverflow上找到一条高度相似的贴子:

https://stackoverflow.com/questions/45628601/client-authentication-using-self-signed-ssl-certificate-for-nginx

        其中提到无意中发现的一个陷阱,即在签发证书时:若CA证书和客户端证书使用相同的组织名称(Organization Name),将会看到上述错误,并提出一个验证方法:若执行openssl verify -verbose -CAfile ca.crt client.crt的结果提示:error 18 at 0 depth lookup: self signed certificate,则符合这种情形。

        笔者进行了验证,验证结果和此文所述完全一致,随即对证书进行重新签发,注意对组织名称进行了重新设定,如下:

openssl genrsa -out ca.key 2048

openssl req -new -sha256 -key ca.key -out ca.csr -subj "/C=CN/ST=Jilin/L=Changchun/O=HQ/OU=IT Department/CN=apisix.dev"

openssl x509 -req -days 36500 -sha256 -extensions v3_ca -signkey ca.key -in ca.csr -out ca.crt

# For server certificate

openssl genrsa -out server.key 2048

openssl req -new -sha256 -key server.key -out server.csr -subj "/O=Branch Office/CN=apisix.dev"

openssl x509 -req -days 36500 -sha256 -extensions v3_req -CA ca.crt -CAkey ca.key -CAserial ca.srl -CAcreateserial -in server.csr -out server.crt

# For client certificate

openssl genrsa -out client.key 2048

openssl req -new -sha256 -key client.key -out client.csr -subj "/O=Branch Office/CN=apisix.dev"

openssl x509 -req -days 36500 -sha256 -extensions v3_req -CA ca.crt -CAkey ca.key -CAserial ca.srl -CAcreateserial -in client.csr -out client.crt

        发布至生产验证成功,问题得以最终解决。

        那么,nginx/OpenSSL为什么要校验证书的Organization呢,GPT一下得知:当CA证书的Organization Name与签发的服务器证书或客户端证书的Organization Name相同时,这可能导致证书链验证失败。证书链验证是确保证书由受信任的CA签发的一个过程。如果CA证书和签发的证书来自同一个组织,这可能会引发信任问题,因为这意味着自签名或自颁发的证书可能没有被外部实体验证

        为了保持证书链的完整性和信任度,建议每个证书(包括CA证书、服务器证书和客户端证书)都使用不同的Organization Name。这有助于确保证书链的正确验证,并避免潜在的安全问题。

4 总结与感悟

        由于技术水平有限,上述问题的定位过程大约用掉了3天时间,最后进行一下复盘:

  1. 问题的诊断需要足够多的日志支持,日志越少、内容越含糊定位越困难,应尽可能从多个方面收集有价值的日志;
  2. 不惜花费时间尝试和验证猜想,尝试越多距离真相越近;
  3. 层层套壳增加了软件架构的复杂性,找到被集成的组件的源码,根据报错内容搜索是一个便捷的手段;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值