You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

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