You need to enable JavaScript to run this app.
导航
使用 STS 临时 AK/SK+Token 访问火山引擎 TOS
最近更新时间:2024.07.09 18:36:23首次发布时间:2022.07.21 20:32:40

您可以通过 STS 服务,创建一个临时访问凭证,限定用户访问 TOS 资源的时间,过期即失效。本文介绍使用 STS 临时 AK/SK 和 Token 访问火山引擎 TOS 的流程及步骤。

视频介绍

您可以观看以下视频,快速了解使用 STS 临时 AK/SK 和 Token 访问火山引擎 TOS 的流程及步骤。

使用场景

在移动 APP 或 PC 客户端直传文件到对象存储的场景中,出于安全考虑,不建议将永久 AK/SK 放到客户端代码中,此时可以由应用服务端通过 STS 机制获取临时 AK/SK,然后再将临时密钥下发给客户端。该方案有以下优点:

  • 临时密钥具有一定的时效性,过期即无效,保证数据安全。
  • 您可以限制临时密钥的权限,降低密钥泄露(比如被破解、劫持)的风险。

使用流程

图片

操作步骤

操作流程如下。
图片

步骤一:创建 IAM 用户并授予 STSAssumeRoleAccess 权限

本章节以创建 tos_user 用户为例,说明创建 IAM 用户及授予权限的步骤。

  1. 登录 IAM 控制台
  2. 在左侧导航栏中,单击身份管理 > 用户
  3. 创建 IAM 用户。
    1. 用户列表页面,单击新建用户
    2. 新建用户页面,选择您需要创建用户的方式。

      说明

      本文以通过用户名创建为例,其他创建方式详情,请参见创建用户

    3. 通过用户名创建页签,设置用户名信息,单击下一步
    4. 审阅页签,确认用户名信息,单击提交
  4. 为 IAM 用户授予 STSAssumeRoleAccess 权限
    1. 用户页面,单击步骤 3 创建的用户名称。
    2. 用户详情页面,单击权限页签。
    3. 权限页签,单击添加权限
    4. 添加权限面板,勾选 STSAssumeRoleAccess 策略,单击确定

      说明

      该策略为系统预置策略,您可以在面板上方搜索该策略并关联。

步骤二:创建 IAM 角色

本章节以创建 tos_role 角色为例,说明创建 IAM 角色的步骤。

  1. 在左侧导航栏中,单击身份管理 > 角色
  2. 角色列表页面,单击新建角色
  3. 新建角色面板,选择信任身份类型账号身份当前账号,然后单击下一步
  4. 配置角色信息面板,设置角色名等信息,单击下一步
  5. 添加权限面板,单击跳过,完成角色的创建。

步骤三:指定 TrustPolicy 的用户

本章节以将 TrustPolicy 的用户修改为 tos_user 为例,说明指定 TrustPolicy 的用户的步骤。

  1. 角色列表,单击步骤二创建的角色名称,进入角色详情页面。

  2. 角色详情页面,单击信任关系

  3. 信任关系页签,单击编辑信任策略

  4. root 用户修改为步骤一中创建的用户名称,单击保存
    TrustPolicy 示例如下。

    {
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "sts:AssumeRole"
                ],
                "Principal": {
                    "IAM": [
                        "trn:iam::2100*****4:user/tos_user"
                    ]
                }
            }
        ]
    }
    

    说明

    信任策略(TrustPolicy)的格式说明如下:

    • 默认信任策略为 "trn:iam::2100*****4:root",需要将 root 修改为创建的 IAM 用户,如trn:iam::2100*****4:user/tos_user
    • trn 用户格式为:trn:iam::{accountID}:user/{userName},其中 {accountID} 为角色所属的账号 ID,{userName} 为用户名。

步骤四:创建 IAM 策略

