您可以通过 STS 服务,创建一个临时访问凭证,限定用户访问 TOS 资源的时间,过期即失效。本文介绍使用 STS 临时 AK/SK 和 Token 访问火山引擎 TOS 的流程及步骤。
您可以观看以下视频,快速了解使用 STS 临时 AK/SK 和 Token 访问火山引擎 TOS 的流程及步骤。
在移动 APP 或 PC 客户端直传文件到对象存储的场景中,出于安全考虑,不建议将永久 AK/SK 放到客户端代码中,此时可以由应用服务端通过 STS 机制获取临时 AK/SK,然后再将临时密钥下发给客户端。该方案有以下优点:
操作流程如下。
本章节以创建 tos_user
用户为例,说明创建 IAM 用户及授予权限的步骤。
说明
本文以通过用户名创建为例,其他创建方式详情,请参见创建用户。
说明
该策略为系统预置策略,您可以在面板上方搜索该策略并关联。
本章节以创建 tos_role
角色为例,说明创建 IAM 角色的步骤。
本章节以将 TrustPolicy 的用户修改为 tos_user
为例,说明指定 TrustPolicy 的用户的步骤。
在角色列表,单击步骤二创建的角色名称,进入角色详情页面。
在角色详情页面,单击信任关系。
在信任关系页签,单击编辑信任策略。
将 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:iam::{accountID}:user/{userName}
,其中 {accountID}
为角色所属的账号 ID,{userName}
为用户名。根据您的业务场景,创建您需要授权的策略。
在左侧导航栏单击策略管理。
在策略列表页面,单击用户自定义策略。
在用户自定义策略页签,单击新建策略。
在弹出的对话框中,设置策略名称和策略内容,然后单击创建策略。
本文以授予存储桶 tos-sts
上传及下载对象的权限为例,示例策略如下。
{ "Statement": [ { "Effect": "Allow", "Action": [ "tos:PutObject", "tos:GetObject" ], "Resource": [ "trn:tos:::tos-sts/*" ] } ] }
参数说明如下。
参数 | 是否必选 | 说明 | 代码块 |
---|---|---|---|
Effect | 是 | 指示策略是允许还是拒绝访问,取值范围为:
|
|
Action | 是 | 指定策略允许或拒绝的操作列表,TOS 支持的操作列表请参见 IAM 策略支持动作。
|
|
Resource | 否 | 设置该策略指定操作适用的资源列表,如果不填则表示所有资源均不匹配。
|
|
创建策略后,您需要为步骤二创建的角色授予策略权限。
注意
火山引擎 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,格式为:
例如本文中创建的角色为 |
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" } } }
获取临时 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) }