assume_role.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /*
  2. * MinIO Go Library for Amazon S3 Compatible Cloud Storage
  3. * Copyright 2020 MinIO, Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package credentials
  18. import (
  19. "encoding/hex"
  20. "encoding/xml"
  21. "errors"
  22. "io"
  23. "io/ioutil"
  24. "net/http"
  25. "net/url"
  26. "strconv"
  27. "strings"
  28. "time"
  29. "github.com/minio/minio-go/v7/pkg/signer"
  30. sha256 "github.com/minio/sha256-simd"
  31. )
  32. // AssumeRoleResponse contains the result of successful AssumeRole request.
  33. type AssumeRoleResponse struct {
  34. XMLName xml.Name `xml:"https://sts.amazonaws.com/doc/2011-06-15/ AssumeRoleResponse" json:"-"`
  35. Result AssumeRoleResult `xml:"AssumeRoleResult"`
  36. ResponseMetadata struct {
  37. RequestID string `xml:"RequestId,omitempty"`
  38. } `xml:"ResponseMetadata,omitempty"`
  39. }
  40. // AssumeRoleResult - Contains the response to a successful AssumeRole
  41. // request, including temporary credentials that can be used to make
  42. // MinIO API requests.
  43. type AssumeRoleResult struct {
  44. // The identifiers for the temporary security credentials that the operation
  45. // returns.
  46. AssumedRoleUser AssumedRoleUser `xml:",omitempty"`
  47. // The temporary security credentials, which include an access key ID, a secret
  48. // access key, and a security (or session) token.
  49. //
  50. // Note: The size of the security token that STS APIs return is not fixed. We
  51. // strongly recommend that you make no assumptions about the maximum size. As
  52. // of this writing, the typical size is less than 4096 bytes, but that can vary.
  53. // Also, future updates to AWS might require larger sizes.
  54. Credentials struct {
  55. AccessKey string `xml:"AccessKeyId" json:"accessKey,omitempty"`
  56. SecretKey string `xml:"SecretAccessKey" json:"secretKey,omitempty"`
  57. Expiration time.Time `xml:"Expiration" json:"expiration,omitempty"`
  58. SessionToken string `xml:"SessionToken" json:"sessionToken,omitempty"`
  59. } `xml:",omitempty"`
  60. // A percentage value that indicates the size of the policy in packed form.
  61. // The service rejects any policy with a packed size greater than 100 percent,
  62. // which means the policy exceeded the allowed space.
  63. PackedPolicySize int `xml:",omitempty"`
  64. }
  65. // A STSAssumeRole retrieves credentials from MinIO service, and keeps track if
  66. // those credentials are expired.
  67. type STSAssumeRole struct {
  68. Expiry
  69. // Required http Client to use when connecting to MinIO STS service.
  70. Client *http.Client
  71. // STS endpoint to fetch STS credentials.
  72. STSEndpoint string
  73. // various options for this request.
  74. Options STSAssumeRoleOptions
  75. }
  76. // STSAssumeRoleOptions collection of various input options
  77. // to obtain AssumeRole credentials.
  78. type STSAssumeRoleOptions struct {
  79. // Mandatory inputs.
  80. AccessKey string
  81. SecretKey string
  82. Location string // Optional commonly needed with AWS STS.
  83. DurationSeconds int // Optional defaults to 1 hour.
  84. // Optional only valid if using with AWS STS
  85. RoleARN string
  86. RoleSessionName string
  87. }
  88. // NewSTSAssumeRole returns a pointer to a new
  89. // Credentials object wrapping the STSAssumeRole.
  90. func NewSTSAssumeRole(stsEndpoint string, opts STSAssumeRoleOptions) (*Credentials, error) {
  91. if stsEndpoint == "" {
  92. return nil, errors.New("STS endpoint cannot be empty")
  93. }
  94. if opts.AccessKey == "" || opts.SecretKey == "" {
  95. return nil, errors.New("AssumeRole credentials access/secretkey is mandatory")
  96. }
  97. return New(&STSAssumeRole{
  98. Client: &http.Client{
  99. Transport: http.DefaultTransport,
  100. },
  101. STSEndpoint: stsEndpoint,
  102. Options: opts,
  103. }), nil
  104. }
  105. const defaultDurationSeconds = 3600
  106. // closeResponse close non nil response with any response Body.
  107. // convenient wrapper to drain any remaining data on response body.
  108. //
  109. // Subsequently this allows golang http RoundTripper
  110. // to re-use the same connection for future requests.
  111. func closeResponse(resp *http.Response) {
  112. // Callers should close resp.Body when done reading from it.
  113. // If resp.Body is not closed, the Client's underlying RoundTripper
  114. // (typically Transport) may not be able to re-use a persistent TCP
  115. // connection to the server for a subsequent "keep-alive" request.
  116. if resp != nil && resp.Body != nil {
  117. // Drain any remaining Body and then close the connection.
  118. // Without this closing connection would disallow re-using
  119. // the same connection for future uses.
  120. // - http://stackoverflow.com/a/17961593/4465767
  121. io.Copy(ioutil.Discard, resp.Body)
  122. resp.Body.Close()
  123. }
  124. }
  125. func getAssumeRoleCredentials(clnt *http.Client, endpoint string, opts STSAssumeRoleOptions) (AssumeRoleResponse, error) {
  126. v := url.Values{}
  127. v.Set("Action", "AssumeRole")
  128. v.Set("Version", STSVersion)
  129. if opts.RoleARN != "" {
  130. v.Set("RoleArn", opts.RoleARN)
  131. }
  132. if opts.RoleSessionName != "" {
  133. v.Set("RoleSessionName", opts.RoleSessionName)
  134. }
  135. if opts.DurationSeconds > defaultDurationSeconds {
  136. v.Set("DurationSeconds", strconv.Itoa(opts.DurationSeconds))
  137. } else {
  138. v.Set("DurationSeconds", strconv.Itoa(defaultDurationSeconds))
  139. }
  140. u, err := url.Parse(endpoint)
  141. if err != nil {
  142. return AssumeRoleResponse{}, err
  143. }
  144. u.Path = "/"
  145. postBody := strings.NewReader(v.Encode())
  146. hash := sha256.New()
  147. if _, err = io.Copy(hash, postBody); err != nil {
  148. return AssumeRoleResponse{}, err
  149. }
  150. postBody.Seek(0, 0)
  151. req, err := http.NewRequest(http.MethodPost, u.String(), postBody)
  152. if err != nil {
  153. return AssumeRoleResponse{}, err
  154. }
  155. req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  156. req.Header.Set("X-Amz-Content-Sha256", hex.EncodeToString(hash.Sum(nil)))
  157. req = signer.SignV4STS(*req, opts.AccessKey, opts.SecretKey, opts.Location)
  158. resp, err := clnt.Do(req)
  159. if err != nil {
  160. return AssumeRoleResponse{}, err
  161. }
  162. defer closeResponse(resp)
  163. if resp.StatusCode != http.StatusOK {
  164. return AssumeRoleResponse{}, errors.New(resp.Status)
  165. }
  166. a := AssumeRoleResponse{}
  167. if err = xml.NewDecoder(resp.Body).Decode(&a); err != nil {
  168. return AssumeRoleResponse{}, err
  169. }
  170. return a, nil
  171. }
  172. // Retrieve retrieves credentials from the MinIO service.
  173. // Error will be returned if the request fails.
  174. func (m *STSAssumeRole) Retrieve() (Value, error) {
  175. a, err := getAssumeRoleCredentials(m.Client, m.STSEndpoint, m.Options)
  176. if err != nil {
  177. return Value{}, err
  178. }
  179. // Expiry window is set to 10secs.
  180. m.SetExpiration(a.Result.Credentials.Expiration, DefaultExpiryWindow)
  181. return Value{
  182. AccessKeyID: a.Result.Credentials.AccessKey,
  183. SecretAccessKey: a.Result.Credentials.SecretKey,
  184. SessionToken: a.Result.Credentials.SessionToken,
  185. SignerType: SignatureV4,
  186. }, nil
  187. }