应用程序使用AK的最佳方案
客户可以使用AK或STS作为访问凭证,用程序访问阿里云。但密钥一旦泄露会导致严重的安全风险,因此有极高的安全性要求。本方案介绍应用程序使用AK的最佳方案。
方案简介
客户场景
单账号程序访问密钥防泄漏管理(AK防泄漏)
场景描述
客户可以使用AK或STS作为访问凭证,用程序访问阿里云。但密钥一旦泄露会导致严重的安全风险,因此有极高的安全性要求。
适用客户
- 所有需要使用程序访问阿里云资源的客户,均为此场景的目标客户。
客户痛点
客户痛点包括:
- AK/SK配置信息分散在多个应用程序中,无法统一管理
- AK/SK硬编码在代码中,无法及时更新AK/SK
- AK/SK以明文方式存在,易发生泄露问题,存在安全隐患
- 无法追踪AK/SK的使用情况
客户价值包括:
- 治理当前的AK使用方式,使用多种方式降低AK泄露风险
- 降低AK泄露一旦发生的损失
- 提高AK审计能力,从而能够更好的做到泄露事件的处置
- 在保证安全性的同时降低AK管理成本
架构设计
AK防泄漏管控管理全景
客户使用AK的最优原则和推荐路径
推荐方案:使用无AK方案,避免程序直接接触AK
如果必须使用固定AK,则参考以下方案
对于固定方案中的前两种,即
- 使用KMS 凭据管家-RAM托管凭据 来做AK配置中心
- 使用Apollo来做AK配置中心
以下会详细介绍这些方案的具体操作步骤。
实施步骤
实施时长
在实施准备工作完成的情况下,本方案实施预计时长:2小时。
使用KMS凭据管家-托管RAM凭据 来做AK配置中心
方案概览
适用场景
- 倾向于使用云原生方案,避免自行维护 Vault 带来的运维成本
- 业务主要部署在阿里云(部署在其他环境也适用,但无法使用 RAM Role可信认证方式和 VPC 来源验证)
方案优势
- 云原生方案,无需单独部署和维护 Vault
- 安全性优,对于专属 KMS 标准版,支持独享 HSM 资源池;支持多种可信验证方式,对于部署在阿里云上的应用,可以限制请求来源为某个 VPC,自建 Vault 无法实现
- 方案可靠性 99.9%,KMS 凭据管家客户端通过客户端缓存等多种措施,支持高并发和大吞吐的访问
前序工作
- 已经开通阿里云KMS服务。开通链接
- 管理动态RAM凭据前,您需要通过RAM服务角色授予凭据管家管理RAM用户AccessKey的权限。具体操作,请参见授予凭据管家管理RAM用户AccessKey的权限。
- 请为待托管RAM凭据的RAM用户创建AccessKey,需要事先有RAM用户并有AK才需要对这个AK进行托管管理。具体操作,请参见为RAM用户创建访问密钥。
费用说明
目前新客户只能购买专属KMS版本,根据企业自身需要选择合适的版本进行购买。具体计费方案参考文档。需要注意,除了实例费用外,专有网络个数也会对最终费用有较大影响。目前暂不支持首次购买KMS实例时未添加凭据数量的 KMS 实例升级扩容凭据数量,因此使用该方案时,需要注意添加凭据数量。
对于有合规需求或者高安全性需求的客户,推荐购买标准版。标准版中,密钥安全存储于用户独享的硬件安全密码模块(HSM)资源池。
操作步骤
1、在KMS-“凭据”页面创建新的凭据。根据不同服务需要,可以使用托管RDS凭据、RAM凭据和ECS凭据,这三类凭据和阿里云服务相连接。本着安全的考虑原则,建议使用RAM凭据,可以使用自动轮转AK的功能,无需管理员手动定时更换凭据。
注意!设置凭据值这里请输入这个RAM用户对应的一个合法的AK值。KMS是用这个AK值去做轮转判断的。
具体配置,请参考官方帮助手册。查看链接
2、信息确认后,可以在凭据页面看见我们刚刚创建的凭据。
3、管理员配置完了之后,应用程序侧访问需要先配置应用接入点AAP(Application Access Point),控制应用程序如何使用凭据。
应用接入点最佳实践:
- 为不同的应用的不同环境创建不同的AAP
- 根据业务部署环境/系统改造成本,确定可信验证方案是 RAM Role 还是 Client Key。对于 RAM Role 方案,一般需要改造应用资源申请/扩容流程或系统,对于 Client Key 方案,一般需要改造代码发布系统。具体区别和适用场景如下。
应用接入点可信认证方式:
可信认证方式 |
RAM Role |
Client Key |
适用场景 |
所有环境均在阿里云上(尤其是开发环境) |
|
优点 |
|
|
需要注意 |
|
|
最佳实践 |
基于ECS部署应用:通过自动化流程(如服务目录/ESS)生产/扩容ECS,自动绑定实例角色用于访问KMS凭据管家 基于ACK部署应用:使用RRSA组件,通过OIDC方式获取临时Token用于访问KMS凭据管家 基于FC部署应用:在FC服务设置中配置对应角色
|
|
4、 创建应用接入点
- 登录密钥管理服务控制台。
- 在页面左上角的地域下拉列表,选择应用接入点所在的地域。
- 在左侧导航栏,单击应用管理。
- 单击创建应用接入点。
- 在创建应用接入点对话框,设置基本信息。
- 输入名称和描述信息。
- 在认证方式区域,选择认证方式。
- 单击下一步。
- 设置权限策略
- 单击可选策略右侧的
图标。
- 在创建权限策略对话框,设置以下参数,然后单击创建。
参数名称 |
参数说明 |
权限策略名称 |
权限策略的名称。 |
作用域 |
权限策略的适用范围。 取值:共享KMS。 |
RBAC权限 |
权限管理模板,表示权限策略对具体资源的操作。 取值:SecretUser,表示可操作的接口为GetSecretValue。 |
允许访问资源 |
权限策略被授权的具体对象。可以通过以下两种方法设置:
|
网络控制规则 |
这条控制非常重要。能够从网络层面直接拦截。 权限策略允许访问的网络类型和IP地址。 您可以在可选规则区域,选择已有规则,或者按照以下步骤创建并添加新规则。
|
c. 选择已有策略,然后单击图标。
d. 单击下一步。
- 检查应用接入点信息,然后单击创建。
5、如果 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私钥文件。
可以参考官方操作步骤。参考链接
6、配置完AAP后,可以在代码中实现了。在代码中需要进行一定改造,需要通过凭据管家客户端(SecretsManager Client)或者其他方式获取该凭据。目前支持的 Client/SDK 如下:
接入方式 |
机制描述 |
需要注意 |
通用 K8S Secrets插件
|
|
虽然 K8S 插件或者 Client 均会刷新缓存的本地 Secret,但是,在动态轮转AK、DB帐密的情形下,业务开发仍然可能需要改造代码,保证业务中使用AK时,每次都做一次读取 |
通用客户端 SDK SecretsManager Client |
|
|
针对动态 AK 优化的 SDK |
在使用普通阿里云SDK的基础上,加上 remote credential provider 获得普通的阿里云 SDK 的开发体验 |
业务开发对AK、帐密是否自动轮转几乎无感
|
针对动态 DB 帐密优化的 SDK |
在使用 JDBCDriver 的基础上,加上 remote credential provider 获得 JDBC Driver 的开发体验 |
对于 Java 语言,推荐使用针对动态 AK 优化的 SDK。业务开发对AK、帐密是否自动轮转几乎无感,参考文档。
对于非 Java 的客户端代码,推荐使用凭据管家客户端。
凭据管家客户端基于KMS凭据管家API封装了业务逻辑、最佳实践和设计模式,更易于开发者在业务系统中集成。主要适用于应用中动态使用托管在凭据管家中的凭据,告别对敏感信息的硬编码。
- 支持开发者在应用中快速集成凭据管家能力,一行代码读取凭据信息。
- 封装凭据在应用中缓存和刷新的功能。
- 封装API错误的重试机制,智能处理服务端错误。
- 开放插件式设计模式,支持开发者自定义扩展缓存、错误重试等功能模块。
使用 SecretsManager Client,推荐根据企业设置的AK轮转周期和访问 KMS QPS(GetSecretValue),设置合理的 Cache TTL。业务开发需要改造代码,保证业务中使用AK时,每次都做一次读取。
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客户端,需要添加的依赖信息如下:
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibabacloud-secretsmanager-client</artifactId>
<version>1.1.7</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>4.5.9</version>
</dependency>
<!--视当前应用包的情况而定,注意包冲突-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.9</version>
</dependency>
示例代码
通过配置文件(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=<your client key private key password from file>
export client_key_private_key_path=<your client key private key file path>
export cache_client_region_id=[{"regionId":"<your region id>"}]
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是否近期活跃。
使用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依赖
<!-- jasypt-->
<!-- 注意jasypt版本不能超过2.0.0,超过后和apollo存在冲突 -->
<!-- 高于2.0.0版本,jaspty会从缓存中读取数据,导致apollo配置更改时应用无法及时更新 -->
<!-- 详见https://github.com/ctripcorp/apollo/issues/2162 -->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
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上改变时,应用也能成功获取最新数据。
注意事项
AK轮转/替换
1) 静默期
a.遵循安全管理规范,在代码/配置中心/Vault等环境中进行AK排查,定位;
b.相关业务通告;
c.替换后测试/日常环境验证;
c.预发环境最小范围灰度;
2) 重启策略
a.fail fast原则,线上环境分批发布,异常快速回滚;
b.非核心应用优先重启;
参考材料
- 密钥管理服务,链接