1// Package awsauth implements AWS request signing using Signed Signature Version 2,
2// Signed Signature Version 3, and Signed Signature Version 4. Supports S3 and STS.
3package awsauth
4
5import (
6	"net/http"
7	"net/url"
8	"time"
9)
10
11// Credentials stores the information necessary to authorize with AWS and it
12// is from this information that requests are signed.
13type Credentials struct {
14	AccessKeyID     string
15	SecretAccessKey string
16	SecurityToken   string `json:"Token"`
17	Expiration      time.Time
18}
19
20// Sign signs a request bound for AWS. It automatically chooses the best
21// authentication scheme based on the service the request is going to.
22func Sign(request *http.Request, credentials ...Credentials) *http.Request {
23	service, _ := serviceAndRegion(request.URL.Host)
24	signVersion := awsSignVersion[service]
25
26	switch signVersion {
27	case 2:
28		return Sign2(request, credentials...)
29	case 3:
30		return Sign3(request, credentials...)
31	case 4:
32		return Sign4(request, credentials...)
33	case -1:
34		return SignS3(request, credentials...)
35	}
36
37	return nil
38}
39
40// Sign4 signs a request with Signed Signature Version 4.
41func Sign4(request *http.Request, credentials ...Credentials) *http.Request {
42	keys := chooseKeys(credentials)
43
44	// Add the X-Amz-Security-Token header when using STS
45	if keys.SecurityToken != "" {
46		request.Header.Set("X-Amz-Security-Token", keys.SecurityToken)
47	}
48
49	prepareRequestV4(request)
50	meta := new(metadata)
51
52	// Task 1
53	hashedCanonReq := hashedCanonicalRequestV4(request, meta)
54
55	// Task 2
56	stringToSign := stringToSignV4(request, hashedCanonReq, meta)
57
58	// Task 3
59	signingKey := signingKeyV4(keys.SecretAccessKey, meta.date, meta.region, meta.service)
60	signature := signatureV4(signingKey, stringToSign)
61
62	request.Header.Set("Authorization", buildAuthHeaderV4(signature, meta, keys))
63
64	return request
65}
66
67// Sign3 signs a request with Signed Signature Version 3.
68// If the service you're accessing supports Version 4, use that instead.
69func Sign3(request *http.Request, credentials ...Credentials) *http.Request {
70	keys := chooseKeys(credentials)
71
72	// Add the X-Amz-Security-Token header when using STS
73	if keys.SecurityToken != "" {
74		request.Header.Set("X-Amz-Security-Token", keys.SecurityToken)
75	}
76
77	prepareRequestV3(request)
78
79	// Task 1
80	stringToSign := stringToSignV3(request)
81
82	// Task 2
83	signature := signatureV3(stringToSign, keys)
84
85	// Task 3
86	request.Header.Set("X-Amzn-Authorization", buildAuthHeaderV3(signature, keys))
87
88	return request
89}
90
91// Sign2 signs a request with Signed Signature Version 2.
92// If the service you're accessing supports Version 4, use that instead.
93func Sign2(request *http.Request, credentials ...Credentials) *http.Request {
94	keys := chooseKeys(credentials)
95
96	// Add the SecurityToken parameter when using STS
97	// This must be added before the signature is calculated
98	if keys.SecurityToken != "" {
99		values := url.Values{}
100		values.Set("SecurityToken", keys.SecurityToken)
101		augmentRequestQuery(request, values)
102	}
103
104	prepareRequestV2(request, keys)
105
106	stringToSign := stringToSignV2(request)
107	signature := signatureV2(stringToSign, keys)
108
109	values := url.Values{}
110	values.Set("Signature", signature)
111
112	augmentRequestQuery(request, values)
113
114	return request
115}
116
117// SignS3 signs a request bound for Amazon S3 using their custom
118// HTTP authentication scheme.
119func SignS3(request *http.Request, credentials ...Credentials) *http.Request {
120	keys := chooseKeys(credentials)
121
122	// Add the X-Amz-Security-Token header when using STS
123	if keys.SecurityToken != "" {
124		request.Header.Set("X-Amz-Security-Token", keys.SecurityToken)
125	}
126
127	prepareRequestS3(request)
128
129	stringToSign := stringToSignS3(request)
130	signature := signatureS3(stringToSign, keys)
131
132	authHeader := "AWS " + keys.AccessKeyID + ":" + signature
133	request.Header.Set("Authorization", authHeader)
134
135	return request
136}
137
138// SignS3Url signs a GET request for a resource on Amazon S3 by appending
139// query string parameters containing credentials and signature. You must
140// specify an expiration date for these signed requests. After that date,
141// a request signed with this method will be rejected by S3.
142func SignS3Url(request *http.Request, expire time.Time, credentials ...Credentials) *http.Request {
143	keys := chooseKeys(credentials)
144
145	stringToSign := stringToSignS3Url("GET", expire, request.URL.Path)
146	signature := signatureS3(stringToSign, keys)
147
148	query := request.URL.Query()
149	query.Set("AWSAccessKeyId", keys.AccessKeyID)
150	query.Set("Signature", signature)
151	query.Set("Expires", timeToUnixEpochString(expire))
152	request.URL.RawQuery = query.Encode()
153
154	return request
155}
156
157// expired checks to see if the temporary credentials from an IAM role are
158// within 4 minutes of expiration (The IAM documentation says that new keys
159// will be provisioned 5 minutes before the old keys expire). Credentials
160// that do not have an Expiration cannot expire.
161func (this *Credentials) expired() bool {
162	if this.Expiration.IsZero() {
163		// Credentials with no expiration can't expire
164		return false
165	}
166	expireTime := this.Expiration.Add(-4 * time.Minute)
167	// if t - 4 mins is before now, true
168	if expireTime.Before(time.Now()) {
169		return true
170	} else {
171		return false
172	}
173}
174
175type metadata struct {
176	algorithm       string
177	credentialScope string
178	signedHeaders   string
179	date            string
180	region          string
181	service         string
182}
183
184const (
185	envAccessKey       = "AWS_ACCESS_KEY"
186	envAccessKeyID     = "AWS_ACCESS_KEY_ID"
187	envSecretKey       = "AWS_SECRET_KEY"
188	envSecretAccessKey = "AWS_SECRET_ACCESS_KEY"
189	envSecurityToken   = "AWS_SECURITY_TOKEN"
190)
191
192var (
193	awsSignVersion = map[string]int{
194		"autoscaling":          4,
195		"ce":                   4,
196		"cloudfront":           4,
197		"cloudformation":       4,
198		"cloudsearch":          4,
199		"monitoring":           4,
200		"dynamodb":             4,
201		"ec2":                  4,
202		"elasticmapreduce":     4,
203		"elastictranscoder":    4,
204		"elasticache":          4,
205		"es":                   4,
206		"glacier":              4,
207		"kinesis":              4,
208		"redshift":             4,
209		"rds":                  4,
210		"sdb":                  2,
211		"sns":                  4,
212		"sqs":                  4,
213		"s3":                   4,
214		"elasticbeanstalk":     4,
215		"importexport":         4,
216		"iam":                  4,
217		"route53":              4,
218		"elasticloadbalancing": 4,
219		"email":                4,
220	}
221)
222