根据您的业务场景,创建您需要授权的策略。

  1. 在左侧导航栏单击策略管理

  2. 策略列表页面,单击用户自定义策略

  3. 用户自定义策略页签,单击新建策略

  4. 在弹出的对话框中,设置策略名称策略内容,然后单击创建策略
    本文以授予存储桶 tos-sts 上传及下载对象的权限为例,示例策略如下。

    {
      "Statement": [
        {
          "Effect": "Allow",
          "Action": [
            "tos:PutObject",
            "tos:GetObject"
          ],
          "Resource": [
            "trn:tos:::tos-sts/*"
          ]
        }
      ]
    }
    

    参数说明如下。

    参数

    是否必选

    说明

    代码块

    Effect

    指示策略是允许还是拒绝访问,取值范围为:

    • Allow:允许
    • Deny:拒绝
    "Effect": "Allow"
    

    Action

    指定策略允许或拒绝的操作列表,TOS 支持的操作列表请参见 IAM 策略支持动作

    • 以字符串形式表示,不区分大小写,格式为 "Action":["tos:Action名称"]
    • 支持通配符*,表示该资源能进行的所有操作。例如:"Action":["tos:List*"]
    "Action": ["tos:List*"]
    

    Resource

    设置该策略指定操作适用的资源列表,如果不填则表示所有资源均不匹配。

    • 以字符串形式表示,不区分大小写,格式为 "Resource": ["trn:tos:::{BucketName}/{ObjectName}"]
    • 如果您只需要对桶执行相应操作,则资源只设置桶名,例如"Resource": ["trn:tos:::{BucketName}"]
    • 如果您只需要对桶中对象执行相应操作,则需要设置桶内资源,例如"Resource": ["trn:tos:::{BucketName}/{ObjectName}"]
    • 支持通配符*,表示所有资源。
    "Resource": [
            "trn:tos:::bucket/*",
            "trn:tos:::bucket"
          ]
    

步骤五:为角色授予相应权限

创建策略后,您需要为步骤二创建的角色授予策略权限。

  1. 策略列表页面,单击新创建的策略名称,进入策略详情页面。
  2. 策略详情页面,单击添加授权
  3. 添加全局权限面板,选择身份角色,选择步骤二中创建的角色,然后单击确定

步骤六:请求 AssumeRole 接口获取临时访问凭证

注意

  • 临时密钥的实际权限是角色具有的权限和 IAM 策略的交集。
  • 如果不指定 IAM 策略,则临时密钥拥有指定角色的预关联策略的权限。
  • 如果角色没有预关联策略,即使指定了 IAM 策略,实际权限也为

火山引擎 API 请求的签名算法,和 AWS V4 基本一致(部分 Header 不同),详情请参见签名机制。您可以调用 STS 服务接口 AssumeRole 来获取临时访问凭证。您可以通过以下 SDK 调用该接口:

完整的请求参数如下。

GET /?RoleTrn=trn:iam::2100****4:role/tos_role&RoleSessionName=tos_role_session&DurationSeconds=3600&Action=AssumeRole&Version=2018-01-01 HTTP/1.1
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Host: open.volcengineapi.com
X-Date: 发请求时指定

Authorization: 待签算(此处用IAM用户tos_user的密钥)

说明

调用 AssumeRole 获取临时访问凭证时,您可以通过 DurationSeconds 参数来设置临时访问凭证的有效时长,详细介绍,请参见AssumeRole

您也可以通过如下代码计算签名并发送请求获取临时访问凭证,如下以 Python 和 Go 为例。

package main

import (
   "crypto/hmac"
   "crypto/sha256"
   "encoding/hex"
   "fmt"
   "io/ioutil"
   "net/http"
   "net/url"
   "time"
)

func sign(key []byte, value string) []byte {
   h := hmac.New(sha256.New, key)
   h.Write([]byte(value))
   return h.Sum(nil)
}

func getSigningKey(key []byte, dateStamp string, regionName, serviceName string) []byte {
   kDate := sign(key, dateStamp)
   kRegion := sign(kDate, regionName)
   kService := sign(kRegion, serviceName)
   kSigning := sign(kService, "request")
   return kSigning
}

type SignHeaderInput struct {
   method    string
   service   string
   host      string
   region    string
   params    url.Values
   accessKey string
   secretKey string
}

const (
   iso8601Layout = "20060102T150405Z"
   yyMMdd        = "20060102"
   emptySHA256   = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)

