关于后端:Apache-APISIX-集成-HashiCorp-Vault生态系统再添一员

5次阅读

共计 8617 个字符,预计需要花费 22 分钟才能阅读完成。

随着微服务架构的衰亡,放弃服务平安变得比以前更有挑战性。多个后端 server 实例应用繁多的动态密钥凭拜访数据库 server 会带来微小的危险。如果产生密钥凭证泄露,整个零碎都会受到影响。为了解决密钥凭证泄露所带来的影响,只能撤销这个密钥凭证。而撤销密钥凭证会导致大规模的服务中断。对开发者来说,服务大规模中断是开发者最不想看到事件。

尽管咱们不能提前预知未来会呈现哪些安全漏洞,然而咱们能够通过配置多个密钥来管制这些安全漏洞的影响范畴。为了防止这样的状况,像 HashiCorp Vault(下文简称 Vault)这样密钥凭证解决方案 应运而生。本文演示了如何将 Vault 与 Apache APISIX 的 jwt-auth 插件集成,在为服务提供高并发低提早的卓越性能的同时,为服务的平安保驾护航。

什么是 Vault

Vault 旨在帮忙用户治理服务密钥的拜访权限,并在多个服务之间平安地传输这些密钥。密钥能够是任何模式的凭证,因为密钥可用于解锁敏感信息,所以须要严密控制密钥。密钥的模式能够是明码、API 密钥、SSH 密钥、RSA 令牌或 OTP。事实上,密钥泄露的状况十分广泛:密钥通常被贮存在配置文件中,或作为变量被贮存在代码中,如果没有妥善保留,密钥甚至会呈现在 GitHub、BitBucket 或 GitLab 等公开可见的代码库中,从而对平安形成了重大威逼。Vault 通过集中密钥解决了这个问题。它为动态密钥提供加密存储,生成具 有 TTL 租约的动静密钥,对用户进行认证,以确保他们有权限拜访特定的密钥。因而,即便在安全漏洞的状况下,影响范畴也小得多,并能失去更好的管制。

Vault 提供了一个用户界面用于密钥治理,使管制和管理权限变得非常容易。不仅如此,它还提供了灵便且具体审计日志性能,能跟踪到所有用户的历史拜访记录。

