基于腾讯云 SSM 的配置管理实践

前言

服务配置参数的托管一直是开发和运维过程中比较重要的一环,对配置数据进行统一托管、安全存储、安全分发对于业务的安全稳定运行有极大的帮助。

这里我们提及的配置数据,通常指的是对于某项服务的启动加载必不可少的参数,常见的比如:

  • 存储组件数据库(Mysql、Mongo、ES 等)的连接信息
  • 消息中间件 (Redis、Kafka等)的配置信息
  • 对业务而言必不可少的规格参数,比如连接池大小、内存缓冲区大小、连接超时时间、服务监听端口等
  • 与业务息息相关的特性参数,比如业务的商品种类、价格配置等等 ......

这些数据都有如下的一些特点:

  • 可结构化,比如可以通过 JSON、YAML 等数据格式进行结构化表示
  • 可灵活配置,比如在不同环境下相同配置项的值会不一样
  • 版本管理,比如在公有云环境、私有化环境、混合云环境下,配置文件分别有不同的版本,需要以版本或环境作为配置区分的依据
  • 数据大小可控,比如一般来说这些数据不会超过 2K 或 4K 字节,在实际的业务场景中,对于比较庞大的配置数据(比如 4K 字节以上),一般建议按照使用场景、作用进行配置拆分,这样可以更加清晰的管理数据 ......

当然,还有一点不可忽视的特点就是,上述数据可能包含业务生产环境的关键信息,因此对这些数据进行安全加固是必不可少的,比如使用安全的加密手段对这些数据进行加密存储以及安全分发(https 信道传输)

除了上面这些特点外,对配置数据进行全生命周期管理比如启用、禁用、新增、删除等也是增加业务运维效率不可缺少的能力。

这篇文章将会基于 SSM凭据管理系统 ,围绕配置数据管理的常见场景进行使用说明。

走近 SSM

总体架构概述

腾讯云 SSM 凭据管理系统最核心的能力是能对用户的凭据数据做安全加密托管,它的底层依赖于腾讯云KMS密钥管理系统对数据进行加解密。

SSM 在整个腾讯云云平台上的架构如下:

SSM 系统架构概览

整个架构可以拆解为:

  1. 用户访问 SSM 可以通过两种方式,一种是使用 SDK,通过 API 接口进行数据的读取;另一种是用户自己登录控制台,通过 Web 界面的方式进行交互操作。
  2. 无论用户通过哪种方式进行操作,最终用户的请求都会被腾讯云云 API 接收,并且云 API 最终会将请求转发送到 SSM 系统, SSM 系统返回给客户端的数据,也是以腾讯云云 API作为统一出口 ,这里客户端与云 API交互链路默认都是 HTTPS 信道进行加密传输。
  3. 在 SSM 系统接收真正的请求之前,腾讯云的 CAM 需要对请求做鉴权操作,这里的鉴权包括对云平台密钥 AKSK 进行鉴权,对用户账号能否访问特定资源的鉴权等。
  4. SSM 系统在对凭据数据做生命周期的管理时,依赖 KMS 系统提供的加解密能力对数据进行加解密保护和存储。

通过控制台访问 SSM(示例)

以自定义凭据为例,通过云控制台访问 SSM 的形式如下:

查看凭据列表
查看凭据内容

通过 SDK 访问 SSM(示例)

通过 SDK 的方式访问 SSM会涉及到编码,这里我们可以 以 通过 python sdk 访问云 API 的方式来举例,展示拉取 SSM 凭据列表的操作:

代码语言:python
代码运行次数:0
复制
Cloud Studio 代码运行
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使用入门

自定义凭据

自定义凭据的概念

SSM 最简单的使用方式,同时在配置管理中也是最常见的使用方式,就是使用自定义凭据

自定义凭据,最大的特点就是自定义,这里的自定义,指的是,用户可以自定义凭据的数据内容格式,当然这里所说的数据仅限于文本数据

无论是结构化的数据如 JSON、YAML,TOML 等,还是非结构化的数据,比如"hello,world"这种常见字符串,都可以托管到自定义凭据中。

对于非文本类数据比如图片,如果确实有必要进行托管,则可以先将数据进行 base64 或其他形式的编码,转化成纯文本数据。

在 SSM 系统中,以用户为纬度(如同一个 APPID 下的账号),凭据名是唯一存在的,即同一个 APPID 下(无论是主账号 UIN 还是子账号 UIN),凭据名在创建时必须保持唯一。

