配置TKE Pod接入CAM角色访问COS

2023-11-30 12:18:36 浏览数 (2)

背景

经常有用户从云主机迁移到容器后,会存在AK安全存放问题,云主机通常支持关联CAM ROLE,无需在业务代码中配置AK密钥,可直接访问云资源,但是容器后,一个节点存在多个Pod,这样就不安全了。本文介绍在TKE中如何为Pod指定cam role,业务代码无需配置AK,直接访问云资源。

限制条件

本示例中,假定您已完成以下限制条件:

  • TKE ServiceAccountIssuerDiscovery功能开白名单,提工单咨询或者找架构师
  • 登录 - 腾讯云,新建TKE集群。集群版本>= 1.20,仅支持托管集群。

交互流程图

操作步骤

步骤1:准备托管集群
  1. 登录 容器服务控制台,新建集群,集群版本>=1.20
    • 如果您没有托管集群,您可以使用容器服务控制台创建 TKE 标准集群,详情见 容器服务 创建集群-TKE 标准集群指南-文档中心-腾讯云。
    • b.如果您已有托管集群,请在集群详情页检查集群版本,当集群版本不满足要求时,请升级集群。对运行中的 Kubernetes 集群进行升级,详情见 容器服务 升级集群-TKE 标准集群指南-文档中心-腾讯云。
  2. 执行如下命令,确保您可以通过 kubectl 客户端访问托管集群。
代码语言:shell复制
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 资源访问控制能力
  1. 进入容器服务控制台,在集群详情页中,单击 ServiceAccountIssuerDiscovery 右侧的。如下图所示:
  2. 进入“修改 ServicAccountIssuerDiscovery 相关参数”页面,若系统提示您无法修改相关参数,请先进行服务授权。 页面,查看授权策略 QcloudAccessForTKERoleInOIDCConfig,单击同意授权。
  3. 授权完毕后,勾选“创建 CAM OIDC 提供商”和“新建 WEBHOOK 组件”,并填写客户端 ID,单击确定。如下图所示: 客户端 ID 是选填参数,当不填写时,默认值是 "sts.cloud.tencent.com",本文示例中创建 CAM OIDC 提供商采用默认值。
  4. 返回集群详情页,当 ServiceAccountIssuerDiscovery 可再次编辑时,表明本次开启 OIDC 资源访问控制结束。 注意 "service-account-issuer" 和 "service-account-jwks-uri" 参数值不允许编辑
步骤3:检查 CAM OIDC 提供商和 WEBHOOK 组件是否创建成功
  1. 在集群详情页中,单击 ServiceAccountIssuerDiscovery 右侧的。
  2. 进入“修改 ServicAccountIssuerDiscovery 相关参数”页面,系统将提示“您创建的身份提供商已存在,前往查看”。单击前往查看。如下图所示:
  3. 进入访问管理-身份提供商-角色SSO,通过TKE集群id查看您刚创建的 CAM OIDC 提供商详细信息。如下图所示:
  4. 在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 角色并关联访问腾讯云数据库和凭据管理系统的策略
  1. 登录 访问管理控制台
  2. 在角色页中,单击新建角色 > 身份提供商。
  3. “新建自定义角色”页,参考以下信息进行设置。

注意:

  • 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代码

0 人点赞