[外链图片转存失败, 源站可能有防盗链机制, 倡议将图片保留下来间接上传(img-snN47EY4-1645609372957)(https://tfzcfxawmk.feishu.cn/…)]

jwt-auth 插件介绍

jwt-auth 是一个认证插件,能够附加到任何 APISIX 路由上,在申请被转发到上游 URI 之前执行 JWT 认证。通常状况下,发行者应用私钥或文本密钥来签订 JWT。JWT 的接收者将验证签名,以确保令牌在被发行者签名后没有被扭转。整个 JWT 机制的整体完整性取决于签名密钥(或 RSA 密钥对的文本密钥)。这使得未经认证的起源很难猜到签名密钥并试图扭转 JWT 中的申明。

因而,在平安的环境中存储这些密钥是十分要害的。如果密钥落入好人之手,可能会危及整个基础设施的平安。尽管 Apache APISIX 采取了所有伎俩来遵循规范的 SecOps 实际,但在生产环境中有一个集中的密钥治理解决方案也是一件坏事。例如 Vault,有具体的审计日志,定时的密钥轮换,密钥撤销等性能。如果每次在整个基础设施产生密钥轮换时,你都要更新 Apache APISIX 配置,这将是一个相当麻烦的问题。

如何集成 Vault 和 Apache APISIX

为了与 Vault 集成,Apache APISIX 须要在 config.yaml 文件中加载 Vault 的相干配置信息。

Apache APISIX 与 Vault server KV Secrets Engine v1 HTTP APIs 进行通信。因为大多数企业解决方案偏向于在生产环境中应用 KV Secrets Engine – Version 1,在 APISIX-Vault 反对的初始阶段,咱们只应用这个版本。在当前的版本中,咱们将减少对 K/V – Version 2 的反对。

应用 Vault 而不是 APISIX etcd 后端的次要顾虑是在低信任度环境下的平安问题。因为 Vault 拜访令牌是小范畴的,能够授予 APISIX server 无限的权限。

配置 Vault

本节分享了在 Apache APISIX 生态系统中应用 Vault 的最佳实际。如果你曾经有了一个具备必要权限的 Vault 实例在运行,请跳过本节。

第 1 步:启动 Vault server

在这里,你有多种抉择,能够自由选择 docker、预编译二进制包或从源代码构建。至于与 Vault server 的通信,你须要一个 Vault CLI 客户端。请运行以下命令启动 server:

$ vault server -dev -dev-root-token-id=root
…
WARNING! dev mode is enabled! In this mode, Vault runs entirely in-memory
and starts unsealed with a single unseal key. The root token is already
authenticated to the CLI, so you can immediately begin using Vault.
You may need to set the following environment variable:
export VAULT_ADDR='http://127.0.0.1:8200'
The unseal key and root token are displayed below in case you want to
seal/unseal the Vault or re-authenticate.
Unseal Key: 12hURx2eDPKK1tzK+8TkgH9pPhPNJFpyfc/imCLgJKY=
Root Token: root
Development mode should NOT be used in production installations!

用正确的环境变量设置 Vault CLI 客户端。

$ export VAULT_ADDR='http://127.0.0.1:8200'
$ export VAULT_TOKEN='root'

用一个适合的 path 前缀启用 vault k/v version 1 的密钥引擎后端。在这个演示中,咱们要抉择 kv 门路,这样就不会与 vault 默认的 kv 版本 2 的密钥门路发生冲突。

$ vault secrets enable -path=kv -version=1 kv
Success! Enabled the kv secrets engine at: kv/


# To reconfirm the status, run
$ vault secrets list
Path          Type         Accessor              Description
----          ----         --------              -----------
cubbyhole/    cubbyhole    cubbyhole_4eeb394c    per-token private secret storage
identity/     identity     identity_5ca6201e     identity store
kv/           kv           kv_92cd6d37           n/a
secret/       kv           kv_6dd46a53           key/value secret storage
sys/          system       system_2045ddb1       system endpoints used for control, policy and debugging

第 2 步:为 Apache APISIX 生成一个 Vault 拜访令牌

本文是对于在 jwt-auth 插件中应用 Vault 的观点。因而,对于一个 APISIX 消费者 jackjwt-auth 插件会在 <vault.prefix inside config.yaml>/consumer/<consumer.username>/jwt-auth 中查找(如果启用了 Vault 配置)secret/s 到 Vault 键值对 存储。在这种状况下,如果你将 kv/apisix 命名空间(Vault 门路)指定为 config.yaml 内的 vault.prefix,用于所有 APISIX 相干数据的检索,咱们倡议你为门路kv/apisix/consumer/ 创立一个策略。最初的星号(*)确保策略容许读取任何具备 kv/apisix/consumer 前缀的门路。

用 HashiCorp 配置语言(HCL)创立一个策略文件。

$ tee apisix-policy.hcl << EOF
path "kv/apisix/consumer/*" {capabilities = ["read"]
}
EOF

将策略利用于 Vault 实例。

$ vault policy write apisix-policy apisix-policy.hcl

Success! Uploaded policy: apisix-policy

用新定义的策略生成一个令牌,该策略已被配置为很小的拜访边界。

$ vault token create -policy="apisix-policy"


Key                  Value
---                  -----
token                s.KUWFVhIXgoRuQbbp3j1eMVGa
token_accessor       nPXT3q0mfZkLmhshfioOyx8L
token_duration       768h
token_renewable      true
token_policies       ["apisix-policy" "default"]
identity_policies    []
policies             ["apisix-policy" "default"]

在这个演示中,s.KUWFVhIXgoRuQbbp3j1eMVGa 是你的拜访令牌。

在 Apache APISIX 中增加 Vault 配置

Apache APISIX 通过 Vault HTTP APIs 与 Vault 实例进行通信。必要的配置必须被增加到 config.yaml 中。

上面是对于你能够应用的不同字段的简要信息。

  • host: 运行 Vault 服务器的主机地址。
  • timeout: 每次申请的 HTTP 超时。
  • token: 从金库实例生成的令牌,能够授予从金库读取数据的权限。

    • prefix:启用前缀能够更好地执行策略,生成无限范畴的令牌,严格控制能够从 APISIX 拜访的数据。无效的前缀是(kv/apisix,secret 等)。
vault:
  host: 'http://0.0.0.0:8200'
  timeout: 10
  token: 's.KUWFVhIXgoRuQbbp3j1eMVGa'
  prefix: 'kv/apisix'

创立一个 APISIX Consumer

APISIX 有一个消费者层面的形象,与认证计划并列。为了启用任何 APISIX 路由的认证,须要一个具备适宜该特定类型认证服务的配置的消费者。而后,只有 APISIX 能够通过胜利执行消费者配置方面的认证,将申请转发到上游 URI。APISIX 消费者有两个字段:一个是username(必填项),用于辨认消费者,另一个是plugins,用于保留消费者所应用的特定插件配置。

在这里,在这篇文章中,咱们将用 jwt-auth 插件创立一个消费者。它为各自的路由或服务执行 JWT 认证。

运行以下命令,启用 Vault 配置的 jwt-auth。

$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{"username":"jack","plugins": {"jwt-auth": {"key":"test-key","vault": {}
        }
    }
}'

在这个实例外面,插件在消费者配置中提到的消费者用户 jack 的 Vault 门路(<vault.prefix from conf.yaml>/consumer/jack/jwt-auth)中查找密钥secret,并将其用于后续的签名和 jwt 验证。如果在同一门路中没有找到密钥,该插件会记录谬误,并且无奈执行 jwt 验证。

设置一个测试的上游服务器

为了测试这个行为,你能够为一个上游创立一个路由(一个简略的 ping 处理程序,返回 pong)。你能够用一个一般的 go HTTP-Server 来设置它。

// simple upstream server
package main


import "net/http"


func ping(w http.ResponseWriter, req *http.Request) {w.Write([]byte("secure/pong\n"))
}


func main() {http.HandleFunc("/secure/ping", ping)
    http.ListenAndServe(":9999", nil)
}

在这里,插件在消费者配置中提到的消费者 jack 的 Vault 门路(<vault.prefix from conf.yaml>/consumer/jack/jwt-auth)中查找密钥机密,并应用它进行后续的签名和 jwt 验证。如果在同一门路中没有找到密钥,该插件会记录谬误,并且无奈执行 jwt 验证。

创立一个启用了认证的 APISIX 路由

用这个平安的 ping HTTP 服务器和启用了 jwt-auth 认证插件创立一个 APISIX 路由。

$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{"plugins": {"jwt-auth": {}
    },
    "upstream": {
        "nodes": {"127.0.0.1:9999": 1},
        "type": "roundrobin"
    },
    "uri": "/secure/ping"
}'

从 jwt-auth 插件生成令牌

当初从 APISIX 签订一个 jwt 密文,能够用于并通过向 APISIX 服务器的http://localhost:9080/secure/ping 代理路由发出请求。

$ curl http://127.0.0.1:9080/apisix/plugin/jwt/sign\?key\=test-key -i
HTTP/1.1 200 OK
Date: Tue, 18 Jan 2022 07:50:57 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.11.0


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODY1N30.nkyev1_KUapVgY_QVYETsSApA6gEkDWS8tsHFV1EpD8

在上一步中,如果你看到相似签订 jwt 失败的信息,请确保你有一个公有密钥存储在 vault kv/apisix/consumers/jack/jwt-auth 门路中。

# example
$ vault kv put kv/apisix/consumer/jack/jwt-auth secret=$ecr3t-c0d3
Success! Data written to: kv/apisix/consumer/jack/jwt-auth

向 APISIX Server 发送申请

当初,向 APISIX 代理收回一个路由 /secure/ping 的申请。验证胜利后,它将把申请转发给咱们的 go HTTP 服务器。

$ curl http://127.0.0.1:9080/secure/ping -H 'Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJ0ZXN0LWtleSIsImV4cCI6MTY0MjU3ODU5M30.IYudBr7FTgRme70u4rEBoYNtGmGByzgfGlt8hctI__Q' -i
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Content-Length: 12
Connection: keep-alive
Date: Tue, 18 Jan 2022 08:00:04 GMT
Server: APISIX/2.11.0

secure/pong

任何有效的 jwt 申请都会抛出 HTTP 401 未受权的谬误。

$ curl http://127.0.0.1:9080/secure/ping -i
HTTP/1.1 401 Unauthorized
Date: Tue, 18 Jan 2022 08:00:33 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.11.0


{"message":"Missing JWT token in request"}

Vault 能够与 APISIX jwt-auth 插件集成的不同用例

Apache APISIX jwt-auth 插件能够被配置为从 Vault 存储中获取简略的文本密钥以及 RS256 公私密钥对。

Note:对于该集成反对的晚期版本,该插件心愿存储到金库门路中的密钥名称在 [ *secret*, *public_key*, *private_key* ] 之间,以胜利应用该密钥。在将来的版本中,咱们将减少对援用自定义命名的密钥的反对。

  1. 你在保险库内存储了 HS256 签名密钥,你想用它来进行 jwt 签名和验证。
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{"username":"jack","plugins": {"jwt-auth": {"key":"key-1","vault": {}
        }
    }
}'

在这里,插件在消费者配置中提到的消费者用户名 jack 的 Vault 门路(<vault.prefix from conf.yaml>/consumer/jack/jwt-auth)中查找密钥secret,并应用它进行后续的签名和 jwt 验证。如果在同一门路中没有找到密钥,该插件将记录一个谬误,并且无奈执行 jwt 验证。

  1. RS256 RSA 密钥对,公钥和私钥都存储在 Vault 中。
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{"username":"jim","plugins": {"jwt-auth": {"key":"rsa-keypair","algorithm":"RS256","vault": {}
        }
    }
}'

该插件在 Vault 键值对 门路(<vault.prefix from conf.yaml>/consumer/jim/jwt-auth)中为插件 vault 配置中提到的用户 jim 查找 public_keyprivate_key。如果没有找到,认证失败。

如果你不确定如何将公钥和私钥存储到 Vault 键值对 中,请应用这个命令

# provided, your current directory contains the files named "public.pem" and "private.pem"
$ vault kv put kv/apisix/consumer/jim/jwt-auth public_key=@public.pem private_key=@private.pem
Success! Data written to: kv/apisix/consumer/jim/jwt-auth
  1. 消费者配置中的公钥,而私钥在 Vault 中。
$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{"username":"john","plugins": {"jwt-auth": {"key":"user-key","algorithm":"RS256","public_key":"-----BEGIN PUBLIC KEY-----\n……\n-----END PUBLIC KEY-----""vault": {}
        }
    }
}'

这个插件应用来自消费者配置的 RSA 公钥,并应用间接从 Vault 获取的私钥。

禁用 Vault 插件

当初,要禁用 jwt-auth 插件的 Vault 查问,只需从消费者插件配置中删除空的 Vault 对象(本例中是 jack)。这将使 jwt 插件在随后对已启用 jwt-auth 配置的 URI 路由的申请中,将查找签名密钥(包含 HS256/HS512 或 RS512 密钥对)纳入插件配置。即便你在 APISIX config.yaml 中启用了 Vault 配置,也不会有申请被发送到 Vault 服务器。

APISIX 插件是热加载的,因而不须要重新启动 APISIX。

$ curl http://127.0.0.1:9080/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '{"username":"jack","plugins": {"jwt-auth": {"key":"test-key","secret":"my-secret-key"}
    }
}'
正文完
 0