🤔 Introducing APISIX AI Gateway – Built for LLMs and AI workloads. Learn More

hmac-auth

描述

hmac-auth 插件支持 HMAC(基于哈希的消息认证码)认证,作为一种确保请求完整性的机制,防止它们在传输过程中被修改。要使用该插件,您需要在 Consumers 上配置 HMAC 密钥,并在 Routes 或 Services 上启用该插件。

当 Consumer 成功通过身份验证后,APISIX 会在将请求代理到上游服务之前向请求添加其他标头,例如 X-Consumer-UsernameX-Credential-Identifier 和其他 Consumer 自定义标头(如果已配置)。上游服务将能够区分 Consumer 并根据需要实现其他逻辑。如果这些值中的任何一个不可用,则不会添加相应的标头。

实现原理

启用后,插件会验证请求 Authorization: Signature ... 中携带的签名,以确认请求内容未被篡改且来自持有对应密钥的调用方。具体来说,APISIX 会先从 Authorization 标头中解析 keyIdalgorithmsignatureheaders 等参数,再根据 keyId 查找对应的 Consumer 或 Credential 配置并获取密钥。随后,插件会按照 headers 列表中声明的顺序拼接 signing string,其中可包含 @request-target 以及其他参与签名的请求标头,然后使用指定算法和密钥生成 HMAC,并将结果与 signature 参数经 base64 解码后的值进行比较。只有两者一致时,请求才会通过身份验证并转发到上游服务。

Date 标头主要用于 clock skew(时钟偏移)校验;只有当它被显式包含在 headers 列表中时,才会作为 signing string 的一部分参与签名计算。 插件实现基于 draft-cavage-http-signatures

属性

以下属性可用于 Consumers 或 Credentials 的配置。

名称类型必选项默认值有效值描述
key_idstring用于标识 HMAC 密钥/凭证的 keyId,并用于查找关联的 Consumer/Credential。
secret_keystring用于生成 HMAC 的密钥。此字段支持使用 APISIX Secret 资源将值保存在 Secret Manager 中。

注意:schema 中还定义了 encrypt_fields = {"secret_key"},这意味着该字段将会被加密存储在 etcd 中。具体参考 加密存储字段

以下属性可用于 Routes 或 Services 的配置。

名称类型必选项默认值有效值描述
allowed_algorithmsarray[string]["hmac-sha1", "hmac-sha256", "hmac-sha512"]"hmac-sha1""hmac-sha256""hmac-sha512" 的组合允许的 HMAC 算法列表。
clock_skewinteger300>=1请求的 Date 头(必须为 GMT 格式)与 APISIX 当前时间之间允许的最大时间差(秒)。该机制用于在客户端与服务端时钟基本同步的前提下拒绝过期请求。要使该时效窗口具有实际意义,date 必须出现在 signed_headers 中,从而将 Date 的取值绑定进签名字符串。signed_headers 默认值为 ["date"],开箱即默认完成该绑定。
signed_headersarray[string]["date"]HMAC 签名字符串中必须包含的请求头列表。默认值为 ["date"],确保 Date 头始终被绑定进签名字符串,使得 clock_skew 时效校验所依据的取值同样受 HMAC 覆盖。如需覆盖默认值,请显式设置该字段;设置为空数组 [] 则完全取消该要求。
validate_request_bodybooleanfalse如果为 true,则验证请求正文的完整性,以确保在传输过程中没有被篡改。具体来说,插件会创建一个 SHA-256 的 base64 编码 digest,并将其与 Digest 头进行比较。如果 Digest 头丢失或 digest 不匹配,验证将失败。
hide_credentialsbooleanfalse如果为 true,则不会将授权请求头传递给上游服务。
anonymous_consumerstring匿名 Consumer 名称。如果已配置,则允许匿名用户绕过身份验证。
realmstringhmac在身份验证失败时,WWW-Authenticate 响应标头中返回的域,状态码为 401 Unauthorized

示例

下面的示例说明了如何在不同场景中使用 hmac-auth 插件。

NOTE

您可以这样从 config.yaml 中获取 admin_key 并存入环境变量:

admin_key=$(yq '.deployment.admin.admin_key[0].key' /usr/local/apisix/conf/config.yaml | sed 's/"//g')

在 Route 上实现 HMAC 身份验证

