全部产品
Search
文档中心

弹性容器实例:为ECI Pod绑定RAM角色

更新时间:Feb 27, 2024

RAM角色是一种具备某些权限的虚拟用户,可以被ECI Pod扮演,从而使得ECI Pod获得相应的权限。本文介绍如何为ECI Pod(即ECI实例)绑定RAM角色,以便ECI Pod内部的应用程序可以基于STS(Security Token Service)临时凭证访问其他阿里云产品的API。

背景信息

ECI Pod上部署的应用程序在云产品通信中,通过云账号或者RAM用户的AccessKey访问阿里云其他云产品(例如OSS、VPC、RDS等)的API。为了方便和快速地调用,部分用户直接把AccessKey固化在实例中,例如直接写在配置文件中。这种方式存在权限过高、泄露信息和难以维护等问题。

实例RAM角色能够避免上述问题。通过RAM角色授权,无需在ECI Pod中保存AccessKey。通过修改RAM角色的权限即可变更ECI Pod的权限,在使用上安全便捷。更多关于RAM角色的信息,请参见RAM角色概览

创建RAM角色并授权

  1. 创建RAM角色。具体操作,请参见创建可信实体为阿里云服务的RAM角色

    创建RAM角色时,可信实体类型选择阿里云服务,受信服务为云服务器

    RAM角色.png

  2. 授权RAM角色。

    1. 创建权限策略。具体操作,请参见创建自定义权限策略

    2. 为RAM角色添加权限策略。具体操作,请参见为RAM角色授权

  3. (可选)授权RAM用户使用RAM角色。

    如果通过RAM用户使用RAM角色,请确保RAM用户具有ram:passRole权限。权限内容参考如下,其中ECIRamRoleTest为要授权的ram:PassRole权限的RAM角色名称。

    {
       "Statement": [
        {
          "Effect": "Allow",
          "Action": "ram:PassRole",
          "Resource": "acs:ram:*:*:role/ECIRamRoleTest" 
        }
      ],
      "Version": "1"
    }                

为ECI Pod绑定RAM角色

创建ECI Pod时,可以通过k8s.aliyun.com/eci-ram-role-name的Annotation为Pod绑定RAM角色,授予Pod访问阿里云产品的能力。

重要
  • Annotation请添加在Pod的metadata下,例如:创建Deployment时,Annotation需添加在spec>template>metadata下。

  • 仅支持在创建ECI Pod时添加ECI相关Annotation来生效ECI功能,更新ECI Pod时添加或者修改ECI相关Annotation均不会生效。

配置示例如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
  labels:
    app: test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      name: test
      labels:
        app: test
        alibabacloud.com/eci: "true" 
      annotations:
        k8s.aliyun.com/eci-ram-role-name : "${your_ram_role_name}"   #绑定RAM角色
    spec:
      containers:
      - name: test
        image: registry.cn-shanghai.aliyuncs.com/eci_open/centos:7
        command: ["sleep"]
        args: ["3600"]

获取STS Token

在ECI实例内部,您可以访问元数据URL来获取RAM角色的STS Token,该STS Token可以执行RAM角色的权限和资源,并且该STS Token会自动周期性地更新。

curl http://100.100.100.200/latest/meta-data/ram/security-credentials/${your_ram_role_name}

请使用实际RAM角色名称替换${your_ram_role_name},假设RAM角色名称为ECIRamRoleTest,命令示例如下:

curl http://100.100.100.200/latest/meta-data/ram/security-credentials/ECIRamRoleTest

返回结果中可以获取STS Token,示例如下:

{
  "AccessKeyId" : "STS.******",
  "AccessKeySecret" : "******",
  "Expiration" : "2023-06-22T19:13:58Z",
  "SecurityToken" : "******",
  "LastUpdated" : "2023-06-22T13:13:58Z",
  "Code" : "Success"
}

基于STS Token访问云服务

以下示例以使用Go SDK访问OSS为例介绍如何基于STS Token访问云服务,该示例可以实现基于STS Token访问某个OSS Bucket并列举其中的所有文件。

说明

该示例仅用于演示基于STS Token访问云服务的方法,实际场景中请根据自身业务编写代码,具体请参考您要使用的云服务的SDK。

package main

import (
	"encoding/json"
	"flag"
	"log"
	"os/exec"

	"github.com/aliyun/aliyun-oss-go-sdk/oss"
)

const (
	securityCredUrl = "http://100.100.100.200/latest/meta-data/ram/security-credentials/"
)

var (
	ossEndpoint   string
	ossBucketName string
)

func init() {
	flag.StringVar(&ossEndpoint, "endpoint", "oss-cn-hangzhou-internal.aliyuncs.com", "Please input oss endpoint, Recommended internal endpoint, eg: oss-cn-hangzhou-internal.aliyuncs.com")
	flag.StringVar(&ossBucketName, "bucket", "", "Please input oss bucket name")
}

type AssumedRoleUserCredentialsWithServiceIdentity struct {
	AccessKeyId     string `json:"AccessKeyId" xml:"AccessKeyId"`
	AccessKeySecret string `json:"AccessKeySecret" xml:"AccessKeySecret"`
	Expiration      string `json:"Expiration" xml:"Expiration"`
	SecurityToken   string `json:"SecurityToken" xml:"SecurityToken"`
	LastUpdated     string `json:"LastUpdated" xml:"LastUpdated"`
	Code            string `json:"Code" xml:"Code"`
}

func main() {
	flag.Parse()

	if ossEndpoint == "" {
		log.Fatal("Please input oss endpoint, eg: oss-cn-hangzhou-internal.aliyuncs.com")
	}
	if ossBucketName == "" {
		log.Fatal("Please input oss endpoint")
	}

	output, err := exec.Command("curl", securityCredUrl).Output()
	if err != nil {
		log.Fatalf("Failed to get ramrole name from metaserver: %s", err)
	}

	output, err = exec.Command("curl", securityCredUrl+string(output)).Output()
	if err != nil {
		log.Fatalf("Failed to get security credentials from metaserver: %s", err)
	}

	authServiceIdentity := new(AssumedRoleUserCredentialsWithServiceIdentity)
	if err := json.Unmarshal(output, authServiceIdentity); err != nil {
		log.Fatalf("Failed to Unmarshal to AssumedRoleUserCredentialsWithServiceIdentity: %s", err)
	}

	// 创建OSS Client实例, 生产环境使用需要定时更新OSS Client,防止SecurityToken过期访问云产品失败
	ossClient, err := oss.New(ossEndpoint, authServiceIdentity.AccessKeyId,
		authServiceIdentity.AccessKeySecret, oss.SecurityToken(authServiceIdentity.SecurityToken))
	if err != nil {
		log.Fatalf("Failed to new oss client: %s", err)
	}

	// 获取存储空间
	bucket, err := ossClient.Bucket(ossBucketName)
	if err != nil {
		log.Fatalf("Failed to get bucket %q: %s", ossBucketName, err)
	}

	// 列举存储空间中文件
	marker := ""
	for {
		lsRes, err := bucket.ListObjects(oss.Marker(marker))
		if err != nil {
			log.Fatalf("Failed to list object from bucket %q: %s", ossBucketName, err)
		}
		// 打印列举文件,默认情况下一次返回100条记录。
		for _, object := range lsRes.Objects {
			log.Println("Bucket: ", object.Key)
		}
		if lsRes.IsTruncated {
			marker = lsRes.NextMarker
		} else {
			break
		}
	}
}