Class: Mongo::Auth::Aws::Request Private
- Inherits:
-
Object
- Object
- Mongo::Auth::Aws::Request
- Defined in:
- lib/mongo/auth/aws/request.rb
Overview
This class is part of a private API. You should avoid using this class if possible, as it may be removed or be changed in the future.
Helper class for working with AWS requests.
The primary purpose of this class is to produce the canonical AWS STS request and calculate the signed headers and signature for it.
Constant Summary collapse
- STS_REQUEST_BODY =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
The body of the STS GetCallerIdentity request.
This is currently the only request that this class supports making.
"Action=GetCallerIdentity&Version=2011-06-15".freeze
- VALIDATE_TIMEOUT =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
The timeout, in seconds, to use for validating credentials via STS.
10
Instance Attribute Summary collapse
-
#access_key_id ⇒ String
readonly
private
Access_key_id The access key id.
-
#host ⇒ String
readonly
private
Host The value of Host HTTP header to use.
-
#secret_access_key ⇒ String
readonly
private
Secret_access_key The secret access key.
-
#server_nonce ⇒ String
readonly
private
Server_nonce The server nonce binary string.
-
#session_token ⇒ String
readonly
private
Session_token The session token for temporary credentials.
-
#time ⇒ Time
readonly
private
Time The time of the request.
Instance Method Summary collapse
-
#authorization ⇒ String
private
Returns the value of the Authorization header, per the AWS signature V4 specification.
-
#canonical_request ⇒ String
private
Returns the canonical request used during calculation of AWS V4 signature.
-
#formatted_date ⇒ String
private
Formatted_date YYYYMMDD formatted date of the request.
-
#formatted_time ⇒ String
private
Formatted_time ISO8601-formatted time of the request, as would be used in X-Amz-Date header.
-
#headers ⇒ <Hash>
private
Returns the hash containing the headers of the calculated canonical request.
-
#headers_to_sign ⇒ <Hash>
private
Returns the hash containing the headers of the calculated canonical request that should be signed, in a ready to sign form.
-
#initialize(access_key_id:, secret_access_key:, session_token: nil, host:, server_nonce:, time: Time.now) ⇒ Request
constructor
private
Constructs the request.
-
#region ⇒ String
private
Region The region of the host, derived from the host.
-
#scope ⇒ String
private
Returns the scope of the request, per the AWS signature V4 specification.
-
#signature ⇒ String
private
Returns the calculated signature of the canonical request, per the AWS signature V4 specification.
-
#signed_headers_string ⇒ String
private
Returns semicolon-separated list of names of signed headers, per the AWS signature V4 specification.
-
#validate! ⇒ Hash
private
Validates the credentials and the constructed request components by sending a real STS GetCallerIdentity request.
Constructor Details
#initialize(access_key_id:, secret_access_key:, session_token: nil, host:, server_nonce:, time: Time.now) ⇒ Request
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
By overriding the time, it is possible to create reproducible requests (in other words, replay a request).
Constructs the request.
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/mongo/auth/aws/request.rb', line 54 def initialize(access_key_id:, secret_access_key:, session_token: nil, host:, server_nonce:, time: Time.now ) @access_key_id = access_key_id @secret_access_key = secret_access_key @session_token = session_token @host = host @server_nonce = server_nonce @time = time %i(access_key_id secret_access_key host server_nonce).each do |arg| value = instance_variable_get("@#{arg}") if value.nil? || value.empty? raise Error::InvalidServerAuthResponse, "Value for '#{arg}' is required" end end if host && host.length > 255 raise Error::InvalidServerAuthHost, "Value for 'host' is too long: #{@host}" end end |
Instance Attribute Details
#access_key_id ⇒ String (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns access_key_id The access key id.
77 78 79 |
# File 'lib/mongo/auth/aws/request.rb', line 77 def access_key_id @access_key_id end |
#host ⇒ String (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns host The value of Host HTTP header to use.
87 88 89 |
# File 'lib/mongo/auth/aws/request.rb', line 87 def host @host end |
#secret_access_key ⇒ String (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns secret_access_key The secret access key.
80 81 82 |
# File 'lib/mongo/auth/aws/request.rb', line 80 def secret_access_key @secret_access_key end |
#server_nonce ⇒ String (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns server_nonce The server nonce binary string.
90 91 92 |
# File 'lib/mongo/auth/aws/request.rb', line 90 def server_nonce @server_nonce end |
#session_token ⇒ String (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns session_token The session token for temporary credentials.
84 85 86 |
# File 'lib/mongo/auth/aws/request.rb', line 84 def session_token @session_token end |
#time ⇒ Time (readonly)
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns time The time of the request.
93 94 95 |
# File 'lib/mongo/auth/aws/request.rb', line 93 def time @time end |
Instance Method Details
#authorization ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the value of the Authorization header, per the AWS signature V4 specification.
235 236 237 |
# File 'lib/mongo/auth/aws/request.rb', line 235 def "AWS4-HMAC-SHA256 Credential=#{access_key_id}/#{scope}, SignedHeaders=#{signed_headers_string}, Signature=#{signature}" end |
#canonical_request ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the canonical request used during calculation of AWS V4 signature.
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/mongo/auth/aws/request.rb', line 196 def canonical_request headers = headers_to_sign serialized_headers = headers.map do |k, v| "#{k}:#{v}" end.join("\n") hashed_payload = Digest::SHA256.new.update(STS_REQUEST_BODY).hexdigest "POST\n/\n\n" + # There are two newlines after serialized headers because the # signature V4 specification treats each header as containing the # terminating newline, and there is an additional newline # separating headers from the signed header names. "#{serialized_headers}\n\n" + "#{signed_headers_string}\n" + hashed_payload end |
#formatted_date ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns formatted_date YYYYMMDD formatted date of the request.
102 103 104 |
# File 'lib/mongo/auth/aws/request.rb', line 102 def formatted_date formatted_time[0, 8] end |
#formatted_time ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns formatted_time ISO8601-formatted time of the request, as would be used in X-Amz-Date header.
97 98 99 |
# File 'lib/mongo/auth/aws/request.rb', line 97 def formatted_time @formatted_time ||= @time.getutc.strftime('%Y%m%dT%H%M%SZ') end |
#headers ⇒ <Hash>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Not all of these headers are part of the signed headers list, the keys of the hash are not necessarily ordered lexicographically, and the keys may be in any case.
Returns the hash containing the headers of the calculated canonical request.
147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/mongo/auth/aws/request.rb', line 147 def headers headers = { 'content-length' => STS_REQUEST_BODY.length.to_s, 'content-type' => 'application/x-www-form-urlencoded', 'host' => host, 'x-amz-date' => formatted_time, 'x-mongodb-gs2-cb-flag' => 'n', 'x-mongodb-server-nonce' => Base64.encode64(server_nonce).gsub("\n", ''), } if session_token headers['x-amz-security-token'] = session_token end headers end |
#headers_to_sign ⇒ <Hash>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the hash containing the headers of the calculated canonical request that should be signed, in a ready to sign form.
The differences between #headers and this method is this method:
-
Removes any headers that are not to be signed. Per AWS specifications it should be possible to sign all headers, but MongoDB server expects only some headers to be signed and will not form the correct request if other headers are signed.
-
Lowercases all header names.
-
Orders the headers lexicographically in the hash.
175 176 177 178 179 180 181 182 |
# File 'lib/mongo/auth/aws/request.rb', line 175 def headers_to_sign headers_to_sign = {} headers.keys.sort_by { |k| k.downcase }.each do |key| write_key = key.downcase headers_to_sign[write_key] = headers[key] end headers_to_sign end |
#region ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns region The region of the host, derived from the host.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/mongo/auth/aws/request.rb', line 107 def region # Common case if host == 'sts.amazonaws.com' return 'us-east-1' end if host.start_with?('.') raise Error::InvalidServerAuthHost, "Host begins with a period: #{host}" end if host.end_with?('.') raise Error::InvalidServerAuthHost, "Host ends with a period: #{host}" end parts = host.split('.') if parts.any? { |part| part.empty? } raise Error::InvalidServerAuthHost, "Host has an empty component: #{host}" end if parts.length == 1 'us-east-1' else parts[1] end end |
#scope ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the scope of the request, per the AWS signature V4 specification.
135 136 137 |
# File 'lib/mongo/auth/aws/request.rb', line 135 def scope "#{formatted_date}/#{region}/sts/aws4_request" end |
#signature ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns the calculated signature of the canonical request, per the AWS signature V4 specification.
216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/mongo/auth/aws/request.rb', line 216 def signature hashed_canonical_request = Digest::SHA256.hexdigest(canonical_request) string_to_sign = "AWS4-HMAC-SHA256\n" + "#{formatted_time}\n" + "#{scope}\n" + hashed_canonical_request # All of the intermediate HMAC operations are not hex-encoded. mac = hmac("AWS4#{secret_access_key}", formatted_date) mac = hmac(mac, region) mac = hmac(mac, 'sts') signing_key = hmac(mac, 'aws4_request') # Only the final HMAC operation is hex-encoded. hmac_hex(signing_key, string_to_sign) end |
#signed_headers_string ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns semicolon-separated list of names of signed headers, per the AWS signature V4 specification.
188 189 190 |
# File 'lib/mongo/auth/aws/request.rb', line 188 def signed_headers_string headers_to_sign.keys.join(';') end |
#validate! ⇒ Hash
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Validates the credentials and the constructed request components by sending a real STS GetCallerIdentity request.
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/mongo/auth/aws/request.rb', line 243 def validate! sts_request = Net::HTTP::Post.new("https://#{host}").tap do |req| headers.each do |k, v| req[k] = v end req['authorization'] = req['accept'] = 'application/json' req.body = STS_REQUEST_BODY end http = Net::HTTP.new(host, 443) http.use_ssl = true http.start do resp = Timeout.timeout(VALIDATE_TIMEOUT, Error::CredentialCheckError, 'GetCallerIdentity request timed out') do http.request(sts_request) end payload = JSON.parse(resp.body) if resp.code != '200' aws_code = payload.fetch('Error').fetch('Code') = payload.fetch('Error').fetch('Message') msg = "Credential check for user #{access_key_id} failed with HTTP status code #{resp.code}: #{aws_code}: #{}" msg += '.' unless msg.end_with?('.') msg += " Please check that the credentials are valid, and if they are temporary (i.e. use the session token) that the session token is provided and not expired" raise Error::CredentialCheckError, msg end payload.fetch('GetCallerIdentityResponse').fetch('GetCallerIdentityResult') end end |