hmac-auth
hmac-auth 插件支持 HMAC (Hash-based Message Authentication Code) 认证,作为确保请求完整性的机制,防止其在传输过程中被修改。要使用该插件,你需要在 消费者 上配置 HMAC 密钥,并在路由或服务上启用该插件。
消费者成功通过身份认证 后,APISIX 会在将请求代理到上游服务前,为请求添加 X-Consumer-Username、X-Credential-Identifier 等请求头,以及已配置的其他消费者自定义请求头。上游服务可以据此区分不同消费者,并按需实现其他逻辑。如果某个值不可用,则不会添加对应的请求头。
使用 Ingress Controller 配置消费者时,消费者名称会生成为 namespace_consumername 格式。因此,X-Consumer-Username 请求头也会采用此格式,而不只是 consumername。
实现
启用后,插件会验证请求 Authorization 请求头中的 HMAC 签名,并检查传入请求是否来自可信来源。具体而言,当 APISIX 收到 HMAC 签名请求时,会从 Authorization 请求头中提取 Key ID,然后检索包含 Secret Key 的对应消费者配置。如果 Key ID 有效且存在,APISIX 会使用请求的 Date 请求头和 Secret Key 生成 HMAC 签名。如果生成的签名与 Authorization 请求头中提供的签名一致,请求将通过身份认证并转发到上游服务。
该插件的实现基于 draft-cavage-http-signatures。
示例
以下示例演示了如何在不同场景下使用 hmac-auth 插件。
在路由上实现 HMAC 认证
以下示例演示如何在路由上实现 HMAC 身份认证。你还将通过 X-Consumer-Custom-Id 请求头为通过身份认证的请求附加消费者自定义 ID,以便按需实现其他逻辑。
- Admin API
- ADC
- Ingress Controller
创建一个带有自定义 ID 标签的消费者 john:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john",
"labels": {
"custom_id": "495aec6a"
}
}'
为消费者创建 hmac-auth 凭据:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
使用默认配置创建一个启用 hmac-auth 插件的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "hmac-auth-route",
"uri": "/get",
"methods": ["GET"],
"plugins": {
"hmac-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个配置了 hmac-auth 凭证的消费者,以及一个配置了 hmac-auth 插件的路由:
consumers:
- username: john
labels:
custom_id: "495aec6a"
credentials:
- name: hmac-auth
type: hmac-auth
config:
key_id: john-key
secret_key: john-secret-key
services:
- name: hmac-auth-service
routes:
- name: hmac-auth-route
uris:
- /get
methods:
- GET
plugins:
hmac-auth: {}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
创建一个配置了 hmac-auth 凭证的消费者,以及一个配置了 hmac-auth 插件的路由:
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: john
labels:
custom_id: "495aec6a"
spec:
gatewayRef:
name: apisix
credentials:
- type: hmac-auth
name: primary-cred
config:
key_id: john-key
secret_key: john-secret-key
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: hmac-auth-plugin-config
spec:
plugins:
- name: hmac-auth
config:
_meta:
disable: false
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /get
method: GET
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: hmac-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: john
labels:
custom_id: "495aec6a"
spec:
ingressClassName: apisix
authParameter:
hmacAuth:
value:
key_id: john-key
secret_key: john-secret-key
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
ingressClassName: apisix
http:
- name: hmac-auth-route
match:
paths:
- /get
methods:
- GET
upstreams:
- name: httpbin-external-domain
plugins:
- name: hmac-auth
enable: true
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
生成签名。你可以使用下面的 Python 代码片段或你选择的其他技术栈:
import hmac
import hashlib
import base64
from datetime import datetime, timezone
key_id = "john-key" # key id
secret_key = b"john-secret-key" # secret key
request_method = "GET" # HTTP method
request_path = "/get" # route URI
algorithm= "hmac-sha256" # 可以使用 allowed_algorithms 中的其他算法
# 获取当前 GMT 时间
# 注意:签名将在时钟偏差时间(默认 300 秒)后失效
# 签名失效后可重新生成,或在建议的安全边界内增大时钟偏差,
# 以延长签名有效期
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# 按顺序构造签名字符串
# date 和后续自定义请求头应转换为小写,并以单个空格分隔,
# 即 `<key>:<space><value>`
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6
signing_string = (
f"{key_id}\n"
f"{request_method} {request_path}\n"
f"date: {gmt_time}\n"
)
# 创建签名
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')
# 构造请求头
headers = {
"Date": gmt_time,
"Authorization": (
f'Signature keyId="{key_id}",algorithm="{algorithm}",'
f'headers="@request-target date",'
f'signature="{signature_base64}"'
)
}
# 输出请求头
print(headers)
运行脚本:
python3 hmac-sig-header-gen.py
你应该看到打印出的请求头:
{'Date': 'Fri, 06 Sep 2024 06:41:29 GMT', 'Authorization': 'Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="wWfKQvPDr0wHQ4IHdluB4IzeNZcj0bGJs2wvoCOT5rM="'}
使用生成的请求头,向路由发送请求:
curl -X GET "http://127.0.0.1:9080/get" \
-H "Date: Fri, 06 Sep 2024 06:41:29 GMT" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="wWfKQvPDr0wHQ4IHdluB4IzeNZcj0bGJs2wvoCOT5rM="'
你应该看到类似于以下的 HTTP/1.1 200 OK 响应:
{
"args": {},
"headers": {
"Accept": "*/*",
"Authorization": "Signature keyId=\"john-key\",algorithm=\"hmac-sha256\",headers=\"@request-target date\",signature=\"wWfKQvPDr0wHQ4IHdluB4IzeNZcj0bGJs2wvoCOT5rM=\"",
"Date": "Fri, 06 Sep 2024 06:41:29 GMT",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66d96513-2e52d4f35c9b6a2772d667ea",
"X-Consumer-Username": "john",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Consumer-Custom-Id": "495aec6a",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "192.168.65.1, 34.0.34.160",
"url": "http://127.0.0.1/get"
}
如果你想将更多消费者自定义请求头添加到已认证请求中,请参阅 attach-consumer-label 插件。
从上游隐藏授权信息
如上例所示,传递给上游的 Authorization 请求头包含签名和所有其他详细信息,这可能会带来安全风险。
此示例接续上一个示例,演示如何防止将这些信息发送到上游服务。
- Admin API
- ADC
- Ingress Controller
更新插件配置,将 hide_credentials 设置为 true:
curl "http://127.0.0.1:9180/apisix/admin/routes/hmac-auth-route" -X PATCH \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"plugins": {
"hmac-auth": {
"hide_credentials": true
}
}
}'
按如下方式更新插件配置:
consumers:
- username: john
labels:
custom_id: "495aec6a"
credentials:
- name: hmac-auth
type: hmac-auth
config:
key_id: john-key
secret_key: john-secret-key
services:
- name: hmac-auth-service
routes:
- name: hmac-auth-route
uris:
- /get
methods:
- GET
plugins:
hmac-auth:
hide_credentials: true
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
更新 PluginConfig,将 hide_credentials 设置为 true:
# 其他配置
# ---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: hmac-auth-plugin-config
spec:
plugins:
- name: hmac-auth
config:
_meta:
disable: false
hide_credentials: true
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
更新 ApisixRoute,将 hide_credentials 设置为 true:
# 其他配置
# ---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
ingressClassName: apisix
http:
- name: hmac-auth-route
match:
paths:
- /get
methods:
- GET
upstreams:
- name: httpbin-external-domain
plugins:
- name: hmac-auth
enable: true
config:
hide_credentials: true
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
向路由发送请求:
curl -X GET "http://127.0.0.1:9080/get" \
-H "Date: Fri, 06 Sep 2024 06:41:29 GMT" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="wWfKQvPDr0wHQ4IHdluB4IzeNZcj0bGJs2wvoCOT5rM="'
你应该看到 HTTP/1.1 200 OK 响应,并注意到 Authorization 请求头已被完全移除:
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66d96513-2e52d4f35c9b6a2772d667ea",
"X-Consumer-Username": "john",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "192.168.65.1, 34.0.34.160",
"url": "http://127.0.0.1/get"
}
启用 Body 校验
以下示例演示了如何启用 Body 校验以确保请求体的完整性。
- Admin API
- ADC
- Ingress Controller
创建消费者 john:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john"
}'
为消费者创建 hmac-auth 凭据:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
创建一个启用 hmac-auth 插件的路由:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "hmac-auth-route",
"uri": "/post",
"methods": ["POST"],
"plugins": {
"hmac-auth": {
"validate_request_body": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个配置了 hmac-auth 凭证的消费者,以及一个配置了 hmac-auth 插件的路由:
consumers:
- username: john
credentials:
- name: hmac-auth
type: hmac-auth
config:
key_id: john-key
secret_key: john-secret-key
services:
- name: hmac-auth-service
routes:
- name: hmac-auth-route
uris:
- /post
methods:
- POST
plugins:
hmac-auth:
validate_request_body: true
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
创建一个配置了 hmac-auth 凭证的消费者,以及一个配置了 hmac-auth 插件的路由:
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: john
spec:
gatewayRef:
name: apisix
credentials:
- type: hmac-auth
name: primary-cred
config:
key_id: john-key
secret_key: john-secret-key
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: hmac-auth-plugin-config
spec:
plugins:
- name: hmac-auth
config:
_meta:
disable: false
validate_request_body: true
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /post
method: POST
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: hmac-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: john
spec:
ingressClassName: apisix
authParameter:
hmacAuth:
value:
key_id: john-key
secret_key: john-secret-key
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
ingressClassName: apisix
http:
- name: hmac-auth-route
match:
paths:
- /post
methods:
- POST
upstreams:
- name: httpbin-external-domain
plugins:
- name: hmac-auth
enable: true
config:
validate_request_body: true
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
生成签名。你可以使用下面的 Python 代码片段或你选择的其他技术栈:
import hmac
import hashlib
import base64
from datetime import datetime, timezone
key_id = "john-key" # key id
secret_key = b"john-secret-key" # secret key
request_method = "POST" # HTTP method
request_path = "/post" # route URI
algorithm= "hmac-sha256" # 可以使用 allowed_algorithms 中的其他算法
body = '{"name": "world"}' # example request body
# 获取当前 GMT 时间
# 注意:签名将在时钟偏差时间(默认 300 秒)后失效
# 签名失效后可重新生成,或在建议的安全边界内增大时钟偏差,
# 以延长签名有效期
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# 按顺序构造签名字符串
# date 和后续自定义请求头应转换为小写,并以单个空格分隔,
# 即 `<key>:<space><value>`
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6
signing_string = (
f"{key_id}\n"
f"{request_method} {request_path}\n"
f"date: {gmt_time}\n"
)
# 创建签名
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')
# 创建请求体的 SHA-256 摘要并进行 Base64 编码
body_digest = hashlib.sha256(body.encode('utf-8')).digest()
body_digest_base64 = base64.b64encode(body_digest).decode('utf-8')
# 构造请求头
headers = {
"Date": gmt_time,
"Digest": f"SHA-256={body_digest_base64}",
"Authorization": (
f'Signature keyId="{key_id}",algorithm="hmac-sha256",'
f'headers="@request-target date",'
f'signature="{signature_base64}"'
)
}
# 输出请求头
print(headers)
运行脚本:
python3 hmac-sig-digest-header-gen.py
你应该看到打印出的请求头:
{'Date': 'Fri, 06 Sep 2024 09:16:16 GMT', 'Digest': 'SHA-256=78qzJuLwSpZ8HacsTdFCQJWxzPMOf8bYctRk2ySLpS8=', 'Authorization': 'Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="rjS6NxOBKmzS8CZL05uLiAfE16hXdIpMD/L/HukOTYE="'}
使用生成的请求头,向路由发送请求:
curl "http://127.0.0.1:9080/post" -X POST \
-H "Date: Fri, 06 Sep 2024 09:16:16 GMT" \
-H "Digest: SHA-256=78qzJuLwSpZ8HacsTdFCQJWxzPMOf8bYctRk2ySLpS8=" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="rjS6NxOBKmzS8CZL05uLiAfE16hXdIpMD/L/HukOTYE="' \
-d '{"name": "world"}'
你应该看到类似于以下的 HTTP/1.1 200 OK 响应:
{
"args": {},
"data": "",
"files": {},
"form": {
"{\"name\": \"world\"}": ""
},
"headers": {
"Accept": "*/*",
"Authorization": "Signature keyId=\"john-key\",algorithm=\"hmac-sha256\",headers=\"@request-target date\",signature=\"rjS6NxOBKmzS8CZL05uLiAfE16hXdIpMD/L/HukOTYE=\"",
"Content-Length": "17",
"Content-Type": "application/x-www-form-urlencoded",
"Date": "Fri, 06 Sep 2024 09:16:16 GMT",
"Digest": "SHA-256=78qzJuLwSpZ8HacsTdFCQJWxzPMOf8bYctRk2ySLpS8=",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66d978c3-49f929ad5237da5340bbbeb4",
"X-Consumer-Username": "john",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"origin": "192.168.65.1, 34.0.34.160",
"url": "http://127.0.0.1/post"
}
如果你发送不带摘要或带无效摘要的请求:
curl "http://127.0.0.1:9080/post" -X POST \
-H "Date: Fri, 06 Sep 2024 09:16:16 GMT" \
-H "Digest: SHA-256=78qzJuLwSpZ8HacsTdFCQJWxzPMOf8bYctRk2ySLpS8=" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="rjS6NxOBKmzS8CZL05uLiAfE16hXdIpMD/L/HukOTYE="' \
-d '{"name": "world"}'
你应该看到带有以下消息的 HTTP/1.1 401 Unauthorized 响应:
{"message":"client request can't be validated"}
强制签名请求头
以下示例演示了如何强制某些请求头必须包含在请求的 HMAC 签名中。
- Admin API
- ADC
- Ingress Controller
创建消费者 john:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john"
}'
为消费者创建 hmac-auth 凭据:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
创建一个启用 hmac-auth 插件的路由,该路由要求 HMAC 签名中必须包含三个请求头:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "hmac-auth-route",
"uri": "/get",
"methods": ["GET"],
"plugins": {
"hmac-auth": {
"signed_headers": ["date","x-custom-header-a", "x-custom-header-b"]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
创建一个配置了 hmac-auth 凭证的消费者,以及一个配置了 hmac-auth 插件的路由:
consumers:
- username: john
credentials:
- name: hmac-auth
type: hmac-auth
config:
key_id: john-key
secret_key: john-secret-key
services:
- name: hmac-auth-service
routes:
- name: hmac-auth-route
uris:
- /get
methods:
- GET
plugins:
hmac-auth:
signed_headers:
- date
- x-custom-header-a
- x-custom-header-b
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
创建一个配置了 hmac-auth 凭证的消费者,以及一个配置了 hmac-auth 插件的路由:
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: john
spec:
gatewayRef:
name: apisix
credentials:
- type: hmac-auth
name: primary-cred
config:
key_id: john-key
secret_key: john-secret-key
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: hmac-auth-plugin-config
spec:
plugins:
- name: hmac-auth
config:
_meta:
disable: false
signed_headers:
- date
- x-custom-header-a
- x-custom-header-b
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /get
method: GET
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: hmac-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: john
spec:
ingressClassName: apisix
authParameter:
hmacAuth:
value:
key_id: john-key
secret_key: john-secret-key
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
ingressClassName: apisix
http:
- name: hmac-auth-route
match:
paths:
- /get
methods:
- GET
upstreams:
- name: httpbin-external-domain
plugins:
- name: hmac-auth
enable: true
config:
signed_headers:
- date
- x-custom-header-a
- x-custom-header-b
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
生成签名。你可以使用下面的 Python 代码片段或你选择的其他技术栈:
import hmac
import hashlib
import base64
from datetime import datetime, timezone
key_id = "john-key" # key id
secret_key = b"john-secret-key" # secret key
request_method = "GET" # HTTP method
request_path = "/get" # route URI
algorithm= "hmac-sha256" # 可以使用 allowed_algorithms 中的其他算法
custom_header_a = "hello123" # required custom header
custom_header_b = "world456" # required custom header
# 获取当前 GMT 时间
# 注意:签名将在时钟偏差时间(默认 300 秒)后失效
# 签名失效后可重新生成,或在建议的安全边界内增大时钟偏差,
# 以延长签名有效期
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# 按顺序构造签名字符串
# date 和后续自定义请求头应转换为小写,并以单个空格分隔,
# 即 `<key>:<space><value>`
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6
signing_string = (
f"{key_id}\n"
f"{request_method} {request_path}\n"
f"date: {gmt_time}\n"
f"x-custom-header-a: {custom_header_a}\n"
f"x-custom-header-b: {custom_header_b}\n"
)
# 创建签名
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')
# 构造请求头
headers = {
"Date": gmt_time,
"Authorization": (
f'Signature keyId="{key_id}",algorithm="hmac-sha256",'
f'headers="@request-target date x-custom-header-a x-custom-header-b",'
f'signature="{signature_base64}"'
),
"x-custom-header-a": custom_header_a,
"x-custom-header-b": custom_header_b
}
# 输出请求头
print(headers)
运行脚本:
python3 hmac-sig-req-header-gen.py
你应该看到打印出的请求头:
{'Date': 'Fri, 06 Sep 2024 09:58:49 GMT', 'Authorization': 'Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date x-custom-header-a x-custom-header-b",signature="MwJR8JOhhRLIyaHlJ3Snbrf5hv0XwdeeRiijvX3A3yE="', 'x-custom-header-a': 'hello123', 'x-custom-header-b': 'world456'}
使用生成的请求头,向路由发送请求:
curl -X GET "http://127.0.0.1:9080/get" \
-H "Date: Fri, 06 Sep 2024 09:58:49 GMT" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date x-custom-header-a x-custom-header-b",signature="MwJR8JOhhRLIyaHlJ3Snbrf5hv0XwdeeRiijvX3A3yE="' \
-H "x-custom-header-a: hello123" \
-H "x-custom-header-b: world456"
你应该看到类似于以下的 HTTP/1.1 200 OK 响应:
{
"args": {},
"headers": {
"Accept": "*/*",
"Authorization": "Signature keyId=\"john-key\",algorithm=\"hmac-sha256\",headers=\"@request-target date x-custom-header-a x-custom-header-b\",signature=\"MwJR8JOhhRLIyaHlJ3Snbrf5hv0XwdeeRiijvX3A3yE=\"",
"Date": "Fri, 06 Sep 2024 09:58:49 GMT",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66d98196-64a58db25ece71c077999ecd",
"X-Consumer-Username": "john",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Custom-Header-A": "hello123",
"X-Custom-Header-B": "world456",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "192.168.65.1, 103.97.2.206",
"url": "http://127.0.0.1/get"
}
匿名消费者的速率限制
以下示例演示如何为普通消费者和匿名消费者配置不同的限流策略。匿名消费者无需进行身份认证,但配额更少。
- Admin API
- ADC
- Ingress Controller
创建常规消费者 john 并配置 limit-count 插件,允许在 30 秒窗口内有 3 次配额:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john",
"plugins": {
"limit-count": {
"count": 3,
"time_window": 30,
"rejected_code": 429,
"policy": "local"
}
}
}'
为消费者 john 创建 hmac-auth 凭据:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
创建匿名用户 anonymous 并配置 limit-count 插件,允许在 30 秒窗口内有 1 次配额:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429,
"policy": "local"
}
}
}'
创建路由并配置 hmac-auth 插件以接受匿名消费者 anonymous 绕过认证:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "hmac-auth-route",
"uri": "/get",
"methods": ["GET"],
"plugins": {
"hmac-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
为消费者配置不同的限流策略,并创建一个接受匿名用户的路由:
consumers:
- username: john
plugins:
limit-count:
count: 3
time_window: 30
rejected_code: 429
policy: local
credentials:
- name: hmac-auth
type: hmac-auth
config:
key_id: john-key
secret_key: john-secret-key
- username: anonymous
plugins:
limit-count:
count: 1
time_window: 30
rejected_code: 429
policy: local
services:
- name: anonymous-rate-limit-service
routes:
- name: hmac-auth-route
uris:
- /get
methods:
- GET
plugins:
hmac-auth:
anonymous_consumer: anonymous
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
将配置同步到网关:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
为消费者配置不同的限流策略,并创建一个接受匿名用户的路由:
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: john
spec:
gatewayRef:
name: apisix
credentials:
- type: hmac-auth
name: primary-cred
config:
key_id: john-key
secret_key: john-secret-key
plugins:
- name: limit-count
config:
count: 3
time_window: 30
rejected_code: 429
policy: local
---
apiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: anonymous
spec:
gatewayRef:
name: apisix
plugins:
- name: limit-count
config:
count: 1
time_window: 30
rejected_code: 429
policy: local
---
apiVersion: v1
kind: Service
metadata:
namespace: aic
name: httpbin-external-domain
spec:
type: ExternalName
externalName: httpbin.org
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: hmac-auth-plugin-config
spec:
plugins:
- name: hmac-auth
config:
anonymous_consumer: aic_anonymous # namespace_consumername
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /get
method: GET
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: hmac-auth-plugin-config
backendRefs:
- name: httpbin-external-domain
port: 80
将配置应用到集群:
kubectl apply -f hmac-auth-ic.yaml
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: john
spec:
ingressClassName: apisix
authParameter:
hmacAuth:
value:
key_id: john-key
secret_key: john-secret-key
plugins:
- name: limit-count
config:
count: 3
time_window: 30
rejected_code: 429
policy: local
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: anonymous
spec:
ingressClassName: apisix
plugins:
- name: limit-count
config:
count: 1
time_window: 30
rejected_code: 429
policy: local
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: httpbin-external-domain
spec:
ingressClassName: apisix
externalNodes:
- type: Domain
name: httpbin.org
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: hmac-auth-route
spec:
ingressClassName: apisix
http:
- name: hmac-auth-route
match:
paths:
- /get
methods:
- GET
upstreams:
- name: httpbin-external-domain
plugins:
- name: hmac-auth
config:
anonymous_consumer: aic_anonymous
将配置应用到集群:
kubectl apply -f hmac-auth-apisix-crd.yaml
生成签名。你可以使用下面的 Python 代码片段或你选择的其他技术栈:
import hmac
import hashlib
import base64
from datetime import datetime, timezone
key_id = "john-key" # key id
secret_key = b"john-secret-key" # secret key
request_method = "GET" # HTTP method
request_path = "/get" # route URI
algorithm= "hmac-sha256" # 可以使用 allowed_algorithms 中的其他算法
# 获取当前 GMT 时间
# 注意:签名将在时钟偏差时间(默认 300 秒)后失效
# 签名失效后可重新生成,或在建议的安全边界内增大时钟偏差,
# 以延长签名有效期
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# 按顺序构造签名字符串
# date 和后续自定义请求头应转换为小写,并以单个空格分隔,
# 即 `<key>:<space><value>`
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6
signing_string = (
f"{key_id}\n"
f"{request_method} {request_path}\n"
f"date: {gmt_time}\n"
)
# 创建签名
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')
# 构造请求头
headers = {
"Date": gmt_time,
"Authorization": (
f'Signature keyId="{key_id}",algorithm="{algorithm}",'
f'headers="@request-target date",'
f'signature="{signature_base64}"'
)
}
# 输出请求头
print(headers)
运行脚本:
python3 hmac-sig-header-gen.py
你应该看到打印出的请求头:
{'Date': 'Mon, 21 Oct 2024 17:31:18 GMT', 'Authorization': 'Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="ztFfl9w7LmCrIuPjRC/DWSF4gN6Bt8dBBz4y+u1pzt8="'}
要进行验证,请使用生成的请求头发送五个连续请求:
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/get" -H "Date: Mon, 21 Oct 2024 17:31:18 GMT" -H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="ztFfl9w7LmCrIuPjRC/DWSF4gN6Bt8dBBz4y+u1pzt8="' -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
你应该看到以下响应,显示在 5 个请求中,3 个请求成功(状态码 200),而其他请求被拒绝(状态码 429)。
200: 3, 429: 2
发送五个匿名请求:
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/get" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
你应该看到以下响应,显示只有一个请求成功:
200: 1, 429: 4