手动创建自定义凭据

最简单的使用方式,就是,配置的管理员登录到 SSM 的控制台,设置好凭据内容,然后,业务进程通过集成 SSM SDK,拉取凭据内容并做内容解析。

比如现在在系统中创建好如下内容的凭据数据:

代码语言:json
复制
{
"username": "hello",
"password": "world",
"token": "1234567890"
}

将凭据名命名为demo_test,其有且仅有一个版本:demo_01

自定义凭据的手工创建

点击确定后,即可生成凭据:

凭据信息

此时,凭据即创建完成。

通过 SDK 读取自定义凭据

在常见的服务配置管理场景中,对于配置的读取,往往都是通过API 接口的形式进行读取的。

SSM 系统提供了标准的腾讯云 SDK,这里我们以 Python SDK 为例演示凭据读取的过程。

代码语言:python
代码运行次数:0
复制
Cloud Studio 代码运行
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</code></pre></div></div><p>无论使用 Python 语言的 SDK亦或是 Golang、Java、CPP 等等其他语言的 SDK,在通过 SDK 调用 SSM 的云 API 接口前都需要先获取云平台的 AKSK,这个步骤在上述示例代码中对应的语句为:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>python</div><div class="rno-markdown-code-toolbar-item is-num"><i class="icon-code"></i><span class="is-m-hidden">代码</span>运行次数:<!-- -->0</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div><button class="rno-markdown-code-toolbar-run"><i class="icon-run"></i><span class="is-m-hidden">Cloud Studio</span> 代码运行</button></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-python"><code class="language-python" style="margin-left:0">    secret_id: str = os.environ.get(&#34;TENCENT_CLOUD_SECRET_ID&#34;)
secret_key: str = os.environ.get(&#34;TENCENT_CLOUD_SECRET_KEY&#34;)</code></pre></div></div><p>获取完 AKSK 后,需要先生成 Credential对象,用于客户端的创建:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>txt</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-txt"><code class="language-txt" style="margin-left:0">    cred: Credential = credential.Credential(secret_id, secret_key)</code></pre></div></div><p>由于 SSM 系统本身是分地域的,因此在查询某个凭据前,需要先确定凭据所处的地域,并生成对应的客户端:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>python</div><div class="rno-markdown-code-toolbar-item is-num"><i class="icon-code"></i><span class="is-m-hidden">代码</span>运行次数:<!-- -->0</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div><button class="rno-markdown-code-toolbar-run"><i class="icon-run"></i><span class="is-m-hidden">Cloud Studio</span> 代码运行</button></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-python"><code class="language-python" style="margin-left:0">    region: str = &#39;ap-guangzhou&#39;
demo_client = ssm_client.SsmClient(cred, region)</code></pre></div></div><p>作为业务方,我们只需要拉取凭据接口,因为我们只需要调用查询接口,读取凭据明文,在发起查询请求前需要先构造请求包:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>python</div><div class="rno-markdown-code-toolbar-item is-num"><i class="icon-code"></i><span class="is-m-hidden">代码</span>运行次数:<!-- -->0</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div><button class="rno-markdown-code-toolbar-run"><i class="icon-run"></i><span class="is-m-hidden">Cloud Studio</span> 代码运行</button></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-python"><code class="language-python" style="margin-left:0">    req = ssm_models.GetSecretValueRequest()
req.SecretName = &#34;demo_test&#34;
req.VersionId = &#34;demo_01&#34;</code></pre></div></div><p>SSM 系统查询凭据明文统一使用 <strong>GetSecretValue 接口</strong>进行查询。</p><p><strong>使用这个接口时,我们需要填充好 VersionId 字段,以明确需要查询的凭据版本,如前文所说,SSM 的自定义凭据是允许用户进行多版本管理的。</strong></p><p>关于更多 SSM 的 API 接口可以点击查阅。</p><p>构造好请求包之后,发送请求即可:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>python</div><div class="rno-markdown-code-toolbar-item is-num"><i class="icon-code"></i><span class="is-m-hidden">代码</span>运行次数:<!-- -->0</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div><button class="rno-markdown-code-toolbar-run"><i class="icon-run"></i><span class="is-m-hidden">Cloud Studio</span> 代码运行</button></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-python"><code class="language-python" style="margin-left:0">rsp = demo_client.GetSecretValue(req)</code></pre></div></div><p>对获取的响应做解析:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>python</div><div class="rno-markdown-code-toolbar-item is-num"><i class="icon-code"></i><span class="is-m-hidden">代码</span>运行次数:<!-- -->0</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div><button class="rno-markdown-code-toolbar-run"><i class="icon-run"></i><span class="is-m-hidden">Cloud Studio</span> 代码运行</button></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-python"><code class="language-python" style="margin-left:0">data = json.loads(rsp.SecretString)

print(rsp.SecretName, rsp.VersionId, data)

自定义凭据的多版本管理

自定义凭据允许用户设置多个版本,以应对不同场景下的差异化配置需求。

自定义凭据的多版本添加很简单:

添加多版本
填充版本号和版本内容

点击确定后,当前凭据就有了两个版本:

多版本数据

对于业务进程来说,在明确当前应该使用哪个版本的凭据后,只需要在请求包的参数中,将版本号替换为对应的版本即可:

代码语言:python
代码运行次数:0
复制
Cloud Studio 代码运行
        # 生成请求并填充参数
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 界面交互操作时,用户在控制台点击操作即可。

通过 SSM 云 API 构建凭据管理服务

对于大部分服务来说,创建凭据的过程如果只能通过 Web 页面进行手动操作,会导致效率低下,且无法自动化,会限制很多场景。

SSM 云 API 提供了凭据生命周期管理的所有接口,业务方可以通过这些接口,构建自己的凭据管理服务,按照自己的使用场景,灵活的进行配置运维。

以下接口是自定义凭据生命周期管理常用的接口:

创建凭据 CreateSecret

查询凭据明文 GetSecretValue

更新指定凭据版本内容 UpdateSecret

新增指定凭据版本 PutSecretValue

删除指定凭据版本 DeleteSecretVersion

查询指定凭据的版本列表 ListSecretVersionIds

查询凭据列表 ListSecrets

禁用凭据 DisableSecret

启用凭据 EnableSecret

删除凭据 DeleteSecret

二进制数据的托管示例

在使用 API 进行凭据的创建和凭据版本的更新时,可以支持对于非文本类数据的托管,比如对于二进制数据的托管:

代码语言:python
代码运行次数:0
复制
Cloud Studio 代码运行
        # 生成请求并填充参数
req = ssm_models.CreateSecretRequest()
req.SecretName = "binary_secret"
req.VersionId = "test_01"

    # 生成二进制数据,示例中为 256 个元素的ascii 码
    binary_data = [x for x in range(0, 256)]
    print(&#34;origin binary_data:&#34;, binary_data)
    
    # 对二进制数据进行编码
    req.SecretBinary = base64.standard_b64encode(bytes(binary_data)).decode(encoding = &#39;utf-8&#39;)
    
    # 发送请求,并获取响应
    rsp = demo_client.CreateSecret(req)
    
    # 使用凭据数据,这里仅仅作打印动作,实际业务场景中根据需要进行自定义
    print(rsp.SecretName, rsp.VersionId)</code></pre></div></div><p>此时数据明文为:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:78.83%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1723302572518957555.png" /></div><div class="figure-desc">二进制数据明文</div></div></div></figure><p>查询二进制凭据明文:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>python</div><div class="rno-markdown-code-toolbar-item is-num"><i class="icon-code"></i><span class="is-m-hidden">代码</span>运行次数:<!-- -->0</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div><button class="rno-markdown-code-toolbar-run"><i class="icon-run"></i><span class="is-m-hidden">Cloud Studio</span> 代码运行</button></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-python"><code class="language-python" style="margin-left:0">        # 构造查询请求包
    query_req = ssm_models.GetSecretValueRequest()
    query_req.SecretName = &#34;binary_secret&#34;
    query_req.VersionId = &#34;test_01&#34;
    
    # 发送请求并获取响应
    query_rsp = demo_client.GetSecretValue(query_req)
    print(query_rsp.to_json_string(indent = &#39;    &#39;))
    
    # 对二进制数据先做 base64 解码操作进而获取真实明文
    plain_bytes = base64.standard_b64decode(query_rsp.SecretBinary)
    print(&#34;secret plain data:&#34;, list(plain_bytes))</code></pre></div></div><p>得到的响应:</p><figure class=""><div class="rno-markdown-img-url" style="text-align:center"><div class="rno-markdown-img-url-inner" style="width:78.83%"><div style="width:100%"><img src="https://cdn.static.attains.cn/app/developer-bbs/upload/1723302572885323942.png" /></div><div class="figure-desc">查询二进制凭据数据</div></div></div></figure><h3 id="eq1ou" name="%E5%9F%BA%E4%BA%8E%E4%BA%91-API-%E6%9E%84%E9%80%A0%E5%A4%9A%E5%9C%B0%E5%9F%9F%E5%87%AD%E6%8D%AE%E5%A4%87%E4%BB%BD%E8%83%BD%E5%8A%9B">基于云 API 构造多地域凭据备份能力</h3><p>在通过云 API 实现了凭据管理的自动化流程后,出于容灾考虑,业务方可以自己新增凭据容灾备份能力。</p><p>业务方可以通过接口:查询 SSM 地域列表 GetRegions 查询当前 SSM 所支持的地域,然后根据自己业务部署的地域,选择一个或多个作为容灾地域。</p><p>举个例子,假设用户的配置主要从 SSM 广州地域拉取,为了防止广州地域 SSM 不可访问(比如广州地域主干网络不可达等等异常情况发生)导致业务配置拉取不了,可以在访问广州失败时,将地域切换为北京和上海,进行配置的读取。</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>python</div><div class="rno-markdown-code-toolbar-item is-num"><i class="icon-code"></i><span class="is-m-hidden">代码</span>运行次数:<!-- -->0</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div><button class="rno-markdown-code-toolbar-run"><i class="icon-run"></i><span class="is-m-hidden">Cloud Studio</span> 代码运行</button></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-python"><code class="language-python" style="margin-left:0">        # 默认指定广州即可
    region: str = &#39;ap-guangzhou&#39;
    demo_client = ssm_client.SsmClient(cred, region)
    
    # 生成请求并填充参数
    req = ssm_models.GetRegionsRequest()
    
    # 发送请求,并获取响应
    rsp = demo_client.GetRegions(req)
    
    # 解析响应数据,如将凭据明文进行结构化
    print(rsp.to_json_string(indent = &#39;    &#39;))</code></pre></div></div><p>得到的 SSM 地域列表:</p><div class="rno-markdown-code"><div class="rno-markdown-code-toolbar"><div class="rno-markdown-code-toolbar-info"><div class="rno-markdown-code-toolbar-item is-type"><span class="is-m-hidden">代码语言:</span>python</div><div class="rno-markdown-code-toolbar-item is-num"><i class="icon-code"></i><span class="is-m-hidden">代码</span>运行次数:<!-- -->0</div></div><div class="rno-markdown-code-toolbar-opt"><div class="rno-markdown-code-toolbar-copy"><i class="icon-copy"></i><span class="is-m-hidden">复制</span></div><button class="rno-markdown-code-toolbar-run"><i class="icon-run"></i><span class="is-m-hidden">Cloud Studio</span> 代码运行</button></div></div><div class="developer-code-block"><pre class="prism-token token line-numbers language-python"><code class="language-python" style="margin-left:0">{
&#34;Regions&#34;: [
    &#34;ap-guangzhou&#34;,
    &#34;ap-beijing&#34;,
    &#34;ap-shanghai&#34;,
    &#34;ap-singapore&#34;,
    &#34;ap-tokyo&#34;
],
&#34;RequestId&#34;: &#34;5f08ef56-9288-4d7b-be63-7a70b84c8555&#34;

}

在构造多地域备份能力时,需要业务方首先在多个地域创建好内容相同的凭据,同时后续的凭据更新操作,也需要保证多地域的数据一致性。

业务方在代码实现时,对于凭据的写操作,建议增加超时操作,避免因为偶尔的超时或者网络抖动,导致更新失败,进而影响数据一致性。

SSM 凭据资源的权限管理

SSM 基于腾讯云CAM服务进行用户的资源访问控制。

当业务的复杂度逐步增大时,其凭据数量必然会逐步变多,这个时候,业务势必需要对凭据的访问进行权限划分。

腾讯云允许主账号生成子账号,并通过对子账号设置 CAM 策略语法,对子账号进行细粒度的权限划分。

比较典型的场景如:

某账号分别有两个子账号 A 和 B,其中 A 对应子业务 A,B 对应子业务 B,

那么业务 A 应该只能通过子账号 A 访问 A 对应的凭据资源,同样业务 B 也应该只能访问 B 对应的凭据资源。

此类场景可以归纳为:

  • 每个子账号只可以对自己创建的凭据做创建删除等任何操作,但是不能看到以及不能对其他子账号/主账号创建的凭据做任何操作。
  • 所有的业务子账号,只能对SSM 产品下自己的资源拥有操作权限,不能拥有其他如新增子账号、删除子账号、删除 CVM 等等权限。

实现上述的效果可以参考如下的 CAM 策略配置:

代码语言:json
复制
{
"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页面时,首先要做的就是去查询当前账号是否授予了相关角色。

代码语言:json
复制
        {
"effect": "allow",
"action": [
"cam:GetRole"
],
"resource": [
"
"
]
},

查询 KMS 是否开通的权限

由于SSM服务依赖于KMS服务,想要使用SSM就必须要检查KMS是否已经开通。

授予此策略是为了让子账号在控制台打开SSM页面时,有权限去查询当前是否已经开通了KMS。

代码语言:json
复制
        {
"effect": "allow",
"action": [
"kms:GetServiceStatus"
],
"resource": [
""
]
},

针对 SSM 产品的接口级授权

由于调用这些接口时不需要指定任何凭据名,故设置为接口级授权,每个子账号都可以通过上述接口进行相关操作。

代码语言:json
复制
        {
"effect": "allow",
"action": [
"ssm:DescribeAsyncRequestInfo",
"ssm:DescribeSupportedProducts",
"ssm:GetServiceStatus",
"ssm:GetRegions",
"ssm:CreateAccessKeySecret",
"ssm:CreateProductSecret",
"ssm:CreateSSHKeyPairSecret",
"ssm:CreateSecret",
"ssm:DescribeResourceIds"
],
"resource": [
"
"
]
},

针对 SSM 产品的资源级授权

由于调用这些接口时必须要指定凭据名,故将其设置为资源级授权,资源范围为当前子账号有权限的所有资源。

当主账号新建一个子账号后,将此策略,且仅将此策略授予子账号,则这个子账号只能查看、修改、删除他自己创建的的凭据资源。

代码语言:json
复制
        {
"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}/"
]
},

查询资源绑定的 TAG 的权限

在控制台拉取SSM的凭据时,可能也需要拉取每个凭据绑定了哪些TAG,因此需要相关读权限。

代码语言:json
复制
        {
"effect": "allow",
"action": [
"tag:DescribeResourceTagsByResourceIds",
"tag:DescribeResourceTags"
],
"resource": [
"
"
]
}

如果业务侧需要更加定制化的策略,也可以根据自己的场景自行创建相关策略。

关于 CAM 策略语法的使用,请参考 CAM 侧相关的文档。

SSM 凭据类型的拓展

SSM 除了支持自定义凭据外,还支持特定产品类型的凭据,目前已有的类型包括:

  • 数据库凭据

    支持的数据库
  • SSH 密钥对凭据
    SSH 密钥对凭据

上述的几种凭据,主要针对的是腾讯云平台上,云原生的数据库以及 SSH 密钥对资源的安全托管。

SSM 对于云上数据库的凭据托管,可以支持自动轮换,SSM 会根据用户预先设定的轮转周期,对凭据中保存的账号密码信息进行更新。客户端通过调用 获取凭据明文 可以获取到最新的有效账号和密码信息。同一个凭据的账号和密码信息会发生变化,但对应的数据库的访问权限是相同的,SSM 会负责在数据库中同步创建或更新具有相同权限的账号或密码。

数据库凭据的轮换,对于云上数据库的安全运维有很大的帮助,可以大大减少数据库凭据泄露带来的数据库安全风险。

SSM 数据库凭据的使用这里不具体展开,请参考:数据库凭据的使用。

SSM 团队在集成腾讯云云原生资源上还在进一步拓展,比如后续会支持 Redis、Kafka、MongoDB、ES 等等云上资源凭据的托管。

基于 SSM 的云上密钥 AKSK 的安全保护

不知道大家注意到没,本篇文章中,所有示例代码里面,在获取 SecretId 和 SecretKey 时,都是使用环境变量从本地读取的方式来做的,其实这是一种不太安全的做法

AKSK 作为云平台的入口凭据,其重要程度不言而喻,但是对于 AKSK 的保护是一个很复杂的话题,其不仅仅涉及到 SDK 集成和编码,还对内部数据安全治理体系提出了更高的要求,因为篇幅限制,这部分内容这里不具体展开,感兴趣的同学可以先看看作者之前的文章:

《关于AKSK安全保护的一点思考》

如果有疑问欢迎在评论区留言!