# 如何实现代码中不出现明文口令

在很多场景中，您需要使用凭据，令牌，API密钥等来访问某些服务。例如，您需要使用数据库口令来访问业务的某些数据库。但是，将它们作为纯文本文档存储在代码中是一个重大的安全漏洞。任何有权访问您的代码库的人都可以读取这些机密，并未经授权访问您的数据库执行恶意操作。

### 什么是AWS Secrets Manager？

此服务允许您轻松轮换、管理和检索口令，比如数据库凭据、API密钥、AWS credentials。可以通过控制台、API、CLI检索口令，不需要以纯文本形式对敏感信息硬编码在代码或者配置文件中。此外，可以通过IAM进行细粒度的权限控制，集中审核AWS云、第三方服务和本地资源的密钥轮换。口令可以轻松复制到其他区域。

[更多详情](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html)。

### 第一步：创建和保存口令

您可以通过控制台或者CLI创建口令。如果配合AWS云原生的数据库，比如RDS、DocumentDB、Redshift cluster。可以选择自动轮换密钥，选择后通过Cloud Formation将自动创建能够执行自动轮换的Lamdba，以及对应的权限赋予这个Lamdba。

如果要保存非AWS托管的云原生数据库，或者其他的口令可以通过下面的操作完成：

**创建密钥（控制台）**

1. 通过<https://console.aws.amazon.com/secretsmanager/> 打开 Secrets Manager 控制台。
2. 选择 **Store a new secret (存储新密钥)**。
3. 在 **Choose secret type**（选择密钥类型）页面上，执行以下操作：

   1. 对于 **Secret type**（密钥类型），请选择 **Other type of secret**（其他密钥类型）。
   2. 以 **Key/value pairs**（键值对）或者 **Plaintext**（明文）格式输入密钥。一些示例：

   API 密钥键值对：

   **`ClientID`** : *`my_client_id`* **`ClientSecret`** : *`wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`*

   凭证键值对：

   **`Username`** : *`saanvis`* **`Password`** : *`EXAMPLE-PASSWORD`*

   数字证书

   公有密钥明文：

   `-----BEGIN CERTIFICATE----- *EXAMPLE* -----END CERTIFICATE-----`

   私有密钥明文：

   `–---BEGIN PRIVATE KEY –-- *EXAMPLE* ––-- END PRIVATE KEY –---`

   c. 对于 **Encryption key**（加密密钥），选择 **aws/secretsmanager** 使用 Secrets Manager 的 Amazon 托管式密钥。使用此密钥不产生任何费用。例如，您还可以使用自己的客户管理型密钥来[访问来自其他 Amazon Web Services 账户 的密钥](https://docs.amazonaws.cn/secretsmanager/latest/userguide/auth-and-access_examples_cross.html)。选择 **Next (下一步)**。
4. 在 **Choose secret type**（选择密钥类型）页面上，执行以下操作：
   1. 输入一个描述性的 **Secret name**（密钥名称）和 **Description**（说明）。
   2. 在 **Resource permissions**（资源权限）中，可以先不配置。
   3. 保存后退出。

### 第二步：配置权限

管理员：负责管理口令，提供Secrets ID；

程序员：要使用RDS或者各类需要口令的场景时，请求管理员提供Secrets ID；

**管理员的权限设置：**&#x6DFB;加policy，可以选择内置的policy: SecretsManagerReadWrite，建议增加condition条件限制访问来源，其中sourceIP替换为自己的IP段。然后将这个policy加在管理员的user或者role的权限集中：

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "secretsmanager:*",
                "cloudformation:CreateChangeSet",
                "cloudformation:DescribeChangeSet",
                "cloudformation:DescribeStackResource",
                "cloudformation:DescribeStacks",
                "cloudformation:ExecuteChangeSet",
                "ec2:DescribeSecurityGroups",
                "ec2:DescribeSubnets",
                "ec2:DescribeVpcs",
                "kms:DescribeKey",
                "kms:ListAliases",
                "kms:ListKeys",
                "lambda:ListFunctions",
                "rds:DescribeDBClusters",
                "rds:DescribeDBInstances",
                "redshift:DescribeClusters",
                "tag:GetResources"
            ],
            "Effect": "Allow",
            "Resource": "*",
            "Condition": {
                "BoolIfExists": {
                    "aws:MultiFactorAuthPresent": "true"
                },
                "IpAddress": {
                    "aws:SourceIp": "210.75.12.75/16"
                }
            }
        },
        {
            "Action": [
                "lambda:AddPermission",
                "lambda:CreateFunction",
                "lambda:GetFunction",
                "lambda:InvokeFunction",
                "lambda:UpdateFunctionConfiguration"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:lambda:*:*:function:SecretsManager*"
        },
        {
            "Action": [
                "serverlessrepo:CreateCloudFormationChangeSet",
                "serverlessrepo:GetApplication"
            ],
            "Effect": "Allow",
            "Resource": "arn:aws:serverlessrepo:*:*:applications/SecretsManager*"
        },
        {
            "Action": [
                "s3:GetObject"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::awsserverlessrepo-changesets*",
                "arn:aws:s3:::secrets-manager-rotation-apps-*/*"
            ]
        }
    ]
}
```

**程序员/程序获取secrets**

获取secrets manager中的secrets需要添加指定哪个角色可以获取的policy，给指定的secrets ID：

[Attach a permissions policy to an AWS Secrets Manager secret - AWS Secrets Manager (amazon.com)](https://docs.aws.amazon.com/secretsmanager/latest/userguide/auth-and-access_resource-policies.html)

**控制台修改**

1. 打开控制台，找到AWS Secrets Manager；
2. 选择需要修改权限策略的Secrets ID，点击进入详情页；
3. 选择“编辑权限”；
   1. 如果secrets是通过AWS托管的密钥做的加密，则写入下面的策略，注意将role的arn替换成自己的。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::123456789012:role/MyRole"
            },
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "*"
        }
    ]
}
```