以下示例演示如何在 Route 上实现 HMAC 身份验证。您还将在 X-Consumer-Custom-Id 标头中将 Consumer 自定义 ID 附加到经过身份验证的请求,该 ID 可用于根据需要实现其他逻辑。

admin-api

创建一个带有自定义 ID 标签的 Consumer john

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "username": "john",
    "labels": {
      "custom_id": "495aec6a"
    }
  }'

为 Consumer 创建 hmac-auth Credential:

curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cred-john-hmac-auth",
    "plugins": {
      "hmac-auth": {
        "key_id": "john-key",
        "secret_key": "john-secret-key"
      }
    }
  }'

使用 hmac-auth 插件的默认配置创建 Route:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "hmac-auth-route",
    "uri": "/get",
    "methods": ["GET"],
    "plugins": {
      "hmac-auth": {}
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

adc

创建带有 hmac-auth Credential 的 Consumer 以及配置了 hmac-auth 插件的 Route:

adc.yaml
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

aic

gateway-api

hmac-auth-ic.yaml
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

apisix-crd

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 代码片段或其他技术栈:

hmac-sig-header-gen.py



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"           # can use other algorithms in allowed_algorithms

# get current datetime in GMT
# note: the signature will become invalid after the clock skew (default 300s)
# you can regenerate the signature after it becomes invalid, or increase the clock
# skew to prolong the validity within the advised security boundary
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')

# construct the signing string (ordered)
# the date and any subsequent custom headers should be lowercased and separated by a
# single space character, i.e. `<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"
)

# create signature
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')

# construct the request headers
headers = {
  "Date": gmt_time,
  "Authorization": (
    f'Signature keyId="{key_id}",algorithm="{algorithm}",'
    f'headers="@request-target date",'
    f'signature="{signature_base64}"'
  )
}

# print headers
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="'}

使用生成的标头,向 Route 发送请求:

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": "aic_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"
}

对上游隐藏授权信息

上一个示例所示,传递给上游的 Authorization 标头包含签名和所有其他详细信息。这可能会带来安全风险。

本示例继续上一个示例,演示如何阻止将这些信息发送到上游服务。

admin-api

更新插件配置,将 hide_credentials 设置为 true

curl "http://127.0.0.1:9180/apisix/admin/routes/hmac-auth-route" -X PATCH \
-H "X-API-KEY: ${admin_key}" \
-d '{
  "plugins": {
    "hmac-auth": {
      "hide_credentials": true
    }
  }
}'

adc

更新插件配置:

adc.yaml
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

aic

gateway-api

更新 PluginConfig,将 hide_credentials 设置为 true

hmac-auth-ic.yaml
# other configs
# ---
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

apisix-crd

更新 ApisixRoute,将 hide_credentials 设置为 true

hmac-auth-ic.yaml
# other configs
# ---
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

向 Route 发送请求:

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"
}

启用请求正文验证

以下示例演示如何启用请求正文验证以确保请求正文的完整性。

admin-api

创建 Consumer john

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "username": "john"
  }'

为 Consumer 创建 hmac-auth Credential:

curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cred-john-hmac-auth",
    "plugins": {
      "hmac-auth": {
        "key_id": "john-key",
        "secret_key": "john-secret-key"
      }
    }
  }'

创建带有 hmac-auth 插件的 Route:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_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
      }
    }
  }'

adc

创建带有 hmac-auth Credential 的 Consumer 以及配置了 hmac-auth 插件的 Route:

adc.yaml
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

aic

gateway-api

hmac-auth-ic.yaml
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

apisix-crd

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 代码片段或其他技术栈:

hmac-sig-digest-header-gen.py



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"            # can use other algorithms in allowed_algorithms
body = '{"name": "world"}'          # example request body

# get current datetime in GMT
# note: the signature will become invalid after the clock skew (default 300s).
# you can regenerate the signature after it becomes invalid, or increase the clock
# skew to prolong the validity within the advised security boundary
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')

# construct the signing string (ordered)
# the date and any subsequent custom headers should be lowercased and separated by a
# single space character, i.e. `<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"
)

# create signature
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')

# create the SHA-256 digest of the request body and base64 encode it
body_digest = hashlib.sha256(body.encode('utf-8')).digest()
body_digest_base64 = base64.b64encode(body_digest).decode('utf-8')

