├── .travis.yml ├── LICENSE ├── README.md └── sts ├── sts.go └── sts_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.5 4 | - 1.6 5 | - 1.7 6 | - 1.8 7 | install: 8 | - go get golang.org/x/tools/cmd/cover 9 | - go get github.com/mattn/goveralls 10 | - go get github.com/satori/go.uuid 11 | - go get gopkg.in/check.v1 12 | script: 13 | - cd sts 14 | - travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m 15 | - "$HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci" 16 | env: 17 | global: 18 | - secure: k4Rjzj9SufzedmXav7LgKi+cHjvq/f0cqEr8sdJ3xvfw+o148PJ6KBbjDY90vJ0e19axbl2F1Vmw5o4Qm0BtX3/o35EBbeK/FNfC3mEZi588r28y1cxHrYwJVlD9jymtSJOPSJ8kOfJ9SAC6QjRWZhrZzYou8o1oMuFDTqSTZk4qgSVHnQ5FVtm+WwQDiP/rnJIsb87JYx7vZTta+GEh7hcbwFuwpFspFFA2mAY8xP86tqjpBoGRa6MtYvRBYZkfVe6ZZ18o1frDdRNQfSqPMaZ68LWbNaXOiqEsiCDc3ehnZk0OIoLb5eg+SzBER0cenKbX3jp9sj2kckLBZFMnIAiPF3br7u+3tXcNw4tyNOMSgi1T17vkmfmZdbWtcs5kvY6WMzY8YM2W8aDKmS5DFabQKKerDRXCKRmlG5vid0Kj2AKhJk4d8vUc3s7rBn2s7mBoT9BJXmkbrjimJ0eof+GlS0y7mnOcbXtRYls7ASgfSbmSzRu3elDP/Q4x8BUBKjYSgKGM2bUtgryVYlnZNazaR/IXXS4rWOJwBzYSU0c8kVBH+IAxWMGMfDiHibbfizDSw050c+Q9Rgq8PNQ6jl6Za3egrtKUnVxIfXzfv2bkvvTEjZZlZxRPZ2ojx5Q4LC/0L0Hf2QXWgbOGARaCAn9aYVhYnKbOUU7tHUOyhr8= 19 | - secure: qh0XzQhK54oFmB56MzR118bvaLQyyZY4VW2V/kPHNKmhcKh2Lar52SWn+16QJ6Egvw1+34Gi+XoS0cw0brZbQ2EtqoHwx6lo22eUQ2378ijIH3aiXtChUZw0h6PsWSJoXd0TaFfw0+eYFwzEFjP1HOcmoF6tI4Qax5TvRxArx1TVHPfHunYCmYTdgxDi05CfGnXnOUBSGX+PJRzMW5oN5Lvv5yEI2vckJjXx842pes2sicDxM1SxQTpzXoaRa4SR+MDWPraac0dp16WYUG4jfTcqQFPrtiP0ZYNxdTAYLzArLrWjsw4xKzDBjDR+Oh4mKEB/jHYiSgDyMvY0Jp2ETT7bbSUbUCawU6t6k15+Osf3XyoErKtiXGSpWo7k/qVIFGdk2QOQxEQxNMScBPuMGLoMhSNZBaTKpjX6JYQ9JazhyQMeqIgDxqmnYTYd2w213tF+0857Enzk8qh95FF0soqN4gHa+hJ02fXTfykwir2uo+qkTwXkTxIPRQ3PNk5eugjowYgpSm1awor3rtzt0WaOsOil+ItFHaYFc0mJefhT7Rvine5XcUQJNksQxRCT/vKSgoSTHx3309eID3R0rf1lO5FnvzMHnNgi24XUE/UY3ahPOFrBGHFpDMePyt2lRGyLdaIJGw+wHCn1gNdBThVZGIm9yUkISJeNmUp58+s= 20 | - secure: TP5C33IC/oOE5NdUTNMij2N08TKtLGthXjQVyqd9WKOWq/+pnGZmV9iYTIZkmNQMHfWvUXW0jJd7dniBemKGTl6ywSozyndSQ5o2BDXa4UP8l28j7VBj3FMmmMxBilVtNf+f82qX6EviHr9Tm9oiMR/SMdKtHQCvrVRb0/18f1FN12+WqfKGiUlWR7gTMpMo+j2Nw/uIasnV/zDkzVBrlqOS1CKdTkr5hltbYqoKkv6+YAZ9m6ReQ0thftdnYdhMQnLy2LYXYYhK3TtEUnNX9p8du9O91gfasAUhgiOCgk6aruWTLhmonFcfXcovvYoX1WG+q/FStUU4efOq+wpI0rCAbu0sBKqickLdXbNTnLMeMhOwsvkLnMpMWxcHgjNAUkYsmJt4rwzm05anI6N+wCIv7zi1nzFzgUZtxH1il+7O3BusLuMkdCNsNy9ZhVEvLOy2Ry8nHeReayNy1cs6IzUsi0caP8vuzz38sqobKVCcZmTOH41nnFXb7+7X6e6L0jpvJogMLSccFMK/nmlo+q8aVDZz8KeFn7iu+q+NEIPt5G/eTU+0G9qLxMLXQVEaNYAsjUAH0OG4NbfP7Di/MGLbBbAyhkovjiRAgOkZXa+f3bKig5VI5Y1btoHICTVabDyGPkezeJIwJsciMgkwJwL8dPoOLleBHLWrPo5ZYUY= 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Aliyun. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Alibaba Cloud STS SDK for Go 2 | 3 | [![GitHub Version](https://badge.fury.io/gh/baiyubin%2Faliyun-sts-go-sdk.svg)](https://badge.fury.io/gh/baiyubin%2Faliyun-sts-go-sdk) 4 | [![Build Status](https://travis-ci.org/baiyubin/aliyun-sts-go-sdk.svg?branch=master)](https://travis-ci.org/baiyubin/aliyun-sts-go-sdk) 5 | [![Coverage Status](https://coveralls.io/repos/github/baiyubin/aliyun-sts-go-sdk/badge.svg?branch=master)](https://coveralls.io/github/baiyubin/aliyun-sts-go-sdk?branch=master) 6 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE) 7 | 8 | ## 关于 9 | 阿里云[STS](https://help.aliyun.com/document_detail/28756.html)(Security Token Service) 是为阿里云账号(或[RAM](https://help.aliyun.com/document_detail/28627.html)用户)提供短期访问权限管理的云服务。通过STS,您可以为联盟用户(您的本地账号系统所管理的用户)颁发一个自定义时效和访问权限的访问凭证。联盟用户可以使用STS短期访问凭证直接调用阿里云服务API,或登录阿里云管理控制台操作被授权访问的资源。 10 | 11 | 如果您需要快速掌握阿里云账号管理体系,请参看[云栖社区](https://yq.aliyun.com/articles/57895)。 12 | 13 | ## 版本 14 | - 当前版本:1.0.0 15 | 16 | ## 运行环境 17 | - Go 1.5及以上 18 | 19 | ## 安装方法 20 | - 执行命令 `go get github.com/aliyun/aliyun-sts-go-sdk/sts` 21 | 22 | ## 使用方法 23 | ```go 24 | package main 25 | 26 | import ( 27 | "fmt" 28 | "os" 29 | 30 | "github.com/aliyun/aliyun-sts-go-sdk/sts" 31 | ) 32 | 33 | func handleError(err error) { 34 | fmt.Println(err) 35 | os.Exit(-1) 36 | } 37 | 38 | const ( 39 | accessKeyID = "" 40 | accessKeySecret = "" 41 | roleArn = "" 42 | sessionName = "sts_demo" 43 | ) 44 | 45 | func main() { 46 | stsClient := sts.NewClient(accessKeyID, accessKeySecret, roleArn, sessionName) 47 | 48 | resp, err := stsClient.AssumeRole(3600) 49 | if err != nil { 50 | handleError(err) 51 | } 52 | 53 | fmt.Printf("Credentials:\n") 54 | fmt.Printf(" AccessKeyID:%s\n", resp.Credentials.AccessKeyId) 55 | fmt.Printf(" AccessKeySecret:%s\n", resp.Credentials.AccessKeySecret) 56 | fmt.Printf(" SecurityToken:%s\n", resp.Credentials.SecurityToken) 57 | fmt.Printf(" Expiration:%s\n", resp.Credentials.Expiration) 58 | } 59 | ``` 60 | 61 | ## 作者 62 | - [Yubin Bai](https://github.com/baiyubin) 63 | 64 | ## 协议 65 | - [MIT](https://github.com/aliyun/aliyun-sts-go-sdk/blob/master/LICENSE) 66 | -------------------------------------------------------------------------------- /sts/sts.go: -------------------------------------------------------------------------------- 1 | package sts 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/sha1" 6 | "crypto/tls" 7 | "encoding/base64" 8 | "encoding/json" 9 | "fmt" 10 | "io/ioutil" 11 | "net/http" 12 | "net/url" 13 | "strconv" 14 | "time" 15 | 16 | "github.com/satori/go.uuid" 17 | ) 18 | 19 | // Client sts client 20 | type Client struct { 21 | AccessKeyId string 22 | AccessKeySecret string 23 | RoleArn string 24 | SessionName string 25 | } 26 | 27 | // ServiceError sts service error 28 | type ServiceError struct { 29 | Code string 30 | Message string 31 | RequestId string 32 | HostId string 33 | RawMessage string 34 | StatusCode int 35 | } 36 | 37 | // Credentials the credentials obtained by AssumedRole, 38 | // used for the peration of Alibaba Cloud service. 39 | type Credentials struct { 40 | AccessKeyId string 41 | AccessKeySecret string 42 | Expiration time.Time 43 | SecurityToken string 44 | } 45 | 46 | // AssumedRoleUser the user to AssumedRole 47 | type AssumedRoleUser struct { 48 | Arn string 49 | AssumedRoleId string 50 | } 51 | 52 | // Response the response of AssumeRole 53 | type Response struct { 54 | Credentials Credentials 55 | AssumedRoleUser AssumedRoleUser 56 | RequestId string 57 | } 58 | 59 | // Error implement interface error 60 | func (e *ServiceError) Error() string { 61 | return fmt.Sprintf("oss: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=%s, RequestId=%s", 62 | e.StatusCode, e.Code, e.Message, e.RequestId) 63 | } 64 | 65 | // NewClient New STS Client 66 | func NewClient(accessKeyId, accessKeySecret, roleArn, sessionName string) *Client { 67 | return &Client{ 68 | AccessKeyId: accessKeyId, 69 | AccessKeySecret: accessKeySecret, 70 | RoleArn: roleArn, 71 | SessionName: sessionName, 72 | } 73 | } 74 | 75 | const ( 76 | // StsSignVersion sts sign version 77 | StsSignVersion = "1.0" 78 | // StsAPIVersion sts api version 79 | StsAPIVersion = "2015-04-01" 80 | // StsHost sts host 81 | StsHost = "https://sts.aliyuncs.com/" 82 | // TimeFormat time fomrat 83 | TimeFormat = "2006-01-02T15:04:05Z" 84 | // RespBodyFormat respone body format 85 | RespBodyFormat = "JSON" 86 | // PercentEncode '/' 87 | PercentEncode = "%2F" 88 | // HTTPGet http get method 89 | HTTPGet = "GET" 90 | ) 91 | 92 | // AssumeRole assume role 93 | func (c *Client) AssumeRole(expiredTime uint) (*Response, error) { 94 | url, err := c.generateSignedURL(expiredTime) 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | body, status, err := c.sendRequest(url) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return c.handleResponse(body, status) 105 | } 106 | 107 | // Private function 108 | func (c *Client) generateSignedURL(expiredTime uint) (string, error) { 109 | queryStr := "SignatureVersion=" + StsSignVersion 110 | queryStr += "&Format=" + RespBodyFormat 111 | queryStr += "&Timestamp=" + url.QueryEscape(time.Now().UTC().Format(TimeFormat)) 112 | queryStr += "&RoleArn=" + url.QueryEscape(c.RoleArn) 113 | queryStr += "&RoleSessionName=" + c.SessionName 114 | queryStr += "&AccessKeyId=" + c.AccessKeyId 115 | queryStr += "&SignatureMethod=HMAC-SHA1" 116 | queryStr += "&Version=" + StsAPIVersion 117 | queryStr += "&Action=AssumeRole" 118 | queryStr += "&SignatureNonce=" + uuid.NewV4().String() 119 | queryStr += "&DurationSeconds=" + strconv.FormatUint((uint64)(expiredTime), 10) 120 | 121 | // Sort query string 122 | queryParams, err := url.ParseQuery(queryStr) 123 | if err != nil { 124 | return "", err 125 | } 126 | result := queryParams.Encode() 127 | 128 | strToSign := HTTPGet + "&" + PercentEncode + "&" + url.QueryEscape(result) 129 | 130 | // Generate signature 131 | hashSign := hmac.New(sha1.New, []byte(c.AccessKeySecret+"&")) 132 | hashSign.Write([]byte(strToSign)) 133 | signature := base64.StdEncoding.EncodeToString(hashSign.Sum(nil)) 134 | 135 | // Build url 136 | assumeURL := StsHost + "?" + queryStr + "&Signature=" + url.QueryEscape(signature) 137 | 138 | return assumeURL, nil 139 | } 140 | 141 | func (c *Client) sendRequest(url string) ([]byte, int, error) { 142 | tr := &http.Transport{ 143 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 144 | } 145 | client := &http.Client{Transport: tr} 146 | 147 | resp, err := client.Get(url) 148 | if err != nil { 149 | return nil, -1, err 150 | } 151 | defer resp.Body.Close() 152 | 153 | body, err := ioutil.ReadAll(resp.Body) 154 | return body, resp.StatusCode, err 155 | } 156 | 157 | func (c *Client) handleResponse(responseBody []byte, statusCode int) (*Response, error) { 158 | if statusCode != http.StatusOK { 159 | se := ServiceError{StatusCode: statusCode, RawMessage: string(responseBody)} 160 | err := json.Unmarshal(responseBody, &se) 161 | if err != nil { 162 | return nil, err 163 | } 164 | return nil, &se 165 | } 166 | 167 | resp := Response{} 168 | err := json.Unmarshal(responseBody, &resp) 169 | if err != nil { 170 | return nil, err 171 | } 172 | return &resp, nil 173 | } 174 | -------------------------------------------------------------------------------- /sts/sts_test.go: -------------------------------------------------------------------------------- 1 | package sts 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | . "gopkg.in/check.v1" 10 | ) 11 | 12 | var ( 13 | accessKeyId = os.Getenv("STS_TEST_ACCESS_KEY_ID") 14 | accessKeySecret = os.Getenv("STS_TEST_ACCESS_KEY_SECRET") 15 | roleArn = os.Getenv("STS_TEST_ROLE_ARN") 16 | ) 17 | 18 | // Hook up gocheck into the "go test" runner. 19 | func Test(t *testing.T) { 20 | TestingT(t) 21 | } 22 | 23 | type StsTestSuite struct { 24 | } 25 | 26 | var _ = Suite(&StsTestSuite{}) 27 | 28 | // Run once when the suite starts running 29 | func (s *StsTestSuite) SetUpSuite(c *C) { 30 | } 31 | 32 | // Run after each test or benchmark starts running 33 | func (s *StsTestSuite) TearDownSuite(c *C) { 34 | } 35 | 36 | func (s *StsTestSuite) TestSendRequest(c *C) { 37 | client := NewClient("", "", "", "") 38 | _, _, err := client.sendRequest(StsHost) 39 | c.Assert(err, IsNil) 40 | 41 | // negative 42 | _, _, err = client.sendRequest("https//x.y.z.com") 43 | c.Assert(err, NotNil) 44 | } 45 | 46 | func (s *StsTestSuite) TestHandleResponse(c *C) { 47 | client := NewClient("", "", "", "") 48 | 49 | body := "{\"RequestId\":\"784B99C1-895F-426C-8E1F-008955D418FB\"," + 50 | "\"HostId\":\"sts.aliyuncs.com\"," + 51 | "\"Code\":\"NoPermission\"," + 52 | "\"Message\":\"Roles may not be assumed by root accounts.\"}" 53 | resp, err := client.handleResponse([]byte(body), 400) 54 | _, isSuc := err.(*ServiceError) 55 | c.Assert(isSuc, Equals, true) 56 | c.Assert(resp, IsNil) 57 | 58 | body = "{{}}" 59 | resp, err = client.handleResponse([]byte(body), 400) 60 | _, isSuc = err.(*ServiceError) 61 | c.Assert(isSuc, Equals, false) 62 | c.Assert(resp, IsNil) 63 | 64 | body = "{\"RequestId\":\"4AB89022-25A3-4427-84A5-4C7E72BD63BE\"}" 65 | resp, err = client.handleResponse([]byte(body), 200) 66 | c.Assert(err, IsNil) 67 | c.Assert(resp, NotNil) 68 | 69 | body = "{{}}" 70 | resp, err = client.handleResponse([]byte(body), 200) 71 | _, isSuc = err.(*ServiceError) 72 | c.Assert(isSuc, Equals, false) 73 | c.Assert(resp, IsNil) 74 | } 75 | 76 | func (s *StsTestSuite) TestAssumeRole(c *C) { 77 | now := time.Now() 78 | client := NewClient(accessKeyId, accessKeySecret, roleArn, "sts_test") 79 | 80 | resp, err := client.AssumeRole(900) 81 | c.Assert(err, IsNil) 82 | 83 | c.Assert(resp.RequestId, Not(Equals), "") 84 | 85 | c.Assert(resp.AssumedRoleUser.Arn, Not(Equals), "") 86 | c.Assert(resp.AssumedRoleUser.AssumedRoleId, Not(Equals), "") 87 | 88 | c.Assert(resp.Credentials.AccessKeyId, Not(Equals), "") 89 | c.Assert(resp.Credentials.AccessKeySecret, Not(Equals), "") 90 | c.Assert(resp.Credentials.SecurityToken, Not(Equals), "") 91 | c.Assert(resp.Credentials.Expiration.After(now), Equals, true) 92 | } 93 | 94 | func (s *StsTestSuite) TestAssumeRoleNegative(c *C) { 95 | // AccessKeyID invalid 96 | client := NewClient("", accessKeySecret, roleArn, "sts_test") 97 | resp, err := client.AssumeRole(900) 98 | c.Assert(resp, IsNil) 99 | c.Assert(err, NotNil) 100 | log.Println("Error:", err) 101 | 102 | srvErr, isSuc := err.(*ServiceError) 103 | c.Assert(isSuc, Equals, true) 104 | c.Assert(srvErr.StatusCode, Equals, 404) 105 | c.Assert(srvErr.Code, Equals, "InvalidAccessKeyId.NotFound") 106 | c.Assert(len(srvErr.Message) > 0, Equals, true) 107 | c.Assert(len(srvErr.RawMessage) > 0, Equals, true) 108 | c.Assert(len(srvErr.RequestId) > 0, Equals, true) 109 | log.Println("ServiceError:", srvErr) 110 | 111 | // AccessKeySecret invalid 112 | client = NewClient(accessKeyId, accessKeySecret+" ", roleArn, "sts_test") 113 | resp, err = client.AssumeRole(900) 114 | c.Assert(resp, IsNil) 115 | c.Assert(err, NotNil) 116 | 117 | srvErr, isSuc = err.(*ServiceError) 118 | c.Assert(isSuc, Equals, true) 119 | c.Assert(srvErr.StatusCode, Equals, 400) 120 | c.Assert(srvErr.Code, Equals, "SignatureDoesNotMatch") 121 | log.Println("ServiceError:", srvErr) 122 | 123 | // SessionName invalid 124 | client = NewClient(accessKeyId, accessKeySecret, roleArn, "x") 125 | 126 | resp, err = client.AssumeRole(900) 127 | c.Assert(resp, IsNil) 128 | c.Assert(err, NotNil) 129 | 130 | srvErr, isSuc = err.(*ServiceError) 131 | c.Assert(isSuc, Equals, true) 132 | c.Assert(srvErr.StatusCode, Equals, 400) 133 | c.Assert(srvErr.Code, Equals, "InvalidParameter.RoleSessionName") 134 | log.Println("ServiceError:", srvErr) 135 | } 136 | --------------------------------------------------------------------------------