From bba758b1ffe87331acf54f6b8d67f1a045681622 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 15:04:38 +0800 Subject: [PATCH 01/28] feat(secret): support aws secret manager --- apisix-master-0.rockspec | 4 +- apisix/secret/aws.lua | 191 ++++++++++++++++++ ci/init-common-test-service.sh | 4 + ci/pod/docker-compose.common.yml | 9 + t/secret/aws.t | 322 +++++++++++++++++++++++++++++++ 5 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 apisix/secret/aws.lua create mode 100644 t/secret/aws.t diff --git a/apisix-master-0.rockspec b/apisix-master-0.rockspec index 6a65f502e0b7..470e1e330309 100644 --- a/apisix-master-0.rockspec +++ b/apisix-master-0.rockspec @@ -81,7 +81,9 @@ dependencies = { "lua-resty-ldap = 0.1.0-0", "lua-resty-t1k = 1.1.5", "brotli-ffi = 0.3-1", - "lua-ffi-zlib = 0.6-0" + "lua-ffi-zlib = 0.6-0", + "lua-resty-aws == 1.5.0", + "luatz == 0.4", } build = { diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua new file mode 100644 index 000000000000..6a501cfadae3 --- /dev/null +++ b/apisix/secret/aws.lua @@ -0,0 +1,191 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +--- AWS Tools. +local core = require("apisix.core") +local norm_path = require("pl.path").normpath +local http = require("resty.http") +local aws = require("resty.aws") + +local sub = core.string.sub +local rfind_char = core.string.rfind_char +local env = core.env + +--- AWS Environment Configuration +local AWS +local AWS_ACCESS_KEY_ID +local AWS_SECRET_ACCESS_KEY +local AWS_SESSION_TOKEN +local AWS_REGION + +local schema = { + type = "object", + properties = { + access_key_id = { + type = "string", + }, + secret_access_key = { + type = "string", + }, + session_token = { + type = "string", + }, + region = { + type = "string", + }, + endpoint_url = core.schema.uri_def, + }, +} + +local _M = { + schema = schema +} + +local function initialize_aws() + AWS_ACCESS_KEY_ID = env.fetch_by_uri("$ENV://AWS_ACCESS_KEY_ID") + AWS_SECRET_ACCESS_KEY = env.fetch_by_uri("$ENV://AWS_SECRET_ACCESS_KEY") + AWS_SESSION_TOKEN = env.fetch_by_uri("$ENV://AWS_SESSION_TOKEN") + AWS_REGION = env.fetch_by_uri("$ENV://AWS_REGION") + AWS = aws() + initialize_aws= nil +end + +local function make_request_to_aws(conf,key) + if initialize_aws then + initialize_aws() + end + + local region = conf.region or AWS_REGION + if not region then + return nil, "aws secret manager requires region" + end + + local access_key_id = env.fetch_by_uri(conf.access_key_id) + if not access_key_id then + access_key_id = conf.access_key_id or AWS_ACCESS_KEY_ID + end + + local secret_access_key = env.fetch_by_uri(conf.secret_access_key) + if not secret_access_key then + secret_access_key = conf.secret_access_key or AWS_SECRET_ACCESS_KEY + end + + local session_token = env.fetch_by_uri(conf.session_token) + if not session_token then + session_token = conf.session_token or AWS_SESSION_TOKEN + end + + local my_creds = nil + if access_key_id and secret_access_key then + my_creds = AWS:Credentials { + accessKeyId = access_key_id, + secretAccessKey = secret_access_key, + sessionToken = session_token, + } + end + + if not my_creds then + return nil, "unable to retrieve secret from aws secret manager (invalid credentials)" + end + + AWS.config.credentials = my_creds + + local pre, host, port, _, _ = unpack(http:parse_uri(conf.endpoint_url or "https://secretsmanager." .. region .. ".amazonaws.com")) + local endpoint = pre .. "://" .. host + + local sm = AWS:SecretsManager { + endpoint = endpoint, + region = region, + port = port, + } + + local res, err = sm:getSecretValue { + SecretId = key, + VersionStage = "AWSCURRENT", + } + + if type(res) ~= "table" then + if err then + return nil, "unable to retrieve secret from aws secret manager " .. err + end + return nil, "unable to retrieve secret from aws secret manager (invalid response)" + end + + if res.status ~= 200 then + local body = res.body + if type(body) == "table" then + err = core.json.decode(body) + end + + if err then + return nil, "failed to retrieve secret from aws secret manager " .. err + end + + return nil, "failed to retrieve secret from aws secret manager (invalid status code received)" + end + + local body = res.body + if type(body) ~= "table" then + return nil, "unable to retrieve secret from aws secret manager (invalid response)" + end + + local secret = res.body.SecretString + if type(secret) ~= "string" then + return nil, "unable to retrieve secret from aws secret manager (invalid secret string)" + end + + return secret +end + +-- key is the aws secretId +local function get(conf,key) + core.log.info("fetching data from aws for key: ", key) + + local idx = rfind_char(key, '/') + if not idx then + return nil, "error key format, key: " .. key + end + + local main_key = sub(key, 1, idx - 1) + if main_key == "" then + return nil, "can't find main key, key: " .. key + end + + local sub_key = sub(key, idx + 1) + if sub_key == "" then + return nil, "can't find sub key, key: " .. key + end + + core.log.info("main: ", main_key, " sub: ", sub_key) + + local res,err = make_request_to_aws(conf,main_key) + if not res then + return nil, "failed to retrtive data from aws: " .. err + end + + local ret = core.json.decode(res) + if not ret then + return nil, "failed to decode result, res: " .. res + end + + return ret[sub_key] +end + +_M.get = get + + +return _M diff --git a/ci/init-common-test-service.sh b/ci/init-common-test-service.sh index 7a54cd49a2b6..f33187b4c28a 100755 --- a/ci/init-common-test-service.sh +++ b/ci/init-common-test-service.sh @@ -19,3 +19,7 @@ # prepare vault kv engine sleep 3s docker exec -i vault sh -c "VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault secrets enable -path=kv -version=1 kv" + +# prepare localstack +sleep 3s +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name /apisix-key/jack --description 'APISIX Secret' --secret-string '{\"key\":\"value\"}'" diff --git a/ci/pod/docker-compose.common.yml b/ci/pod/docker-compose.common.yml index 222dc1e1eed4..9409c8961f45 100644 --- a/ci/pod/docker-compose.common.yml +++ b/ci/pod/docker-compose.common.yml @@ -102,3 +102,12 @@ services: VAULT_DEV_ROOT_TOKEN_ID: root VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200 command: [ "vault", "server", "-dev" ] + + + ## LocalStack + localstack: + image: localstack/localstack + container_name: localstack + restart: unless-stopped + ports: + - "127.0.0.1:4566:4566" # LocalStack Gateway \ No newline at end of file diff --git a/t/secret/aws.t b/t/secret/aws.t new file mode 100644 index 000000000000..105d5c788a0e --- /dev/null +++ b/t/secret/aws.t @@ -0,0 +1,322 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +BEGIN { + $ENV{AWS_REGION} = "us-east-1"; + $ENV{AWS_ACCESS_KEY_ID} = "access"; + $ENV{AWS_SECRET_ACCESS_KEY} = "secret"; + $ENV{AWS_SESSION_TOKEN} = "token"; + $ENV{AWS_REGION_LOCAL} = "us-east-1"; + $ENV{AWS_ACCESS_KEY_ID_LOCAL} = "access"; + $ENV{AWS_SECRET_ACCESS_KEY_LOCAL} = "secret"; + $ENV{AWS_SESSION_TOKEN_LOCAL} = "token"; +} + +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +log_level("info"); + +run_tests; + +__DATA__ + +=== TEST 1: check key: error format +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "apisix") + if err then + return ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +error key format, key: apisix + + + +=== TEST 2: check key: no main key +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "/apisix") + if err then + return ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +can't find main key, key: /apisix + + + +=== TEST 3: check key: no sub key +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "apisix/") + if err then + return ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +can't find sub key, key: apisix/ + + + +=== TEST 4: error aws endpoint_url +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:8080", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "/apisix-key/jack/key") + if err then + return ngx.say(err) + end + ngx.say("done") + } + } +--- request +GET /t +--- response_body +failed to retrtive data from aws: unable to retrieve secret from aws secret manager SecretsManager:getSecretValue() failed to connect to 'http://127.0.0.1:8080': connection refused +--- timeout: 6 + + + +=== TEST 5: error aws region +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "error-region", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "/apisix-key/jack/key") + if err then + return ngx.say(err) + end + ngx.say("done") + } + } +--- request +GET /t +--- response_body +failed to retrtive data from aws: failed to retrieve secret from aws secret manager (invalid status code received) +--- timeout: 20 + + + +=== TEST 6: get value from aws +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "/apisix-key/jack/key") + if err then + return ngx.say(err) + end + ngx.say("value") + } + } +--- request +GET /t +--- response_body +value + + + +=== TEST 7: get value from aws using access_key and secret_access_key and token in an env var +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "$ENV://AWS_ACCESS_KEY_ID_LOCAL", + secret_access_key = "$ENV://AWS_SECRET_ACCESS_KEY_LOCAL", + session_token = "$ENV://AWS_SESSION_TOKEN_LOCAL", + } + local data, err = aws.get(conf, "/apisix-key/jack/key") + if err then + return ngx.say(err) + end + ngx.say("value") + } + } +--- request +GET /t +--- response_body +value + + + +=== TEST 8: get value from aws using access_key and secret_access_key and token in an global env var +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + } + local data, err = aws.get(conf, "/apisix-key/jack/key") + if err then + return ngx.say(err) + end + ngx.say("value") + } + } +--- request +GET /t +--- response_body +value + + + +=== TEST 9: add secret && consumer && check +--- request +GET /t +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + -- put secret aws config + local code, body = t('/apisix/admin/secrets/aws/mysecret', + ngx.HTTP_PUT, + [[{ + "endpoint_url": "http://127.0.0.1:4566", + "region": "us-east-1", + "access_key_id": "access", + "secret_access_key": "secret", + "session_token": "token" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.say(body) + end + + -- change consumer with secrets ref: aws + code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "$secret://aws/mysecret/jack/key" + } + } + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.say(body) + end + + + local secret = require("apisix.secret") + local value = secret.fetch_by_uri("$secret://aws/mysecret/jack/key") + + + local code, body = t('/apisix/admin/secrets/aws/mysecret', ngx.HTTP_DELETE) + if code >= 300 then + ngx.status = code + return ngx.say(body) + end + + code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "$secret://aws/mysecret/jack/key" + } + } + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.say(body) + end + + local secret = require("apisix.secret") + local value = secret.fetch_by_uri("$secret://aws/mysecret/jack/key") + ngx.say(value) + } + } +--- response_body +nil From d08df1f151982d43b84a2898b3301e0ed1a5e9d1 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 15:08:06 +0800 Subject: [PATCH 02/28] fix(cli): add newline to commom.yml --- ci/pod/docker-compose.common.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/pod/docker-compose.common.yml b/ci/pod/docker-compose.common.yml index 9409c8961f45..67504cbe8565 100644 --- a/ci/pod/docker-compose.common.yml +++ b/ci/pod/docker-compose.common.yml @@ -110,4 +110,4 @@ services: container_name: localstack restart: unless-stopped ports: - - "127.0.0.1:4566:4566" # LocalStack Gateway \ No newline at end of file + - "127.0.0.1:4566:4566" # LocalStack Gateway From 5dec4a8d9a22789efbfbe3ee27318817b87c0001 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 19:42:04 +0800 Subject: [PATCH 03/28] docs(secret): Integrating AWS Usage Introduction --- docs/en/latest/terminology/secret.md | 95 +++++++++++++++++++++++++++ docs/zh/latest/terminology/secret.md | 97 ++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+) diff --git a/docs/en/latest/terminology/secret.md b/docs/en/latest/terminology/secret.md index bc233f3d9ce1..40064c8f13e6 100644 --- a/docs/en/latest/terminology/secret.md +++ b/docs/en/latest/terminology/secret.md @@ -39,6 +39,7 @@ APISIX currently supports storing secrets in the following ways: - [Environment Variables](#use-environment-variables-to-manage-secrets) - [HashiCorp Vault](#use-vault-to-manage-secrets) +- [AWS Secrets Manager](#use-aws-secrets-manager-to-manage-secrets) You can use APISIX Secret functions by specifying format variables in the consumer configuration of the following plugins, such as `key-auth`. @@ -190,3 +191,97 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \ ``` Through the above two steps, when the user request hits the `key-auth` plugin, the real value of the key in the Vault will be obtained through the APISIX Secret component. + +## Use AWS Secrets Manager to manage secrets + +Managing secrets with AWS Secrets Manager is a secure and convenient way to store and manage sensitive information. This method allows you to save secret information in AWS Secrets Manager and reference these secrets in a specific format when configuring APISIX plugins. + +APISIX currently supports two access methods: [long-term credential access](https://docs.aws.amazon.com/zh_cn/sdkref/latest/guide/access-iam-users.html) and [short-term credential access](https://docs.aws.amazon.com/zh_cn/sdkref/latest/guide/access-temp-idc.html). + +### Usage + +``` +$secret://$manager/$id/$secret_name/$key +``` + +- manager: secrets management service, could be the HashiCorp Vault, AWS, etc. +- id: APISIX Secrets resource ID, which needs to be consistent with the one specified when adding the APISIX Secrets resource +- secret_name: the secret name in the secrets management service +- key: the key corresponding to the secret in the secrets management service + +### Example: use in key-auth plugin + +Here, we use the key-auth plugin as an example to demonstrate how to manage secrets through AWS Secrets Manager. + +Step 1: Create the corresponding key in the aws secrets manager.Here, [localstack]((https://www.localstack.cloud/)) is used for simulation, and you can use the following command: + +```shell +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name jack --description 'APISIX Secret' --secret-string '{\"auth-key\":\"value\"}'" +``` + +Step 2: Add APISIX Secrets resources through the Admin API, configure the connection information such as the address of AWS Secrets Manager: + +In the configuration where APISIX is connected to a specific AWS Secrets Manager, custom configurations will override environment variable configurations. + +You can expose your information directly in the environment variables, so it can be shared everywhere: + +```shell +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +export AWS_SESSION_TOKEN= +export AWS_REGION= +``` + +Alternatively, you can specify information through custom configurations, which will override the environment variable configurations: + +```shell +curl http://127.0.0.1:9180/apisix/admin/secrets/aws/1 \ +-H "X-API-KEY: $admin_key" -X PUT -d ' +{ + "endpoint_url": "http://127.0.0.1:4566", + "region": "us-east-1", + "access_key_id": "access", + "secret_access_key": "secret", + "session_token": "token" +}' +``` + +If you use APISIX Standalone mode, you can add the following configuration in `apisix.yaml` configuration file: + +```yaml +secrets: + - id: aws/1 + endpoint_url: http://127.0.0.1:4566 + region: us-east-1 + access_key_id: access + secret_access_key: secret + session_token: token +``` + +Step 3: Reference the APISIX Secrets resource in the `key-auth` plugin and fill in the key information: + +```shell +curl http://127.0.0.1:9180/apisix/admin/consumers \ +-H "X-API-KEY: $admin_key" -X PUT -d ' +{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "$secret://aws/1/jack/auth-key" + } + } +}' +``` + +Through the above two steps, when the user request hits the `key-auth` plugin, the real value of the key in the Vault will be obtained through the APISIX Secret component. + +### Verification + +You can verify this with the following command: + +```shell +#Replace the following your_route with the actual route path. +curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' +``` + +This will verify whether the key-auth plugin is correctly using the key from AWS Secrets Manager. diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index 100a44475eb3..22448ed58225 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -39,6 +39,7 @@ APISIX 目前支持通过以下方式存储密钥: - [环境变量](#使用环境变量管理密钥) - [HashiCorp Vault](#使用-vault-管理密钥) +- [AWS Secrets Manager](#使用-aws-secrets-manager-管理密钥) 你可以在以下插件的 consumer 配置中通过指定格式的变量来使用 APISIX Secret 功能,比如 `key-auth` 插件。 @@ -191,3 +192,99 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \ ``` 通过上面两步操作,当用户请求命中 `key-auth` 插件时,会通过 APISIX Secret 组件获取到 key 在 Vault 中的真实值。 + +## 使用 AWS Secrets Manager 管理密钥 + +使用 AWS Secrets Manager 管理密钥是一种安全且便捷的方式来存储和管理敏感信息。通过这种方式,你可以将密钥信息保存在 AWS Secret Manager 中,并在配置 APISIX 插件时通过特定的格式引用这些密钥。 + +APISIX 目前支持两种访问方式: [长期凭证的访问方式](https://docs.aws.amazon.com/zh_cn/sdkref/latest/guide/access-iam-users.html) 和 [短期凭证的访问方式](https://docs.aws.amazon.com/zh_cn/sdkref/latest/guide/access-temp-idc.html)。 + +### 引用方式 + +在 APISIX 中引用密钥时,可以使用以下格式: + +``` +$secret://$manager/$id/$secret_name/$key +``` + +- manager: 密钥管理服务,可以是 Vault、AWS 等 +- APISIX Secret 资源 ID,需要与添加 APISIX Secret 资源时指定的 ID 保持一致 +- secret_name: 密钥管理服务中的密钥名称 +- key:密钥管理服务中密钥对应的 key + +### 示例:在 key-auth 插件中使用 + +这里以 key-auth 插件的使用为例,展示如何通过 AWS Secret Manager 管理密钥: + +第一步:在 AWS Secret Manager 中创建对应的密钥,这里使用 [localstack](https://www.localstack.cloud/) 模拟,可以使用如下命令: + +```shell +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name jack --description 'APISIX Secret' --secret-string '{\"auth-key\":\"value\"}'" +``` + +第二步:通过 Admin API 添加 Secret 资源,配置 AWS Secret Manager 的地址等连接信息: + +APISIX 对接到具体 AWS Secret Manager 的配置中,自定义配置会覆盖环境变量配置。 + +你可以直接在环境变量中暴露你的信息,这样所有地方都可以共享: + +```shell +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +export AWS_SESSION_TOKEN= +export AWS_REGION= +``` + +或者,可以通过自定义配置的方式指定信息,这会覆盖环境变量的配置: + +```shell +curl http://127.0.0.1:9180/apisix/admin/secrets/aws/1 \ +-H "X-API-KEY: $admin_key" -X PUT -d ' +{ + "endpoint_url": "http://127.0.0.1:4566", + "region": "us-east-1", + "access_key_id": "access", + "secret_access_key": "secret", + "session_token": "token" +}' +``` + +如果使用 APISIX Standalone 版本,则可以在 `apisix.yaml` 文件中添加如下配置: + +```yaml +secrets: + - id: aws/1 + endpoint_url: http://127.0.0.1:4566 + region: us-east-1 + access_key_id: access + secret_access_key: secret + session_token: token +``` + +第三步:在 `key-auth` 插件中引用 APISIX Secret 资源,填充秘钥信息: + +```shell +curl http://127.0.0.1:9180/apisix/admin/consumers \ +-H "X-API-KEY: $admin_key" -X PUT -d ' +{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "$secret://aws/1/jack/auth-key" + } + } +}' +``` + +通过上面两步操作,当用户请求命中 `key-auth` 插件时,会通过 APISIX Secret 组件获取到 key 在 AWS Secret Manager 中的真实值。 +### 验证 + +你可以通过如下指令进行验证: + +```shell +# 示例:将下面的 your_route 替换为实际的路由路径 +curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' +``` + +这将验证 key-auth 插件是否正确地使用 AWS Secret Manager 中的密钥。 + From b76dd1e17208aabdcb2761136379facbc23374c0 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 19:50:48 +0800 Subject: [PATCH 04/28] docs(secret): fix the secret.md docs --- docs/zh/latest/terminology/secret.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index 22448ed58225..e88270d017fb 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -277,6 +277,7 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \ ``` 通过上面两步操作,当用户请求命中 `key-auth` 插件时,会通过 APISIX Secret 组件获取到 key 在 AWS Secret Manager 中的真实值。 + ### 验证 你可以通过如下指令进行验证: From 1f113febf3ae76e4f3cc670799060cc57671166f Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 19:53:14 +0800 Subject: [PATCH 05/28] docs(secret): fix the secret.md docs --- docs/zh/latest/terminology/secret.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index e88270d017fb..0834b48d5a02 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -278,7 +278,7 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \ 通过上面两步操作,当用户请求命中 `key-auth` 插件时,会通过 APISIX Secret 组件获取到 key 在 AWS Secret Manager 中的真实值。 -### 验证 +### 验证 你可以通过如下指令进行验证: From 5daf7cc84aa2c661187fe86bfc8082da9f5a1569 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 20:02:36 +0800 Subject: [PATCH 06/28] docs(secret): fix secret.md:292: MD012 Multiple consecutive blank lines --- docs/zh/latest/terminology/secret.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index 0834b48d5a02..019191b9f3d8 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -288,4 +288,3 @@ curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' ``` 这将验证 key-auth 插件是否正确地使用 AWS Secret Manager 中的密钥。 - From eddb5aa587210d4b5b292e0df2e281d00616cc85 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 20:16:40 +0800 Subject: [PATCH 07/28] docs(secret): fix secret.md style --- docs/en/latest/terminology/secret.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/latest/terminology/secret.md b/docs/en/latest/terminology/secret.md index 40064c8f13e6..de05b313c531 100644 --- a/docs/en/latest/terminology/secret.md +++ b/docs/en/latest/terminology/secret.md @@ -213,7 +213,7 @@ $secret://$manager/$id/$secret_name/$key Here, we use the key-auth plugin as an example to demonstrate how to manage secrets through AWS Secrets Manager. -Step 1: Create the corresponding key in the aws secrets manager.Here, [localstack]((https://www.localstack.cloud/)) is used for simulation, and you can use the following command: +Step 1: Create the corresponding key in the aws secrets manager.Here, [localstack](https://www.localstack.cloud/) is used for simulation, and you can use the following command: ```shell docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name jack --description 'APISIX Secret' --secret-string '{\"auth-key\":\"value\"}'" @@ -281,7 +281,7 @@ You can verify this with the following command: ```shell #Replace the following your_route with the actual route path. -curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' +curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' ``` This will verify whether the key-auth plugin is correctly using the key from AWS Secrets Manager. From 60f5f26b890e1cd34c93c481e0c63a8dd7a41f14 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 20:24:24 +0800 Subject: [PATCH 08/28] docs(secret): fix secret.md style --- docs/zh/latest/terminology/secret.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index 019191b9f3d8..791720482a26 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -284,7 +284,7 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \ ```shell # 示例:将下面的 your_route 替换为实际的路由路径 -curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' +curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' ``` 这将验证 key-auth 插件是否正确地使用 AWS Secret Manager 中的密钥。 From 2e5c833093eccebace477a0039137ce74f211abb Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 18 Jul 2024 21:00:02 +0800 Subject: [PATCH 09/28] fix(secret): Improve exception in aws.lua --- apisix/secret/aws.lua | 20 ++++++++++---------- t/secret/aws.t | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index 6a501cfadae3..ce01b45caa4a 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -17,7 +17,6 @@ --- AWS Tools. local core = require("apisix.core") -local norm_path = require("pl.path").normpath local http = require("resty.http") local aws = require("resty.aws") @@ -99,12 +98,13 @@ local function make_request_to_aws(conf,key) end if not my_creds then - return nil, "unable to retrieve secret from aws secret manager (invalid credentials)" + return nil, "(invalid credentials)" end AWS.config.credentials = my_creds - local pre, host, port, _, _ = unpack(http:parse_uri(conf.endpoint_url or "https://secretsmanager." .. region .. ".amazonaws.com")) + local default_endpoint = "https://secretsmanager." .. region .. ".amazonaws.com" + local pre, host, port, _, _ = unpack(http:parse_uri(conf.endpoint_url or default_endpoint)) local endpoint = pre .. "://" .. host local sm = AWS:SecretsManager { @@ -120,9 +120,9 @@ local function make_request_to_aws(conf,key) if type(res) ~= "table" then if err then - return nil, "unable to retrieve secret from aws secret manager " .. err + return nil, err end - return nil, "unable to retrieve secret from aws secret manager (invalid response)" + return nil, "(invalid response)" end if res.status ~= 200 then @@ -132,20 +132,20 @@ local function make_request_to_aws(conf,key) end if err then - return nil, "failed to retrieve secret from aws secret manager " .. err + return nil, err end - return nil, "failed to retrieve secret from aws secret manager (invalid status code received)" + return nil, "(invalid status)" end local body = res.body if type(body) ~= "table" then - return nil, "unable to retrieve secret from aws secret manager (invalid response)" + return nil, "(invalid response)" end local secret = res.body.SecretString if type(secret) ~= "string" then - return nil, "unable to retrieve secret from aws secret manager (invalid secret string)" + return nil, "(invalid secret)" end return secret @@ -174,7 +174,7 @@ local function get(conf,key) local res,err = make_request_to_aws(conf,main_key) if not res then - return nil, "failed to retrtive data from aws: " .. err + return nil, "failed to retrtive data from aws secret manager: " .. err end local ret = core.json.decode(res) diff --git a/t/secret/aws.t b/t/secret/aws.t index 105d5c788a0e..517097891fed 100644 --- a/t/secret/aws.t +++ b/t/secret/aws.t @@ -140,7 +140,7 @@ can't find sub key, key: apisix/ --- request GET /t --- response_body -failed to retrtive data from aws: unable to retrieve secret from aws secret manager SecretsManager:getSecretValue() failed to connect to 'http://127.0.0.1:8080': connection refused +failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() failed to connect to 'http://127.0.0.1:8080': connection refused --- timeout: 6 @@ -167,7 +167,7 @@ failed to retrtive data from aws: unable to retrieve secret from aws secret mana --- request GET /t --- response_body -failed to retrtive data from aws: failed to retrieve secret from aws secret manager (invalid status code received) +failed to retrtive data from aws secret manager: (invalid status) --- timeout: 20 From e34011f25b454c5b6987c28bb7b8c269014ea3fb Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Tue, 23 Jul 2024 13:20:27 +0800 Subject: [PATCH 10/28] cli(common): adding dependencies --- apisix/secret/aws.lua | 2 ++ ci/common.sh | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index ce01b45caa4a..d865dae0204c 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -23,6 +23,8 @@ local aws = require("resty.aws") local sub = core.string.sub local rfind_char = core.string.rfind_char local env = core.env +local type = type +local unpack = unpack --- AWS Environment Configuration local AWS diff --git a/ci/common.sh b/ci/common.sh index 146b7aa5080a..b603769c3960 100644 --- a/ci/common.sh +++ b/ci/common.sh @@ -179,7 +179,7 @@ GRPC_SERVER_EXAMPLE_VER=20210819 linux_get_dependencies () { apt update apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev libldap2-dev - apt-get install -y libyaml-dev + apt-get install -y libyaml-dev libexpat1-dev wget https://github.com/mikefarah/yq/releases/download/3.4.1/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq } From 0fd82a72daeb55b661bc931b1cd8a9d84c9d5584 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Tue, 23 Jul 2024 17:37:05 +0800 Subject: [PATCH 11/28] cli(common): modify the installation method of Expat --- ci/common.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/common.sh b/ci/common.sh index b603769c3960..81f3c6f15f82 100644 --- a/ci/common.sh +++ b/ci/common.sh @@ -179,7 +179,8 @@ GRPC_SERVER_EXAMPLE_VER=20210819 linux_get_dependencies () { apt update apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev libldap2-dev - apt-get install -y libyaml-dev libexpat1-dev + apt-get install -y libyaml-dev + wget https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz && tar -xzf expat-2.5.0.tar.gz && cd expat-2.5.0 && ./configure && sudo make install wget https://github.com/mikefarah/yq/releases/download/3.4.1/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq } From 20f3618be034b31302850b6c8a56df8b8887223c Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Wed, 24 Jul 2024 12:36:48 +0800 Subject: [PATCH 12/28] cli(common): fixing installation path --- ci/common.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/common.sh b/ci/common.sh index 81f3c6f15f82..054414cbd94c 100644 --- a/ci/common.sh +++ b/ci/common.sh @@ -180,7 +180,8 @@ linux_get_dependencies () { apt update apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev libldap2-dev apt-get install -y libyaml-dev - wget https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz && tar -xzf expat-2.5.0.tar.gz && cd expat-2.5.0 && ./configure && sudo make install + wget https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz + tar -xzf expat-2.5.0.tar.gz && cd expat-2.5.0 && ./configure && sudo make install && cd .. wget https://github.com/mikefarah/yq/releases/download/3.4.1/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq } From 92f6f09ab3b8068fd0e8b5f6d9cef2a372c62cce Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Sat, 27 Jul 2024 20:04:01 +0800 Subject: [PATCH 13/28] fix(secret): refactor the code --- apisix/secret/aws.lua | 96 +++++++++++++++---------------------------- t/secret/aws.t | 33 ++++++++------- 2 files changed, 50 insertions(+), 79 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index d865dae0204c..f0eac69a5066 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -26,13 +26,6 @@ local env = core.env local type = type local unpack = unpack ---- AWS Environment Configuration -local AWS -local AWS_ACCESS_KEY_ID -local AWS_SECRET_ACCESS_KEY -local AWS_SESSION_TOKEN -local AWS_REGION - local schema = { type = "object", properties = { @@ -47,69 +40,40 @@ local schema = { }, region = { type = "string", + default = "us-east-1", }, endpoint_url = core.schema.uri_def, }, + required = {"access_key_id", "secret_access_key"}, } local _M = { schema = schema } -local function initialize_aws() - AWS_ACCESS_KEY_ID = env.fetch_by_uri("$ENV://AWS_ACCESS_KEY_ID") - AWS_SECRET_ACCESS_KEY = env.fetch_by_uri("$ENV://AWS_SECRET_ACCESS_KEY") - AWS_SESSION_TOKEN = env.fetch_by_uri("$ENV://AWS_SESSION_TOKEN") - AWS_REGION = env.fetch_by_uri("$ENV://AWS_REGION") - AWS = aws() - initialize_aws= nil -end - local function make_request_to_aws(conf,key) - if initialize_aws then - initialize_aws() - end + local aws_instance = aws() - local region = conf.region or AWS_REGION - if not region then - return nil, "aws secret manager requires region" - end + local region = conf.region - local access_key_id = env.fetch_by_uri(conf.access_key_id) - if not access_key_id then - access_key_id = conf.access_key_id or AWS_ACCESS_KEY_ID - end + local access_key_id = env.fetch_by_uri(conf.access_key_id) or conf.access_key_id - local secret_access_key = env.fetch_by_uri(conf.secret_access_key) - if not secret_access_key then - secret_access_key = conf.secret_access_key or AWS_SECRET_ACCESS_KEY - end + local secret_access_key = env.fetch_by_uri(conf.secret_access_key) or conf.secret_access_key - local session_token = env.fetch_by_uri(conf.session_token) - if not session_token then - session_token = conf.session_token or AWS_SESSION_TOKEN - end + local session_token = env.fetch_by_uri(conf.session_token) or conf.session_token - local my_creds = nil - if access_key_id and secret_access_key then - my_creds = AWS:Credentials { - accessKeyId = access_key_id, - secretAccessKey = secret_access_key, - sessionToken = session_token, - } - end - - if not my_creds then - return nil, "(invalid credentials)" - end - - AWS.config.credentials = my_creds + local credentials = aws_instance:Credentials { + accessKeyId = access_key_id, + secretAccessKey = secret_access_key, + sessionToken = session_token, + } local default_endpoint = "https://secretsmanager." .. region .. ".amazonaws.com" local pre, host, port, _, _ = unpack(http:parse_uri(conf.endpoint_url or default_endpoint)) local endpoint = pre .. "://" .. host - local sm = AWS:SecretsManager { + local sm = aws_instance:SecretsManager { + credentials = credentials, endpoint = endpoint, region = region, port = port, @@ -124,30 +88,34 @@ local function make_request_to_aws(conf,key) if err then return nil, err end - return nil, "(invalid response)" + + return nil, "invalid response" end if res.status ~= 200 then local body = res.body if type(body) == "table" then - err = core.json.decode(body) - end - - if err then - return nil, err + local data, err = core.json.encode(body) + if err then + return nil, "invalid status code " .. res.status .. ", " .. err + end + + if data then + return nil, "invalid status code " .. res.status .. ", " .. data + end end - return nil, "(invalid status)" + return nil, "invalid status code received " .. res.status end local body = res.body if type(body) ~= "table" then - return nil, "(invalid response)" + return nil, "invalid response body" end local secret = res.body.SecretString if type(secret) ~= "string" then - return nil, "(invalid secret)" + return nil, "invalid secret string" end return secret @@ -179,12 +147,16 @@ local function get(conf,key) return nil, "failed to retrtive data from aws secret manager: " .. err end - local ret = core.json.decode(res) - if not ret then + local data, err = core.json.decode(res) + if not data then + if err then + return nil, "failed to decode result, res: " .. res .. ", " .. err + end + return nil, "failed to decode result, res: " .. res end - return ret[sub_key] + return data[sub_key] end _M.get = get diff --git a/t/secret/aws.t b/t/secret/aws.t index 517097891fed..6e1ccae2daee 100644 --- a/t/secret/aws.t +++ b/t/secret/aws.t @@ -20,10 +20,6 @@ BEGIN { $ENV{AWS_ACCESS_KEY_ID} = "access"; $ENV{AWS_SECRET_ACCESS_KEY} = "secret"; $ENV{AWS_SESSION_TOKEN} = "token"; - $ENV{AWS_REGION_LOCAL} = "us-east-1"; - $ENV{AWS_ACCESS_KEY_ID_LOCAL} = "access"; - $ENV{AWS_SECRET_ACCESS_KEY_LOCAL} = "secret"; - $ENV{AWS_SESSION_TOKEN_LOCAL} = "token"; } use t::APISIX 'no_plan'; @@ -145,7 +141,7 @@ failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() -=== TEST 5: error aws region +=== TEST 5: get value from aws(err region, status ~= 200) --- config location /t { content_by_lua_block { @@ -159,7 +155,7 @@ failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() } local data, err = aws.get(conf, "/apisix-key/jack/key") if err then - return ngx.say(err) + return ngx.say("err") end ngx.say("done") } @@ -167,12 +163,11 @@ failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() --- request GET /t --- response_body -failed to retrtive data from aws secret manager: (invalid status) ---- timeout: 20 +err -=== TEST 6: get value from aws +=== TEST 6: get value from aws(err key, status ~= 200) --- config location /t { content_by_lua_block { @@ -184,9 +179,9 @@ failed to retrtive data from aws secret manager: (invalid status) secret_access_key = "secret", session_token = "token", } - local data, err = aws.get(conf, "/apisix-key/jack/key") + local data, err = aws.get(conf, "/apisix-key/jack-error/key") if err then - return ngx.say(err) + return ngx.say("err") end ngx.say("value") } @@ -194,11 +189,11 @@ failed to retrtive data from aws secret manager: (invalid status) --- request GET /t --- response_body -value +err -=== TEST 7: get value from aws using access_key and secret_access_key and token in an env var +=== TEST 7: get value from aws --- config location /t { content_by_lua_block { @@ -206,9 +201,9 @@ value local conf = { endpoint_url = "http://127.0.0.1:4566", region = "us-east-1", - access_key_id = "$ENV://AWS_ACCESS_KEY_ID_LOCAL", - secret_access_key = "$ENV://AWS_SECRET_ACCESS_KEY_LOCAL", - session_token = "$ENV://AWS_SESSION_TOKEN_LOCAL", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", } local data, err = aws.get(conf, "/apisix-key/jack/key") if err then @@ -224,13 +219,17 @@ value -=== TEST 8: get value from aws using access_key and secret_access_key and token in an global env var +=== TEST 8: get value from aws using env var --- config location /t { content_by_lua_block { local aws = require("apisix.secret.aws") local conf = { endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "$ENV://AWS_ACCESS_KEY_ID", + secret_access_key = "$ENV://AWS_SECRET_ACCESS_KEY", + session_token = "$ENV://AWS_SESSION_TOKEN", } local data, err = aws.get(conf, "/apisix-key/jack/key") if err then From f4098c085e699de0971dfe0846048dcc0f498a8d Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Sat, 27 Jul 2024 20:23:00 +0800 Subject: [PATCH 14/28] docs(secret): update the aws --- docs/en/latest/terminology/secret.md | 16 ++++++++++++---- docs/zh/latest/terminology/secret.md | 17 ++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/en/latest/terminology/secret.md b/docs/en/latest/terminology/secret.md index de05b313c531..850fce301c44 100644 --- a/docs/en/latest/terminology/secret.md +++ b/docs/en/latest/terminology/secret.md @@ -209,6 +209,16 @@ $secret://$manager/$id/$secret_name/$key - secret_name: the secret name in the secrets management service - key: the key corresponding to the secret in the secrets management service +### Required Parameters + +| Name | Required | Default Value | Description | +| --- | --- | --- | --- | +| access_key_id | Yes | | AWS Access Key ID | +| secret_access_key | Yes | | AWS Secret Access Key | +| session_token | No | | Temporary access credential information | +| region | No | us-east-1 | AWS Region | +| endpoint_url | No | https://secretsmanager.{region}.amazonaws.com | AWS Secret Manager URL | + ### Example: use in key-auth plugin Here, we use the key-auth plugin as an example to demonstrate how to manage secrets through AWS Secrets Manager. @@ -221,9 +231,7 @@ docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name ja Step 2: Add APISIX Secrets resources through the Admin API, configure the connection information such as the address of AWS Secrets Manager: -In the configuration where APISIX is connected to a specific AWS Secrets Manager, custom configurations will override environment variable configurations. - -You can expose your information directly in the environment variables, so it can be shared everywhere: +You can store the critical key information in environment variables to ensure the configuration information is secure, and reference it where it is used: ```shell export AWS_ACCESS_KEY_ID= @@ -232,7 +240,7 @@ export AWS_SESSION_TOKEN= export AWS_REGION= ``` -Alternatively, you can specify information through custom configurations, which will override the environment variable configurations: +Alternatively, you can also specify all the information directly in the configuration: ```shell curl http://127.0.0.1:9180/apisix/admin/secrets/aws/1 \ diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index 791720482a26..e1efadfa9426 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -212,6 +212,16 @@ $secret://$manager/$id/$secret_name/$key - secret_name: 密钥管理服务中的密钥名称 - key:密钥管理服务中密钥对应的 key +### 相关参数 + +| 名称 | 必选项 | 默认值 | 描述 | +| --- | --- | --- | --- | +| access_key_id | 是 | | AWS 访问密钥 ID | +| secret_access_key | 是 | | AWS 访问密钥 | +| session_token | 否 | | 临时访问凭证信息 | +| region | 否 | us-east-1 | AWS 区域 | +| endpoint_url | 否 | https://secretsmanager.{region}.amazonaws.com | AWS Secret Manager 地址 | + ### 示例:在 key-auth 插件中使用 这里以 key-auth 插件的使用为例,展示如何通过 AWS Secret Manager 管理密钥: @@ -224,18 +234,15 @@ docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name ja 第二步:通过 Admin API 添加 Secret 资源,配置 AWS Secret Manager 的地址等连接信息: -APISIX 对接到具体 AWS Secret Manager 的配置中,自定义配置会覆盖环境变量配置。 - -你可以直接在环境变量中暴露你的信息,这样所有地方都可以共享: +你可以在环境变量中存储关键密钥信息,保证配置信息是安全的,在使用到地方进行引用: ```shell export AWS_ACCESS_KEY_ID= export AWS_SECRET_ACCESS_KEY= export AWS_SESSION_TOKEN= -export AWS_REGION= ``` -或者,可以通过自定义配置的方式指定信息,这会覆盖环境变量的配置: +当然,你也可以通过直接在配置中指定所有信息内容: ```shell curl http://127.0.0.1:9180/apisix/admin/secrets/aws/1 \ From 76acb11d61f033508366174bcdde1cfa934d584c Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Mon, 29 Jul 2024 19:00:16 +0800 Subject: [PATCH 15/28] fix(secret): code and cli --- apisix/secret/aws.lua | 30 +++++++++++------------------- ci/common.sh | 2 -- utils/install-dependencies.sh | 2 ++ 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index f0eac69a5066..efa6407de5c6 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -62,27 +62,27 @@ local function make_request_to_aws(conf,key) local session_token = env.fetch_by_uri(conf.session_token) or conf.session_token - local credentials = aws_instance:Credentials { + local credentials = aws_instance:Credentials({ accessKeyId = access_key_id, secretAccessKey = secret_access_key, sessionToken = session_token, - } + }) local default_endpoint = "https://secretsmanager." .. region .. ".amazonaws.com" local pre, host, port, _, _ = unpack(http:parse_uri(conf.endpoint_url or default_endpoint)) local endpoint = pre .. "://" .. host - local sm = aws_instance:SecretsManager { + local sm = aws_instance:SecretsManager({ credentials = credentials, endpoint = endpoint, region = region, port = port, - } + }) - local res, err = sm:getSecretValue { + local res, err = sm:getSecretValue({ SecretId = key, VersionStage = "AWSCURRENT", - } + }) if type(res) ~= "table" then if err then @@ -95,17 +95,13 @@ local function make_request_to_aws(conf,key) if res.status ~= 200 then local body = res.body if type(body) == "table" then - local data, err = core.json.encode(body) - if err then - return nil, "invalid status code " .. res.status .. ", " .. err - end - + local data = core.json.encode(body) if data then return nil, "invalid status code " .. res.status .. ", " .. data end end - return nil, "invalid status code received " .. res.status + return nil, "invalid status code " .. res.status end local body = res.body @@ -147,16 +143,12 @@ local function get(conf,key) return nil, "failed to retrtive data from aws secret manager: " .. err end - local data, err = core.json.decode(res) - if not data then - if err then - return nil, "failed to decode result, res: " .. res .. ", " .. err - end - + local ret = core.json.decode(res) + if not ret then return nil, "failed to decode result, res: " .. res end - return data[sub_key] + return ret[sub_key] end _M.get = get diff --git a/ci/common.sh b/ci/common.sh index 054414cbd94c..146b7aa5080a 100644 --- a/ci/common.sh +++ b/ci/common.sh @@ -180,8 +180,6 @@ linux_get_dependencies () { apt update apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev libldap2-dev apt-get install -y libyaml-dev - wget https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz - tar -xzf expat-2.5.0.tar.gz && cd expat-2.5.0 && ./configure && sudo make install && cd .. wget https://github.com/mikefarah/yq/releases/download/3.4.1/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq } diff --git a/utils/install-dependencies.sh b/utils/install-dependencies.sh index 305421e8363e..c6ffc7f6dd3b 100755 --- a/utils/install-dependencies.sh +++ b/utils/install-dependencies.sh @@ -125,6 +125,8 @@ function install_apisix_runtime() { chmod +x build-apisix-runtime.sh ./build-apisix-runtime.sh latest rm build-apisix-runtime.sh + wget https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz + tar -xzf expat-2.5.0.tar.gz && cd expat-2.5.0 && ./configure && sudo make install && cd .. } # Install LuaRocks From 1a163824c2e514ac392fa4d345208e0effe76776 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Fri, 2 Aug 2024 17:14:09 +0800 Subject: [PATCH 16/28] style(secret): aws code style --- apisix/secret/aws.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index efa6407de5c6..5c61f9edff3c 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -51,7 +51,7 @@ local _M = { schema = schema } -local function make_request_to_aws(conf,key) +local function make_request_to_aws(conf, key) local aws_instance = aws() local region = conf.region @@ -118,7 +118,7 @@ local function make_request_to_aws(conf,key) end -- key is the aws secretId -local function get(conf,key) +local function get(conf, key) core.log.info("fetching data from aws for key: ", key) local idx = rfind_char(key, '/') @@ -138,7 +138,7 @@ local function get(conf,key) core.log.info("main: ", main_key, " sub: ", sub_key) - local res,err = make_request_to_aws(conf,main_key) + local res, err = make_request_to_aws(conf, main_key) if not res then return nil, "failed to retrtive data from aws secret manager: " .. err end @@ -155,3 +155,5 @@ _M.get = get return _M + + From 09e2dc61643eac8f8709d92074da30eff9bc3d53 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Fri, 2 Aug 2024 21:38:25 +0800 Subject: [PATCH 17/28] test(secret): add the aws sanity test --- t/secret/aws.t | 52 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/t/secret/aws.t b/t/secret/aws.t index 6e1ccae2daee..c70b1f5fc59b 100644 --- a/t/secret/aws.t +++ b/t/secret/aws.t @@ -141,7 +141,7 @@ failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() -=== TEST 5: get value from aws(err region, status ~= 200) +=== TEST 5: get value from aws (err region, status ~= 200) --- config location /t { content_by_lua_block { @@ -167,7 +167,7 @@ err -=== TEST 6: get value from aws(err key, status ~= 200) +=== TEST 6: get value from aws (err key, status ~= 200) --- config location /t { content_by_lua_block { @@ -319,3 +319,51 @@ GET /t } --- response_body nil + + + +=== TEST 10: sanity +--- request +GET /t +--- config + location /t { + content_by_lua_block { + local test_case = { + {}, + {access_key_id = "access"}, + {secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = 1234}, + {access_key_id = 1234, secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = "secret", session_token = "token"}, + {access_key_id = "access", secret_access_key = "secret", session_token = 1234}, + {access_key_id = "access", secret_access_key = "secret", region = "us-east-1"}, + {access_key_id = "access", secret_access_key = "secret", region = 1234}, + {access_key_id = "access", secret_access_key = "secret", endpoint_url = "http://127.0.0.1:4566"}, + {access_key_id = "access", secret_access_key = "secret", endpoint_url = 1234}, + {access_key_id = "access", secret_access_key = "secret", session_token = "token", endpoint_url = "http://127.0.0.1:4566", region = "us-east-1"}, + } + local aws = require("apisix.secret.aws") + local core = require("apisix.core") + local metadata_schema = aws.schema + + for _, conf in ipairs(test_case) do + local ok, err = core.schema.check(metadata_schema, conf) + ngx.say(ok and "done" or err) + end + } + } +--- response_body +property "access_key_id" is required +property "secret_access_key" is required +property "access_key_id" is required +property "secret_access_key" validation failed: wrong type: expected string, got number +property "access_key_id" validation failed: wrong type: expected string, got number +done +done +property "session_token" validation failed: wrong type: expected string, got number +done +property "region" validation failed: wrong type: expected string, got number +done +property "endpoint_url" validation failed: wrong type: expected string, got number +done From 4eba7a3c376c8ac95c0a2062e5d3aab836450507 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Sat, 3 Aug 2024 00:05:35 +0800 Subject: [PATCH 18/28] feat(secret): support the aws string value --- apisix/secret/aws.lua | 23 ++-- ci/init-common-test-service.sh | 4 +- docs/en/latest/terminology/secret.md | 4 +- docs/zh/latest/terminology/secret.md | 2 +- t/secret/aws.t | 160 +++++++++++---------------- 5 files changed, 85 insertions(+), 108 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index 5c61f9edff3c..acba144a41c0 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -21,7 +21,7 @@ local http = require("resty.http") local aws = require("resty.aws") local sub = core.string.sub -local rfind_char = core.string.rfind_char +local find = core.string.find local env = core.env local type = type local unpack = unpack @@ -121,28 +121,29 @@ end local function get(conf, key) core.log.info("fetching data from aws for key: ", key) - local idx = rfind_char(key, '/') - if not idx then - return nil, "error key format, key: " .. key - end + local idx = find(key, '/') - local main_key = sub(key, 1, idx - 1) + local main_key = idx and sub(key, 1, idx - 1) or key if main_key == "" then return nil, "can't find main key, key: " .. key end - local sub_key = sub(key, idx + 1) - if sub_key == "" then - return nil, "can't find sub key, key: " .. key + local sub_key = idx and sub(key, idx + 1) or nil + if not sub_key then + core.log.info("main: ", main_key) + else + core.log.info("main: ", main_key, " sub: ", sub_key) end - core.log.info("main: ", main_key, " sub: ", sub_key) - local res, err = make_request_to_aws(conf, main_key) if not res then return nil, "failed to retrtive data from aws secret manager: " .. err end + if not sub_key then + return res + end + local ret = core.json.decode(res) if not ret then return nil, "failed to decode result, res: " .. res diff --git a/ci/init-common-test-service.sh b/ci/init-common-test-service.sh index f33187b4c28a..602f01a4ad23 100755 --- a/ci/init-common-test-service.sh +++ b/ci/init-common-test-service.sh @@ -22,4 +22,6 @@ docker exec -i vault sh -c "VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' # prepare localstack sleep 3s -docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name /apisix-key/jack --description 'APISIX Secret' --secret-string '{\"key\":\"value\"}'" +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name apisix-key --description 'APISIX Secret' --secret-string '{\"jack\":\"value\"}'" +sleep 3s +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name apisix-mysql --description 'APISIX Secret' --secret-string 'secret'" diff --git a/docs/en/latest/terminology/secret.md b/docs/en/latest/terminology/secret.md index 850fce301c44..b1ad2df6735a 100644 --- a/docs/en/latest/terminology/secret.md +++ b/docs/en/latest/terminology/secret.md @@ -38,7 +38,7 @@ Its working principle is shown in the figure: APISIX currently supports storing secrets in the following ways: - [Environment Variables](#use-environment-variables-to-manage-secrets) -- [HashiCorp Vault](#use-vault-to-manage-secrets) +- [HashiCorp Vault](#use-hashicorp-vault-to-manage-secrets) - [AWS Secrets Manager](#use-aws-secrets-manager-to-manage-secrets) You can use APISIX Secret functions by specifying format variables in the consumer configuration of the following plugins, such as `key-auth`. @@ -207,7 +207,7 @@ $secret://$manager/$id/$secret_name/$key - manager: secrets management service, could be the HashiCorp Vault, AWS, etc. - id: APISIX Secrets resource ID, which needs to be consistent with the one specified when adding the APISIX Secrets resource - secret_name: the secret name in the secrets management service -- key: the key corresponding to the secret in the secrets management service +- key: get the value of a property when the value of the secret is a JSON string ### Required Parameters diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index e1efadfa9426..03dbf0c1be2b 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -210,7 +210,7 @@ $secret://$manager/$id/$secret_name/$key - manager: 密钥管理服务,可以是 Vault、AWS 等 - APISIX Secret 资源 ID,需要与添加 APISIX Secret 资源时指定的 ID 保持一致 - secret_name: 密钥管理服务中的密钥名称 -- key:密钥管理服务中密钥对应的 key +- key:当密钥的值是 JSON 字符串时,获取某个属性的值 ### 相关参数 diff --git a/t/secret/aws.t b/t/secret/aws.t index c70b1f5fc59b..e54fbafc01f5 100644 --- a/t/secret/aws.t +++ b/t/secret/aws.t @@ -33,30 +33,53 @@ run_tests; __DATA__ -=== TEST 1: check key: error format +=== TEST 1: sanity +--- request +GET /t --- config location /t { content_by_lua_block { - local aws = require("apisix.secret.aws") - local conf = { - endpoint_url = "http://127.0.0.1:4566", - region = "us-east-1", - access_key_id = "access", - secret_access_key = "secret", - session_token = "token", + local test_case = { + {}, + {access_key_id = "access"}, + {secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = 1234}, + {access_key_id = 1234, secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = "secret", session_token = "token"}, + {access_key_id = "access", secret_access_key = "secret", session_token = 1234}, + {access_key_id = "access", secret_access_key = "secret", region = "us-east-1"}, + {access_key_id = "access", secret_access_key = "secret", region = 1234}, + {access_key_id = "access", secret_access_key = "secret", endpoint_url = "http://127.0.0.1:4566"}, + {access_key_id = "access", secret_access_key = "secret", endpoint_url = 1234}, + {access_key_id = "access", secret_access_key = "secret", session_token = "token", endpoint_url = "http://127.0.0.1:4566", region = "us-east-1"}, } - local data, err = aws.get(conf, "apisix") - if err then - return ngx.say(err) + local aws = require("apisix.secret.aws") + local core = require("apisix.core") + local metadata_schema = aws.schema + + for _, conf in ipairs(test_case) do + local ok, err = core.schema.check(metadata_schema, conf) + ngx.say(ok and "done" or err) end - - ngx.say("done") } } ---- request -GET /t +--- timeout +6 --- response_body -error key format, key: apisix +property "access_key_id" is required +property "secret_access_key" is required +property "access_key_id" is required +property "secret_access_key" validation failed: wrong type: expected string, got number +property "access_key_id" validation failed: wrong type: expected string, got number +done +done +property "session_token" validation failed: wrong type: expected string, got number +done +property "region" validation failed: wrong type: expected string, got number +done +property "endpoint_url" validation failed: wrong type: expected string, got number +done @@ -87,48 +110,48 @@ can't find main key, key: /apisix -=== TEST 3: check key: no sub key +=== TEST 3: error aws endpoint_url --- config location /t { content_by_lua_block { local aws = require("apisix.secret.aws") local conf = { - endpoint_url = "http://127.0.0.1:4566", + endpoint_url = "http://127.0.0.1:8080", region = "us-east-1", access_key_id = "access", secret_access_key = "secret", session_token = "token", } - local data, err = aws.get(conf, "apisix/") + local data, err = aws.get(conf, "apisix-key/jack") if err then return ngx.say(err) end - ngx.say("done") } } --- request GET /t --- response_body -can't find sub key, key: apisix/ +failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() failed to connect to 'http://127.0.0.1:8080': connection refused +--- timeout: 6 -=== TEST 4: error aws endpoint_url +=== TEST 4: get value from aws (err region, status ~= 200) --- config location /t { content_by_lua_block { local aws = require("apisix.secret.aws") local conf = { - endpoint_url = "http://127.0.0.1:8080", - region = "us-east-1", + endpoint_url = "http://127.0.0.1:4566", + region = "error-region", access_key_id = "access", secret_access_key = "secret", session_token = "token", } - local data, err = aws.get(conf, "/apisix-key/jack/key") + local data, err = aws.get(conf, "apisix-key/jack") if err then - return ngx.say(err) + return ngx.say("err") end ngx.say("done") } @@ -136,28 +159,27 @@ can't find sub key, key: apisix/ --- request GET /t --- response_body -failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() failed to connect to 'http://127.0.0.1:8080': connection refused ---- timeout: 6 +err -=== TEST 5: get value from aws (err region, status ~= 200) +=== TEST 5: get value from aws (err key, status ~= 200) --- config location /t { content_by_lua_block { local aws = require("apisix.secret.aws") local conf = { endpoint_url = "http://127.0.0.1:4566", - region = "error-region", + region = "us-east-1", access_key_id = "access", secret_access_key = "secret", session_token = "token", } - local data, err = aws.get(conf, "/apisix-key/jack/key") + local data, err = aws.get(conf, "apisix-error-key/jack") if err then return ngx.say("err") end - ngx.say("done") + ngx.say("value") } } --- request @@ -167,7 +189,7 @@ err -=== TEST 6: get value from aws (err key, status ~= 200) +=== TEST 6: get json value from aws --- config location /t { content_by_lua_block { @@ -179,9 +201,9 @@ err secret_access_key = "secret", session_token = "token", } - local data, err = aws.get(conf, "/apisix-key/jack-error/key") + local data, err = aws.get(conf, "apisix-key/jack") if err then - return ngx.say("err") + return ngx.say(err) end ngx.say("value") } @@ -189,11 +211,11 @@ err --- request GET /t --- response_body -err +value -=== TEST 7: get value from aws +=== TEST 7: get json value from aws using env var --- config location /t { content_by_lua_block { @@ -201,11 +223,11 @@ err local conf = { endpoint_url = "http://127.0.0.1:4566", region = "us-east-1", - access_key_id = "access", - secret_access_key = "secret", - session_token = "token", + access_key_id = "$ENV://AWS_ACCESS_KEY_ID", + secret_access_key = "$ENV://AWS_SECRET_ACCESS_KEY", + session_token = "$ENV://AWS_SESSION_TOKEN", } - local data, err = aws.get(conf, "/apisix-key/jack/key") + local data, err = aws.get(conf, "apisix-key/jack") if err then return ngx.say(err) end @@ -219,7 +241,7 @@ value -=== TEST 8: get value from aws using env var +=== TEST 8: get string value from aws --- config location /t { content_by_lua_block { @@ -231,17 +253,17 @@ value secret_access_key = "$ENV://AWS_SECRET_ACCESS_KEY", session_token = "$ENV://AWS_SESSION_TOKEN", } - local data, err = aws.get(conf, "/apisix-key/jack/key") + local data, err = aws.get(conf, "apisix-mysql") if err then return ngx.say(err) end - ngx.say("value") + ngx.say(data) } } --- request GET /t --- response_body -value +secret @@ -319,51 +341,3 @@ GET /t } --- response_body nil - - - -=== TEST 10: sanity ---- request -GET /t ---- config - location /t { - content_by_lua_block { - local test_case = { - {}, - {access_key_id = "access"}, - {secret_access_key = "secret"}, - {access_key_id = "access", secret_access_key = 1234}, - {access_key_id = 1234, secret_access_key = "secret"}, - {access_key_id = "access", secret_access_key = "secret"}, - {access_key_id = "access", secret_access_key = "secret", session_token = "token"}, - {access_key_id = "access", secret_access_key = "secret", session_token = 1234}, - {access_key_id = "access", secret_access_key = "secret", region = "us-east-1"}, - {access_key_id = "access", secret_access_key = "secret", region = 1234}, - {access_key_id = "access", secret_access_key = "secret", endpoint_url = "http://127.0.0.1:4566"}, - {access_key_id = "access", secret_access_key = "secret", endpoint_url = 1234}, - {access_key_id = "access", secret_access_key = "secret", session_token = "token", endpoint_url = "http://127.0.0.1:4566", region = "us-east-1"}, - } - local aws = require("apisix.secret.aws") - local core = require("apisix.core") - local metadata_schema = aws.schema - - for _, conf in ipairs(test_case) do - local ok, err = core.schema.check(metadata_schema, conf) - ngx.say(ok and "done" or err) - end - } - } ---- response_body -property "access_key_id" is required -property "secret_access_key" is required -property "access_key_id" is required -property "secret_access_key" validation failed: wrong type: expected string, got number -property "access_key_id" validation failed: wrong type: expected string, got number -done -done -property "session_token" validation failed: wrong type: expected string, got number -done -property "region" validation failed: wrong type: expected string, got number -done -property "endpoint_url" validation failed: wrong type: expected string, got number -done From ceee23938f4e2750573a7586d143b9c146c5d904 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Sat, 3 Aug 2024 14:33:09 +0800 Subject: [PATCH 19/28] style(secret): fix aws test lint --- t/secret/aws.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/secret/aws.t b/t/secret/aws.t index e54fbafc01f5..14e1ac782f83 100644 --- a/t/secret/aws.t +++ b/t/secret/aws.t @@ -57,7 +57,7 @@ GET /t local aws = require("apisix.secret.aws") local core = require("apisix.core") local metadata_schema = aws.schema - + for _, conf in ipairs(test_case) do local ok, err = core.schema.check(metadata_schema, conf) ngx.say(ok and "done" or err) @@ -241,7 +241,7 @@ value -=== TEST 8: get string value from aws +=== TEST 8: get string value from aws --- config location /t { content_by_lua_block { From 8a9e75620b5873f46d6805283aa6b5ea01b3d74d Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Sun, 4 Aug 2024 20:31:08 +0800 Subject: [PATCH 20/28] feat(secret): return decode err --- apisix/secret/aws.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index acba144a41c0..d64ab1838fae 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -144,12 +144,12 @@ local function get(conf, key) return res end - local ret = core.json.decode(res) - if not ret then - return nil, "failed to decode result, res: " .. res + local data, err = core.json.decode(res) + if not data then + return nil, "failed to decode result, res: " .. res .. ", err: " .. err end - return ret[sub_key] + return data[sub_key] end _M.get = get From d3237c8515157c211e3fd56da5fe0ef83f3aed19 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Wed, 7 Aug 2024 17:12:06 +0800 Subject: [PATCH 21/28] style(secret): change the log logic --- apisix/secret/aws.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index d64ab1838fae..05dae6ff645d 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -129,11 +129,8 @@ local function get(conf, key) end local sub_key = idx and sub(key, idx + 1) or nil - if not sub_key then - core.log.info("main: ", main_key) - else - core.log.info("main: ", main_key, " sub: ", sub_key) - end + + core.log.info("main: ", main_key, sub_key and ", sub: " .. sub_key or "") local res, err = make_request_to_aws(conf, main_key) if not res then From 10bfb0421c26a5c18f89f7f45e9095c5c608a649 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Thu, 8 Aug 2024 10:10:01 +0800 Subject: [PATCH 22/28] cli(common): add the expact --- ci/common.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/common.sh b/ci/common.sh index 146b7aa5080a..054414cbd94c 100644 --- a/ci/common.sh +++ b/ci/common.sh @@ -180,6 +180,8 @@ linux_get_dependencies () { apt update apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev libldap2-dev apt-get install -y libyaml-dev + wget https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz + tar -xzf expat-2.5.0.tar.gz && cd expat-2.5.0 && ./configure && sudo make install && cd .. wget https://github.com/mikefarah/yq/releases/download/3.4.1/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq } From d7b6d381550c8c7d0acdf06f0d644bcbb40b04e6 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Fri, 9 Aug 2024 18:35:20 -0700 Subject: [PATCH 23/28] feat(secret): use api7-lua-resty-aws --- apisix-master-0.rockspec | 2 +- ci/common.sh | 2 -- utils/install-dependencies.sh | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/apisix-master-0.rockspec b/apisix-master-0.rockspec index 470e1e330309..b5c42d650120 100644 --- a/apisix-master-0.rockspec +++ b/apisix-master-0.rockspec @@ -82,7 +82,7 @@ dependencies = { "lua-resty-t1k = 1.1.5", "brotli-ffi = 0.3-1", "lua-ffi-zlib = 0.6-0", - "lua-resty-aws == 1.5.0", + "api7-lua-resty-aws == 2.0.0-1", "luatz == 0.4", } diff --git a/ci/common.sh b/ci/common.sh index 054414cbd94c..146b7aa5080a 100644 --- a/ci/common.sh +++ b/ci/common.sh @@ -180,8 +180,6 @@ linux_get_dependencies () { apt update apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl libpcre3 libpcre3-dev libldap2-dev apt-get install -y libyaml-dev - wget https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz - tar -xzf expat-2.5.0.tar.gz && cd expat-2.5.0 && ./configure && sudo make install && cd .. wget https://github.com/mikefarah/yq/releases/download/3.4.1/yq_linux_amd64 -O /usr/bin/yq && sudo chmod +x /usr/bin/yq } diff --git a/utils/install-dependencies.sh b/utils/install-dependencies.sh index c6ffc7f6dd3b..305421e8363e 100755 --- a/utils/install-dependencies.sh +++ b/utils/install-dependencies.sh @@ -125,8 +125,6 @@ function install_apisix_runtime() { chmod +x build-apisix-runtime.sh ./build-apisix-runtime.sh latest rm build-apisix-runtime.sh - wget https://github.com/libexpat/libexpat/releases/download/R_2_5_0/expat-2.5.0.tar.gz - tar -xzf expat-2.5.0.tar.gz && cd expat-2.5.0 && ./configure && sudo make install && cd .. } # Install LuaRocks From 93844d89a7db9e32ddc40d9ec238cb358fd53298 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Sat, 10 Aug 2024 08:34:37 -0700 Subject: [PATCH 24/28] feat(secret): update the sdk and fix some test --- apisix-master-0.rockspec | 3 +-- t/secret/aws.t | 58 +++++++++++----------------------------- 2 files changed, 16 insertions(+), 45 deletions(-) diff --git a/apisix-master-0.rockspec b/apisix-master-0.rockspec index b5c42d650120..390103cb3508 100644 --- a/apisix-master-0.rockspec +++ b/apisix-master-0.rockspec @@ -82,8 +82,7 @@ dependencies = { "lua-resty-t1k = 1.1.5", "brotli-ffi = 0.3-1", "lua-ffi-zlib = 0.6-0", - "api7-lua-resty-aws == 2.0.0-1", - "luatz == 0.4", + "api7-lua-resty-aws == 2.0.1-1", } build = { diff --git a/t/secret/aws.t b/t/secret/aws.t index 14e1ac782f83..e543594ff4ad 100644 --- a/t/secret/aws.t +++ b/t/secret/aws.t @@ -43,9 +43,9 @@ GET /t {}, {access_key_id = "access"}, {secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = "secret"}, {access_key_id = "access", secret_access_key = 1234}, {access_key_id = 1234, secret_access_key = "secret"}, - {access_key_id = "access", secret_access_key = "secret"}, {access_key_id = "access", secret_access_key = "secret", session_token = "token"}, {access_key_id = "access", secret_access_key = "secret", session_token = 1234}, {access_key_id = "access", secret_access_key = "secret", region = "us-east-1"}, @@ -60,25 +60,23 @@ GET /t for _, conf in ipairs(test_case) do local ok, err = core.schema.check(metadata_schema, conf) - ngx.say(ok and "done" or err) + ngx.say(ok and "done" or "err") end } } ---- timeout -6 --- response_body -property "access_key_id" is required -property "secret_access_key" is required -property "access_key_id" is required -property "secret_access_key" validation failed: wrong type: expected string, got number -property "access_key_id" validation failed: wrong type: expected string, got number +err +err +err done +err +err done -property "session_token" validation failed: wrong type: expected string, got number +err done -property "region" validation failed: wrong type: expected string, got number +err done -property "endpoint_url" validation failed: wrong type: expected string, got number +err done @@ -137,33 +135,7 @@ failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() -=== TEST 4: get value from aws (err region, status ~= 200) ---- config - location /t { - content_by_lua_block { - local aws = require("apisix.secret.aws") - local conf = { - endpoint_url = "http://127.0.0.1:4566", - region = "error-region", - access_key_id = "access", - secret_access_key = "secret", - session_token = "token", - } - local data, err = aws.get(conf, "apisix-key/jack") - if err then - return ngx.say("err") - end - ngx.say("done") - } - } ---- request -GET /t ---- response_body -err - - - -=== TEST 5: get value from aws (err key, status ~= 200) +=== TEST 4: get value from aws (status ~= 200) --- config location /t { content_by_lua_block { @@ -189,7 +161,7 @@ err -=== TEST 6: get json value from aws +=== TEST 5: get json value from aws --- config location /t { content_by_lua_block { @@ -215,7 +187,7 @@ value -=== TEST 7: get json value from aws using env var +=== TEST 6: get json value from aws using env var --- config location /t { content_by_lua_block { @@ -241,7 +213,7 @@ value -=== TEST 8: get string value from aws +=== TEST 7: get string value from aws --- config location /t { content_by_lua_block { @@ -267,7 +239,7 @@ secret -=== TEST 9: add secret && consumer && check +=== TEST 8: add secret && consumer && check --- request GET /t --- config From b7341bc5de820871eeda15ca5d8003ad81cc2684 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Tue, 13 Aug 2024 08:04:24 -0700 Subject: [PATCH 25/28] fix(secreat): simplify logic and fix test --- apisix/secret/aws.lua | 30 ++++++------------------------ t/secret/aws.t | 18 ++++++++---------- 2 files changed, 14 insertions(+), 34 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index 05dae6ff645d..92bc0df0494c 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -23,7 +23,6 @@ local aws = require("resty.aws") local sub = core.string.sub local find = core.string.find local env = core.env -local type = type local unpack = unpack local schema = { @@ -84,37 +83,20 @@ local function make_request_to_aws(conf, key) VersionStage = "AWSCURRENT", }) - if type(res) ~= "table" then - if err then - return nil, err - end - - return nil, "invalid response" + if not res then + return nil, err end if res.status ~= 200 then - local body = res.body - if type(body) == "table" then - local data = core.json.encode(body) - if data then - return nil, "invalid status code " .. res.status .. ", " .. data - end + local data = core.json.encode(res.body) + if data then + return nil, "invalid status code " .. res.status .. ", " .. data end return nil, "invalid status code " .. res.status end - local body = res.body - if type(body) ~= "table" then - return nil, "invalid response body" - end - - local secret = res.body.SecretString - if type(secret) ~= "string" then - return nil, "invalid secret string" - end - - return secret + return res.body.SecretString end -- key is the aws secretId diff --git a/t/secret/aws.t b/t/secret/aws.t index e543594ff4ad..3f3852f50833 100644 --- a/t/secret/aws.t +++ b/t/secret/aws.t @@ -40,7 +40,6 @@ GET /t location /t { content_by_lua_block { local test_case = { - {}, {access_key_id = "access"}, {secret_access_key = "secret"}, {access_key_id = "access", secret_access_key = "secret"}, @@ -60,23 +59,22 @@ GET /t for _, conf in ipairs(test_case) do local ok, err = core.schema.check(metadata_schema, conf) - ngx.say(ok and "done" or "err") + ngx.say(ok and "done" or err) end } } --- response_body -err -err -err +property "secret_access_key" is required +property "access_key_id" is required done -err -err +property "secret_access_key" validation failed: wrong type: expected string, got number +property "access_key_id" validation failed: wrong type: expected string, got number done -err +property "session_token" validation failed: wrong type: expected string, got number done -err +property "region" validation failed: wrong type: expected string, got number done -err +property "endpoint_url" validation failed: wrong type: expected string, got number done From e2a8b8682d3113bbd5500b7231140f56805e75c8 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Sat, 17 Aug 2024 01:35:28 -0700 Subject: [PATCH 26/28] doc(secret): fix some word --- docs/en/latest/terminology/secret.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/en/latest/terminology/secret.md b/docs/en/latest/terminology/secret.md index b1ad2df6735a..e27ee79fc1ac 100644 --- a/docs/en/latest/terminology/secret.md +++ b/docs/en/latest/terminology/secret.md @@ -196,7 +196,7 @@ Through the above two steps, when the user request hits the `key-auth` plugin, t Managing secrets with AWS Secrets Manager is a secure and convenient way to store and manage sensitive information. This method allows you to save secret information in AWS Secrets Manager and reference these secrets in a specific format when configuring APISIX plugins. -APISIX currently supports two access methods: [long-term credential access](https://docs.aws.amazon.com/zh_cn/sdkref/latest/guide/access-iam-users.html) and [short-term credential access](https://docs.aws.amazon.com/zh_cn/sdkref/latest/guide/access-temp-idc.html). +APISIX currently supports two authentication methods: using [long-term credentials](https://docs.aws.amazon.com/sdkref/latest/guide/access-iam-users.html) and [short-term credentials](https://docs.aws.amazon.com/sdkref/latest/guide/access-temp-idc.html). ### Usage @@ -213,23 +213,23 @@ $secret://$manager/$id/$secret_name/$key | Name | Required | Default Value | Description | | --- | --- | --- | --- | -| access_key_id | Yes | | AWS Access Key ID | -| secret_access_key | Yes | | AWS Secret Access Key | -| session_token | No | | Temporary access credential information | -| region | No | us-east-1 | AWS Region | -| endpoint_url | No | https://secretsmanager.{region}.amazonaws.com | AWS Secret Manager URL | +| access_key_id | True | | AWS Access Key ID | +| secret_access_key | True | | AWS Secret Access Key | +| session_token | False | | Temporary access credential information | +| region | False | us-east-1 | AWS Region | +| endpoint_url | False | https://secretsmanager.{region}.amazonaws.com | AWS Secret Manager URL | ### Example: use in key-auth plugin Here, we use the key-auth plugin as an example to demonstrate how to manage secrets through AWS Secrets Manager. -Step 1: Create the corresponding key in the aws secrets manager.Here, [localstack](https://www.localstack.cloud/) is used for simulation, and you can use the following command: +Step 1: Create the corresponding key in the AWS secrets manager. Here, [localstack](https://www.localstack.cloud/) is used for as the example environment, and you can use the following command: ```shell docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name jack --description 'APISIX Secret' --secret-string '{\"auth-key\":\"value\"}'" ``` -Step 2: Add APISIX Secrets resources through the Admin API, configure the connection information such as the address of AWS Secrets Manager: +Step 2: Add APISIX Secrets resources through the Admin API, configure the connection information such as the address of AWS Secrets Manager. You can store the critical key information in environment variables to ensure the configuration information is secure, and reference it where it is used: @@ -292,4 +292,4 @@ You can verify this with the following command: curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' ``` -This will verify whether the key-auth plugin is correctly using the key from AWS Secrets Manager. +This will verify whether the `key-auth` plugin is correctly using the key from AWS Secrets Manager. From cb26bb8c74bd0fb1b502c4a11e28580ace9ffc1a Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Sat, 24 Aug 2024 20:45:34 -0700 Subject: [PATCH 27/28] style(secret): pre to scheme --- apisix/secret/aws.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index 92bc0df0494c..b3ff6c80c666 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -68,8 +68,8 @@ local function make_request_to_aws(conf, key) }) local default_endpoint = "https://secretsmanager." .. region .. ".amazonaws.com" - local pre, host, port, _, _ = unpack(http:parse_uri(conf.endpoint_url or default_endpoint)) - local endpoint = pre .. "://" .. host + local scheme, host, port, _, _ = unpack(http:parse_uri(conf.endpoint_url or default_endpoint)) + local endpoint = scheme .. "://" .. host local sm = aws_instance:SecretsManager({ credentials = credentials, From b9d317cda7a9007f639a63b869836558b87c424a Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <1056216208@qq.com> Date: Tue, 27 Aug 2024 02:59:06 -0700 Subject: [PATCH 28/28] style(secret): _M.get and test case --- apisix/secret/aws.lua | 6 +----- t/secret/aws.t | 7 +++++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua index b3ff6c80c666..e194fff0889f 100644 --- a/apisix/secret/aws.lua +++ b/apisix/secret/aws.lua @@ -100,7 +100,7 @@ local function make_request_to_aws(conf, key) end -- key is the aws secretId -local function get(conf, key) +function _M.get(conf, key) core.log.info("fetching data from aws for key: ", key) local idx = find(key, '/') @@ -131,9 +131,5 @@ local function get(conf, key) return data[sub_key] end -_M.get = get - return _M - - diff --git a/t/secret/aws.t b/t/secret/aws.t index 3f3852f50833..ae0e09b63398 100644 --- a/t/secret/aws.t +++ b/t/secret/aws.t @@ -306,8 +306,11 @@ GET /t local secret = require("apisix.secret") local value = secret.fetch_by_uri("$secret://aws/mysecret/jack/key") - ngx.say(value) + if value then + ngx.say("secret value: ", value) + end + ngx.say("all done") } } --- response_body -nil +all done