# construct the request headers
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
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="'}

使用生成的标头,向 Route 发送请求:

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"
}

如果您发送的请求没有 digest 或 digest 无效:

curl "http://127.0.0.1:9080/post" -X POST \
  -H "Date: Fri, 06 Sep 2024 09:16:16 GMT" \
  -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

创建 Consumer john

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "username": "john"
  }'

为 Consumer 创建 hmac-auth Credential:

curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "cred-john-hmac-auth",
    "plugins": {
      "hmac-auth": {
        "key_id": "john-key",
        "secret_key": "john-secret-key"
      }
    }
  }'

使用 hmac-auth 插件创建 Route,该插件要求 HMAC 签名中存在三个标头:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_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
      }
    }
  }'

adc

创建带有 hmac-auth Credential 的 Consumer 以及配置了 hmac-auth 插件的 Route:

adc.yaml
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

aic

gateway-api

hmac-auth-ic.yaml
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

apisix-crd

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 代码片段或其他技术栈:

hmac-sig-req-header-gen.py



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"           # can use other algorithms in allowed_algorithms
custom_header_a = "hello123"       # required custom header
custom_header_b = "world456"       # required custom header

# get current datetime in GMT
# note: the signature will become invalid after the clock skew (default 300s)
# you can regenerate the signature after it becomes invalid, or increase the clock
# skew to prolong the validity within the advised security boundary
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')

# construct the signing string (ordered)
# the date and any subsequent custom headers should be lowercased and separated by a
# single space character, i.e. `<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"
)

# create signature
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')

# construct the request headers
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
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'}

使用生成的标头,向 Route 发送请求:

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"
}

匿名 Consumer 的速率限制

以下示例演示了如何为常规 Consumer 和匿名 Consumer 配置不同的速率限制策略,其中匿名 Consumer 不需要进行身份验证,配额较少。

admin-api

创建常规 Consumer john,并配置 limit-count 插件,以允许 30 秒内的配额为 3:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "username": "john",
    "plugins": {
      "limit-count": {
        "count": 3,
        "time_window": 30,
        "rejected_code": 429,
        "policy": "local"
      }
    }
  }'

为 Consumer john 创建 hmac-auth Credential:

curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
  -H "X-API-KEY: ${admin_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_key}" \
  -d '{
    "username": "anonymous",
    "plugins": {
      "limit-count": {
        "count": 1,
        "time_window": 30,
        "rejected_code": 429,
        "policy": "local"
      }
    }
  }'

创建 Route 并配置 hmac-auth 插件以接受匿名 Consumer anonymous 绕过身份验证:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "hmac-auth-route",
    "uri": "/get",
    "methods": ["GET"],
    "plugins": {
      "hmac-auth": {
        "anonymous_consumer": "anonymous"
      }
    },
    "upstream": {
      "type": "roundrobin",
      "nodes": {
        "httpbin.org:80": 1
      }
    }
  }'

adc

配置具有不同速率限制的 Consumer 以及接受匿名用户的 Route:

adc.yaml
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

aic

gateway-api

配置具有不同速率限制的 Consumer 以及接受匿名用户的 Route:

hmac-auth-ic.yaml
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

apisix-crd

配置具有不同速率限制的 Consumer 以及接受匿名用户的 Route:

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
      enable: true
      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
      enable: true
      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
          enable: true
          config:
            anonymous_consumer: aic_anonymous  # namespace_consumername

将配置应用到集群:

kubectl apply -f hmac-auth-ic.yaml

生成签名。您可以使用以下 Python 代码片段或其他技术栈:

hmac-sig-header-gen.py



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"           # can use other algorithms in allowed_algorithms

# get current datetime in GMT
# note: the signature will become invalid after the clock skew (default 300s)
# you can regenerate the signature after it becomes invalid, or increase the clock
# skew to prolong the validity within the advised security boundary
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')

# construct the signing string (ordered)
# the date and any subsequent custom headers should be lowercased and separated by a
# single space character, i.e. `<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"
)

# create signature
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')

# construct the request headers
headers = {
  "Date": gmt_time,
  "Authorization": (
    f'Signature keyId="{key_id}",algorithm="{algorithm}",'
    f'headers="@request-target date",'
    f'signature="{signature_base64}"'
  )
}

# print headers
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