Switch to AWS V4 signatures
This commit is contained in:
parent
189c304a08
commit
02820203f0
3
Gemfile
3
Gemfile
@ -3,8 +3,9 @@ source "https://rubygems.org"
|
||||
gem "sinatra", "~> 2.2.0"
|
||||
gem "sinatra-contrib", "~> 2.2.0"
|
||||
gem "activesupport", "~> 6.1.0"
|
||||
gem "rest-client", "~> 2.1.0"
|
||||
gem "redis", "~> 4.6.0"
|
||||
gem "rest-client", "~> 2.1.0"
|
||||
gem "aws-sigv4", "~> 1.0.0"
|
||||
# Remove require when we can update to 3.0, which sets the new storage
|
||||
# format to columnar by default. Increases performance
|
||||
gem "mime-types"
|
||||
|
@ -9,6 +9,7 @@ GEM
|
||||
zeitwerk (~> 2.3)
|
||||
addressable (2.8.5)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
aws-sigv4 (1.0.3)
|
||||
base64 (0.1.1)
|
||||
concurrent-ruby (1.2.2)
|
||||
crack (0.4.5)
|
||||
@ -93,6 +94,7 @@ PLATFORMS
|
||||
|
||||
DEPENDENCIES
|
||||
activesupport (~> 6.1.0)
|
||||
aws-sigv4 (~> 1.0.0)
|
||||
m
|
||||
mime-types
|
||||
minitest
|
||||
|
@ -10,46 +10,58 @@ module RemoteStorage
|
||||
|
||||
private
|
||||
|
||||
# S3 already wraps the ETag around quotes
|
||||
def s3_signer
|
||||
signer ||= Aws::Sigv4::Signer.new(
|
||||
service: 's3',
|
||||
region: settings.s3["region"],
|
||||
access_key_id: settings.s3["access_key_id"].to_s,
|
||||
secret_access_key: settings.s3["secret_key_id"].to_s
|
||||
)
|
||||
end
|
||||
|
||||
# S3 already wraps the ETag with quotes
|
||||
def format_etag(etag)
|
||||
etag
|
||||
end
|
||||
|
||||
def do_put_request(url, data, content_type)
|
||||
deal_with_unauthorized_requests do
|
||||
md5 = Digest::MD5.base64digest(data)
|
||||
authorization_headers = authorization_headers_for(
|
||||
"PUT", url, md5, content_type
|
||||
).merge({ "Content-Type" => content_type, "Content-Md5" => md5 })
|
||||
res = RestClient.put(url, data, authorization_headers)
|
||||
headers = { "Content-Type" => content_type }
|
||||
auth_headers = auth_headers_for("PUT", url, headers, data)
|
||||
|
||||
res = RestClient.put(url, data, headers.merge(auth_headers))
|
||||
|
||||
return [
|
||||
res.headers[:etag].delete('"'),
|
||||
timestamp_for(res.headers[:date]) # S3 does not return a Last-Modified response header on PUTs
|
||||
]
|
||||
end
|
||||
|
||||
rescue RestClient::Forbidden => ex
|
||||
puts ex.response.to_s
|
||||
raise ex
|
||||
end
|
||||
|
||||
def do_get_request(url, &block)
|
||||
deal_with_unauthorized_requests do
|
||||
headers = { }
|
||||
headers = {}
|
||||
headers["Range"] = server.env["HTTP_RANGE"] if server.env["HTTP_RANGE"]
|
||||
authorization_headers = authorization_headers_for("GET", url)
|
||||
RestClient.get(url, authorization_headers.merge(headers), &block)
|
||||
auth_headers = auth_headers_for("GET", url, headers)
|
||||
RestClient.get(url, headers.merge(auth_headers), &block)
|
||||
end
|
||||
end
|
||||
|
||||
def do_head_request(url, &block)
|
||||
deal_with_unauthorized_requests do
|
||||
authorization_headers = authorization_headers_for("HEAD", url)
|
||||
RestClient.head(url, authorization_headers, &block)
|
||||
auth_headers = auth_headers_for("HEAD", url, {})
|
||||
RestClient.head(url, auth_headers, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def do_delete_request(url)
|
||||
deal_with_unauthorized_requests do
|
||||
authorization_headers = authorization_headers_for("DELETE", url)
|
||||
RestClient.delete(url, authorization_headers)
|
||||
auth_headers = auth_headers_for("DELETE", url, {})
|
||||
RestClient.delete(url, auth_headers)
|
||||
end
|
||||
end
|
||||
|
||||
@ -67,40 +79,19 @@ module RemoteStorage
|
||||
return found
|
||||
end
|
||||
|
||||
# This is using the S3 authorizations, not the newer AW V4 Signatures
|
||||
# (https://s3.amazonaws.com/doc/s3-developer-guide/RESTAuthentication.html)
|
||||
def authorization_headers_for(http_verb, url, md5 = nil, content_type = nil)
|
||||
url = File.join("/", url.gsub(base_url, ""))
|
||||
date = Time.now.httpdate
|
||||
signed_data = generate_s3_signature(http_verb, md5, content_type, date, url)
|
||||
{
|
||||
"Authorization" => "AWS #{credentials[:access_key_id]}:#{signed_data}",
|
||||
"Date" => date
|
||||
}
|
||||
end
|
||||
def auth_headers_for(http_method, url, headers, data = nil)
|
||||
signature = s3_signer.sign_request(
|
||||
http_method: http_method, url: url, headers: headers, body: data
|
||||
)
|
||||
|
||||
def credentials
|
||||
@credentials ||= { access_key_id: settings.s3["access_key_id"], secret_key_id: settings.s3["secret_key_id"] }
|
||||
end
|
||||
|
||||
def digest(secret, string_to_sign)
|
||||
Base64.encode64(hmac(secret, string_to_sign)).strip
|
||||
end
|
||||
|
||||
def hmac(key, value)
|
||||
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), key, value)
|
||||
puts signature.headers.inspect
|
||||
signature.headers
|
||||
end
|
||||
|
||||
def uri_escape(s)
|
||||
CGI.escape(s).gsub('%5B', '[').gsub('%5D', ']')
|
||||
end
|
||||
|
||||
def generate_s3_signature(http_verb, md5, content_type, date, url)
|
||||
string_to_sign = [http_verb, md5, content_type, date, url].join "\n"
|
||||
signature = digest(credentials[:secret_key_id], string_to_sign)
|
||||
uri_escape(signature)
|
||||
end
|
||||
|
||||
def base_url
|
||||
@base_url ||= settings.s3["endpoint"]
|
||||
end
|
||||
@ -109,5 +100,4 @@ module RemoteStorage
|
||||
"#{base_url}/#{settings.s3["bucket"]}/#{user}"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user