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