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 27 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
3 changes: 2 additions & 1 deletion apisix-master-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ 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",
"api7-lua-resty-aws == 2.0.1-1",
}

build = {
Expand Down
139 changes: 139 additions & 0 deletions apisix/secret/aws.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
--
-- 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 find = core.string.find
local env = core.env
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))
nic-6443 marked this conversation as resolved.
Show resolved Hide resolved
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",
nic-6443 marked this conversation as resolved.
Show resolved Hide resolved
})

if not res then
return nil, err
end

if res.status ~= 200 then
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

return res.body.SecretString
end

-- key is the aws secretId
local function get(conf, key)
core.log.info("fetching data from aws for key: ", key)

local idx = find(key, '/')

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 = idx and sub(key, idx + 1) or nil

core.log.info("main: ", main_key, sub_key and ", sub: " .. sub_key or "")
membphis marked this conversation as resolved.
Show resolved Hide resolved
nic-6443 marked this conversation as resolved.
Show resolved Hide resolved

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 data, err = core.json.decode(res)
if not data then
return nil, "failed to decode result, res: " .. res .. ", err: " .. err
end

return data[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


6 changes: 6 additions & 0 deletions ci/init-common-test-service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@
# 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 --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'"
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
105 changes: 104 additions & 1 deletion docs/en/latest/terminology/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ 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`.

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

```
$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: get the value of a property when the value of the secret is a JSON string

### Required Parameters

| Name | Required | Default Value | Description |
| --- | --- | --- | --- |
| 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 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.

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