背景
经常有用户从云主机迁移到容器后,会存在AK安全存放问题,云主机通常支持关联CAM ROLE,无需在业务代码中配置AK密钥,可直接访问云资源,但是容器后,一个节点存在多个Pod,这样就不安全了。本文介绍在TKE中如何为Pod指定cam role,业务代码无需配置AK,直接访问云资源。
限制条件
本示例中,假定您已完成以下限制条件:
- TKE ServiceAccountIssuerDiscovery功能开白名单,提工单咨询或者找架构师
- 登录 - 腾讯云,新建TKE集群。集群版本>= 1.20,仅支持托管集群。
交互流程图
操作步骤
步骤1:准备托管集群
- 登录 容器服务控制台,新建集群,集群版本>=1.20
- 如果您没有托管集群,您可以使用容器服务控制台创建 TKE 标准集群,详情见 容器服务 创建集群-TKE 标准集群指南-文档中心-腾讯云。
- b.如果您已有托管集群,请在集群详情页检查集群版本,当集群版本不满足要求时,请升级集群。对运行中的 Kubernetes 集群进行升级,详情见 容器服务 升级集群-TKE 标准集群指南-文档中心-腾讯云。
- 执行如下命令,确保您可以通过 kubectl 客户端访问托管集群。
kubectl get node
返回如下结果,则说明可正常访问集群
代码语言:shell复制kubectl get node
NAME STATUS ROLES AGE VERSION
10.0.4.144 Ready <none> 24h v1.22.5-tke.1
说明
您可以通过 Kubernetes 命令行工具 Kubectl 从本地客户端机器连接到 TKE 集群。详情见 容器服务 连接集群-TKE 标准集群指南-文档中心-腾讯云。
步骤2:开启 OIDC 资源访问控制能力
- 进入容器服务控制台,在集群详情页中,单击 ServiceAccountIssuerDiscovery 右侧的。如下图所示:
- 进入“修改 ServicAccountIssuerDiscovery 相关参数”页面,若系统提示您无法修改相关参数,请先进行服务授权。 页面,查看授权策略 QcloudAccessForTKERoleInOIDCConfig,单击同意授权。
- 授权完毕后,勾选“创建 CAM OIDC 提供商”和“新建 WEBHOOK 组件”,并填写客户端 ID,单击确定。如下图所示: 客户端 ID 是选填参数,当不填写时,默认值是 "sts.cloud.tencent.com",本文示例中创建 CAM OIDC 提供商采用默认值。
- 返回集群详情页,当 ServiceAccountIssuerDiscovery 可再次编辑时,表明本次开启 OIDC 资源访问控制结束。 注意 "service-account-issuer" 和 "service-account-jwks-uri" 参数值不允许编辑
步骤3:检查 CAM OIDC 提供商和 WEBHOOK 组件是否创建成功
- 在集群详情页中,单击 ServiceAccountIssuerDiscovery 右侧的。
- 进入“修改 ServicAccountIssuerDiscovery 相关参数”页面,系统将提示“您创建的身份提供商已存在,前往查看”。单击前往查看。如下图所示:
- 进入访问管理-身份提供商-角色SSO,通过TKE集群id查看您刚创建的 CAM OIDC 提供商详细信息。如下图所示:
- 在TKE集群信息 > 组件管理中,如在列表看到 pod-identity-webhook 组件状态是“成功”,即表示安装组件成功。如下图所示:
您也可以执行查看命令,以 "pod-identity-webhook" 作为前缀的 Pod 状态是 Running,即表示安装组件成功。
代码语言:shell复制# kubectl get pod -n kube-system
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system pod-identity-的webhook-78c76****-9qrpj 1/1 Run
步骤4:创建 CAM 角色并关联访问腾讯云数据库和凭据管理系统的策略
- 登录 访问管理控制台
- 在角色页中,单击新建角色 > 身份提供商。
- “新建自定义角色”页,参考以下信息进行设置。
注意:
- oidc:aud 的 value 值需要和 CAM OIDC 提供商的客户端 ID value 值保持一致,默认值为 sts.cloud.tencent.com
- oidc:aud 的 value 值标识为$my_pod_audience,当odic:aud的 value 值有多个时,任选其中之一即可。
- oidc:sub的 value标识为system:serviceaccount:<namespace>:<serviceaccount>,表示角色授权给某个具体的serviceaccount。这里我们填写为system:serviceaccount:my-namespace:test,表示将角色绑定给TKE集群my-namespace命令空间的test服务账号
注意:
根据您自身业务需求,选择或创建自定义的策略进行关联。本示例中在搜索框中搜索 QcloudTKEFullAccess 进行与角色关联。
注意:
RoleArn的 value 值标识为$my_pod_role_arn。
步骤5:部署示例应用程序
1.创建一个 Kubernetes 命名空间来部署资源。
代码语言:shell复制kubectl create namespace my-namespace
将以下内容保存到 my-serviceaccount.yaml 中。将$my_pod_role_arn替换为 RoleArn 的 value 值,将$my_pod_audience替换为 odic:aud 的 value 值。
代码语言:yaml复制apiVersion: v1
kind: ServiceAccount
metadata:
name: test
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"
2.将以下内容保存到sample-application.yaml中
代码语言:yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: test-deployment
namespace: my-namespace
spec:
selector:
matchLabels:
app: my-app
replicas: 1
template:
metadata:
labels:
app: my-app
spec:
serviceAccountName: test
containers:
- name: demo
image: tencentcom/tencentcloud-cli
需注意,在本示例中,$image选择tencentcom/tencentcloud-cli,该镜像集成了 腾讯云命令行工具,方便进行示例演示。您可以根据自身业务进行填写。
3.部署示例。
代码语言:shell复制# kubectl apply -f my-serviceaccount.yaml
# kubectl apply -f sample-application.yaml
4.查看使用示例应用程序部署的 Pod。
代码语言:shell复制# kubectl get pods -n my-namespace
示例输出如下:
代码语言:shell复制NAME READY STATUS RESTARTS AGE
test-deployment-748755cd5f-lkqsj 1/1 Running 0 79s
5.查看工作负载环境变量信息。
代码语言:shell复制# kubectl -n my-namespace describe pod test-deployment-748755cd5f-lkqsj
示例输出如下:
注意:
环境变量 TKE_REGION/TKE_PROVIDER_ID/TKE_ROLE_ARN/TKE_WEB_IDENTITY_TOKEN_FILE 作为 AssumeRoleWithWebIdentity 接口参数获取临时密钥id和密钥key
步骤6:获取角色临时密钥
注意: 临时密钥默认有效期7200秒,过期请及时重新获取密钥。
手动获取临时密钥
1.基于部署示例的部署结果,进入到 test 容器查看service account token:
代码语言:shell复制kubectl exec -ti test-deployment-748755cd5f-lkqsj -n my-namespace -- /bin/bash
cat /var/run/secrets/cloud.tencent.com/serviceaccount/token
2.发送curl请求sts API获取角色临时密钥
代码语言:shell复制curl -X POST https://sts.tencentcloudapi.com -H "Authorization: SKIP" -H "Content-Type: application/json; charset=utf-8" -H "Host: sts.tencentcloudapi.com" -H "X-TC-Action: AssumeRoleWithWebIdentity" -H "X-TC-Timestamp: 1673580521" -H "X-TC-Version: 2018-08-13" -H "X-TC-Region: ap-guangzhou" -d '{"ProviderId": "cls-ram9r4bb", "RoleArn": "qcs::cam::uin/123:roleName/tke-oidc", "RoleSessionName": "abc", "WebIdentityToken": "eyJhbGciOiJS***-8PPNd-O6Kg"}'
这个curl示例中访问的是sts的公网域名,如果访问sts的内网域名,请把https://sts.tencentcloudapi.com修改为https://sts.internal.tencentcloudapi.com
header说明:
- X-TC-Region:填写就近地域访问,参考云服务器 地域和可用区-产品简介-文档中心-腾讯云
入参说明:
- ProviderId:填步骤5的 TKE_PROVIDER_ID
- RoleArn:填如下图所示的信息(步骤4中创建的角色:$my_pod_role_arn)
- RoleSessionName:会话名称,可自行定义
- WebIdentityToken:填service account token
curl返回包可以拿到临时密钥,客户可基于不同的语言封装成HTTP请求获取
自动获取临时密钥
腾讯云命令行工具以及tencentcloud-sdk-python、tencentcloud-sdk-go均支持通过TKE Pod自动注入的环境变量(TKE_WEB_IDENTITY_TOKEN_FILE,TKE_ROLE_ARN,TKE_REGION,TKE_PROVIDER_ID,TKE_DEFAULT_REGION)自动获取临时密钥
在pod内测试tccli测试访问TKE集群列表
代码语言:shell复制# root@test-deployment-748755cd5f-hd8rm:/# tccli tke DescribeClusters | more
{
"TotalCount": 15,
"Clusters": [
{
"ClusterId": "cls-pzqxxxxa6",
"ClusterName": "xxxx-test",
"ClusterDescription": "",
"ClusterVersion": "1.22.5",
"ClusterOs": "tlinux3.1x86_64",
"ClusterType": "MANAGED_CLUSTER",
"ClusterNetworkSettings": {
"ClusterCIDR": "",
"IgnoreClusterCIDRConflict": false,
"MaxNodePodNum": 64,
"MaxClusterServiceNum": 1024,
"Ipvs": false,
"VpcId": "vpc-k4l6pld3",
"Cni": true,
"KubeProxyMode": "",
"ServiceCIDR": "10.241.0.0/22",
"Subnets": [
"subnet-xxxw3hjk",
"subnet-xxxjgt4"
],
"IgnoreServiceCIDRConflict": false,
"IsDualStack": false,
步骤7: 通过角色临时密钥检索TKE集群列表
安装依赖,建议python3 版本
代码语言:shell复制# pip install tencentcloud-sdk-python -i https://mirrors.cloud.tencent.com/pypi/simple
代码语言:shell复制import json
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from tencentcloud.tke.v20180525 import tke_client, models
try:
# 申请OIDC角色临时访问凭证,可参考https://cloud.tencent.com/document/product/1312/73070
#cred = credential.Credential("SecretId", "SecretKey")
cred = credential.DefaultTkeOIDCRoleArnProvider().get_credentials()
# 实例化一个http选项,可选的,没有特殊需求可以跳过
httpProfile = HttpProfile()
httpProfile.endpoint = "tke.tencentcloudapi.com"
# 实例化一个client选项,可选的,没有特殊需求可以跳过
clientProfile = ClientProfile()
clientProfile.httpProfile = httpProfile
# 实例化要请求产品的client对象,clientProfile是可选的
client = tke_client.TkeClient(cred, "ap-guangzhou", clientProfile)
# 实例化一个请求对象,每个接口都会对应一个request对象
req = models.DescribeClustersRequest()
params = {
}
req.from_json_string(json.dumps(params))
# 返回的resp是一个DescribeClustersResponse的实例,与请求对象对应
resp = client.DescribeClusters(req)
# 输出json格式的字符串回包
print(resp.to_json_string())
except TencentCloudSDKException as err:
print(err)
代码语言:shell复制# python index.py
{"TotalCount": 15, "Clusters": [{"ClusterId": "cls-pzqsx5a6", "ClusterName": "firmly-test", "ClusterDescription": "", "ClusterVersion": "1.22.5", "ClusterOs": "tlinux3.1x86_64", "ClusterType": "MANAGED_CLUSTER", "ClusterNetworkSettings": {"ClusterCIDR": "", "IgnoreClusterCIDRConflict": false, "MaxNodePodNum": 64, "MaxClusterServiceNum": 1024, "Ipvs": false, "VpcId": "vpc-k4l6pld3", "Cni": true, "KubeProxyMode": "", "ServiceCIDR": "10.241.0.0/22", "Subnets": ["subnet-o3hw3hjk", "subnet-6itsjgt4"], "IgnoreServiceCIDRConflict": false,.........
我们在之前创建的角色中移除TKE权限后,再次执行将返回no permission
代码语言:shell复制# python index.py
[TencentCloudSDKException] code:UnauthorizedOperation.CamNoAuth message:ACTION_NO_AUTH([E500003 InternalComponentClientBuildFailed] call OAUTH/CAM v2 default check failed, result code 11008, result message: [request id:4978]you are not authorized to perform operation (ccs:DescribeCluster)
resource (qcs::ccs:gz:uin/3321337994:cluster/*) has no permission
) requestId:6a65f0ed-7ee0-42f8-b812-f2efb7345678
步骤8:通过角色临时密钥访问COS对象存储
Golang Demo
1.执行以下命令安装 COS Go SDK
代码语言:shell复制go get -u github.com/tencentyun/cos-go-sdk-v5
2.以下代码是通过 cos 的go sdk查看同账号下某个存储桶的ACL
对象存储 GET Bucket acl-API 文档-文档中心-腾讯云
对象存储 访问控制-SDK 文档-文档中心-腾讯云
代码语言:go复制package main
import (
"context"
"fmt"
"github.com/tencentyun/cos-go-sdk-v5"
"net/http"
"net/url"
)
func main() {
u, _ := url.Parse("https://bucketname-1251231234.cos.ap-nanjing.myzijiebao.com") //取存储桶的访问域名
b := &cos.BaseURL{BucketURL: u}
client := cos.NewClient(b, &http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: "AKIDRBIuvAqPo8XtWGwXPhsEdNTR-QjxvuN9XV7UjTzr7r5pWPb23paYRCncPIxxxxxxx", //取步骤6的curl返回包的TmpSecretId字段
SecretKey: "GZDQbv9RNkMbHPSYv7EaSc6gyqaTLUbuHUBJjlCIxxxx", //取步骤6的curl返回包的TmpSecretKey字段
SessionToken: "4S4B9db5jHkmm17aTUU4uJ9Y1ttmQ3Ca4833e0a12c6e02bac5fead9055378647vzq7XdxEcj5wqmudXXh_zNLFXVA2E5te3OWYvdoZKs4Rl5EvWkecE4Qma9Ddx9QT0tv1Q-UqCqVhJsU4OFYs0J2UDcYHhcZT24L3od26AJJiHFToBhtgSa8mR1BPQJI2CTFqoOS27vmIafPRuOiTcddKOUtQ4_wqLIUozTzRXuLdOZkjCG0KrheqEH7RtoZPJBzYNfI274XOT5pnJCALCNPVBrkFr5nggr2DDDHZ05KFdJWXEvNyrVXTNpZE6f_YuNYLu6W3ImER9ZcVT5njy-Euv4JCBTBCvQKg0I-MzsP_cg8ZQ2sGhPIqPoGat7iPyvaivhJDue_nFm27LrJlZwidxbpku1scYnmUiSEbNAGoQYfqYiNw96QUGem1Wi6Xwj_sh0mrl1a9YQfzsYEAOEIaAmJirgEsOrsrLPey7WtNDxD33eSPJm-kEldlMqFPArEbWXTlVMwrpJecwicqPP0Zk1QDzn80YKrUsJJpp5C0MBZDaYv9HRwoM3RlJx3bISZFyRthO8qW8TThZMUBGw", //取步骤6的curl返回包的Token字段
},
})
res, resp, err := client.Bucket.GetACL(context.Background())
if err != nil {
// ERROR
fmt.Printf("failed:% vn", resp.Status)
return
}
fmt.Printf("success getnowner:% vn", *res.Owner)
for i := 0; i < len(res.AccessControlList); i {
fmt.Printf("AccessControlList:Grantee:% vnpermission:%sn", *res.AccessControlList[i].Grantee, res.AccessControlList[i].Permission)
}
}
3.执行go程序,得到该存储桶的bucket acl
如果把步骤4中角色解除QcloudCOSFullAccess策略授权,执行程序会返回failed:403 Forbidden
Python Demo
首先给角色赋予cos读权限,通过角色临时密钥访问COS对象存储,基于腾讯云API SDK获取临时密钥,再通过COS SDK访问存储桶文件
安装依赖
代码语言:shell复制pip install cos-python-sdk-v5 tencentcloud-sdk-python -i https://mirrors.cloud.tencent.com/pypi/simple
代码语言:python代码运行次数:0复制from tencentcloud.common import credential
from tencentcloud.common.exception.tencent_cloud_sdk_exception import TencentCloudSDKException
from qcloud_cos import CosConfig
from qcloud_cos import CosS3Client
import sys
import os
import logging
# 正常情况日志级别使用 INFO,需要定位时可以修改为 DEBUG,此时 SDK 会打印和服务端的通信信息
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
try:
#cred = credential.Credential("SecretId", "SecretKey")
# 通过腾讯云API SDK获取临时密钥
cred = credential.DefaultTkeOIDCRoleArnProvider().get_credentials()
region = 'ap-shanghai'
scheme = 'https'
# 通过COS SDK获取存储桶文件
config = CosConfig(Region=region, SecretId=cred.secret_id, SecretKey=cred.secret_key, Token=cred.token, Scheme=scheme)
client = CosS3Client(config)
response = client.list_objects(
Bucket='enterwang-1251707795',
)
print(response)
except TencentCloudSDKException as err:
print(err)
代码语言:shell复制# python index.py
INFO:qcloud_cos.cos_client:generate built-in connection pool success. maxsize=10,10
INFO:qcloud_cos.cos_client:list objects, url=:https://enterwang-1251707795.cos.ap-shanghai.myzijiebao.com/ ,headers=:{}
{'Name': 'enterwang-1251707795', 'EncodingType': 'url', 'Prefix': None, 'Marker': None, 'MaxKeys': '1000', 'IsTruncated': 'false', 'Contents': [{'Key': 'benchmark.sh', 'LastModified': '2021-05-14T06:10:11.000Z', 'ETag': '"812221459c0ad30727ea203c18270f41"', 'Size': '1666', 'Owner': {'ID':...........
我们在之前创建的角色中移除COS权限后,再次执行将返回no permission
代码语言:shell复制# python index.py
INFO:qcloud_cos.cos_client:generate built-in connection pool success. maxsize=10,10
INFO:qcloud_cos.cos_client:list objects, url=:https://enterwang-1251707795.cos.ap-shanghai.myzijiebao.com/ ,headers=:{}
ERROR:qcloud_cos.cos_client:<?xml version='1.0' encoding='utf-8' ?>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied.</Message>
<ServerTime>2023-07-25T05:56:16Z</ServerTime>
<Resource>/</Resource>
<RequestId>NjRiZjY0MDBfYTUwMTQwYl8xMjU3MF8xYzM1ZWY5</RequestId>
.........
qcloud_cos.cos_exception.CosServiceError: {'code': 'AccessDenied', 'message': 'Access Denied.', 'resource': '/', 'requestid': 'NjRiZjY0MDBfYTUwMTQwYl8xMjU3MF8xYzM1ZWY5', 'traceid': 'OGVmYzZiMmQzYjA2OWNhODk0NTRkMTBiOWVmMDAxODc0OWRkZjk0ZDM1NmI1M2E2MTRlY2MzZDhmNmI5MWI1OTBjYzE2MjAxN2M1MzJiOTdkZjMxMDVlYTZjN2FiMmI0MTE0YzYyZTM1YjZkZTNjMjgyZGViMzJkNTZiMjgxODg='}
附录:
通过sts获取申请OIDC角色临时访问凭证
代码语言:python代码运行次数:0复制import os
import requests
url = "https://sts.tencentcloudapi.com"
headers = {
"Authorization": "SKIP",
"Content-Type": "application/json; charset=utf-8",
"Host": "sts.tencentcloudapi.com",
"X-TC-Action": "AssumeRoleWithWebIdentity",
"X-TC-Timestamp": "1673580521",
"X-TC-Version": "2018-08-13",
}
# Retrieve values from environment variables
provider_id = os.getenv("TKE_PROVIDER_ID")
role_arn = os.getenv("TKE_ROLE_ARN")
role_session_name = "abc" # You can set this to a specific value or get it from an environment variable as well.
web_identity_token_file = os.getenv("TKE_WEB_IDENTITY_TOKEN_FILE")
default_region = os.getenv("TKE_DEFAULT_REGION")
if not (provider_id and role_arn and web_identity_token_file and default_region):
raise ValueError("One or more required environment variables are not set.")
with open(web_identity_token_file, "r") as token_file:
web_identity_token = token_file.read()
data = {
"ProviderId": provider_id,
"RoleArn": role_arn,
"RoleSessionName": role_session_name,
"WebIdentityToken": web_identity_token
}
# Add the region to the headers
headers["X-TC-Region"] = default_region
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
print(response.json())
else:
print("Error occurred: ", response.status_code, response.text)
通过临时密钥访问cos对象存储
代码语言:python代码运行次数:0复制# pip install cos-python-sdk-v5 -i https://mirrors.cloud.tencent.com/pypi/simple
import os
import requests
import json
from qcloud_cos import CosConfig, CosS3Client
def get_web_identity_token_response():
url = "https://sts.tencentcloudapi.com"
headers = {
"Authorization": "SKIP",
"Content-Type": "application/json; charset=utf-8",
"Host": "sts.tencentcloudapi.com",
"X-TC-Action": "AssumeRoleWithWebIdentity",
"X-TC-Timestamp": "1673580521",
"X-TC-Version": "2018-08-13",
}
# Retrieve values from environment variables
provider_id = os.getenv("TKE_PROVIDER_ID")
role_arn = os.getenv("TKE_ROLE_ARN")
role_session_name = 'tencentcloud-python-sdk-' str(time.time() * 1e5)
web_identity_token_file = os.getenv("TKE_WEB_IDENTITY_TOKEN_FILE")
region = os.getenv("TKE_REGION")
if not (provider_id and role_arn and web_identity_token_file and region):
raise ValueError("One or more required environment variables are not set.")
with open(web_identity_token_file, "r") as token_file:
web_identity_token = token_file.read()
data = {
"ProviderId": provider_id,
"RoleArn": role_arn,
"RoleSessionName": role_session_name,
"WebIdentityToken": web_identity_token
}
# Add the region to the headers
headers["X-TC-Region"] = region
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
response_dict = json.loads(response.text)
return response_dict
else:
print("Error occurred: ", response.status_code, response.text)
return None
def get_cos_client(response_dict):
if not response_dict:
return None
tmp_secret_id = response_dict['Response']['Credentials']['TmpSecretId']
tmp_secret_key = response_dict['Response']['Credentials']['TmpSecretKey']
token = response_dict['Response']['Credentials']['Token']
scheme = 'https'
region = 'ap-shanghai'
config = CosConfig(Region=region, SecretId=tmp_secret_id, SecretKey=tmp_secret_key, Token=token, Scheme=scheme)
client = CosS3Client(config)
return client
def list_bucket_objects(client):
if not client:
print("CosS3Client not initialized.")
return
response = client.list_objects(Bucket='enterwang-1251707795')
print(response)
def main():
response_dict = get_web_identity_token_response()
if response_dict:
cos_client = get_cos_client(response_dict)
list_bucket_objects(cos_client)
if __name__ == "__main__":
main()
TODO: TF代码