一、使用背景
这篇向你介绍运行在腾讯云容器服务 TKE 上的工作负载如何使用 CAM 对数据库身份验证。在示例中,首先在腾讯云数据库和凭据管理系统(SSM)中分别创建一个数据库实例和数据库凭据;然后开启 OIDC 资源访问控制能力,将创建的 CAM OIDC 提供商作为创建角色的载体,并关联访问腾讯云数据库和凭据管理系统(SSM)的策略;最后利用 Kubernetes 服务账户、腾讯云访问控制管理(CAM)以及凭据管理系统(SSM)安全地连接到腾讯云数据库。整体架构如下:
二、限制条件
本示例中,假定你已完成以下限制条件:
·该功能仅支持 TKE 托管集群。
·集群版本 ≥ v1.20.6-tke.27/v1.22.5-tke.1。
·业务 Pod 可访问外网。
四、步骤1:准备托管集群
1. 登录 容器服务控制台,新建集群。
注意:说明:
2. 执行如下命令,确保你可以通过 kubectl 客户端访问托管集群。
kubectl get node
返回如下结果,则说明可正常访问集群。
NAME STATUS ROLES AGE VERSION10.0.4.144 Ready <none> 24h v1.22.5-tke.1
注意:说明:
注意:你可以通过 Kubernetes 命令行工具 Kubectl 从本地客户端机器连接到 TKE 集群。详情见 连接集群。
五、步骤2:开启 OIDC 资源访问控制能力
1. 在基本信息 > APIServer 信息中,单击 ServiceAccountIssuerDiscovery 右侧的。
2. 进入修改 ServiceAccountIssuerDiscovery 相关参数页面,若系统提示你无法修改相关参数,请先进行服务授权。
在角色管理页面,查看授权策略 QcloudAccessForTKERoleInOIDCConfig,单击同意授权。
3. 授权完毕后,勾选“创建 CAM OIDC 提供商”和“创建 webhook 组件”,并填写客户端 ID,单击确定。如下:
注意:说明:
注意:客户端 ID 是选填参数,当不填写时,默认值是 "sts.cloud.tencent.com",这篇示例中创建 CAM OIDC 提供商采用默认值。
4. 返回基本信息 > APIServer 信息页,当 ServiceAccountIssuerDiscovery 可再次编辑时,表明本次开启 OIDC 资源访问控制结束。
注意:"service-account-issuer" 和 "service-account-jwks-uri" 参数值不允许编辑,采用默认规则。
六、步骤3:检查 CAM OIDC 提供商和 WEBHOOK 组件是否创建成功
1. 在集群详情页中,单击 ServiceAccountIssuerDiscovery 右侧的。
2. 进入修改 ServiceAccountIssuerDiscovery 相关参数页面,系统将提示“你创建的身份提供商已存在,前往查看”。单击前往查看。
3. 查看你刚创建的 CAM OIDC 提供商详细信息。如下:
4. 在集群信息 > 组件管理中,如在列表看到 pod-identity-webhook 组件状态是“成功”,即表示安装组件成功。
七、步骤4:确认数据库实例
你需要确认是否存在腾讯云数据库实例,若没有腾讯云数据库实例,请你先行创建,并在数据库实例中创建数据库。若已有腾讯云数据库实例,请你跳过数据库创建。
本示例采用腾讯云 MySQL 实例,同时开启 MySQL 实例公网。创建步骤请参考 创建 MySQL 实例。
八、步骤5:更新数据库安全组
托管集群上的 Pod 想要被允许访问腾讯云 MySQL 数据库,需要给腾讯云 MySQL 数据库的安全组添加一些规则。在数据库实例的安全组页面中,修改安全组规则。如下:
为了给 Kubernetes Pod 创建入站规则,你需要单击安全组 ID 后跳转到安全组实例页面。在安全组实例详情页,选择安全组规则 > 入站规则 > 添加规则。在“添加入站规则”弹窗中,进行入站规则的创建。本示例中使用来源为0.0.0.0/0,协议端口为 TCP:3306。
九、步骤6:测试数据库连接性
在安装 MySQL 客户端的实例中,确认你使用用户名 root 和你在创建数据库设置的密码连接到数据库。如果无法连接到数据库,请返回查看是否开启 公网 及是否正确配置 安全组。
mysql -h $db_address -P $db_port -uroot -pEnter password:Welcome to the MariaDB monitor. Commands end with ; or \\g.Your MySQL connection id is 4238098Server version: 5.7.36-txsql-log 20211230Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.Type 'help;' or '\\h' for help. Type '\\c' to clear the current input statement.MySQL [(none)]>
十、步骤7:创建数据库、表、数据
为了对数据库连通性和操作权限进行验证,请你先创建个人数据库。
MySQL [(none)]> CREATE DATABASE mydb;Query OK, 1 row affected (0.00 sec)MySQL [(none)]> CREATE TABLE mydb.user (Id VARCHAR(120), Name VARCHAR(120));Query OK, 0 rows affected (0.00 sec)MySQL [(none)]> INSERT INTO mydb.user (Id,Name) VALUES ('123','tke-oidc');Query OK, 1 row affected (0.01 sec)MySQL [(none)]> SELECT * FROM mydb.user;+——+———-+| Id | Name |+——+———-+| 123 | tke-oidc |+——+———-+1 row in set (0.01 sec)
创建完成后,在控制台查看已创建的数据库。如下:
注意:数据库名的 value 值标识为$db_name。
十一、步骤8:在凭据管理系统中创建数据库凭据实例
请你检查是否存在数据库凭据。如果不存在数据库凭据,请你在凭据管理系统控制台中创建数据库凭据,开启凭据轮转及选择加密,降低账号的泄露风险与安全威胁。在这篇示例中,将创建两个数据库凭证,两者的区别是是否具备对数据库的 select 权限,为了增强可读性,通过描述 value 值加以区分。
1. 登录 凭据管理系统控制台。选择数据库凭据。
2. 在新建凭据页面,参考如下信息进行数据库账号设置。字段详情可参考 创建数据库凭据。
·关联的实例:选择新建数据库实例或者已存在数据库实例。
·主机:是指客户端 IP,不指定时填写%。
·权限配置:根据对数据库实例的操作需求进行授权。
创建第一个数据库凭证创建第二个数据库凭证单击授权,在“权限配置”页面,勾选如下权限:
单击授权,在“权限配置”页面,勾选全部权限:
3. 单击确认。在凭据列表页面查看已创建的凭据。
十二、步骤9:创建 CAM 角色并关联访问腾讯云数据库和凭据管理系统的策略
1. 登录 访问管理控制台。
2. 在角色页中,单击新建角色 > 身份提供商。
3. 在新建自定义角色页,参考以下信息进行设置。
注意:根据你的业务需求,你可以选择或创建自定义的策略来进行关联。在本示例中,你可以在搜索框中搜索 QcloudSSMReadOnlyAccess 和 QcloudCDBReadOnlyAccess,然后将它们与角色进行关联。
注意:RoleArn 的 value 值标识为$my_pod_role_arn。
十三、步骤10:部署示例应用程序
1. 创建一个 Kubernetes 命名空间来部署资源。
kubectl create namespace my-namespace
2. 将以下内容保存到 my-serviceaccount.yaml 中。将 $my_pod_role_arn 替换为 RoleArn 的 value 值,将 $my_pod_audience 替换为 oidc:aud 的 value 值。
apiVersion: v1kind: ServiceAccountmetadata: name: my-serviceaccount namespace: my-namespace annotations: tke.cloud.tencent.com/role-arn: $my_pod_role_arn tke.cloud.tencent.com/audience: $my_pod_audience tke.cloud.tencent.com/token-expiration: "86400"
3. 将以下内容保存到 sample-application.yaml 中。
apiVersion: apps/v1kind: Deploymentmetadata: name: nginx-deployment namespace: my-namespacespec: selector: matchLabels: app: my-app replicas: 1 template: metadata: labels: app: my-app spec: serviceAccountName: my-serviceaccount containers: – name: nginx image: $image ports: – containerPort: 80
需注意,在本示例中,$image 选择 ccr.ccs.tencentyun.com/tkeimages/sample-application:latest,该镜像集成了编译的 demo 文件,方便进行示例演示。你可以根据自身业务进行填写。
4. 部署示例。
kubectl apply -f my-serviceaccount.yamlkubectl apply -f sample-application.yaml
5. 查看使用示例应用程序部署的 Pod。
kubectl get pods -n my-namespace
示例输出如下:
NAME READY STATUS RESTARTS AGEnginx-deployment-6bfd845f47-9zxld 1/1 Running 0 67s
6. 查看工作负载环境变量信息。
kubectl describe pod nginx-deployment-6bfd845f47-9zxld -n my-namespace
示例输出如下:
十四、步骤11:访问数据库 demo 伪代码实现
1. 确认子账号所有访问 AssumeRoleWithWebIdentity 接口的权限。如果没有权限请联系管理员添加。
2. 确认有访问 AssumeRoleWithWebIdentity 接口的权限后,请参考 凭证管理 中步骤5获取访问 DB + SSM 的临时密钥,详情见 数据库凭据的应用。
3. 克隆 ssm-rotation-sdk-golang 代码。
git clone https://github.com/TencentCloud/ssm-rotation-sdk-golang.git
4. 替换 demo 中伪代码实现:
package mainimport ( "flag" "fmt" _ "github.com/go-sql-driver/mysql" "github.com/tencentcloud/ssm-rotation-sdk-golang/lib/db" "github.com/tencentcloud/ssm-rotation-sdk-golang/lib/ssm" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "log" "time")var ( roleArn, tokenPath, providerId, regionName, saToken string secretName, dbAddress, dbName, ssmRegionName string dbPort uint64 dbConn *db.DynamicSecretRotationDb Header = map[string]string{ "Authorization": "SKIP", "X-TC-Action": "AssumeRoleWithWebIdentity", "Host": "sts.internal.tencentcloudapi.com", "X-TC-RequestClient": "PHP_SDK", "X-TC-Version": "2018-08-13", "X-TC-Region": regionName, "X-TC-Timestamp": "1659944952", "Content-type": "application/json", })type Credentials struct { TmpSecretId string TmpSecretKey string Token string ExpiredTime uint64}func main() { flag.StringVar(&secretName, "ssmName", "", "ssm名称") flag.StringVar(&ssmRegionName, "ssmRegionName", "", "ssm地域") flag.StringVar(&dbAddress, "dbAddress", "", "数据库地址") flag.StringVar(&dbName, "dbName", "", "数据库名称") flag.Uint64Var(&dbPort, "dbPort", 0, "数据库端口") flag.Parse() provider, err := common.DefaultTkeOIDCRoleArnProvider() if err != nil { log.Fatal("failed to assume role with web identity, err:", err) } assumeResp, err := provider.GetCredential() if err != nil { log.Fatal("failed to assume role with web identity, err:", err) } var credential Credentials if assumeResp != nil { credential = Credentials{ TmpSecretId: assumeResp.GetSecretId(), TmpSecretKey: assumeResp.GetSecretKey(), Token: assumeResp.GetToken(), } } log.Printf("secretId:%v,secretkey%v,token%v\\n", credential.TmpSecretId, credential.TmpSecretKey, credential.Token) DB(credential)}func DB(credential Credentials) { // 初始化数据库连接 dbConn = &db.DynamicSecretRotationDb{} err := dbConn.Init(&db.Config{ DbConfig: &db.DbConfig{ MaxOpenConns: 100, MaxIdleConns: 50, IdleTimeoutSeconds: 100, ReadTimeoutSeconds: 5, WriteTimeoutSeconds: 5, SecretName: secretName, // 凭据名 IpAddress: dbAddress, // 数据库地址 Port: dbPort, // 数据库端口 DbName: dbName, // 可以为空,或指定具体的数据库名 ParamStr: "charset=utf8&loc=Local", }, SsmServiceConfig: &ssm.SsmAccount{ SecretId: credential.TmpSecretId, // 需填写实际可用的SecretId SecretKey: credential.TmpSecretKey, // 需填写实际可用的SecretKey Token: credential.Token, Region: ssmRegionName, // 选择凭据所存储的地域 }, WatchChangeInterval: time.Second * 10, // 多长时间检查一下 凭据是否发生了轮转 }) if err != nil { fmt.Errorf("failed to init dbConn, err:%v\\n", err) return } // 模拟业务处理中,每过一段时间(一般是几毫秒),需要拿到db连接,来操作数据库的场景 t := time.Tick(time.Second) for { select { case <-t: accessDb() queryDb() } }}func accessDb() { fmt.Println("— accessDb start") c := dbConn.GetConn() if err := c.Ping(); err != nil { log.Fatal("failed to access db with err:", err) } log.Println("— succeed to access db")}func queryDb() { var ( id int name string ) log.Println("— queryDb start") c := dbConn.GetConn() rows, err := c.Query("select id, name from user where id = ?", 1) if err != nil { log.Printf("failed to query db with err: ", err) log.Fatal(err) } defer rows.Close() for rows.Next() { err := rows.Scan(&id, &name) if err != nil { log.Fatal(err) } log.Println(id, name) } err = rows.Err() if err != nil { log.Fatal(err) } log.Println("— succeed to query db")}
十五、步骤12:测试 demo 示例
基于 部署示例 的部署结果,进入到 nginx 容器:
kubectl exec -ti nginx-deployment-6bfd845f47-9zxld -n my-namespace — /bin/bashcd /root/
将 $ssm_name 和 $ssm_region_name 标识参照 SSM 实例 进行替换,将 $db_address、$db_name 和 $db_port 标识参照 数据库实例 进行替换。
./demo –ssmName=$ssm_name –ssmRegionName=$ssm_region_name –dbAddress=$db_address –dbName=$db_name –dbPort=$db_port
在本示例中,当 $ssm_name=tke-oidc-1 时,没有数据库的 select 权限。
在本示例中,当 $ssm_name=tke-oidc-2 时,有数据库的 select 权限。
十六、测试结论
测试表明满足预期的效果。通过 CAM 对托管集群工作负载短暂的身份验证令牌的验证,确保了身份验证的安全性;另外借助凭据管理系统对数据库用户名和密码的轮转和加密特性,使得你不必担心数据库凭据的存储和生命周期问题,这样你在托管集群连接到数据库时无需使用用户名和密码。
十八、权限说明
该组件权限是当前功能实现的最小权限依赖。
十九、权限场景
| 功能 | 涉及对象 | 涉及操作权限 |
| 需要查询创建的 pod 上指定的 serviceaccounts 的资源情况。 | serviceaccount | list/watch/get |
| 创建组件时需要在 mutatingwebhookconfigurations 的资源注入客户端的证书。 | mutatingwebhookconfigurations | get/update |
20、权限定义
rules: – apiGroups: – "" resources: – serviceaccounts verbs: – get – watch – list – apiGroups: – "" resources: – events verbs: – patch – update – apiGroups: – "admissionregistration.k8s.io" resources: – "mutatingwebhookconfigurations" verbs: – get