实例RAM角色允许您将一个角色关联到ECI实例,在实例内部基于STS(Security Token Service)临时凭证访问其他云产品的API。本文介绍如何通过API创建、授权实例RAM角色,并将该角色授予给ECI实例。
应用场景
ECI实例上部署的应用程序在云产品通信中,通过云账号或者RAM用户的AccessKey访问阿里云其他云产品(例如OSS、VPC、RDS等)的API。为了方便和快速地调用,部分用户直接把AccessKey固化在实例中,例如直接写在配置文件中。这种方式存在权限过高、泄露信息和难以维护等问题。实例RAM角色能够避免此类问题。
RAM角色是一种具备某些权限的虚拟用户,可以被ECI实例扮演,从而使得ECI实例获得相应的权限。使用RAM角色,无需在实例中保存AccessKey,通过修改RAM角色的权限即可变更ECI实例的权限,在使用上安全便捷。更多关于RAM角色的信息,请参见RAM角色概览。
使用流程
使用实例RAM角色的步骤如下:
- 调用CreateRole创建实例RAM角色,设置允许扮演该角色的可信实体为ECI服务(受信服务为云服务器ECS)。 
- 根据需要调用CreatePolicy创建权限策略,然后调用AttachPolicyToRole将该权限策略授予给实例RAM角色。 
- (可选)授权RAM用户使用实例RAM角色 - 如果您使用RAM用户创建ECI实例并指定实例RAM角色,则必须先授权RAM用户可以使用实例RAM角色。 
- 调用CreateContainerGroup创建ECI实例,通过RamRoleName参数为ECI实例授予实例RAM角色,使得ECI实例获得对应的权限。一个ECI实例只能授予一个实例RAM角色。 
- 为ECI实例授予了实例RAM角色后,如果需要在ECI实例内部部署的应用程序中访问云产品的API,您需要通过实例元数据获取实例RAM角色的STS Token。 
创建实例RAM角色
调用CreateRole可以创建一个实例RAM角色。具体参数信息,请参见CreateRole。
您可以自定义设置RoleName来指定角色名(假设为ECIRamRoleTest),然后按如下策略文本设置AssumeRolePolicyDocument。
{
"Statement": [
{
  "Action": "sts:AssumeRole",
  "Effect": "Allow",
  "Principal": {
    "Service": [
      "ecs.aliyuncs.com"
    ]
  }
}
],
"Version": "1"
}授权实例RAM角色
- 调用CreatePolicy创建一个自定义权限策略。 - 调用时,需设置以下参数: - PolicyName:权限策略名称(假设为ECIRamRoleTestPolicy)。 
- PolicyDocument:权限策略内容。 - { "Statement": [ { "Action": [ "oss:Get*", "oss:List*" ], "Effect": "Allow", "Resource": "*" } ], "Version": "1" }
 - 更多信息,请参见CreatePolicy。 
- 调用AttachPolicyToRole为RAM角色添加权限策略。 - 调用时,需设置以下参数: - PolicyName:指定权限策略名称,例如ECIRamRoleTestPolicy。 
- PolicyType:权限策略类型,配置为Custom,表示是自定义权限策略。 
- RoleName:指定RAM角色,例如ECIRamRoleTest。 
 - 更多信息,请参见AttachPolicyToRole。 
授权RAM用户使用实例RAM角色
如果您使用RAM用户,则必须先授权RAM用户具备该实例RAM角色的ram:PassRole权限,RAM用户才可以使用实例RAM角色。ram:PassRole权限决定RAM用户能否直接执行角色策略赋予的权限。
为ECI实例授予实例RAM角色
调用CreateContainerGroup创建ECI实例时,可以通过RamRoleName参数来指定RAM角色。更多信息,请参见CreateContainerGroup。
一个ECI实例只能授予一个实例RAM角色。如果ECI实例已有RAM角色,则会报错提示您不能附加新的角色。
获取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
		}
	}
}