This is an automated email from the ASF dual-hosted git repository.
bzp2010 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 2fcfbd83e feat: support gcp secret manager (#11436)
2fcfbd83e is described below
commit 2fcfbd83e22301aea4f027738d628f19c262a458
Author: HuanXin-Chen <[email protected]>
AuthorDate: Sun Sep 22 11:53:48 2024 +0800
feat: support gcp secret manager (#11436)
---
apisix/secret/gcp.lua | 202 ++++++++++
apisix/utils/google-cloud-oauth.lua | 130 ++++++
docs/en/latest/terminology/secret.md | 54 +++
docs/zh/latest/terminology/secret.md | 56 ++-
t/lib/server.lua | 186 +++++++++
t/secret/conf/error.json | 9 +
t/secret/conf/success.json | 10 +
t/secret/gcp.t | 737 +++++++++++++++++++++++++++++++++++
8 files changed, 1383 insertions(+), 1 deletion(-)
diff --git a/apisix/secret/gcp.lua b/apisix/secret/gcp.lua
new file mode 100644
index 000000000..6b6e661c4
--- /dev/null
+++ b/apisix/secret/gcp.lua
@@ -0,0 +1,202 @@
+--
+-- 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.
+--
+
+--- GCP Tools.
+local core = require("apisix.core")
+local http = require("resty.http")
+local google_oauth = require("apisix.utils.google-cloud-oauth")
+
+local str_sub = core.string.sub
+local str_find = core.string.find
+local decode_base64 = ngx.decode_base64
+
+local lrucache = core.lrucache.new({ ttl = 300, count = 8 })
+
+local schema = {
+ type = "object",
+ properties = {
+ auth_config = {
+ type = "object",
+ properties = {
+ client_email = { type = "string" },
+ private_key = { type = "string" },
+ project_id = { type = "string" },
+ token_uri = {
+ type = "string",
+ default = "https://oauth2.googleapis.com/token"
+ },
+ scope = {
+ type = "array",
+ items = {
+ type = "string"
+ },
+ default = {
+ "https://www.googleapis.com/auth/cloud-platform"
+ }
+ },
+ entries_uri = {
+ type = "string",
+ default = "https://secretmanager.googleapis.com/v1"
+ },
+ },
+ required = { "client_email", "private_key", "project_id" }
+ },
+ ssl_verify = {
+ type = "boolean",
+ default = true
+ },
+ auth_file = { type = "string" },
+ },
+ oneOf = {
+ { required = { "auth_config" } },
+ { required = { "auth_file" } },
+ },
+}
+
+local _M = {
+ schema = schema
+}
+
+local function fetch_oauth_conf(conf)
+ if conf.auth_config then
+ return conf.auth_config
+ end
+
+ local file_content, err = core.io.get_file(conf.auth_file)
+ if not file_content then
+ return nil, "failed to read configuration, file: " .. conf.auth_file
.. ", err: " .. err
+ end
+
+ local config_tab, err = core.json.decode(file_content)
+ if not config_tab then
+ return nil, "config parse failure, data: " .. file_content .. ", err:
" .. err
+ end
+
+ local config = {
+ auth_config = {
+ client_email = config_tab.client_email,
+ private_key = config_tab.private_key,
+ project_id = config_tab.project_id
+ }
+ }
+
+ local ok, err = core.schema.check(schema, config)
+ if not ok then
+ return nil, "config parse failure, file: " .. conf.auth_file .. ",
err: " .. err
+ end
+
+ return config_tab
+end
+
+
+local function get_secret(oauth, secrets_id)
+ local httpc = http.new()
+
+ local access_token = oauth:generate_access_token()
+ if not access_token then
+ return nil, "failed to get google oauth token"
+ end
+
+ local entries_uri = oauth.entries_uri .. "/projects/" .. oauth.project_id
+ .. "/secrets/" .. secrets_id ..
"/versions/latest:access"
+
+ local res, err = httpc:request_uri(entries_uri, {
+ ssl_verify = oauth.ssl_verify,
+ method = "GET",
+ headers = {
+ ["Content-Type"] = "application/json",
+ ["Authorization"] = (oauth.access_token_type or "Bearer") .. " "
.. access_token,
+ },
+ })
+
+ if not res then
+ return nil, err
+ end
+
+ if res.status ~= 200 then
+ return nil, res.body
+ end
+
+ local body, err = core.json.decode(res.body)
+ if not body then
+ return nil, "failed to parse response data, " .. err
+ end
+
+ local payload = body.payload
+ if not payload then
+ return nil, "invalid payload"
+ end
+
+ return decode_base64(payload.data)
+end
+
+
+local function make_request_to_gcp(conf, secrets_id)
+ local auth_config, err = fetch_oauth_conf(conf)
+ if not auth_config then
+ return nil, err
+ end
+
+ local lru_key = auth_config.client_email .. "#" .. auth_config.project_id
+
+ local oauth, err = lrucache(lru_key, "gcp", google_oauth.new, auth_config,
conf.ssl_verify)
+ if not oauth then
+ return nil, "failed to create oauth object, " .. err
+ end
+
+ local secret, err = get_secret(oauth, secrets_id)
+ if not secret then
+ return nil, err
+ end
+
+ return secret
+end
+
+
+function _M.get(conf, key)
+ core.log.info("fetching data from gcp for key: ", key)
+
+ local idx = str_find(key, '/')
+
+ local main_key = idx and str_sub(key, 1, idx - 1) or key
+ if main_key == "" then
+ return nil, "can't find main key, key: " .. key
+ end
+
+ local sub_key = idx and str_sub(key, idx + 1)
+
+ core.log.info("main: ", main_key, sub_key and ", sub: " .. sub_key or "")
+
+ local res, err = make_request_to_gcp(conf, main_key)
+ if not res then
+ return nil, "failed to retrtive data from gcp secret manager: " .. err
+ end
+
+ if not sub_key then
+ return res
+ end
+
+ local data, err = core.json.decode(res)
+ if not data then
+ return nil, "failed to decode result, err: " .. err
+ end
+
+ return data[sub_key]
+end
+
+
+return _M
diff --git a/apisix/utils/google-cloud-oauth.lua
b/apisix/utils/google-cloud-oauth.lua
new file mode 100644
index 000000000..6cb352848
--- /dev/null
+++ b/apisix/utils/google-cloud-oauth.lua
@@ -0,0 +1,130 @@
+--
+-- 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.
+--
+
+local core = require("apisix.core")
+local type = type
+local setmetatable = setmetatable
+
+local ngx_update_time = ngx.update_time
+local ngx_time = ngx.time
+local ngx_encode_args = ngx.encode_args
+
+local http = require("resty.http")
+local jwt = require("resty.jwt")
+
+
+local function get_timestamp()
+ ngx_update_time()
+ return ngx_time()
+end
+
+
+local _M = {}
+
+
+function _M.generate_access_token(self)
+ if not self.access_token or get_timestamp() >
self.access_token_expire_time - 60 then
+ self:refresh_access_token()
+ end
+ return self.access_token
+end
+
+
+function _M.refresh_access_token(self)
+ local http_new = http.new()
+ local res, err = http_new:request_uri(self.token_uri, {
+ ssl_verify = self.ssl_verify,
+ method = "POST",
+ body = ngx_encode_args({
+ grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer",
+ assertion = self:generate_jwt_token()
+ }),
+ headers = {
+ ["Content-Type"] = "application/x-www-form-urlencoded",
+ },
+ })
+
+ if not res then
+ core.log.error("failed to refresh google oauth access token, ", err)
+ return
+ end
+
+ if res.status ~= 200 then
+ core.log.error("failed to refresh google oauth access token: ",
res.body)
+ return
+ end
+
+ res, err = core.json.decode(res.body)
+ if not res then
+ core.log.error("failed to parse google oauth response data: ", err)
+ return
+ end
+
+ self.access_token = res.access_token
+ self.access_token_type = res.token_type
+ self.access_token_expire_time = get_timestamp() + res.expires_in
+end
+
+
+function _M.generate_jwt_token(self)
+ local payload = core.json.encode({
+ iss = self.client_email,
+ aud = self.token_uri,
+ scope = self.scope,
+ iat = get_timestamp(),
+ exp = get_timestamp() + (60 * 60)
+ })
+
+ local jwt_token = jwt:sign(self.private_key, {
+ header = { alg = "RS256", typ = "JWT" },
+ payload = payload,
+ })
+
+ return jwt_token
+end
+
+
+function _M.new(config, ssl_verify)
+ local oauth = {
+ client_email = config.client_email,
+ private_key = config.private_key,
+ project_id = config.project_id,
+ token_uri = config.token_uri or "https://oauth2.googleapis.com/token",
+ auth_uri = config.auth_uri or
"https://accounts.google.com/o/oauth2/auth",
+ entries_uri = config.entries_uri,
+ access_token = nil,
+ access_token_type = nil,
+ access_token_expire_time = 0,
+ }
+
+ oauth.ssl_verify = ssl_verify
+
+ if config.scope then
+ if type(config.scope) == "string" then
+ oauth.scope = config.scope
+ end
+
+ if type(config.scope) == "table" then
+ oauth.scope = core.table.concat(config.scope, " ")
+ end
+ end
+
+ return setmetatable(oauth, { __index = _M })
+end
+
+
+return _M
diff --git a/docs/en/latest/terminology/secret.md
b/docs/en/latest/terminology/secret.md
index e27ee79fc..94c1b8843 100644
--- a/docs/en/latest/terminology/secret.md
+++ b/docs/en/latest/terminology/secret.md
@@ -40,6 +40,7 @@ APISIX currently supports storing secrets in the following
ways:
- [Environment Variables](#use-environment-variables-to-manage-secrets)
- [HashiCorp Vault](#use-hashicorp-vault-to-manage-secrets)
- [AWS Secrets Manager](#use-aws-secrets-manager-to-manage-secrets)
+- [GCP Secrets Manager](#use-gcp-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`.
@@ -293,3 +294,56 @@ 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.
+
+## Use GCP Secrets Manager to manage secrets
+
+Using the GCP Secrets Manager to manage secrets means you can store the secret
information in the GCP service, and reference it using a specific format of
variables when configuring plugins. APISIX currently supports integration with
the GCP Secrets Manager, and the supported authentication method is [OAuth
2.0](https://developers.google.com/identity/protocols/oauth2).
+
+### Reference Format
+
+```
+$secret://$manager/$id/$secret_name/$key
+```
+
+The reference format is the same as before:
+
+- manager: secrets management service, could be the HashiCorp Vault, AWS, GCP
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: get the value of a property when the value of the secret is a JSON
string
+
+### Required Parameters
+
+| Name | Required | Default
|
Description
|
+|-------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| auth_config | True |
|
Either `auth_config` or `auth_file` must be provided.
|
+| auth_config.client_email | True |
|
Email address of the Google Cloud service account.
|
+| auth_config.private_key | True |
|
Private key of the Google Cloud service account.
|
+| auth_config.project_id | True |
|
Project ID in the Google Cloud service account.
|
+| auth_config.token_uri | False | https://oauth2.googleapis.com/token
| Token URI of the Google Cloud service account.
|
+| auth_config.entries_uri | False | https://secretmanager.googleapis.com/v1
| The API
access endpoint for the Google Secrets Manager.
|
+| auth_config.scope | False |
https://www.googleapis.com/auth/cloud-platform | Access scopes of the Google
Cloud service account. See [OAuth 2.0 Scopes for Google
APIs](https://developers.google.com/identity/protocols/oauth2/scopes) |
+| auth_file | True |
|
Path to the Google Cloud service account authentication JSON file. Either
`auth_config` or `auth_file` must be provided.
|
+| ssl_verify | False | true
|
When set to `true`, enables SSL verification as mentioned in [OpenResty
docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake).
|
+
+You need to configure the corresponding authentication parameters, or specify
the authentication file through auth_file, where the content of auth_file is in
JSON format.
+
+### Example
+
+Here is a correct configuration example:
+
+```
+curl http://127.0.0.1:9180/apisix/admin/secrets/gcp/1 \
+-H "X-API-KEY: $admin_key" -X PUT -d '
+{
+ "auth_config" : {
+ "client_email": "[email protected]",
+ "private_key": "private_key",
+ "project_id": "apisix-project",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "entries_uri": "https://secretmanager.googleapis.com/v1",
+ "scope": ["https://www.googleapis.com/auth/cloud-platform"]
+ }
+}'
+
+```
diff --git a/docs/zh/latest/terminology/secret.md
b/docs/zh/latest/terminology/secret.md
index 03dbf0c1b..810abb7dd 100644
--- a/docs/zh/latest/terminology/secret.md
+++ b/docs/zh/latest/terminology/secret.md
@@ -40,6 +40,7 @@ APISIX 目前支持通过以下方式存储密钥:
- [环境变量](#使用环境变量管理密钥)
- [HashiCorp Vault](#使用-vault-管理密钥)
- [AWS Secrets Manager](#使用-aws-secrets-manager-管理密钥)
+- [GCP Secrets Manager](#使用-gcp-secrets-manager-管理密钥)
你可以在以下插件的 consumer 配置中通过指定格式的变量来使用 APISIX Secret 功能,比如 `key-auth` 插件。
@@ -135,7 +136,7 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \
$secret://$manager/$id/$secret_name/$key
```
-- manager: 密钥管理服务,可以是 Vault、AWS 等
+- manager: 密钥管理服务,可以是 Vault、AWS、GCP 等
- APISIX Secret 资源 ID,需要与添加 APISIX Secret 资源时指定的 ID 保持一致
- secret_name: 密钥管理服务中的密钥名称
- key:密钥管理服务中密钥对应的 key
@@ -295,3 +296,56 @@ curl -i http://127.0.0.1:9080/your_route -H 'apikey: value'
```
这将验证 key-auth 插件是否正确地使用 AWS Secret Manager 中的密钥。
+
+## 使用 GCP Secrets Manager 管理密钥
+
+使用 GCP Secret Manager 来管理密钥意味着你可以将密钥信息保存在 GCP 服务中,在配置插件时通过特定格式的变量来引用。APISIX
目前支持对接 GCP Secret Manager, 所支持的验证方式是[OAuth
2.0](https://developers.google.com/identity/protocols/oauth2?hl=zh-cn)。
+
+### 引用方式
+
+```
+$secret://$manager/$id/$secret_name/$key
+```
+
+引用方式和之前保持一致:
+
+- manager: 密钥管理服务,可以是 Vault、AWS\GCP 等
+- APISIX Secret 资源 ID,需要与添加 APISIX Secret 资源时指定的 ID 保持一致
+- secret_name: 密钥管理服务中的密钥名称
+- key:当密钥的值是 JSON 字符串时,获取某个属性的值
+
+### 必要参数
+
+| 名称 | 必选项 | 默认值
| 描述
|
+| ----------------------- | -------- |
------------------------------------------------ |
-------------------------------------------------------------------------------------------------------------------------------
|
+| auth_config | 是 |
| `auth_config` 和 `auth_file` 必须配置一个。
|
+| auth_config.client_email | 是 |
| 谷歌服务帐号的 email 参数。
|
+| auth_config.private_key | 是 |
| 谷歌服务帐号的私钥参数。
|
+| auth_config.project_id | 是 |
| 谷歌服务帐号的项目 ID。
|
+| auth_config.token_uri | 否 | https://oauth2.googleapis.com/token
| 请求谷歌服务帐户的令牌的 URI。
|
+| auth_config.entries_uri | 否 |
https://secretmanager.googleapis.com/v1 | 谷歌密钥服务访问端点 API。
|
+| auth_config.scope | 否 |
https://www.googleapis.com/auth/cloud-platform
| 谷歌服务账号的访问范围,可参考 [OAuth 2.0 Scopes for Google
APIs](https://developers.google.com/identity/protocols/oauth2/scopes)|
+| auth_file | 是 |
| `auth_config` 和 `auth_file` 必须配置一个。 |
+| ssl_verify | 否 | true
| 当设置为 `true` 时,启用 `SSL` 验证。 |
+
+你需要配置相应的认证参数,或者通过 auth_file 来指定认证文件,其中 auth_file 的内容为认证参数的 json 格式。
+
+### 示例
+
+以下一种正确的配置实例:
+
+```
+curl http://127.0.0.1:9180/apisix/admin/secrets/gcp/1 \
+-H "X-API-KEY: $admin_key" -X PUT -d '
+{
+ "auth_config" : {
+ "client_email": "[email protected]",
+ "private_key": "private_key",
+ "project_id": "apisix-project",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "entries_uri": "https://secretmanager.googleapis.com/v1",
+ "scope": ["https://www.googleapis.com/auth/cloud-platform"]
+ }
+}'
+
+```
diff --git a/t/lib/server.lua b/t/lib/server.lua
index 7cc8101a3..309873636 100644
--- a/t/lib/server.lua
+++ b/t/lib/server.lua
@@ -558,6 +558,192 @@ function _M.google_logging_entries()
ngx.say(data)
end
+function _M.google_secret_token()
+ local args = ngx.req.get_uri_args()
+ local args_token_type = args.token_type or "Bearer"
+ ngx.req.read_body()
+ local data = ngx.decode_args(ngx.req.get_body_data())
+ local jwt = require("resty.jwt")
+ local access_scopes = "https://www.googleapis.com/auth/cloud"
+ local verify = jwt:verify(rsa_public_key, data["assertion"])
+ if not verify.verified then
+ ngx.status = 401
+ ngx.say(json_encode({ error = "identity authentication failed" }))
+ return
+ end
+
+ local scopes_valid = type(verify.payload.scope) == "string" and
+ verify.payload.scope:find(access_scopes)
+ if not scopes_valid then
+ ngx.status = 403
+ ngx.say(json_encode({ error = "no access to this scope" }))
+ return
+ end
+
+ local expire_time = (verify.payload.exp or ngx.time()) - ngx.time()
+ if expire_time <= 0 then
+ expire_time = 0
+ end
+
+ local jwt_token = jwt:sign(rsa_private_key, {
+ header = { typ = "JWT", alg = "RS256" },
+ payload = { exp = verify.payload.exp, scope = access_scopes }
+ })
+
+ ngx.say(json_encode({
+ access_token = jwt_token,
+ expires_in = expire_time,
+ token_type = args_token_type
+ }))
+end
+
+function _M.google_secret_apisix_jack()
+ local args = ngx.req.get_uri_args()
+ local args_token_type = args.token_type or "Bearer"
+ local jwt = require("resty.jwt")
+ local access_scopes = "https://www.googleapis.com/auth/cloud"
+
+ local headers = ngx.req.get_headers()
+ local token = headers["Authorization"]
+ if not token then
+ ngx.status = 401
+ ngx.say(json_encode({ error = "authentication header not exists" }))
+ return
+ end
+
+ token = string.sub(token, #args_token_type + 2)
+ local verify = jwt:verify(rsa_public_key, token)
+ if not verify.verified then
+ ngx.status = 401
+ ngx.say(json_encode({ error = "identity authentication failed" }))
+ return
+ end
+
+ local scopes_valid = type(verify.payload.scope) == "string" and
+ verify.payload.scope:find(access_scopes)
+ if not scopes_valid then
+ ngx.status = 403
+ ngx.say(json_encode({ error = "no access to this scope" }))
+ return
+ end
+
+ local expire_time = (verify.payload.exp or ngx.time()) - ngx.time()
+ if expire_time <= 0 then
+ ngx.status = 403
+ ngx.say(json_encode({ error = "token has expired" }))
+ return
+ end
+
+ local response = {
+ name = "projects/647037004838/secrets/apisix/versions/1",
+ payload = {
+ data = "eyJrZXkiOiJ2YWx1ZSJ9",
+ dataCrc32c = "2296192492"
+ }
+ }
+
+ ngx.status = 200
+ ngx.say(json_encode(response))
+end
+
+function _M.google_secret_apisix_error_jack()
+ local args = ngx.req.get_uri_args()
+ local args_token_type = args.token_type or "Bearer"
+ local jwt = require("resty.jwt")
+ local access_scopes = "https://www.googleapis.com/auth/root/cloud"
+
+ local headers = ngx.req.get_headers()
+ local token = headers["Authorization"]
+ if not token then
+ ngx.status = 401
+ ngx.say(json_encode({ error = "authentication header not exists" }))
+ return
+ end
+
+ token = string.sub(token, #args_token_type + 2)
+ local verify = jwt:verify(rsa_public_key, token)
+ if not verify.verified then
+ ngx.status = 401
+ ngx.say(json_encode({ error = "identity authentication failed" }))
+ return
+ end
+
+ local scopes_valid = type(verify.payload.scope) == "string" and
+ verify.payload.scope:find(access_scopes)
+ if not scopes_valid then
+ ngx.status = 403
+ ngx.say(json_encode({ error = "no access to this scope" }))
+ return
+ end
+
+ local expire_time = (verify.payload.exp or ngx.time()) - ngx.time()
+ if expire_time <= 0 then
+ ngx.status = 403
+ ngx.say(json_encode({ error = "token has expired" }))
+ return
+ end
+
+ local response = {
+ name = "projects/647037004838/secrets/apisix_error/versions/1",
+ payload = {
+ data = "eyJrZXkiOiJ2YWx1ZSJ9",
+ dataCrc32c = "2296192492"
+ }
+ }
+
+ ngx.status = 200
+ ngx.say(json_encode(response))
+end
+
+function _M.google_secret_apisix_mysql()
+ local args = ngx.req.get_uri_args()
+ local args_token_type = args.token_type or "Bearer"
+ local jwt = require("resty.jwt")
+ local access_scopes = "https://www.googleapis.com/auth/cloud"
+
+ local headers = ngx.req.get_headers()
+ local token = headers["Authorization"]
+ if not token then
+ ngx.status = 401
+ ngx.say(json_encode({ error = "authentication header not exists" }))
+ return
+ end
+
+ token = string.sub(token, #args_token_type + 2)
+ local verify = jwt:verify(rsa_public_key, token)
+ if not verify.verified then
+ ngx.status = 401
+ ngx.say(json_encode({ error = "identity authentication failed" }))
+ return
+ end
+
+ local scopes_valid = type(verify.payload.scope) == "string" and
+ verify.payload.scope:find(access_scopes)
+ if not scopes_valid then
+ ngx.status = 403
+ ngx.say(json_encode({ error = "no access to this scope" }))
+ return
+ end
+
+ local expire_time = (verify.payload.exp or ngx.time()) - ngx.time()
+ if expire_time <= 0 then
+ ngx.status = 403
+ ngx.say(json_encode({ error = "token has expired" }))
+ return
+ end
+
+ local response = {
+ name = "projects/647037004838/secrets/apisix/versions/1",
+ payload = {
+ data = "c2VjcmV0",
+ dataCrc32c = "0xB03C4D4D"
+ }
+ }
+
+ ngx.status = 200
+ ngx.say(json_encode(response))
+end
+
function _M.plugin_proxy_rewrite_resp_header()
ngx.req.read_body()
local s = "plugin_proxy_rewrite_resp_header"
diff --git a/t/secret/conf/error.json b/t/secret/conf/error.json
new file mode 100644
index 000000000..3d0bb6295
--- /dev/null
+++ b/t/secret/conf/error.json
@@ -0,0 +1,9 @@
+{
+ "private_key": "-----BEGIN PRIVATE
KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR\naeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC\nUuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF\n2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4\nv5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep\nAB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw\nIu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4p
[...]
+ "project_id": "apisix",
+ "token_uri": "http://127.0.0.1:1980/google/logging/token",
+ "scope": [
+ "https://apisix.apache.org/logs:admin"
+ ],
+ "entries_uri": "http://127.0.0.1:1980/google/logging/entries"
+}
diff --git a/t/secret/conf/success.json b/t/secret/conf/success.json
new file mode 100644
index 000000000..d9cfbc3c8
--- /dev/null
+++ b/t/secret/conf/success.json
@@ -0,0 +1,10 @@
+{
+ "private_key": "-----BEGIN PRIVATE
KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR\naeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC\nUuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF\n2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4\nv5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep\nAB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw\nIu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4p
[...]
+ "project_id": "apisix",
+ "token_uri": "http://127.0.0.1:1980/google/secret/token",
+ "scope": [
+ "https://www.googleapis.com/auth/cloud"
+ ],
+ "entries_uri": "http://127.0.0.1:1984",
+ "client_email": "[email protected]"
+}
diff --git a/t/secret/gcp.t b/t/secret/gcp.t
new file mode 100644
index 000000000..b7fc5331c
--- /dev/null
+++ b/t/secret/gcp.t
@@ -0,0 +1,737 @@
+#
+# 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.
+#
+
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+log_level("info");
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: validate different schema situation
+--- config
+ location /t {
+ content_by_lua_block {
+ local test_case = {
+ {},
+ {auth_file = "123"},
+ {auth_file = 123},
+ {auth_config = {client_email = "client", private_key =
"private_key"}},
+ {auth_config = {private_key = "private_key", project_id =
"project_id"}},
+ {auth_config = {client_email = "client", project_id =
"project_id"}},
+ {auth_config = {client_email = "client", private_key =
"private_key", project_id = "project_id"}},
+ {auth_config = {client_email = 1234, private_key =
"private_key", project_id = "project_id"}},
+ {auth_config = {client_email = "client", private_key = 1234,
project_id = "project_id"}},
+ {auth_config = {client_email = "client", private_key =
"private_key", project_id = 1234}},
+ {auth_config = {client_email = "client", private_key =
"private_key", project_id = "project_id"}, ssl_verify = 1234},
+ {auth_config = {client_email = "client", private_key =
"private_key", project_id = "project_id", token_uri = 1234}},
+ {auth_config = {client_email = "client", private_key =
"private_key", project_id = "project_id", scope = 1234}},
+ {auth_config = {client_email = "client", private_key =
"private_key", project_id = "project_id", entries_uri = 1234}},
+ {auth_config = {client_email = "client", private_key =
"private_key", project_id = "project_id", token_uri = "token_uri",
+ scope = {"scope"}, entries_uri = "entries_uri"},
ssl_verify = true},
+ }
+ local gcp = require("apisix.secret.gcp")
+ local core = require("apisix.core")
+ local metadata_schema = gcp.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
+ }
+ }
+--- request
+GET /t
+--- response_body
+value should match only one schema, but matches none
+done
+property "auth_file" validation failed: wrong type: expected string, got number
+property "auth_config" validation failed: property "project_id" is required
+property "auth_config" validation failed: property "client_email" is required
+property "auth_config" validation failed: property "private_key" is required
+done
+property "auth_config" validation failed: property "client_email" validation
failed: wrong type: expected string, got number
+property "auth_config" validation failed: property "private_key" validation
failed: wrong type: expected string, got number
+property "auth_config" validation failed: property "project_id" validation
failed: wrong type: expected string, got number
+property "ssl_verify" validation failed: wrong type: expected boolean, got
number
+property "auth_config" validation failed: property "token_uri" validation
failed: wrong type: expected string, got number
+property "auth_config" validation failed: property "scope" validation failed:
wrong type: expected array, got number
+property "auth_config" validation failed: property "entries_uri" validation
failed: wrong type: expected string, got number
+done
+
+
+
+=== TEST 2: check key: no main key
+--- config
+ location /t {
+ content_by_lua_block {
+ local gcp = require("apisix.secret.gcp")
+ local conf = {
+ auth_config = {
+ client_email = "[email protected]",
+ private_key = [[
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
+aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
+UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
+2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
+v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
+AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
+Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
+PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
+DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
+sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
+afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
+l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
+lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
+rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
+tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
+UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
+Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
+1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
+GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
+xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
+upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
+FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
+y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
+vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
+Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
+kEJQcmfVew5mFXyxuEn3zA==
+-----END PRIVATE KEY-----]],
+ project_id = "apisix",
+ token_uri = "http://127.0.0.1:1980/token",
+ scope = {
+ "https://www.googleapis.com/auth/cloud-platform"
+ },
+ },
+ }
+ local data, err = gcp.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: add secret && consumer && check
+--- request
+GET /t
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_config = {
+ client_email = "[email protected]",
+ private_key = [[
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
+aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
+UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
+2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
+v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
+AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
+Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
+PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
+DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
+sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
+afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
+l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
+lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
+rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
+tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
+UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
+Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
+1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
+GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
+xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
+upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
+FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
+y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
+vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
+Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
+kEJQcmfVew5mFXyxuEn3zA==
+-----END PRIVATE KEY-----]],
+ project_id = "apisix",
+ token_uri = "http://127.0.0.1:1980/google/secret/token",
+ scope = {
+ "https://www.googleapis.com/auth/cloud-platform"
+ },
+ entries_uri = "http://127.0.0.1:1984"
+ },
+ }
+
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/secrets/gcp/mysecret',
ngx.HTTP_PUT, conf)
+
+ if code >= 300 then
+ ngx.status = code
+ return ngx.say(body)
+ end
+
+ -- change consumer with secrets ref: gcp
+ code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "jack",
+ "plugins": {
+ "key-auth": {
+ "key": "$secret://gcp/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://gcp/mysecret/jack/key")
+
+
+ local code, body = t('/apisix/admin/secrets/gcp/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://gcp/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://gcp/mysecret/jack/key")
+ if value then
+ ngx.say("secret value: ", value)
+ end
+ ngx.say("all done")
+ }
+ }
+--- response_body
+all done
+
+
+
+=== TEST 4: setup route (/projects/apisix/secrets/jack/versions/latest:access)
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "serverless-pre-function": {
+ "phase": "rewrite",
+ "functions": [
+ "return function(conf, ctx)
+
require('lib.server').google_secret_apisix_jack()
+ end"
+ ]
+ }
+ },
+ "uri":
"/projects/apisix/secrets/jack/versions/latest:access",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 5: setup route
(/projects/apisix_error/secrets/jack/versions/latest:access)
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/2',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "serverless-pre-function": {
+ "phase": "rewrite",
+ "functions": [
+ "return function(conf, ctx)
+
require('lib.server').google_secret_apisix_error_jack()
+ end"
+ ]
+ }
+ },
+ "uri":
"/projects/apisix_error/secrets/jack/versions/latest:access",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 6: setup route (/projects/apisix/secrets/mysql/versions/latest:access)
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/3',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "serverless-pre-function": {
+ "phase": "rewrite",
+ "functions": [
+ "return function(conf, ctx)
+
require('lib.server').google_secret_apisix_mysql()
+ end"
+ ]
+ }
+ },
+ "uri":
"/projects/apisix/secrets/mysql/versions/latest:access",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 7: get value from gcp by auth_file(fetch_oatuh_conf failed, read
failed)
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_file = "t/secret/conf/nofind.json",
+ }
+ local gcp = require("apisix.secret.gcp")
+ local value, err = gcp.get(conf, "jack/key")
+ if not value then
+ return ngx.say(err)
+ end
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+failed to retrtive data from gcp secret manager: failed to read configuration,
file: t/secret/conf/nofind.json, err: t/secret/conf/nofind.json: No such file
or directory
+
+
+
+=== TEST 8: get value from gcp by auth_file(fetch_oatuh_conf success)
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_file = "t/secret/conf/success.json",
+ }
+ local gcp = require("apisix.secret.gcp")
+ local value, err = gcp.get(conf, "jack/key")
+ if not value then
+ return ngx.say(err)
+ end
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+value
+
+
+
+=== TEST 9: get value from gcp by auth_file(fetch_oatuh_conf failed, undefined)
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_file = "t/secret/conf/error.json",
+ }
+ local gcp = require("apisix.secret.gcp")
+ local value, err = gcp.get(conf, "jack/key")
+ if not value then
+ return ngx.say(err)
+ end
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+failed to retrtive data from gcp secret manager: config parse failure, file:
t/secret/conf/error.json, err: property "auth_config" validation failed:
property "client_email" is required
+
+
+
+=== TEST 10: get json value from gcp
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_config = {
+ client_email = "[email protected]",
+ private_key = [[
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
+aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
+UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
+2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
+v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
+AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
+Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
+PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
+DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
+sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
+afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
+l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
+lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
+rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
+tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
+UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
+Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
+1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
+GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
+xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
+upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
+FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
+y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
+vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
+Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
+kEJQcmfVew5mFXyxuEn3zA==
+-----END PRIVATE KEY-----]],
+ project_id = "apisix",
+ token_uri = "http://127.0.0.1:1980/google/secret/token",
+ scope = {
+ "https://www.googleapis.com/auth/cloud-platform"
+ },
+ entries_uri = "http://127.0.0.1:1984"
+ },
+ }
+ local gcp = require("apisix.secret.gcp")
+ local value, err = gcp.get(conf, "jack/key")
+ if not value then
+ return ngx.say(err)
+ end
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+value
+
+
+
+=== TEST 11: get string value from gcp
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_config = {
+ client_email = "[email protected]",
+ private_key = [[
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
+aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
+UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
+2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
+v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
+AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
+Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
+PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
+DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
+sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
+afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
+l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
+lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
+rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
+tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
+UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
+Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
+1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
+GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
+xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
+upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
+FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
+y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
+vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
+Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
+kEJQcmfVew5mFXyxuEn3zA==
+-----END PRIVATE KEY-----]],
+ project_id = "apisix",
+ token_uri = "http://127.0.0.1:1980/google/secret/token",
+ scope = {
+ "https://www.googleapis.com/auth/cloud-platform"
+ },
+ entries_uri = "http://127.0.0.1:1984"
+ },
+ }
+ local gcp = require("apisix.secret.gcp")
+ local value, err = gcp.get(conf, "mysql")
+ if not value then
+ return ngx.say(err)
+ end
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+secret
+
+
+
+=== TEST 12: get value from gcp(failed to get google oauth token)
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_config = {
+ client_email = "[email protected]",
+ private_key = [[
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
+aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
+UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
+2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
+v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
+AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
+Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
+PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
+DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
+sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
+afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
+l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
+lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
+rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
+tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
+UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
+Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
+1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
+GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
+xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
+upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
+FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
+y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
+vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
+Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
+kEJQcmfVew5mFXyxuEn3zA==
+-----END PRIVATE KEY-----]],
+ project_id = "apisix",
+ token_uri = "http://127.0.0.1:1980/google/secret/token",
+ scope = {
+ "https://www.googleapis.com/auth/root/cloud-platform"
+ },
+ entries_uri = "http://127.0.0.1:1984"
+ },
+ }
+ local gcp = require("apisix.secret.gcp")
+ local value, err = gcp.get(conf, "jack/key")
+ if not value then
+ return ngx.say(err)
+ end
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+failed to retrtive data from gcp secret manager: failed to get google oauth
token
+--- grep_error_log eval
+qr/\{\"error\"\:\"[\w+\s+]*\"\}/
+--- grep_error_log_out
+{"error":"no access to this scope"}
+
+
+
+=== TEST 13: get value from gcp (not res)
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_config = {
+ client_email = "[email protected]",
+ private_key = [[
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
+aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
+UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
+2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
+v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
+AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
+Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
+PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
+DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
+sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
+afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
+l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
+lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
+rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
+tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
+UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
+Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
+1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
+GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
+xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
+upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
+FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
+y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
+vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
+Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
+kEJQcmfVew5mFXyxuEn3zA==
+-----END PRIVATE KEY-----]],
+ project_id = "apisix_error",
+ token_uri = "http://127.0.0.1:1980/google/secret/token",
+ scope = {
+ "https://www.googleapis.com/auth/cloud-platform"
+ },
+ entries_uri = "http://127.0.0.1:1984"
+ },
+ }
+ local gcp = require("apisix.secret.gcp")
+ local value, err = gcp.get(conf, "jack/key")
+ if not value then
+ return ngx.say("err")
+ end
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+err
+
+
+
+=== TEST 14: get value from gcp (res status ~= 200)
+--- config
+ location /t {
+ content_by_lua_block {
+ local conf = {
+ auth_config = {
+ client_email = "[email protected]",
+ private_key = [[
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDzrFwnA3EvYyR
+aeMgaLD3hBjvxKrz10uox1X8q7YYhf2ViRtLRUMa2bEMYksE5hbhwpNf6mKAnLOC
+UuAT6cPPdUl/agKpJXviBPIR2LuzD17WsLJHp1HxUDssSkgfCaGcOGGNfLUhhIpF
+2JUctLmxiZoAZySlSjcwupSuDJ0aPm0XO8r9H8Qu5kF2Vkz5e5bFivLTmvzrQTe4
+v5V1UI6hThElCSeUmdNF3uG3wopxlvq4zXgLTnuLbrNf/Gc4mlpV+UDgTISj32Ep
+AB2vxKEbvQw4ti8YJnGXWjxLerhfrszFw+V8lpeduiDYA44ZFoVqvzxeIsVZNtcw
+Iu7PvEPNAgMBAAECggEAVpyN9m7A1F631/aLheFpLgMbeKt4puV7zQtnaJ2XrZ9P
+PR7pmNDpTu4uF3k/D8qrIm+L+uhVa+hkquf3wDct6w1JVnfQ93riImbnoKdK13ic
+DcEZCwLjByfjFMNCxZ/gAZca55fbExlqhFy6EHmMjhB8s2LsXcTHRuGxNI/Vyi49
+sxECibe0U53aqdJbVWrphIS67cpwl4TUkN6mrHsNuDYNJ9dgkpapoqp4FTFQsBqC
+afOK5qgJ68dWZ47FBUng+AZjdCncqAIuJxxItGVQP6YPsFs+OXcivIVHJr363TpC
+l85FfdvqWV5OGBbwSKhNwiTNUVvfSQVmtURGWG/HbQKBgQD4gZ1z9+Lx19kT9WTz
+lw93lxso++uhAPDTKviyWSRoEe5aN3LCd4My+/Aj+sk4ON/s2BV3ska5Im93j+vC
+rCv3uPn1n2jUhWuJ3bDqipeTW4n/CQA2m/8vd26TMk22yOkkqw2MIA8sjJ//SD7g
+tdG7up6DgGMP4hgbO89uGU7DAwKBgQDJtkKd0grh3u52Foeh9YaiAgYRwc65IE16
+UyD1OJxIuX/dYQDLlo5KyyngFa1ZhWIs7qC7r3xXH+10kfJY+Q+5YMjmZjlL8SR1
+Ujqd02R9F2//6OeswyReachJZbZdtiEw3lPa4jVFYfhSe0M2ZPxMwvoXb25eyCNI
+1lYjSKq87wKBgHnLTNghjeDp4UKe6rNYPgRm0rDrhziJtX5JeUov1mALKb6dnmkh
+GfRK9g8sQqKDfXwfC6Z2gaMK9YaryujGaWYoCpoPXtmJ6oLPXH4XHuLh4mhUiP46
+xn8FEfSimuQS4/FMxH8A128GHQSI7AhGFFzlwfrBWcvXC+mNDsTvMmLxAoGARc+4
+upppfccETQZ7JsitMgD1TMwA2f2eEwoWTAitvlXFNT9PYSbYVHaAJbga6PLLCbYF
+FzAjHpxEOKYSdEyu7n/ayDL0/Z2V+qzc8KarDsg/0RgwppBbU/nUgeKb/U79qcYo
+y4ai3UKNCS70Ei1dTMvmdpnwXwlxfNIBufB6dy0CgYBMYq9Lc31GkC6PcGEEbx6W
+vjImOadWZbuOVnvEQjb5XCdcOsWsMcg96PtoeuyyHmhnEF1GsMzcIdQv/PHrvYpK
+Yp8D0aqsLEgwGrJQER26FPpKmyIwvcL+nm6q5W31PnU9AOC/WEkB6Zs58hsMzD2S
+kEJQcmfVew5mFXyxuEn3zA==
+-----END PRIVATE KEY-----]],
+ project_id = "apisix_error",
+ token_uri = "http://127.0.0.1:1980/google/secret/token",
+ scope = {
+ "https://www.googleapis.com/auth/cloud-platform"
+ },
+ entries_uri = "http://127.0.0.1:1984"
+ },
+ }
+ local gcp = require("apisix.secret.gcp")
+ local value, err = gcp.get(conf, "jack/key")
+ if not value then
+ return ngx.say("err")
+ end
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+err