func getSigningHeader(input SignHeaderInput) map[string]string {
   contentType := "application/x-www-form-urlencoded"
   accept := "application/json"
   t := time.Now().UTC()
   xdate := t.Format(iso8601Layout)
   datestamp := t.Format(yyMMdd)
   // *************  1: 拼接规范请求串*************
   canonicalUri := "/"
   canonicalQueryString := input.params.Encode()
   canonicalHeaders := "content-type:" + contentType + "\n" + "host:" + input.host + "\n" + "x-date:" + xdate + "\n"
   signedHeaders := "content-type;host;x-date"
   canonicalRequest := input.method + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + emptySHA256
   // *************  2:拼接待签名字符串*************
   algorithm := "HMAC-SHA256"
   credentialScope := datestamp + "/" + input.region + "/" + input.service + "/" + "request"
   cr256 := sha256.Sum256([]byte(canonicalRequest))
   stringToSign := algorithm + "\n" + xdate + "\n" + credentialScope + "\n" + hex.EncodeToString(cr256[:])

   // *************  3:计算签名 *************
   signingKey := getSigningKey([]byte(input.secretKey), datestamp, input.region, input.service)

   signature := sign(signingKey, stringToSign)
   // *************4:添加签名到请求header中 * ************
   authorizationHeader := algorithm + " " + "Credential=" + input.accessKey + "/" + credentialScope + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + hex.EncodeToString(signature)

   headers := map[string]string{"Accpet": accept, "Content-Type": contentType, "X-Date": xdate, "Authorization": authorizationHeader}
   return headers
}

func main() {
   service := "sts"
   host := "open.volcengineapi.com"
   endpoint := "https://open.volcengineapi.com"
   region := "{your region ID}"
   accessKey := "{your access key}"
   secretKey := "{your secret key}"
   queryParams := url.Values{"Action": []string{"AssumeRole"},
      "RoleSessionName": []string{"{your session name}"},
      "RoleTrn":         []string{"trn:iam::{your account ID}:role/{your role name}"},
      "Version":         []string{"2018-01-01"},

   }
   header := getSigningHeader(SignHeaderInput{
      method:    http.MethodGet,
      service:   service,
      host:      host,
      region:    region,
      params:    queryParams,
      accessKey: accessKey,
      secretKey: secretKey,
   })
   req, err := http.NewRequest(http.MethodGet, endpoint+"?"+queryParams.Encode(), nil)
   if err != nil {
      panic(err)
   }
   for key, value := range header {
      req.Header.Set(key, value)
   }
   resp, err := http.DefaultClient.Do(req)
   if err != nil {
      panic(err)
   }
   defer resp.Body.Close()
   body, err := ioutil.ReadAll(resp.Body)
   if err != nil {
      panic(err)
   }
   fmt.Println(fmt.Sprintf("status code:%d, body: %s", resp.Status, string(body)))
}

参数说明如下。

参数

示例值

说明

service

sts

请求的服务,默认为 sts

host

open.volcengineapi.com

请求的 host 地址,默认为 open.volcengineapi.com

endpoint

https://open.volcengineapi.com

请求的 Endpoint 地址,默认为 https://open.volcengineapi.com

region

cn-beijing

请求的地域信息。 TOS 支持的地域信息,请参见访问域名 Endpoint

accessKey

AKLTYTMyNTRlMTY5NmM0NDdiMjg3MmRlMzFl****NjU

步骤一创建的用户的 AK 信息。

secretKey

TWpoaU9HRmlPV1ZrTURJNExxxxxx4WkRoaF****

步骤一创建的用户的 SK 信息。查看 AK/SK 信息的具体步骤,请参见查看 AK/SK 信息

action

AssumeRole

请求的 API 名称,默认为 AssumeRole

RoleSessionName

tos_role_session

请求的临时名称,可根据需要设置。

RoleTrn

trn:iam::2100xxxx4:role/tos_role

步骤二创建的角色 trn,格式为:trn:iam::{accountID}:role/{roleName},说明如下:

  • {accountID}:角色所属的账号 ID。
  • {roleName}:角色名。

例如本文中创建的角色为 tos_role,则 trn 为 trn:iam::2100xxxx4:role/tos_role

Version

2018-01-01

请求的版本信息,默认为 2018-01-01

成功响应示例如下。

