diff options
author | Alexander Pakulov <apakulov@stripe.com> | 2019-07-03 16:35:59 -0700 |
---|---|---|
committer | Hiroshi SHIBATA <hsbt@ruby-lang.org> | 2019-07-31 07:47:01 +0800 |
commit | 4a8c2dd4192517fa030b6585ae82bf925c80516a (patch) | |
tree | 27d52fd4bc55eca0714198406e730015c69f0100 /lib/rubygems/remote_fetcher.rb | |
parent | 1dfe132aa5138a5664b353739fc2ad32d6cd68dd (diff) | |
download | ruby-4a8c2dd4192517fa030b6585ae82bf925c80516a.tar.gz |
[rubygems/rubygems] Extracting sign_s3_url & s3_source_auth into a separate S3URISigner class
https://github.com/rubygems/rubygems/commit/c30d21ec7a
Diffstat (limited to 'lib/rubygems/remote_fetcher.rb')
-rw-r--r-- | lib/rubygems/remote_fetcher.rb | 116 |
1 files changed, 12 insertions, 104 deletions
diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 529fdab620..1e0c23dc33 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require 'rubygems' require 'rubygems/request' +require 'rubygems/request/connection_pools' +require 'rubygems/s3_uri_signer' require 'rubygems/uri_formatter' require 'rubygems/user_interaction' -require 'rubygems/request/connection_pools' require 'resolv' ## @@ -284,10 +285,19 @@ class Gem::RemoteFetcher end def fetch_s3(uri, mtime = nil, head = false) - public_uri = sign_s3_url(uri) + begin + public_uri = s3_uri_signer(uri).sign + rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e + raise FetchError.new(e.message, "s3://#{uri.host}") + end fetch_https public_uri, mtime, head end + # we have our own signing code here to avoid a dependency on the aws-sdk gem + def s3_uri_signer(uri) + Gem::S3URISigner.new(uri) + end + ## # Downloads +uri+ to +path+ if necessary. If no path is given, it just # passes the data. @@ -341,73 +351,8 @@ class Gem::RemoteFetcher @pools.each_value {|pool| pool.close_all} end - protected - - S3Config = Struct.new :access_key_id, :secret_access_key, :security_token, :region - - # we have our own signing code here to avoid a dependency on the aws-sdk gem - def sign_s3_url(uri, expiration = nil) - require 'base64' - require 'digest' - require 'openssl' - - s3_config = s3_source_auth uri - expiration ||= 86400 - - current_time = Time.now.utc - date_time = current_time.strftime("%Y%m%dT%H%m%SZ") - date = date_time[0,8] - - credential_info = "#{date}/#{s3_config.region}/s3/aws4_request" - canonical_host = "#{uri.host}.s3.#{s3_config.region}.amazonaws.com" - - canonical_params = {} - canonical_params['X-Amz-Algorithm'] = "AWS4-HMAC-SHA256" - canonical_params['X-Amz-Credential'] = "#{s3_config.access_key_id}/#{credential_info}" - canonical_params['X-Amz-Date'] = date_time - canonical_params['X-Amz-Expires'] = expiration.to_s - canonical_params['X-Amz-SignedHeaders'] = "host" - canonical_params['X-Amz-Security-Token'] = s3_config.security_token if s3_config.security_token - - # Sorting is required to generate proper signature - query_params = canonical_params.sort.to_h.map do |key, value| - "#{base64_uri_escape(key)}=#{base64_uri_escape(value)}" - end.join('&') - - canonical_request = [ - 'GET', - uri.path, - query_params, - "host:#{canonical_host}", - '', # empty params - 'host', - 'UNSIGNED-PAYLOAD', - ].join("\n") - - string_to_sign = [ - "AWS4-HMAC-SHA256", - date_time, - credential_info, - Digest::SHA256.hexdigest(canonical_request) - ].join("\n") - - date_key = OpenSSL::HMAC.digest('sha256', "AWS4" + s3_config.secret_access_key, date) - date_region_key = OpenSSL::HMAC.digest('sha256', date_key, s3_config.region) - date_region_service_key = OpenSSL::HMAC.digest('sha256', date_region_key, "s3") - signing_key = OpenSSL::HMAC.digest('sha256', date_region_service_key, "aws4_request") - signature = OpenSSL::HMAC.hexdigest('sha256', signing_key, string_to_sign) - - URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}") - end - - BASE64_URI_TRANSLATE = { '+' => '%2B', '/' => '%2F', '=' => '%3D' }.freeze - private - def base64_uri_escape(str) - str.gsub("\n", '').gsub(/[\+\/=]/) { |c| BASE64_URI_TRANSLATE[c] } - end - def proxy_for(proxy, uri) Gem::Request.proxy_uri(proxy || Gem::Request.get_proxy_from_env(uri.scheme)) end @@ -418,41 +363,4 @@ class Gem::RemoteFetcher end end - def s3_source_auth(uri) - return S3Config.new(uri.user, uri.password, nil, 'us-east-1') if uri.user && uri.password - - s3_source = Gem.configuration[:s3_source] || Gem.configuration['s3_source'] - host = uri.host - raise FetchError.new("no s3_source key exists in .gemrc", "s3://#{host}") unless s3_source - - auth = s3_source[host] || s3_source[host.to_sym] - raise FetchError.new("no key for host #{host} in s3_source in .gemrc", "s3://#{host}") unless auth - - provider = auth[:provider] || auth['provider'] - case provider - when 'env' - id = ENV['AWS_ACCESS_KEY_ID'] - secret = ENV['AWS_SECRET_ACCESS_KEY'] - security_token = ENV['AWS_SESSION_TOKEN'] - when 'instance_profile' - require 'json' - credentials_response = fetch_http URI(EC2_METADATA_CREDENTIALS) - credentials = JSON.parse(credentials_response) - id = credentials['AccessKeyId'] - secret = credentials['SecretAccessKey'] - security_token = credentials['Token'] - else - id = auth[:id] || auth['id'] - secret = auth[:secret] || auth['secret'] - raise FetchError.new("s3_source for #{host} missing id or secret", "s3://#{host}") unless id && secret - - security_token = auth[:security_token] || auth['security_token'] - end - - region = auth[:region] || auth['region'] || 'us-east-1' - S3Config.new(id, secret, security_token, region) - end - - EC2_METADATA_CREDENTIALS = "http://169.254.169.254/latest/meta-data/identity-credentials/ec2/security-credentials/ec2-instance" - end |