mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-08-24 00:00:54 -04:00
added initial aws sigv4 functional test
This commit is contained in:
parent
c588614c2c
commit
755b895b08
1
Gemfile
1
Gemfile
@ -49,6 +49,7 @@ group :test do
|
||||
|
||||
gem "faraday"
|
||||
gem "oga"
|
||||
gem "aws-sdk-s3"
|
||||
|
||||
if RUBY_VERSION >= "3.0"
|
||||
gem "rbs", git: "https://github.com/ruby/rbs.git", branch: "master"
|
||||
|
@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "set"
|
||||
require "aws-sdk-s3"
|
||||
|
||||
module HTTPX
|
||||
module Plugins
|
||||
@ -17,10 +18,13 @@ module HTTPX
|
||||
service:,
|
||||
region:,
|
||||
provider_prefix: "aws",
|
||||
header_provider_field: "amz",
|
||||
unsigned_headers: [],
|
||||
apply_checksum_header: true,
|
||||
security_token: nil
|
||||
security_token: nil,
|
||||
algorithm: "SHA256"
|
||||
)
|
||||
# TODO: add clock skew
|
||||
@username = username
|
||||
@password = password
|
||||
@service = service
|
||||
@ -30,31 +34,34 @@ module HTTPX
|
||||
@unsigned_headers << "authorization"
|
||||
@unsigned_headers << "x-amzn-trace-id"
|
||||
@unsigned_headers << "expect"
|
||||
# TODO: remove it
|
||||
@unsigned_headers << "accept"
|
||||
@unsigned_headers << "user-agent"
|
||||
@unsigned_headers << "content-type"
|
||||
|
||||
@apply_checksum_header = apply_checksum_header
|
||||
@provider_prefix = provider_prefix
|
||||
@header_provider_field = header_provider_field
|
||||
|
||||
@security_token = security_token
|
||||
|
||||
@algorithm = "AWS4-HMAC-SHA256"
|
||||
@algorithm = algorithm
|
||||
end
|
||||
|
||||
def sign!(request)
|
||||
datetime = (request.headers["x-amz-date"] ||= Time.now.utc.strftime("%Y%m%dT%H%M%SZ"))
|
||||
lower_provider_prefix = "#{@provider_prefix}4"
|
||||
upper_provider_prefix = lower_provider_prefix.upcase
|
||||
|
||||
datetime = (request.headers["x-#{@header_provider_field}-date"] ||= Time.now.utc.strftime("%Y%m%dT%H%M%SZ"))
|
||||
date = datetime[0, 8]
|
||||
|
||||
content_sha256 = request.headers["x-amz-content-sha256"] || sha256_hexdigest(request.body)
|
||||
content_sha256 = request.headers["x-#{@header_provider_field}-content-sha256"] || sha256_hexdigest(request.body)
|
||||
|
||||
request.headers["x-amz-content-sha256"] ||= content_sha256 if @apply_checksum_header
|
||||
request.headers["x-amz-security-token"] ||= @security_token if @security_token
|
||||
request.headers["x-#{@header_provider_field}-content-sha256"] ||= content_sha256 if @apply_checksum_header
|
||||
request.headers["x-#{@header_provider_field}-security-token"] ||= @security_token if @security_token
|
||||
|
||||
signature_headers = request.headers.each.reject do |k, _|
|
||||
@unsigned_headers.include?(k)
|
||||
end.sort_by(&:first)
|
||||
end
|
||||
# aws sigv4 needs to declare the host, regardless of protocol version
|
||||
signature_headers << ["host", request.authority] unless request.headers.key?("host")
|
||||
signature_headers.sort_by!(&:first)
|
||||
|
||||
signed_headers = signature_headers.map(&:first).join(";")
|
||||
|
||||
@ -74,25 +81,27 @@ module HTTPX
|
||||
credential_scope = "#{date}" \
|
||||
"/#{@region}" \
|
||||
"/#{@service}" \
|
||||
"/aws4_request"
|
||||
"/#{lower_provider_prefix}_request"
|
||||
|
||||
algo_line = "#{upper_provider_prefix}-HMAC-#{@algorithm}"
|
||||
# string to sign
|
||||
sts = "#{@algorithm}" \
|
||||
sts = "#{algo_line}" \
|
||||
"\n#{datetime}" \
|
||||
"\n#{credential_scope}" \
|
||||
"\n#{sha256_hexdigest(creq)}"
|
||||
|
||||
# signature
|
||||
k_date = hmac("AWS4#{@password}", date)
|
||||
k_date = hmac("#{upper_provider_prefix}#{@password}", date)
|
||||
k_region = hmac(k_date, @region)
|
||||
k_service = hmac(k_region, @service)
|
||||
k_credentials = hmac(k_service, "aws4_request")
|
||||
k_credentials = hmac(k_service, "#{lower_provider_prefix}_request")
|
||||
sig = hexhmac(k_credentials, sts)
|
||||
|
||||
credential = "#{@username}/#{credential_scope}"
|
||||
# apply signature
|
||||
request.headers["authorization"] =
|
||||
"AWS4-HMAC-SHA256 Credential=#{credential}, " \
|
||||
"#{algo_line} " \
|
||||
"Credential=#{credential}, " \
|
||||
"SignedHeaders=#{signed_headers}, " \
|
||||
"Signature=#{sig}"
|
||||
end
|
||||
@ -120,11 +129,11 @@ module HTTPX
|
||||
end
|
||||
|
||||
def hmac(key, value)
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), key, value)
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest.new("SHA256"), key, value)
|
||||
end
|
||||
|
||||
def hexhmac(key, value)
|
||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), key, value)
|
||||
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("SHA256"), key, value)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "support/http_helpers"
|
||||
require "aws-sdk-s3"
|
||||
|
||||
class HTTPXAwsSigv4Test < Minitest::Test
|
||||
include ResponseHelpers
|
||||
@ -73,7 +74,7 @@ class HTTPXAwsSigv4Test < Minitest::Test
|
||||
end
|
||||
|
||||
def test_plugin_aws_sigv4_authorization_unsigned_headers
|
||||
request = sigv4_session(service: "SERVICE", region: "REGION", unsigned_headers: ["content-length"])
|
||||
request = sigv4_session(service: "SERVICE", region: "REGION", unsigned_headers: %w[accept user-agent content-type content-length])
|
||||
.build_request(:put, "http://domain.com", headers: {
|
||||
"Host" => "domain.com",
|
||||
"Foo" => "foo",
|
||||
@ -89,9 +90,39 @@ class HTTPXAwsSigv4Test < Minitest::Test
|
||||
"Signature=4a7d3e06d1950eb64a3daa1becaa8ba030d9099858516cb2fa4533fab4e8937d"
|
||||
end
|
||||
|
||||
AWS_URI = ENV.fetch("AMZ_HOST", "aws:4566")
|
||||
USERNAME = ENV.fetch("AWS_ACCESS_KEY_ID", "test")
|
||||
PASSWORD = ENV.fetch("AWS_SECRET_ACCESS_KEY", "test")
|
||||
def test_plugin_aws_sigv4_get_object
|
||||
s3_client = Aws::S3::Client.new(
|
||||
endpoint: "http://#{AWS_URI}",
|
||||
force_path_style: true
|
||||
# http_wire_trace: true,
|
||||
# logger: Logger.new(STDERR)
|
||||
)
|
||||
s3_client.create_bucket(bucket: "test", acl: "private")
|
||||
object = s3_client.put_object(bucket: "test", key: "testimage", body: "bucketz")
|
||||
|
||||
# now let's get it
|
||||
# no_sig_response = HTTPX.get("http://#{AWS_URI}/test/testimage")
|
||||
# verify_error_response(no_sig_response)
|
||||
|
||||
aws_req_headers = object.context.http_request.headers
|
||||
|
||||
response = sigv4_session(username: USERNAME, password: PASSWORD, unsigned_headers: %w[accept content-type content-length])
|
||||
.with(headers: {
|
||||
"user-agent" => aws_req_headers["user-agent"],
|
||||
"expect" => "100-continue",
|
||||
"x-amz-date" => aws_req_headers["x-amz-date"],
|
||||
"content-md5" => OpenSSL::Digest.base64digest("MD5", "bucketz"),
|
||||
})
|
||||
.put("http://#{AWS_URI}/test/testimage", body: "bucketz")
|
||||
verify_status(response, 200)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sigv4_session(**options)
|
||||
HTTPX.plugin(:aws_sigv4).aws_sigv4_authentication(service: "s3", region: "eu-west-1", username: "akid", password: "secret", **options)
|
||||
HTTPX.plugin(:aws_sigv4).aws_sigv4_authentication(service: "s3", region: "us-east-1", username: "akid", password: "secret", **options)
|
||||
end
|
||||
end
|
||||
|
Loading…
x
Reference in New Issue
Block a user