通过STS来对AWS资源进行更灵活的权限控制
一、前言
背景:一个S3 bucket,存储用户的文件,每个用户只允许上传、下载自己目录下的文件。
如何让Policy更灵活、更动态,可以让获取到的权限凭证可以匹配到单个终端用户的S3文件目录下。
本节主要介绍,以编程方式调用 AWS Security Token Service (AWS STS) 的API,获取访问AWS资源的临时安全凭证,并将这些凭证下发给终端用户,用于后续终端用户发起访问AWS资源请求时进行身份验证。
二、整体图
- 终端用户的客户端发出访问请求到服务器端。
- 服务器端调用STS API,以获取临时安全凭证。
- STS服务返回临时安全凭证。
- Server将获取到的临时安全凭证下发给客户端。
- 客户端使用临时安全凭证来访问AWS资源(本文中指S3 bucket)。
三、程序
package main import ( "context" "errors" "fmt" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/sts" "strconv" "strings" "time" ) func main() { var bucketName = "{bucket名称}" var authPaths = []string{"uid"} //uid可替换用户真实uid对应的目录,可支持多层级目录 var expire int32 = 3600 //STS token有效期 cfg := &StoreClientConf{ RoleArn: "{roleArn}", Region: "{bucket region}", AccessKeyID: "{bucket ak}", AccessKeySecret: "{bucket sk}", } client := NewAwsClient(cfg) stsInfo, err := client.GetStsCredentials(context.Background(), bucketName, authPaths, expire) if err != nil { fmt.Println("client.GetStsCredentials err: " + err.Error()) return } fmt.Println("sts ak: " + stsInfo.AccessKeyId) fmt.Println("sts sk: " + stsInfo.AccessSecret) fmt.Println("sts token: " + stsInfo.SecurityToken) } type AwsClient struct { roleArn string region string accessKeyID string accessKeySecret string } type StoreClientConf struct { RoleArn string Region string AccessKeyID string AccessKeySecret string } type StsCredentials struct { AccessKeyId string AccessSecret string SecurityToken string ExpireTime int64 } func NewAwsClient(cfg *StoreClientConf) *AwsClient { return &AwsClient{ roleArn: cfg.RoleArn, region: cfg.Region, accessKeyID: cfg.AccessKeyID, accessKeySecret: cfg.AccessKeySecret, } } func (s *AwsClient) loadConfig(ctx context.Context) (aws.Config, error) { cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(s.region), config.WithCredentialsProvider(credentials.StaticCredentialsProvider{ Value: aws.Credentials{ AccessKeyID: s.accessKeyID, SecretAccessKey: s.accessKeySecret, SessionToken: "", Source: "", }, }), ) if err != nil { fmt.Println("awsClient LoadDefaultConfig err:" + err.Error()) return aws.Config{}, errors.New("awsClient LoadDefaultConfig err") } return cfg, nil } // 定义自己想要的policy func (s *AwsClient) authPolicy(ctx context.Context, bucket string, authPaths []string) string { var resource []string for _, v := range authPaths { path := strings.TrimRight(v, "/") //去除最后一个/ resource = append(resource, `"arn:aws:s3:::` + bucket+ `/` + path + `/*"`) } policy := `{ "Version": "2012-10-17", "Statement": [ { "Action": [ "s3:GetObject", "s3:GetObjectAttributes", "s3:GetObjectTagging", "s3:PutObject", "s3:PutObjectTagging", "s3:UploadPart" ], "Effect": "Allow", "Resource": [` + strings.Join(resource, ",") + `] } ] }` return policy } func (s *AwsClient) GetStsCredentials(ctx context.Context, bucket string, authPaths []string, expired int32) (*StsCredentials, error) { // 1.拼装授权策略 policy := s.authPolicy(ctx, bucket, authPaths) // 2.初始化client cfg, err := s.loadConfig(ctx) if err != nil { return nil, err } client := sts.NewFromConfig(cfg) // 3.调用s3接口,获取sts token roleSessionName := "s3bucket" + strconv.FormatInt(time.Now().Unix(), 10) //需要按用户的维度去修改 input := &sts.AssumeRoleInput{ RoleArn: &s.roleArn, RoleSessionName: &roleSessionName, DurationSeconds: &expired, Policy: aws.String(policy), } resp, err := client.AssumeRole(ctx, input) if err != nil { fmt.Println("GetStsCredentials client.AssumeRole err:" + err.Error()) return nil, err } if resp == nil { fmt.Println("GetStsCredentials response is nil") return nil, err } var expire int64 if resp.Credentials != nil && resp.Credentials.Expiration != nil { expire = resp.Credentials.Expiration.Unix() } return &StsCredentials{ AccessKeyId: *resp.Credentials.AccessKeyId, AccessSecret: *resp.Credentials.SecretAccessKey, SecurityToken: *resp.Credentials.SessionToken, ExpireTime: expire, }, nil }