Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support aws secret manager #11417

Merged
merged 29 commits into from
Aug 28, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bba758b
feat(secret): support aws secret manager
HuanXin-Chen Jul 18, 2024
d08df1f
fix(cli): add newline to commom.yml
HuanXin-Chen Jul 18, 2024
5dec4a8
docs(secret): Integrating AWS Usage Introduction
HuanXin-Chen Jul 18, 2024
b76dd1e
docs(secret): fix the secret.md docs
HuanXin-Chen Jul 18, 2024
1f113fe
docs(secret): fix the secret.md docs
HuanXin-Chen Jul 18, 2024
5daf7cc
docs(secret): fix secret.md:292: MD012 Multiple consecutive blank lines
HuanXin-Chen Jul 18, 2024
eddb5aa
docs(secret): fix secret.md style
HuanXin-Chen Jul 18, 2024
60f5f26
docs(secret): fix secret.md style
HuanXin-Chen Jul 18, 2024
2e5c833
fix(secret): Improve exception in aws.lua
HuanXin-Chen Jul 18, 2024
e34011f
cli(common): adding dependencies
HuanXin-Chen Jul 23, 2024
0fd82a7
cli(common): modify the installation method of Expat
HuanXin-Chen Jul 23, 2024
20f3618
cli(common): fixing installation path
HuanXin-Chen Jul 24, 2024
92f6f09
fix(secret): refactor the code
HuanXin-Chen Jul 27, 2024
f4098c0
docs(secret): update the aws
HuanXin-Chen Jul 27, 2024
76acb11
fix(secret): code and cli
HuanXin-Chen Jul 29, 2024
1a16382
style(secret): aws code style
HuanXin-Chen Aug 2, 2024
09e2dc6
test(secret): add the aws sanity test
HuanXin-Chen Aug 2, 2024
4eba7a3
feat(secret): support the aws string value
HuanXin-Chen Aug 2, 2024
ceee239
style(secret): fix aws test lint
HuanXin-Chen Aug 3, 2024
8a9e756
feat(secret): return decode err
HuanXin-Chen Aug 4, 2024
d3237c8
style(secret): change the log logic
HuanXin-Chen Aug 7, 2024
855210f
merge(): remote-tracking branch 'upstream/master' into feat-aws-secret
HuanXin-Chen Aug 7, 2024
10bfb04
cli(common): add the expact
HuanXin-Chen Aug 8, 2024
d7b6d38
feat(secret): use api7-lua-resty-aws
HuanXin-Chen Aug 10, 2024
93844d8
feat(secret): update the sdk and fix some test
HuanXin-Chen Aug 10, 2024
b7341bc
fix(secreat): simplify logic and fix test
HuanXin-Chen Aug 13, 2024
e2a8b86
doc(secret): fix some word
HuanXin-Chen Aug 17, 2024
cb26bb8
style(secret): pre to scheme
HuanXin-Chen Aug 25, 2024
b9d317c
style(secret): _M.get and test case
HuanXin-Chen Aug 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apisix-master-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
157 changes: 157 additions & 0 deletions apisix/secret/aws.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
--
-- 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 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
local type = type
local unpack = unpack

local schema = {
type = "object",
properties = {
access_key_id = {
type = "string",
},
secret_access_key = {
type = "string",
},
session_token = {
type = "string",
},
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 make_request_to_aws(conf,key)
local aws_instance = aws()

local region = conf.region

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) or conf.secret_access_key

local session_token = env.fetch_by_uri(conf.session_token) or conf.session_token

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({
credentials = credentials,
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, err
end

return nil, "invalid response"
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
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
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 secret manager: " .. 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this style is clearer and easier to understand

function _M.get(conf, key)
  ... ...
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that vault.lua is also using this style. I believe keeping the original style will help maintain consistency for future expansions.
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we need to reuse the function get, the current style is good.

In this code, we do not need to reuse function get, so _M.get is better to read and understand

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed



return _M
4 changes: 4 additions & 0 deletions ci/init-common-test-service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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\"}'"
9 changes: 9 additions & 0 deletions ci/pod/docker-compose.common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
103 changes: 103 additions & 0 deletions docs/en/latest/terminology/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -190,3 +191,105 @@ 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

### 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.

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:

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=<access_key_id>
export AWS_SECRET_ACCESS_KEY=<secrets_access_key>
export AWS_SESSION_TOKEN=<token>
export AWS_REGION=<aws-region>
```

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 \
-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.
Loading
Loading