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

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

什么是AWS Secrets Manager?

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

更多详情

第一步:创建和保存口令

您可以通过控制台或者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 账户 的密钥。选择 Next (下一步)

  4. Choose secret type(选择密钥类型)页面上,执行以下操作:

    1. 输入一个描述性的 Secret name(密钥名称)和 Description(说明)。

    2. Resource permissions(资源权限)中,可以先不配置。

    3. 保存后退出。

第二步:配置权限

管理员:负责管理口令,提供Secrets ID;

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

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

{
    "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)

控制台修改

  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。

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

AWS CLI的方法

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

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

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

然后执行下面的命令,其中secret ID替换为自己的,文件地址替换为刚才创建好的mypolicy.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)

为了节约调用次数产生的费用,还可以使用缓存功能,支持java、python、.net、go写的程序使用缓存功能。以java编码为例:如何在JAVA应用中缓存AWS Secrets Manager安全凭据;默认1小时刷新获取一次secrets,可以重新设定默认的刷新时间。

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

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格式的配置文件连接数据库,则类似以下代码:

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,不同的配置文件需要的依赖不同,详细可以参考此文档

以使用workato进行配置管理为例,在配置文件中,添加一个secrets部分,带上 providerregion

可参见详细文档

secrets:
  provider: aws
  region: <YOUR_REGION>

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

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

其他有用的blog:

最后更新于