Response code: 200
{
        "ResponseMetadata": {
                "RequestId": "20220626182126010212063166****",
                "Action": "AssumeRole",
                "Version": "2018-01-01",
                "Service": "sts"
        },
        "Result": {
                "Credentials": {
                        "CurrentTime": "2022-06-26T18:21:27+08:00",
                        "ExpiredTime": "2022-06-26T19:21:27+08:00",
                        "AccessKeyId": "AKTPYmI1ZGQwMDA0NjlhNGFkMzhjNzM0N2Q0OTQ3ZTV****",
                        "SecretAccessKey": "T1dJM01UUXpOak0wTVdWak5EUmtOR0poWldJNU1HWmxaV1V5TkdReVl6****",
                        "SessionToken": "STSeyJBY2NvdW50SWQiOjIxMDAwMDUyMjQsIklkZW50aXR5VHlwZSI6NCwiSWRlbnRpdHlJZCI6MTE3MjI5NiwiQ2hhbm5lbCI6IlVzZXIiLCJBY2Nlc3NLZXlJZCI6IkFLVFBZbUkxWkdRd01EQTBOamxoTkdGa016aGpOek0wTjJRME9UUTNaVFZqTXpFIiwiU2lnbmVkU2VjcmV0QWNjZXNzS2V5IjoidVIvMmhvTE9Yd1lwNlR6QkxUOVZQVDQrbVVaQjMzMEJoQ0NPWk9JMVBRWkpZMFZPcGRPendybFNVYytGNlorRVR1ZDJsMlg0UkUyMWJEYnZ1QWl6S3c9PSIsIkV4cGlyZWRUaW1lIjoxNjU2MjQyNDg3LCJQb2xpY3lTdHJpbmciOiIiLCJTaWduYXR1cmUiOiIzOTlmMjZkNjIzZWUxMmU2NWViMDIwY2RlOWZkMDZkZDc4MTBkNjhkYjQyYzBjZTE3ZDA5MjY4NWYwMDQyYThlIiwiU2Vzc2lvbk5hbWUiOiJ0b3Nfcm9sZV9z************"
                },
                "AssumedRoleUser": {
                        "Trn": "trn:sts::21000****4:assumed-role/tos_role/tos_role_session",
                        "AssumedRoleId": "117****:tos_role_session"
                }
        }
}

步骤七:使用临时密钥访问 TOS

获取临时 AK/SK+Token 之后,您可以使用 TOS SDK 或者 AWS S3 SDK 访问 TOS 了,本文以 TOS Go SDK 为例,介绍示例代码。

说明

package main

import (
   "context"
   "fmt"
   "strings"

   "github.com/volcengine/ve-tos-golang-sdk/v2/tos"
)

func main() {
   // 使用上一步中获取的临时AK/SK+Token
   ak := "AKTPYmI1ZGQwMDA0NjlhNGFkMzhjNzM0N2Q0OTQ3ZTV****"
   sk := "T1dJM01UUXpOak0wTVdWak5EUmtOR0poWldJNU1HWmxaV1V5TkdReVl6****"
   stToken := "STSeyJBY2NvdW50SWQiOjIxMDAwMDUyMjQsIklkZW50aXR5VHlwZSI6NCwiSWRlbnRpdHlJZCI6MTE3MjI5NiwiQ2hhbm5lbCI6IlVzZXIiLCJBY2Nlc3NLZXlJZCI6IkFLVFBZbUkxWkdRd01EQTBOamxoTkdGa016aGpOek0wTjJRME9UUTNaVFZqTXpFIiwiU2lnbmVkU2VjcmV0QWNjZXNzS2V5IjoidVIvMmhvTE9Yd1lwNlR6QkxUOVZQVDQrbVVaQjMzMEJoQ0NPWk9JMVBRWkpZMFZPcGRPendybFNVYytGNlorRVR1ZDJsMlg0UkUyMWJEYnZ1QWl6S3c9PSIsIkV4cGlyZWRUaW1lIjoxNjU2MjQyNDg3LCJQb2xpY3lTdHJpbmciOiIiLCJTaWduYXR1cmUiOiIzOTlmMjZkNjIzZWUxMmU2NWViMDIwY2RlOWZkMDZkZDc4MTBkNjhkYjQyYzBjZTE3ZDA5MjY4NWYwMDQyYThlIiwiU2Vzc2lvbk5hbWUiOiJ0b3Nfcm9sZV9z************"
   // 使用tos Endpoint 请参考https://www.volcengine.com/docs/6349/107356
   endpoint := "https://tos-cn-beijing.volces.com"
   region := "cn-beijing"
   //  创建TosClient实例
   cred := tos.NewStaticCredentials(ak, sk)
   cred.WithSecurityToken(stToken)
   client, err := tos.NewClientV2(endpoint, tos.WithRegion(region), tos.WithCredentials(cred))
   if err != nil {
      panic(err)
   }
   res, err := client.PutObjectV2(context.Background(), &tos.PutObjectV2Input{PutObjectBasicInput: tos.PutObjectBasicInput{Bucket: "tos-sts", Key: "sts.txt"}, Content: strings.NewReader("hello STS")})

   if err != nil {
      panic(err)
   }
   fmt.Println(res)
}