使用密钥环加密数据
数据加密是 APISIX 的一个关键考虑因素。在实施身份认证或与其他系统集成时,网关配置中经常会包含用户密码和私钥等敏感信息。APISIX 可以在存储前加密这些信息,并在网关需要使用时通过已配置的密钥环解密。
在 APISIX 中,启用数据加密后,以下数据在保存到 etcd 之前将被加密:
- 敏感插件字段
- TLS 私钥,包括上游客户端私钥。
本指南将帮助你了解为什么要对敏感数据启用数据加密,以及如何启用数据加密来加强安全性。
启用数据加密
默认情况下,APISIX 启用了数据加密,并带有两个默认密钥:
apisix ={
...,
data_encryption = {
enable_encrypt_fields = true,
keyring = { "qeddd145sfvddff3", "edd1c9f0985e76a2" }
},
...
}
要查看数据加密的效果,请创建一个消费者 JohnDoe:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "JohnDoe"
}'
为 JohnDoe 配置 key-auth 凭证:
curl "http://127.0.0.1:9180/apisix/admin/consumers/JohnDoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
在此配置中,用户密钥是不应以明文存储的敏感信息。
创建凭证时,你应该会看到一个响应,其中显示了加密后的密钥:
{
"key": "/apisix/consumers/JohnDoe/credentials/cred-john-key-auth",
"value": {
"create_time": 1726300662,
"update_time": 1726300662,
"plugins": {
"key-auth": {
"key": "a/TGSySA4i1LGn4ZXlYuew=="
}
},
"id": "cred-john-key-auth"
}
}
为了进一步验证密钥已加密,你还可以检查保存到 etcd 的条目:
etcdctl get /apisix/consumers/JohnDoe/credentials/cred-john-key-auth
你应该看到密钥已加密:
{
"update_time":1726300662,
"create_time":1726300662,
"plugins":{
"key-auth":{
"key":"a/TGSySA4i1LGn4ZXlYuew=="
}
},
"id": "cred-john-key-auth"
}
同样,APISIX 也会在将 TLS 证书私钥保存到 etcd 之前对其进行加密。要验证,你可以按照 配置客户端和 APISIX 之间的 HTTPS 中的步骤操作,并观察 server_key 是否已加密。
APISIX 也会加密 Upstream 和内联 Stream Route 上游中的 upstream.tls.client_key。Admin API 不会以明文返回内联 Stream Route 客户端密钥。有关 Stream 配置,请参阅使用 mTLS 向 TLS 上游进行身份认证。
验证解密
要验证消费者 key-auth 密钥将被解密并按预期用于身份认证,请创建一个启用了 key-auth 的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "auth-route",
"uri": "/get",
"plugins": {
"key-auth": {},
},
"upstream" : {
"nodes": {
"httpbin.org":1
}
}
}'
使用消费者凭证向路由发送请求:
curl -i "http://127.0.0.1:9080/get" -H 'apikey: john-key'
你应该收到 HTTP/1.1 200 OK 响应,验证密钥已成功解密并用于身份认证。
更新密钥环
可以使用正确的密钥环将加密数据还原为明文。因此,强烈建议你在生产环境中使用自定义密钥环。
每个密钥必须恰好包含 16 或 32 个字节。请使用 ASCII 字符,以保证字符长度与字节长度一致。16 字节密钥使用 AES-128-CBC,32 字节密钥使用 AES-256-CBC。APISIX 会在启动或重新加载前校验 config.yaml 时拒绝其他长度。
密钥环在轮换期间可以同时包含两种长度。APISIX 使用第一个密钥加密新值,并在解密现有值时依次尝试所有密钥。若要从 AES-128 轮换到 AES-256,请先添加新的 32 字节密钥,并在其后保留旧密钥:
apisix:
data_encryption:
enable_encrypt_fields: true
keyring:
- 0123456789abcdef0123456789abcdef
- qeddd145sfvddff3
- edd1c9f0985e76a2
❶ 为 敏感插件字段 启用加密。
❷ 先添加新的 32 字节 ASCII 密钥。请使用安全生成的密钥替换此示例。
❸ 添加第一个和第二个旧密钥。
如果你的 APISIX 已经在运行并且有数据被加密,请不要删除旧密钥。如上所示,将新密钥添加到数组顶部,以便可以正确解密加密数据。直接删除旧密钥可能会导致加密数据不可逆。
如果没有数据被加密,你可以直接使用自定义密钥配置该部分。
如果你的 APISIX 已经在运行,重新加载 APISIX 以使配置更改生效。
排查升级后的解密失败
升级可能会将现有插件字段新增到 encrypt_fields。因此,早期版本写入的配置可能在当前版本要求加密 的位置包含未加密值。如果已配置的密钥环不再包含最初用于加密现有数据的密钥,APISIX 也会无法解密这些数据。
如果错误日志报告解密失败:
- 保留完整的旧密钥环。排查期间不要删除旧密钥或调整其顺序。
- 将所有新密钥添加到
apisix.data_encryption.keyring的开头,并在其后保留每个旧密钥。 - 重新加载 APISIX,并确认它可以读取现有配置。
- 通过 Admin API
GET请求获取受影响的资源。旧密钥可用时,APISIX 会以明文返回受保护字段。请在更新请求中重新提交该明文,使 APISIX 使用当前密钥环中的第一个密钥进行加密。不要复用早期写入响应中的密文,也不要直接使用 etcd 中的密文。 - 确认资源正常工作后,再考虑后续停用旧密钥。
不要先删除旧密钥。一旦 APISIX 无法解密某个值,重新保存这份不可读数据也无法恢复原始 Secret。
禁用数据加密
要禁用数据加密,只需将 enable_encrypt_fields 更新为 false:
apisix:
data_encryption:
enable_encrypt_fields: false
如果你的 APISIX 已经在运行,重新加载 APISIX 以使配置更改生效。
现在,如果你再次使用 key-auth 配置消费者凭证:
curl "http://127.0.0.1:9180/apisix/admin/consumers/JohnDoe/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
你应该看到密钥以明文保存的响应:
{
"key": "/apisix/consumers/JohnDoe/credentials/cred-john-key-auth",
"value": {
"create_time": 1726300662,
"update_time": 1726300668,
"plugins": {
"key-auth": {
"key": "john-key"
}
},
"id": "cred-john-key-auth"
}
}
了解插件加密字段
需要加密的插件字段在每个插件 Schema 的 encrypt_fields 属性中定义。字段路径可以指向顶层字段或嵌套字段。例如:
local consumer_schema = {
type = "object",
title = "work with consumer object",
properties = {
username = { type = "string" },
password = { type = "string" },
},
# highlight-next-line
encrypt_fields = {"password"},
required = {"username", "password"},
}
一旦定义,启用数据加密时这些字段将被加密。
对于嵌套字段,请使用点分隔路径,例如 session.secret、redis.password 或 auth_config.private_key。路径可以具有任意嵌套深度。当中间路径段是数组时,APISIX 会遍历数组中的每个对象,因此支持 ai-proxy-multi 中的 instances.auth.aws.secret_access_key 等字段。
在路径的最后一个字段处,APISIX 会加密以下值类型:
- 字符串;
- 字符串数组;
- 值均为字符串的对象。
最 后一个字段中的非字符串项会保持不变。仅添加确实包含 Secret,且运行时代码在解密后仍预期相同值类型的字段。
下表总结了所有插件的加密插件字段:
对于日志插件,加密字段可以是其字符串值被加密的对象,例如 elasticsearch-logger 和 loki-logger 中的 headers。加密会保护静态存储的数据;已获授权的 Admin API 读取仍可返回解密后的插件配置。
| 插件 | 字段 |
|---|---|
authz-casdoor | client_secret |
authz-keycloak | client_secret |
azure-functions | authorization.apikey、master_apikey |
basic-auth | password |
cas-auth | cookie.secret |
clickhouse-logger | password |
dingtalk-auth | app_secret,secret,secret_fallbacks |
elasticsearch-logger | auth.password、headers |
feishu-auth | app_secret,secret,secret_fallbacks |
rocketmq-logger | secret_key |
sls-logger | access_key_secret |
error-log-logger | clickhouse.password、kafka.brokers.sasl_config.password |
google-cloud-logging | auth_config.private_key |
csrf | key |
hmac-auth | secret_key |
http-logger | auth_header |
jwt-auth | secret |
kafka-logger | brokers.sasl_config.password |
key-auth | key |
lago | token |
loggly | customer_token |
loki-logger | headers |
openwhisk | service_token |
openfunction | authorization.service_token |
tencent-cloud-cls | secret_key |
openid-connect | client_secret、client_rsa_private_key、dpop.private_key、session.secret、session.redis.password |
kafka-proxy | sasl.password |
jwe-decrypt | key、secret |
ai-cache | redis_password、semantic.embedding.openai.api_key、semantic.embedding.azure_openai.api_key |
ai-aliyun-content-moderation | access_key_secret |
ai-aws-content-moderation | comprehend.secret_access_key |
ai-lakera-guard | api_key |
ai-proxy | auth.header,auth.query,auth.gcp.service_account_json,auth.aws.secret_access_key,auth.aws.session_token |
ai-proxy-multi | instances.auth.header,instances.auth.query,instances.auth.gcp.service_account_json,instances.auth.aws.secret_access_key,instances.auth.aws.session_token |
ai-rag | embeddings_provider.azure_openai.api_key、vector_search_provider.azure_ai_search.api_key |
ai-rate-limiting | redis_password、sentinel_password |
aws-lambda | authorization.apikey,authorization.iam.accesskey,authorization.iam.secretkey |
ldap-auth-advanced | ldap_password |
limit-conn | redis_password |
limit-count | redis_password、sentinel_password |
limit-req | redis_password |
saml-auth | sp_private_key,secret,secret_fallbacks |
splunk-hec-logging | endpoint.token |