服务配置参数的托管一直是开发和运维过程中比较重要的一环,对配置数据进行统一托管、安全存储、安全分发对于业务的安全稳定运行有极大的帮助。
这里我们提及的配置数据,通常指的是对于某项服务的启动加载必不可少的参数,常见的比如:
这些数据都有如下的一些特点:
当然,还有一点不可忽视的特点就是,上述数据可能包含业务生产环境的关键信息,因此对这些数据进行安全加固是必不可少的,比如使用安全的加密手段对这些数据进行加密存储以及安全分发(https 信道传输)。
除了上面这些特点外,对配置数据进行全生命周期管理比如启用、禁用、新增、删除等也是增加业务运维效率不可缺少的能力。
这篇文章将会基于 SSM凭据管理系统 ,围绕配置数据管理的常见场景进行使用说明。
腾讯云 SSM 凭据管理系统最核心的能力是能对用户的凭据数据做安全加密托管,它的底层依赖于腾讯云KMS密钥管理系统对数据进行加解密。
SSM 在整个腾讯云云平台上的架构如下:
整个架构可以拆解为:
以自定义凭据为例,通过云控制台访问 SSM 的形式如下:
通过 SDK 的方式访问 SSM会涉及到编码,这里我们可以 以 通过 python sdk 访问云 API 的方式来举例,展示拉取 SSM 凭据列表的操作:
import os
from tencentcloud.common import credential
from tencentcloud.common.credential import Credential
from tencentcloud.common.exception import TencentCloudSDKException
from tencentcloud.ssm.v20190923 import models as ssm_models, ssm_client
if __name__ == '__main__':
# 通过环境变量的方式获取腾讯云 AKSK(即 secretID、secretKey)
# 这里的环境变量的名称用户可以自定义
# 设置环境变量:
# export TENCENT_CLOUD_SECRET_ID="AKIDxxxxxxx"
# export TENCENT_CLOUD_SECRET_KEY="mX3Ixxxxxx"
# 如果希望环境变量长期生效,根据用户使用的 shell 环境,可以将环境变量写到对应的配置文件中
# 比如用户使用的 bash,则写入 ~/.bash_profile并source ~/.bash_profile
# 如果使用的是 zsh,则写入~/.zshrc 并 source ~/.zshrc
SECRET_ID_ENV_NAME: str = "TENCENT_CLOUD_SECRET_ID"
SECRET_KEY_ENV_NAME: str = "TENCENT_CLOUD_SECRET_KEY"
secret_id: str = os.environ.get(SECRET_ID_ENV_NAME)
secret_key: str = os.environ.get(SECRET_KEY_ENV_NAME)
region: str = 'ap-guangzhou'
try:
cred: Credential = credential.Credential(secret_id, secret_key)
demo_client = ssm_client.SsmClient(cred, region)
req = ssm_models.ListSecretsRequest()
req.Limit = 10
rsp = demo_client.ListSecrets(req)
print(rsp.to_json_string(indent = ' '))
except TencentCloudSDKException as e:
print(e)
raise e
执行这段代码后将会输出:
SSM 最简单的使用方式,同时在配置管理中也是最常见的使用方式,就是使用自定义凭据。
自定义凭据,最大的特点就是自定义,这里的自定义,指的是,用户可以自定义凭据的数据内容格式,当然这里所说的数据仅限于文本数据。
无论是结构化的数据如 JSON、YAML,TOML 等,还是非结构化的数据,比如"hello,world"这种常见字符串,都可以托管到自定义凭据中。
对于非文本类数据比如图片,如果确实有必要进行托管,则可以先将数据进行 base64 或其他形式的编码,转化成纯文本数据。
在 SSM 系统中,以用户为纬度(如同一个 APPID 下的账号),凭据名是唯一存在的,即同一个 APPID 下(无论是主账号 UIN 还是子账号 UIN),凭据名在创建时必须保持唯一。
最简单的使用方式,就是,配置的管理员登录到 SSM 的控制台,设置好凭据内容,然后,业务进程通过集成 SSM SDK,拉取凭据内容并做内容解析。
比如现在在系统中创建好如下内容的凭据数据:
{
"username": "hello",
"password": "world",
"token": "1234567890"
}
将凭据名命名为demo_test
,其有且仅有一个版本:demo_01
点击确定后,即可生成凭据:
此时,凭据即创建完成。
在常见的服务配置管理场景中,对于配置的读取,往往都是通过API 接口的形式进行读取的。
SSM 系统提供了标准的腾讯云 SDK,这里我们以 Python SDK 为例演示凭据读取的过程。
import json
import os
from tencentcloud.common import credential
from tencentcloud.common.credential import Credential
from tencentcloud.common.exception import TencentCloudSDKException
from tencentcloud.ssm.v20190923 import models as ssm_models, ssm_client
if __name__ == '__main__':
# 获取 SecretId 和 SecretKey
secret_id: str = os.environ.get("TENCENT_CLOUD_SECRET_ID")
secret_key: str = os.environ.get("TENCENT_CLOUD_SECRET_KEY")
try:
# 生成 credential 对象
cred: Credential = credential.Credential(secret_id, secret_key)
# 指定需要调用的 SSM 的地域
region: str = 'ap-guangzhou'
demo_client = ssm_client.SsmClient(cred, region)
# 生成请求并填充参数
req = ssm_models.GetSecretValueRequest()
req.SecretName = "demo_test"
req.VersionId = "demo_01"
# 发送请求,并获取响应
rsp = demo_client.GetSecretValue(req)
# 解析响应数据,如将凭据明文进行结构化
data = json.loads(rsp.SecretString)
# 使用凭据数据,这里仅仅作打印动作,实际业务场景中根据需要进行自定义
print(rsp.SecretName, rsp.VersionId, data)
except TencentCloudSDKException as e:
print(e)
raise e
无论使用 Python 语言的 SDK亦或是 Golang、Java、CPP 等等其他语言的 SDK,在通过 SDK 调用 SSM 的云 API 接口前都需要先获取云平台的 AKSK,这个步骤在上述示例代码中对应的语句为:
secret_id: str = os.environ.get("TENCENT_CLOUD_SECRET_ID")
secret_key: str = os.environ.get("TENCENT_CLOUD_SECRET_KEY")
获取完 AKSK 后,需要先生成 Credential对象,用于客户端的创建:
cred: Credential = credential.Credential(secret_id, secret_key)
由于 SSM 系统本身是分地域的,因此在查询某个凭据前,需要先确定凭据所处的地域,并生成对应的客户端:
region: str = 'ap-guangzhou'
demo_client = ssm_client.SsmClient(cred, region)
作为业务方,我们只需要拉取凭据接口,因为我们只需要调用查询接口,读取凭据明文,在发起查询请求前需要先构造请求包:
req = ssm_models.GetSecretValueRequest()
req.SecretName = "demo_test"
req.VersionId = "demo_01"
SSM 系统查询凭据明文统一使用 GetSecretValue 接口进行查询。
使用这个接口时,我们需要填充好 VersionId 字段,以明确需要查询的凭据版本,如前文所说,SSM 的自定义凭据是允许用户进行多版本管理的。
关于更多 SSM 的 API 接口可以点击查阅。
构造好请求包之后,发送请求即可:
rsp = demo_client.GetSecretValue(req)
对获取的响应做解析:
data = json.loads(rsp.SecretString)
print(rsp.SecretName, rsp.VersionId, data)
自定义凭据允许用户设置多个版本,以应对不同场景下的差异化配置需求。
自定义凭据的多版本添加很简单:
点击确定后,当前凭据就有了两个版本:
对于业务进程来说,在明确当前应该使用哪个版本的凭据后,只需要在请求包的参数中,将版本号替换为对应的版本即可:
# 生成请求并填充参数
req = ssm_models.GetSecretValueRequest()
req.SecretName = "demo_test"
req.VersionId = "demo_special_01"
最终得到响应:
demo_test demo_special_01 {'special': 'this is a special config data'}
如前面的截图所示,多版本既可以新增,也可以更改和删除,通过 Web 界面交互操作时,用户在控制台点击操作即可。
对于大部分服务来说,创建凭据的过程如果只能通过 Web 页面进行手动操作,会导致效率低下,且无法自动化,会限制很多场景。
SSM 云 API 提供了凭据生命周期管理的所有接口,业务方可以通过这些接口,构建自己的凭据管理服务,按照自己的使用场景,灵活的进行配置运维。
以下接口是自定义凭据生命周期管理常用的接口:
查询指定凭据的版本列表 ListSecretVersionIds
在使用 API 进行凭据的创建和凭据版本的更新时,可以支持对于非文本类数据的托管,比如对于二进制数据的托管:
# 生成请求并填充参数
req = ssm_models.CreateSecretRequest()
req.SecretName = "binary_secret"
req.VersionId = "test_01"
# 生成二进制数据,示例中为 256 个元素的ascii 码
binary_data = [x for x in range(0, 256)]
print("origin binary_data:", binary_data)
# 对二进制数据进行编码
req.SecretBinary = base64.standard_b64encode(bytes(binary_data)).decode(encoding = 'utf-8')
# 发送请求,并获取响应
rsp = demo_client.CreateSecret(req)
# 使用凭据数据,这里仅仅作打印动作,实际业务场景中根据需要进行自定义
print(rsp.SecretName, rsp.VersionId)
此时数据明文为:
查询二进制凭据明文:
# 构造查询请求包
query_req = ssm_models.GetSecretValueRequest()
query_req.SecretName = "binary_secret"
query_req.VersionId = "test_01"
# 发送请求并获取响应
query_rsp = demo_client.GetSecretValue(query_req)
print(query_rsp.to_json_string(indent = ' '))
# 对二进制数据先做 base64 解码操作进而获取真实明文
plain_bytes = base64.standard_b64decode(query_rsp.SecretBinary)
print("secret plain data:", list(plain_bytes))
得到的响应:
在通过云 API 实现了凭据管理的自动化流程后,出于容灾考虑,业务方可以自己新增凭据容灾备份能力。
业务方可以通过接口:查询 SSM 地域列表 GetRegions 查询当前 SSM 所支持的地域,然后根据自己业务部署的地域,选择一个或多个作为容灾地域。
举个例子,假设用户的配置主要从 SSM 广州地域拉取,为了防止广州地域 SSM 不可访问(比如广州地域主干网络不可达等等异常情况发生)导致业务配置拉取不了,可以在访问广州失败时,将地域切换为北京和上海,进行配置的读取。
# 默认指定广州即可
region: str = 'ap-guangzhou'
demo_client = ssm_client.SsmClient(cred, region)
# 生成请求并填充参数
req = ssm_models.GetRegionsRequest()
# 发送请求,并获取响应
rsp = demo_client.GetRegions(req)
# 解析响应数据,如将凭据明文进行结构化
print(rsp.to_json_string(indent = ' '))
得到的 SSM 地域列表:
{
"Regions": [
"ap-guangzhou",
"ap-beijing",
"ap-shanghai",
"ap-singapore",
"ap-tokyo"
],
"RequestId": "5f08ef56-9288-4d7b-be63-7a70b84c8555"
}
在构造多地域备份能力时,需要业务方首先在多个地域创建好内容相同的凭据,同时后续的凭据更新操作,也需要保证多地域的数据一致性。
业务方在代码实现时,对于凭据的写操作,建议增加超时操作,避免因为偶尔的超时或者网络抖动,导致更新失败,进而影响数据一致性。
SSM 基于腾讯云CAM服务进行用户的资源访问控制。
当业务的复杂度逐步增大时,其凭据数量必然会逐步变多,这个时候,业务势必需要对凭据的访问进行权限划分。
腾讯云允许主账号生成子账号,并通过对子账号设置 CAM 策略语法,对子账号进行细粒度的权限划分。
比较典型的场景如:
某账号分别有两个子账号 A 和 B,其中 A 对应子业务 A,B 对应子业务 B,
那么业务 A 应该只能通过子账号 A 访问 A 对应的凭据资源,同样业务 B 也应该只能访问 B 对应的凭据资源。
此类场景可以归纳为:
实现上述的效果可以参考如下的 CAM 策略配置:
{
"version": "2.0",
"statement": [
{
"effect": "allow",
"action": [
"ssm:DescribeAsyncRequestInfo",
"ssm:DescribeSupportedProducts",
"ssm:GetServiceStatus",
"ssm:GetRegions",
"ssm:CreateAccessKeySecret",
"ssm:CreateProductSecret",
"ssm:CreateSSHKeyPairSecret",
"ssm:CreateSecret",
"ssm:DescribeResourceIds"
],
"resource": [
"*"
]
},
{
"effect": "allow",
"action": [
"ssm:ListSecrets",
"ssm:GetSecretValue",
"ssm:UpdateRotationStatus",
"ssm:ListSecretVersionIds",
"ssm:RestoreSecret",
"ssm:PutSecretValue",
"ssm:DeleteSecret",
"ssm:DeleteSecretVersion",
"ssm:DescribeSecret",
"ssm:DisableSecret",
"ssm:EnableSecret",
"ssm:UpdateDescription",
"ssm:UpdateSecret",
"ssm:GetSSHKeyPairValue",
"ssm:RotateProductSecret",
"ssm:DescribeRotationDetail",
"ssm:DescribeRotationHistory"
],
"resource": [
"qcs::ssm::uin/${主账号UIN}:secret/creatorUin/${子账号UIN}/*"
]
},
{
"effect": "allow",
"action": [
"kms:GetServiceStatus"
],
"resource": [
"*"
]
},
{
"effect": "allow",
"action": [
"cam:GetRole"
],
"resource": [
"*"
]
},
{
"effect": "allow",
"action": [
"tag:DescribeResourceTagsByResourceIds",
"tag:DescribeResourceTags"
],
"resource": [
"*"
]
}
]
}
注意,需要将 ${主账号UIN}和 ${子账号UIN} 替换为用户自己的UIN。
由于在开通SSM(首次使用SSM)时,需要对当前开通SSM的主账号进行角色授权操作,以保证此主账号(APPID和主账号UIN)具备相关产品的角色以及对应角色下的预设策略权限。
当使用子账号在控制台打开SSM页面时,首先要做的就是去查询当前账号是否授予了相关角色。
{
"effect": "allow",
"action": [
"cam:GetRole"
],
"resource": [
"*"
]
},
由于SSM服务依赖于KMS服务,想要使用SSM就必须要检查KMS是否已经开通。
授予此策略是为了让子账号在控制台打开SSM页面时,有权限去查询当前是否已经开通了KMS。
{
"effect": "allow",
"action": [
"kms:GetServiceStatus"
],
"resource": [
"*"
]
},
由于调用这些接口时不需要指定任何凭据名,故设置为接口级授权,每个子账号都可以通过上述接口进行相关操作。
{
"effect": "allow",
"action": [
"ssm:DescribeAsyncRequestInfo",
"ssm:DescribeSupportedProducts",
"ssm:GetServiceStatus",
"ssm:GetRegions",
"ssm:CreateAccessKeySecret",
"ssm:CreateProductSecret",
"ssm:CreateSSHKeyPairSecret",
"ssm:CreateSecret",
"ssm:DescribeResourceIds"
],
"resource": [
"*"
]
},
由于调用这些接口时必须要指定凭据名,故将其设置为资源级授权,资源范围为当前子账号有权限的所有资源。
当主账号新建一个子账号后,将此策略,且仅将此策略授予子账号,则这个子账号只能查看、修改、删除他自己创建的的凭据资源。
{
"effect": "allow",
"action": [
"ssm:ListSecrets",
"ssm:GetSecretValue",
"ssm:UpdateRotationStatus",
"ssm:ListSecretVersionIds",
"ssm:RestoreSecret",
"ssm:PutSecretValue",
"ssm:DeleteSecret",
"ssm:DeleteSecretVersion",
"ssm:DescribeSecret",
"ssm:DisableSecret",
"ssm:EnableSecret",
"ssm:UpdateDescription",
"ssm:UpdateSecret",
"ssm:GetSSHKeyPairValue",
"ssm:RotateProductSecret",
"ssm:DescribeRotationDetail",
"ssm:DescribeRotationHistory"
],
"resource": [
"qcs::ssm::uin/${主账号UIN}:secret/creatorUin/${子账号UIN}/*"
]
},
在控制台拉取SSM的凭据时,可能也需要拉取每个凭据绑定了哪些TAG,因此需要相关读权限。
{
"effect": "allow",
"action": [
"tag:DescribeResourceTagsByResourceIds",
"tag:DescribeResourceTags"
],
"resource": [
"*"
]
}
如果业务侧需要更加定制化的策略,也可以根据自己的场景自行创建相关策略。
关于 CAM 策略语法的使用,请参考 CAM 侧相关的文档。
SSM 除了支持自定义凭据外,还支持特定产品类型的凭据,目前已有的类型包括:
上述的几种凭据,主要针对的是腾讯云平台上,云原生的数据库以及 SSH 密钥对资源的安全托管。
SSM 对于云上数据库的凭据托管,可以支持自动轮换,SSM 会根据用户预先设定的轮转周期,对凭据中保存的账号密码信息进行更新。客户端通过调用 获取凭据明文 可以获取到最新的有效账号和密码信息。同一个凭据的账号和密码信息会发生变化,但对应的数据库的访问权限是相同的,SSM 会负责在数据库中同步创建或更新具有相同权限的账号或密码。
数据库凭据的轮换,对于云上数据库的安全运维有很大的帮助,可以大大减少数据库凭据泄露带来的数据库安全风险。
SSM 数据库凭据的使用这里不具体展开,请参考:数据库凭据的使用。
SSM 团队在集成腾讯云云原生资源上还在进一步拓展,比如后续会支持 Redis、Kafka、MongoDB、ES 等等云上资源凭据的托管。
不知道大家注意到没,本篇文章中,所有示例代码里面,在获取 SecretId 和 SecretKey 时,都是使用环境变量从本地读取的方式来做的,其实这是一种不太安全的做法。
AKSK 作为云平台的入口凭据,其重要程度不言而喻,但是对于 AKSK 的保护是一个很复杂的话题,其不仅仅涉及到 SDK 集成和编码,还对内部数据安全治理体系提出了更高的要求,因为篇幅限制,这部分内容这里不具体展开,感兴趣的同学可以先看看作者之前的文章:
如果有疑问欢迎在评论区留言!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。