如果secrets使用了客户自主管理的AWS密钥进行的加密，则使用以下策略，也可以加condition，限制使用的前提条件：

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
           "Principal": {
                "AWS": "arn:aws:iam::000000000000:role/MyRole"
            },
            "Action": [
                "secretsmanager:GetSecretValue",
                "kms:Decrypt"
            ],
            "Effect": "Allow"
        }
    ]
}
```

`000000000000`是你的 AWS account ID, `us-east-1`是 AWS region code 以及 `mySecretID` 是secrete的ID，可以到控制台查看具体的ID。

4. 编辑好以后点击“保存”。

**AWS CLI的方法**

执行下面的命令，其中secret ID替换为自己的：

```json
aws secretsmanager get-resource-policy \\
--secret-id MyTestSecret
```

创建一个文件，mypolicy.json里面写上允许哪个角色获取secret value.

然后执行下面的命令，其中secret ID替换为自己的，文件地址替换为刚才创建好的mypolicy.json的路径：

```json
aws secretsmanager put-resource-policy \\
    --secret-id MyTestSecret \\
    --resource-policy file://mypolicy.json \\
    --block-public-policy
```

### 第三步：代码中使用secrets manager

以请求访问数据库为例，未使用secrets manager的时候，IP地址、端口号、用户名和密码直接写在代码里。

更多连接方式参见：[Connect to a SQL database with credentials in an AWS Secrets Manager secret - AWS Secrets Manager (amazon.com)](https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_jdbc.html)

为了节约调用次数产生的费用，还可以使用缓存功能，支持java、python、.net、go写的程序使用缓存功能。以java编码为例：[如何在JAVA应用中缓存AWS Secrets Manager安全凭据；](https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_cache-java.html)默认1小时刷新获取一次secrets，可以重新设定默认的刷新时间。

以python代码为例，数据库的IP地址，端口号，用户名和密码都是从secrets manager获取的，而不是直接明文写在代码中：

```python
import botocore 
import botocore.session 
import jaydebeapi
import json
from aws_secretsmanager_caching import SecretCache, SecretCacheConfig 

client = botocore.session.get_session().create_client('secretsmanager')
cache_config = SecretCacheConfig()
cache = SecretCache( config = cache_config, client = client)

secret = cache.get_secret_string('test/mysql')
json_object = json.loads(secret)
host = json_object["host"]
port = json_object["port"]

jdbc_url="jdbc:mysql://" + host + ":" + port
user = json_object["username"]
pwd = json_object["password"]
driver = "com.mysql.jdbc.Driver"
jar_file = './mysql-connector-java-5.1.34.jar'
sql = 'select * from table_name'

connect = jaydebeapi.connect(driver, jdbc_url, [user, pwd], jar_file)
connect.close
```

### 配置文件中如何使用？

如果是通过yml格式的配置文件连接数据库，则类似以下代码：

```python
server:
	port: 8080
	ervlet:
	context-path: /
	spring:
	datasource:
		username: root
		password: 123456
		url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
		type: com.alibaba.druid.pool.DruidDataSource
		driver-class-name: com.mysql.cj.jdbc.Driver
```

如果使用secrets manager，则如下：

首先，安装依赖 dependency，不同的配置文件需要的依赖不同，详细可以[参考此文档](https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets.html)。

以使用workato进行配置管理为例，在配置文件中，添加一个`secrets`部分，带上 `provider`和 `region`：

[可参见详细文档](https://docs.workato.com/on-prem/agents/secrets-manager/amazon-web-services-secret-manager.html#create-secret)

```yaml
secrets:
  provider: aws
  region: <YOUR_REGION>
```

如果是连接数据库，可以在配置文件中写入：

```yaml
database:
  sales_database:
    adapter: sqlserver
    host: localhost
    port: 1433
    database: test
    username: sales_user
    password: { secret: '<SECRET_NAME>' }
```

其他有用的blog：

* [如何将 AWS Secrets & Configuration Provider 与您的 Kubernetes Secrets Store CSI 驱动程序配合使用](https://aws.amazon.com/blogs/security/how-to-use-aws-secrets-configuration-provider-with-kubernetes-secrets-store-csi-driver/)
