Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

436 строки
11 KiB

  1. //
  2. // goamz - Go packages to interact with the Amazon Web Services.
  3. //
  4. // https://wiki.ubuntu.com/goamz
  5. //
  6. // Copyright (c) 2011 Canonical Ltd.
  7. //
  8. // Written by Gustavo Niemeyer <gustavo.niemeyer@canonical.com>
  9. //
  10. package aws
  11. import (
  12. "encoding/json"
  13. "encoding/xml"
  14. "errors"
  15. "fmt"
  16. "io/ioutil"
  17. "net/http"
  18. "net/url"
  19. "os"
  20. "time"
  21. "github.com/vaughan0/go-ini"
  22. )
  23. // Defines the valid signers
  24. const (
  25. V2Signature = iota
  26. V4Signature = iota
  27. Route53Signature = iota
  28. )
  29. // Defines the service endpoint and correct Signer implementation to use
  30. // to sign requests for this endpoint
  31. type ServiceInfo struct {
  32. Endpoint string
  33. Signer uint
  34. }
  35. // Region defines the URLs where AWS services may be accessed.
  36. //
  37. // See http://goo.gl/d8BP1 for more details.
  38. type Region struct {
  39. Name string // the canonical name of this region.
  40. EC2Endpoint string
  41. S3Endpoint string
  42. S3BucketEndpoint string // Not needed by AWS S3. Use ${bucket} for bucket name.
  43. S3LocationConstraint bool // true if this region requires a LocationConstraint declaration.
  44. S3LowercaseBucket bool // true if the region requires bucket names to be lower case.
  45. SDBEndpoint string
  46. SESEndpoint string
  47. SNSEndpoint string
  48. SQSEndpoint string
  49. IAMEndpoint string
  50. ELBEndpoint string
  51. DynamoDBEndpoint string
  52. CloudWatchServicepoint ServiceInfo
  53. AutoScalingEndpoint string
  54. RDSEndpoint ServiceInfo
  55. STSEndpoint string
  56. CloudFormationEndpoint string
  57. ECSEndpoint string
  58. DynamoDBStreamsEndpoint string
  59. }
  60. var Regions = map[string]Region{
  61. APNortheast.Name: APNortheast,
  62. APNortheast2.Name: APNortheast2,
  63. APSoutheast.Name: APSoutheast,
  64. APSoutheast2.Name: APSoutheast2,
  65. EUCentral.Name: EUCentral,
  66. EUWest.Name: EUWest,
  67. USEast.Name: USEast,
  68. USEast2.Name: USEast2,
  69. USWest.Name: USWest,
  70. USWest2.Name: USWest2,
  71. USGovWest.Name: USGovWest,
  72. SAEast.Name: SAEast,
  73. CNNorth.Name: CNNorth,
  74. }
  75. // Designates a signer interface suitable for signing AWS requests, params
  76. // should be appropriately encoded for the request before signing.
  77. //
  78. // A signer should be initialized with Auth and the appropriate endpoint.
  79. type Signer interface {
  80. Sign(method, path string, params map[string]string)
  81. }
  82. // An AWS Service interface with the API to query the AWS service
  83. //
  84. // Supplied as an easy way to mock out service calls during testing.
  85. type AWSService interface {
  86. // Queries the AWS service at a given method/path with the params and
  87. // returns an http.Response and error
  88. Query(method, path string, params map[string]string) (*http.Response, error)
  89. // Builds an error given an XML payload in the http.Response, can be used
  90. // to process an error if the status code is not 200 for example.
  91. BuildError(r *http.Response) error
  92. }
  93. // Implements a Server Query/Post API to easily query AWS services and build
  94. // errors when desired
  95. type Service struct {
  96. service ServiceInfo
  97. signer Signer
  98. }
  99. // Create a base set of params for an action
  100. func MakeParams(action string) map[string]string {
  101. params := make(map[string]string)
  102. params["Action"] = action
  103. return params
  104. }
  105. // Create a new AWS server to handle making requests
  106. func NewService(auth Auth, service ServiceInfo) (s *Service, err error) {
  107. var signer Signer
  108. switch service.Signer {
  109. case V2Signature:
  110. signer, err = NewV2Signer(auth, service)
  111. // case V4Signature:
  112. // signer, err = NewV4Signer(auth, service, Regions["eu-west-1"])
  113. default:
  114. err = fmt.Errorf("Unsupported signer for service")
  115. }
  116. if err != nil {
  117. return
  118. }
  119. s = &Service{service: service, signer: signer}
  120. return
  121. }
  122. func (s *Service) Query(method, path string, params map[string]string) (resp *http.Response, err error) {
  123. params["Timestamp"] = time.Now().UTC().Format(time.RFC3339)
  124. u, err := url.Parse(s.service.Endpoint)
  125. if err != nil {
  126. return nil, err
  127. }
  128. u.Path = path
  129. s.signer.Sign(method, path, params)
  130. if method == "GET" {
  131. u.RawQuery = multimap(params).Encode()
  132. resp, err = http.Get(u.String())
  133. } else if method == "POST" {
  134. resp, err = http.PostForm(u.String(), multimap(params))
  135. }
  136. return
  137. }
  138. func (s *Service) BuildError(r *http.Response) error {
  139. errors := ErrorResponse{}
  140. xml.NewDecoder(r.Body).Decode(&errors)
  141. var err Error
  142. err = errors.Errors
  143. err.RequestId = errors.RequestId
  144. err.StatusCode = r.StatusCode
  145. if err.Message == "" {
  146. err.Message = r.Status
  147. }
  148. return &err
  149. }
  150. type ErrorResponse struct {
  151. Errors Error `xml:"Error"`
  152. RequestId string // A unique ID for tracking the request
  153. }
  154. type Error struct {
  155. StatusCode int
  156. Type string
  157. Code string
  158. Message string
  159. RequestId string
  160. }
  161. func (err *Error) Error() string {
  162. return fmt.Sprintf("Type: %s, Code: %s, Message: %s",
  163. err.Type, err.Code, err.Message,
  164. )
  165. }
  166. type Auth struct {
  167. AccessKey, SecretKey string
  168. token string
  169. expiration time.Time
  170. }
  171. func (a *Auth) Token() string {
  172. if a.token == "" {
  173. return ""
  174. }
  175. if time.Since(a.expiration) >= -30*time.Second { //in an ideal world this should be zero assuming the instance is synching it's clock
  176. *a, _ = GetAuth("", "", "", time.Time{})
  177. }
  178. return a.token
  179. }
  180. func (a *Auth) Expiration() time.Time {
  181. return a.expiration
  182. }
  183. // To be used with other APIs that return auth credentials such as STS
  184. func NewAuth(accessKey, secretKey, token string, expiration time.Time) *Auth {
  185. return &Auth{
  186. AccessKey: accessKey,
  187. SecretKey: secretKey,
  188. token: token,
  189. expiration: expiration,
  190. }
  191. }
  192. // ResponseMetadata
  193. type ResponseMetadata struct {
  194. RequestId string // A unique ID for tracking the request
  195. }
  196. type BaseResponse struct {
  197. ResponseMetadata ResponseMetadata
  198. }
  199. var unreserved = make([]bool, 128)
  200. var hex = "0123456789ABCDEF"
  201. func init() {
  202. // RFC3986
  203. u := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890-_.~"
  204. for _, c := range u {
  205. unreserved[c] = true
  206. }
  207. }
  208. func multimap(p map[string]string) url.Values {
  209. q := make(url.Values, len(p))
  210. for k, v := range p {
  211. q[k] = []string{v}
  212. }
  213. return q
  214. }
  215. type credentials struct {
  216. Code string
  217. LastUpdated string
  218. Type string
  219. AccessKeyId string
  220. SecretAccessKey string
  221. Token string
  222. Expiration string
  223. }
  224. // GetMetaData retrieves instance metadata about the current machine.
  225. //
  226. // See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-instancedata.html for more details.
  227. func GetMetaData(path string) (contents []byte, err error) {
  228. url := "http://169.254.169.254/latest/meta-data/" + path
  229. resp, err := RetryingClient.Get(url)
  230. if err != nil {
  231. return
  232. }
  233. defer resp.Body.Close()
  234. if resp.StatusCode != 200 {
  235. err = fmt.Errorf("Code %d returned for url %s", resp.StatusCode, url)
  236. return
  237. }
  238. body, err := ioutil.ReadAll(resp.Body)
  239. if err != nil {
  240. return
  241. }
  242. return []byte(body), err
  243. }
  244. func getInstanceCredentials() (cred credentials, err error) {
  245. credentialPath := "iam/security-credentials/"
  246. // Get the instance role
  247. role, err := GetMetaData(credentialPath)
  248. if err != nil {
  249. return
  250. }
  251. // Get the instance role credentials
  252. credentialJSON, err := GetMetaData(credentialPath + string(role))
  253. if err != nil {
  254. return
  255. }
  256. err = json.Unmarshal([]byte(credentialJSON), &cred)
  257. return
  258. }
  259. // GetAuth creates an Auth based on either passed in credentials,
  260. // environment information or instance based role credentials.
  261. func GetAuth(accessKey string, secretKey, token string, expiration time.Time) (auth Auth, err error) {
  262. // First try passed in credentials
  263. if accessKey != "" && secretKey != "" {
  264. return Auth{accessKey, secretKey, token, expiration}, nil
  265. }
  266. // Next try to get auth from the shared credentials file
  267. auth, err = SharedAuth()
  268. if err == nil {
  269. // Found auth, return
  270. return
  271. }
  272. // Next try to get auth from the environment
  273. auth, err = EnvAuth()
  274. if err == nil {
  275. // Found auth, return
  276. return
  277. }
  278. // Next try getting auth from the instance role
  279. cred, err := getInstanceCredentials()
  280. if err == nil {
  281. // Found auth, return
  282. auth.AccessKey = cred.AccessKeyId
  283. auth.SecretKey = cred.SecretAccessKey
  284. auth.token = cred.Token
  285. exptdate, err := time.Parse("2006-01-02T15:04:05Z", cred.Expiration)
  286. if err != nil {
  287. err = fmt.Errorf("Error Parseing expiration date: cred.Expiration :%s , error: %s \n", cred.Expiration, err)
  288. }
  289. auth.expiration = exptdate
  290. return auth, err
  291. }
  292. err = errors.New("No valid AWS authentication found")
  293. return auth, err
  294. }
  295. // EnvAuth creates an Auth based on environment information.
  296. // The AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment
  297. // variables are used.
  298. // AWS_SESSION_TOKEN is used if present.
  299. func EnvAuth() (auth Auth, err error) {
  300. auth.AccessKey = os.Getenv("AWS_ACCESS_KEY_ID")
  301. if auth.AccessKey == "" {
  302. auth.AccessKey = os.Getenv("AWS_ACCESS_KEY")
  303. }
  304. auth.SecretKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
  305. if auth.SecretKey == "" {
  306. auth.SecretKey = os.Getenv("AWS_SECRET_KEY")
  307. }
  308. if auth.AccessKey == "" {
  309. err = errors.New("AWS_ACCESS_KEY_ID or AWS_ACCESS_KEY not found in environment")
  310. }
  311. if auth.SecretKey == "" {
  312. err = errors.New("AWS_SECRET_ACCESS_KEY or AWS_SECRET_KEY not found in environment")
  313. }
  314. auth.token = os.Getenv("AWS_SESSION_TOKEN")
  315. return
  316. }
  317. // SharedAuth creates an Auth based on shared credentials stored in
  318. // $HOME/.aws/credentials. The AWS_PROFILE environment variables is used to
  319. // select the profile.
  320. func SharedAuth() (auth Auth, err error) {
  321. var profileName = os.Getenv("AWS_PROFILE")
  322. if profileName == "" {
  323. profileName = "default"
  324. }
  325. var credentialsFile = os.Getenv("AWS_CREDENTIAL_FILE")
  326. if credentialsFile == "" {
  327. var homeDir = os.Getenv("HOME")
  328. if homeDir == "" {
  329. err = errors.New("Could not get HOME")
  330. return
  331. }
  332. credentialsFile = homeDir + "/.aws/credentials"
  333. }
  334. file, err := ini.LoadFile(credentialsFile)
  335. if err != nil {
  336. err = errors.New("Couldn't parse AWS credentials file")
  337. return
  338. }
  339. var profile = file[profileName]
  340. if profile == nil {
  341. err = errors.New("Couldn't find profile in AWS credentials file")
  342. return
  343. }
  344. auth.AccessKey = profile["aws_access_key_id"]
  345. auth.SecretKey = profile["aws_secret_access_key"]
  346. auth.token = profile["aws_session_token"]
  347. if auth.AccessKey == "" {
  348. err = errors.New("AWS_ACCESS_KEY_ID not found in environment in credentials file")
  349. }
  350. if auth.SecretKey == "" {
  351. err = errors.New("AWS_SECRET_ACCESS_KEY not found in credentials file")
  352. }
  353. return
  354. }
  355. // Encode takes a string and URI-encodes it in a way suitable
  356. // to be used in AWS signatures.
  357. func Encode(s string) string {
  358. encode := false
  359. for i := 0; i != len(s); i++ {
  360. c := s[i]
  361. if c > 127 || !unreserved[c] {
  362. encode = true
  363. break
  364. }
  365. }
  366. if !encode {
  367. return s
  368. }
  369. e := make([]byte, len(s)*3)
  370. ei := 0
  371. for i := 0; i != len(s); i++ {
  372. c := s[i]
  373. if c > 127 || !unreserved[c] {
  374. e[ei] = '%'
  375. e[ei+1] = hex[c>>4]
  376. e[ei+2] = hex[c&0xF]
  377. ei += 3
  378. } else {
  379. e[ei] = c
  380. ei += 1
  381. }
  382. }
  383. return string(e[:ei])
  384. }