added initial aws sigv4 functional test

This commit is contained in:
HoneyryderChuck 2021-01-29 18:03:28 +00:00
parent c588614c2c
commit 755b895b08
3 changed files with 61 additions and 20 deletions

View File

@ -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"

View File

@ -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

View File

@ -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