应用程序使用AK的最佳方案
客户可以使用AK或STS作为访问凭证,用程序访问阿里云。但密钥一旦泄露会导致严重的安全风险,因此有极高的安全性要求。本方案介绍应用程序使用AK的最佳方案。
该方案即将下线,请参考《第三方配置中心Apollo集中管控AccessKey》、《阿里云KMS全托管RAM凭据集中管控AK/SK》。
一、方案简介
二、客户场景
客户场景:单账号程序访问密钥防泄漏管理(AK防泄漏)
场景描述
客户可以使用AK或STS作为访问凭证,用程序访问阿里云。但密钥一旦泄露会导致严重的安全风险,因此有极高的安全性要求。我们除了提供AK防泄漏产品能力和最佳实践,考虑到已有很多客户在错误的使用密钥,还应该提供足够友好的迁移方案。
适用客户
- 所有需要使用程序访问阿里云资源的客户,均为此场景的目标客户。
三、客户痛点
3.1 客户痛点包括:
- AK/SK配置信息分散在多个应用程序中,无法统一管理
- AK/SK硬编码在代码中,无法及时更新AK/SK
- AK/SK以明文方式存在,易发生泄露问题,存在安全隐患
- 无法追踪AK/SK的使用情况
3.2 客户价值包括:
- 治理当前的AK使用方式,使用多种方式降低AK泄露风险
- 降低AK泄露一旦发生的损失
- 提高AK审计能力,从而能够更好的做到泄露事件的处置
- 在保证安全性的同时降低AK管理成本
四、架构设计
AK防泄漏管控管理全景
客户使用AK的最优原则
- 推荐方案:使用无AK方案,避免程序直接接触AK
AK存储 |
AK使用 |
AK审计 |
1、ECS实例角色 2、程序SSO(用SAML/OIDC Provider实现) 3、AK Vault (KMS凭据管家 或第三方Vault) |
仅针对AK Vault方案 |
AK最后使用时间 |
方案内容包括
- 使用KMS 凭据管家-RAM托管凭据 来做AK配置中心
- 使用KMS 凭据管家来做AK配置中心
- 使用Apollo来做AK配置中心
以下会详细介绍这三种方式的具体操作步骤。
五、部署指导书
5.1 使用KMS凭据管家来做AK配置中心
- 在KMS-“凭据”页面创建新的凭据。根据不同服务需要,可以使用托管RDS凭据、RAM凭据和ECS凭据,这三类凭据和阿里云服务相连接,可以使用自动轮转的功能,无需管理员手动定时更换凭据。为简便操作,这里我们使用其他凭据,且暂时不配置自动轮转。
- 信息确认后,可以在凭据页面看见我们刚刚创建的凭据。凭据会自动加密存储,并且我们无法在页面上查看凭据的内容;在凭据下发时,会自动进行解密,并用HTTPS协议传输凭据,无需用户手动加解密。
- 我们使用一个简单的Java程序作为示例,获取KMS中凭据的值。
- 添加maven依赖
com.aliyun
aliyun-java-sdk-core
4.5.16
com.aliyun
aliyun-java-sdk-kms
2.12.0
com.alibaba
fastjson
1.2.9
- 为了访问KMS服务,最简单的做法时在阿里云控制台RAM中创建拥有访问KMS权限的角色(不推荐这么做,建议从ECS实例维度安全访问KMS,可参考文档)
- 权限策略包括:只读访问密钥管理服务(KMS)的权限、获取KMS中的凭据的权限
- 编写Java代码
public class RdsSecretSampleCode {
private static KmsClient kmsClient;
// 拥有访问KMS权限的角色的AK和SK
private static String accessKey = "x";
private static String secretKey = "xx";
// 配置KMS Client
static {
kmsClient = KmsClient.getKMSClient("cn-hangzhou", accessKey, secretKey);
}
static class KmsClient {
private DefaultAcsClient acsClient;
private KmsClient(DefaultAcsClient acsClient) {
this.acsClient = acsClient;
}
private static KmsClient getKMSClient(String regionId, String accessKeyId, String accessKeySecret) {
IClientProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
DefaultAcsClient client = new DefaultAcsClient(profile);
return new KmsClient(client);
}
}
// 通过凭据信息获取指定的AK
private static String getAK(String secretName) throws ClientException {
// 配置获取AK的请求
final GetSecretValueRequest request = new GetSecretValueRequest();
// 配置凭据名称
request.setSecretName(secretName);
GetSecretValueResponse response = kmsClient.acsClient.getAcsResponse(request);
JSONObject secretDataJSON = JSON.parseObject(response.getSecretData());
return secretDataJSON.getString("AK");
}
public static void main(String[] args) throws ClientException, InterruptedException {
while (true) {
System.out.println(getAK("test"));
TimeUnit.SECONDS.sleep(5);
}
}
}
- 控制台能够正确地读取AK信息
- 接下来我们需要更新KMS中的凭据信息。进入凭据详情页面,可以看见凭据拥有版本状态。只有版本状态为
ACSCurrent
的凭据信息会被获取。
- 点击“存入凭据值”,设置版本号为v2,并且填入新的AK值。
b. 存入完成后,v1和v2版本号都会出现。但此时只有v2版本被标记为 ACSCurrent
,因此只能读取v2的凭据信息。
c. 测试3.c中的代码能够成功接受到凭据更新。
5.2 使用KMS凭据管家-托管RAM凭据 来做AK配置中心(推荐)
前序工作
- 已经开通阿里云KMS服务。开通链接
- 管理动态RAM凭据前,您需要通过RAM服务角色授予凭据管家管理RAM用户AccessKey的权限。具体操作,请参见授予凭据管家管理RAM用户AccessKey的权限。
- 请为待托管RAM凭据的RAM用户创建AccessKey,需要事先有RAM用户并有AK才需要对这个AK进行托管管理。具体操作,请参见为RAM用户创建访问密钥。
费用说明
- 密钥托管费用
密钥创建者 |
计费项 |
公共云地域的单价(元/天) |
用户 |
软件密钥②的密钥版本 |
0.014 |
基础硬件密钥③的密钥版本 |
0.237 |
|
高级硬件密钥④的密钥版本 |
|
②保护级别为Software的用户主密钥。
③密钥规格为Aliyun_AES_256、Aliyun_SM4或RSA_2048,保护级别为HSM的用户主密钥。
④密钥规格为Aliyun_SM2、RSA_3072、EC_P256或EC_P256K,保护级别为HSM的用户主密钥。
- API调用费用: 每月20,000次的免费调用额度。超出此免费额度的服务密钥调用,以及对您自己创建的密钥的调用,按照以下规则进行计费。
密钥类别 |
适用于中国内地公共云地域的单价(元/万次) |
适用于中国内地之外公共云地域的单价(元/万次) |
基础密钥② |
0.6 |
0.214 |
高级密钥③ |
1.8 |
1.068 |
②密钥规格为Aliyun_AES_256、Aliyun_SM4或RSA_2048的用户主密钥。
③密钥规格为Aliyun_SM2、RSA_3072、EC_P256或EC_P256K的用户主密钥。
操作步骤
1、在KMS-“凭据”页面创建新的凭据。根据不同服务需要,可以使用托管RDS凭据、RAM凭据和ECS凭据,这三类凭据和阿里云服务相连接。本着安全的考虑原则,建议使用RAM凭据,可以使用自动轮转AK的功能,无需管理员手动定时更换凭据。
注意!设置凭据值这里请输入这个RAM用户对应的一个合法的AK值。KMS是用这个AK值去做轮转判断的。别写错了!
具体配置,请参考官方帮助手册。查看链接
2、信息确认后,可以在凭据页面看见我们刚刚创建的凭据。
3、管理员配置完了之后,如何让应用程序访问拿到这个凭据呢。推荐使用凭据管家客户端。
凭据管家客户端(SecretsManager Client)基于KMS凭据管家API封装了业务逻辑、最佳实践和设计模式,更易于开发者在业务系统中集成。主要适用于应用中动态使用托管在凭据管家中的凭据,告别对敏感信息的硬编码。
- 支持开发者在应用中快速集成凭据管家能力,一行代码读取凭据信息。
- 封装凭据在应用中缓存和刷新的功能。
- 封装API错误的重试机制,智能处理服务端错误。
- 开放插件式设计模式,支持开发者自定义扩展缓存、错误重试等功能模块。
建议您采用基于Client Key的应用接入点来构建凭据管家客户端。能够控制应用程序更安全地使用凭据。
4、管理应用接入点
您可以创建应用接入点AAP(Application Access Point),控制应用程序如何使用凭据。
4.1 创建应用接入点
- 登录密钥管理服务控制台。
- 在页面左上角的地域下拉列表,选择应用接入点所在的地域。
- 在左侧导航栏,单击应用管理。
- 单击创建应用接入点。
- 在创建应用接入点对话框,设置基本信息。
- 输入名称和描述信息。
- 在认证方式区域,选择认证方式。
- RAMRole: 如果您为应用程序的运行环境(例如:ECS实例、ACK集群、函数计算)绑定了RAM角色,可以使用RAMRole的认证方式. (一般企业还没习惯这个用法。)
- Client Key: 您可以使用Client Key的认证方式,为AAP绑定客户端证书。AAP使用证书的公钥,对应用程序进行身份认证。
- 单击下一步。
- 设置权限策略
- 单击可选策略右侧的图标。
- 在创建权限策略对话框,设置以下参数,然后单击创建。
参数名称 |
参数说明 |
权限策略名称 |
权限策略的名称。 |
作用域 |
权限策略的适用范围。 取值:共享KMS。 |
RBAC权限 |
权限管理模板,表示权限策略对具体资源的操作。 取值:SecretUser,表示可操作的接口为GetSecretValue。 |
允许访问资源 |
权限策略被授权的具体对象。可以通过以下两种方法设置:
|
网络控制规则 |
这条控制非常重要。能够从网络层面直接拦截。 权限策略允许访问的网络类型和IP地址。 您可以在可选规则区域,选择已有规则,或者按照以下步骤创建并添加新规则。
|
c. 选择已有策略,然后单击图标。
d. 单击下一步。
- 检查应用接入点信息,然后单击创建。
4.2 为AAP绑定Client Key
AAP创建完成后,您可以为其绑定用于身份认证的Client Key。
- 单击应用接入点名称。
- 在Client Key区域,单击创建Client Key。
- 在创建Client Key对话框,设置以下参数。
- Client Key加密口令在您使用Client Key访问KMS时需要使用该口令对Client Key文件进行解密,请妥善保管。
- 有效期有效期时间范围外使用Client Key会访问失败。
- 单击确定。
- 在Client Key对话框,单击下载,保存Client Key私钥文件。
可以参考官方操作步骤。参考链接
5、有了Client key之后,就可以在代码中实现了。
Python语言实现
pip install aliyun-secret-manager-client
示例代码
通过配置文件(secretsmanager.properties)构建客户端
建议您采用基于Client Key的应用接入点,通过凭据管家Python SDK使用Client Key。关于如何创建Client Key,请参见为AAP绑定Client Key。
凭据管家Python客户端在0.0.4及以上版本支持基于Client Key应用接入点访问凭据管家,需要配置以下配置文件:
## 配置访问方式。
credentials_type=client_key
## 读取Client Key的解密密码:支持从环境变量或者文件读取。
client_key_password_from_env_variable=#your client key private key password environment variable name#
client_key_password_from_file_path=#your client key private key password file path#
## 读取Client Key的私钥文件。
client_key_private_key_path=#your client key private key file path#
## 配置关联的KMS地域。
cache_client_region_id=[{"regionId":"#regionId#"}]
解释一下:
client_key_password_from_env_variable 与 client_key_password_from_file_path 这两个参数只需要填写其中的一个即可。表示的是
可以将这个加密口令写到一个文件里面或者设置到环境变量。
client_key_private_key_path 表示的
下载下来的文件。
相应的Python代码
from alibaba_cloud_secretsmanager_client.secret_manager_cache_client_builder import SecretManagerCacheClientBuilder
if __name__ == '__main__':
secret_cache_client = SecretManagerCacheClientBuilder.new_client()
secret_info = secret_cache_client.get_secret_info("#secretName#")
print(secret_info.__dict__)
Go语言
安装SDK
凭据管家客户端支持Python语言,您可以访问SecretsManager Client for Go开源代码仓库了解更多代码信息。
您可以执行如下安装命令,在项目中使用凭据管家Go客户端。
go get -u github.com/aliyun/alibabacloud-sdk-client-go
示例代码
通过配置文件(secretsmanager.properties)构建客户端
建议您采用基于Client Key的应用接入点,通过凭据管家Go SDK使用Client Key。关于如何创建Client Key,请参见为AAP绑定Client Key。
凭据管家Go客户端在v1.0.1及以上版本支持基于Client Key应用接入点访问凭据管家,需要配置以下配置文件:
配置文件(secretsmanager.properties)
## 配置访问方式。
credentials_type=client_key
## 读取Client Key的解密密码:支持从环境变量或者文件读取。
client_key_password_from_env_variable=#your client key private key password environment variable name#
client_key_password_from_file_path=#your client key private key password file path#
## 读取Client Key的私钥文件。
client_key_private_key_path=#your client key private key file path#
## 配置关联的KMS地域。
cache_client_region_id=[{"regionId":"#regionId#"}]
示例代码
package main
import (
"fmt"
"github.com/aliyun/aliyun-secretsmanager-client-go/sdk/service"
)
func main() {
client, err := service.NewClient()
if err != nil {
// Handle exceptions
panic(err)
}
secretInfo, err := client.GetSecretInfo("#secretName#")
if err != nil {
// Handle exceptions
panic(err)
}
fmt.Printf("SecretValue:%s\n",secretInfo.SecretValue)
}
Java语言实现
安装SDK
凭据管家客户端支持Java语言,您可以访问SecretsManager Client for Java开源代码仓库了解更多代码信息。
您可以通过Maven在项目中使用凭据管家Java客户端,需要添加的依赖信息如下:
com.aliyun
alibabacloud-secretsmanager-client
1.1.7
com.aliyun
aliyun-java-sdk-core
4.5.9
org.slf4j
slf4j-jdk14
1.7.9
示例代码
通过配置文件(secretsmanager.properties)构建客户端
建议您采用基于Client Key的应用接入点,通过凭据管家Java SDK使用Client Key。关于如何创建Client Key,请参见为AAP绑定Client Key。
凭据管家Java客户端在1.1.8及以上版本支持基于Client Key应用接入点访问凭据管家,需要配置以下配置文件:
## 配置访问方式。
credentials_type=client_key
## 读取Client Key的解密密码:支持从环境变量或者文件读取。
## 可以定义一个环境变量用于保存私钥密码。再将这个环境变量值赋给这个值
client_key_password_from_env_variable=#your client key private key password environment variable name#
client_key_password_from_file_path=#your client key private key password file path#
## 读取Client Key的私钥文件。
client_key_private_key_path=#your client key private key file path#
## 配置关联的KMS地域。
cache_client_region_id=[{"regionId":"#regionId#"}]
通过配置文件(secretsmanager.properties)构建客户端的示例代码如下:
配置环境变量
export credentials_type=client_key
export app_client_key=<设置的私钥值>
export client_key_password_from_env_variable=app_client_key
# 可选跟client_key_password_from_env_variable 二选一
export client_key_password_from_file_path=
export client_key_private_key_path=
export cache_client_region_id=[{"regionId":""}]
import com.aliyuncs.kms.secretsmanager.client.SecretCacheClient;
import com.aliyuncs.kms.secretsmanager.client.SecretCacheClientBuilder;
import com.aliyuncs.kms.secretsmanager.client.exception.CacheSecretException;
import com.aliyuncs.kms.secretsmanager.client.model.SecretInfo;
public class CacheClientEnvironmentSample {
public static void main(String[] args) {
try {
SecretCacheClient client = SecretCacheClientBuilder.newClient();
SecretInfo secretInfo = client.getSecretInfo("#secretName#");
System.out.println(secretInfo);
} catch (CacheSecretException e) {
e.printStackTrace();
}
}
}
可以参考官方指南,参考链接
- AK最后使用时间
身份管理 -> 用户 -> 认证管理。找到指定的ak就可以看到当前这个ak的最后使用时间。通过分析最后使用时间来判断这个AK是否近期活跃。
5.3 使用Apollo来做AK配置中心
注意事项
在使用Apollo作为配置中心时,管理员和开发人员需要拥有一个统一的密钥,用于对敏感数据(如AK/SK)加密存储与配置中心,以及在应用程序中对数据解密。该密钥在实际生产环境中,出于安全考虑,不应写在代码中或和敏感数据存放于同一配置中心之中,需要使用其他方式单独存放。(比如使用Jasypt-spring-boot-starter,在mvn打包时传入密钥)
前序工作
- 已经部署apollo,部署步骤可参考官网
操作步骤
- 登录apollo页面,根据应用创建项目。建议为每一个应用单独创建项目,实现应用之间配置的隔离。
- 进入项目详情页面,点击左下方的“管理密钥”,为项目添加密钥。只有持有密钥的客户端才能访问apollo中该项目的配置信息。(注意:需要apollo在1.6.0版本以上)
- 为了达到明文不落盘的目的,需要先使用加密工具对AK信息加密之后,再放入apollo中存储。这里我们选择
jaspty
作为加密工具,它能和spring-boot完美结合,在placeholder中自动解密。这里提供一个简单的使用jaspty的加密工具(关于更多jaspty-spring-boot用法请参考网站)
- 在maven中添加jaspty依赖
com.github.ulisesbocchio
jasypt-spring-boot-starter
2.0.0
b. JasptyUtils加密解密工具
import org.jasypt.encryption.pbe.PooledPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
public class JasyptUtils {
// String key, could not store as Plaintext in code
private static String PASSWORD = "password";
private static String ALGORITHM = "PBEWithMD5AndDES";
// Custom StringEncryptor. Jasypt uses an StringEncryptor to encrypt and decrypt properties.
private final static PooledPBEStringEncryptor pooledPBEStringEncryptor = new PooledPBEStringEncryptor();
static {
SimpleStringPBEConfig config = new SimpleStringPBEConfig();
config.setPassword(PASSWORD);
config.setAlgorithm(ALGORITHM);
config.setKeyObtentionIterations("1000");
config.setPoolSize("1");
config.setProviderName("SunJCE");
config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator");
config.setStringOutputType("base64");
pooledPBEStringEncryptor.setConfig(config);
}
public static String encrypt(String message) {
return pooledPBEStringEncryptor.encrypt(message);
}
public static String decrypt(String message) {
return pooledPBEStringEncryptor.decrypt(message);
}
public static void main(String[] args) {
String EncryptedMessage = JasyptUtils.encrypt("Hello World!");
System.out.println("Encrypted Message: " + EncryptedMessage);
System.out.println("Decrypted Message: " + JasyptUtils.decrypt(EncryptedMessage));
}
}
- 我们将所有AK/SK等敏感数据加密后,使用
ENC()
围住,并在apollo中新增并发布配置。不使用ENC()
包围的数据不会被解密
- 我们以SpringBoot项目为例,测试在SpringBoot项目中使用apollo获取AK,并用jaspty自动解密。
- 在SpringBoot项目中引入apollo和jaspty依赖
- 添加apollo和jaspty配置信息,这里为介绍清晰将apollo的访问密钥和jasypt的加密算法和密钥均硬编码在配置文件中,在实际开发环境时禁止这样操作。可以使用操作系统环境变量等其他方式添加密钥。(这里配置的算法和密钥必须和之前加密操作配置的一样)
# apollo configuration
apollo.bootstrap.enabled=true
app.id=apollo-access
apollo.meta=http://localhost:8080
apollo.accesskey.secret=d91462f532604b169bc594bb90eb10b6
# jasypt configuration
jasypt.encryptor.password=password
jasypt.encryptor.algorithm=PBEWithMD5AndDES
- 获取AK的测试代码
@SpringBootApplication
public class ApolloDemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(ApolloDemoApplication.class, args);
}
# 使用placeholder动态获取,并且jaspty自动解密
@Value("${AK}")
String AK;
@Override
public void run(String... args) throws Exception {
while(true) {
System.out.println("AK: " + AK);
TimeUnit.SECONDS.sleep(5);
}
}
}
- 代码运行后能够成功获取AK,并且当AK在apollo上改变时,应用也能成功获取最新数据。
六、注意事项
6.1 AK轮转/替换
1) 静默期
a.遵循安全管理规范,进行AK排查,定位;
b.相关业务通告;
c.替换后测试/日常环境验证;
c.预发环境最小范围灰度;
2) 重启策略
a.fail fast原则,线上环境分批发布,异常快速回滚;
b.非核心应用优先重启;
七、参考材料
- 密钥管理服务,链接