rubygems-update 3.0.4 → 3.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/History.txt +85 -0
  4. data/Manifest.txt +5 -3
  5. data/Rakefile +8 -6
  6. data/bundler/lib/bundler/build_metadata.rb +2 -2
  7. data/lib/rubygems.rb +6 -12
  8. data/lib/rubygems/commands/push_command.rb +2 -0
  9. data/lib/rubygems/commands/setup_command.rb +9 -11
  10. data/lib/rubygems/commands/uninstall_command.rb +16 -6
  11. data/lib/rubygems/commands/which_command.rb +1 -3
  12. data/lib/rubygems/defaults.rb +1 -8
  13. data/lib/rubygems/dependency.rb +1 -1
  14. data/lib/rubygems/dependency_installer.rb +1 -2
  15. data/lib/rubygems/exceptions.rb +0 -4
  16. data/lib/rubygems/gemcutter_utilities.rb +9 -5
  17. data/lib/rubygems/installer.rb +8 -5
  18. data/lib/rubygems/installer_test_case.rb +2 -2
  19. data/lib/rubygems/package/tar_header.rb +11 -2
  20. data/lib/rubygems/remote_fetcher.rb +15 -54
  21. data/lib/rubygems/request.rb +1 -1
  22. data/lib/rubygems/request_set/gem_dependency_api.rb +3 -5
  23. data/lib/rubygems/resolver.rb +4 -1
  24. data/lib/rubygems/s3_uri_signer.rb +183 -0
  25. data/lib/rubygems/security_option.rb +0 -1
  26. data/lib/rubygems/specification.rb +13 -14
  27. data/lib/rubygems/ssl_certs/{index.rubygems.org → rubygems.org}/GlobalSignRootCA.pem +0 -0
  28. data/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem +21 -0
  29. data/lib/rubygems/stub_specification.rb +1 -2
  30. data/lib/rubygems/test_case.rb +8 -4
  31. data/lib/rubygems/util.rb +12 -0
  32. data/rubygems-update.gemspec +1 -1
  33. data/test/rubygems/test_bundled_ca.rb +7 -4
  34. data/test/rubygems/test_gem.rb +40 -3
  35. data/test/rubygems/test_gem_commands_push_command.rb +15 -0
  36. data/test/rubygems/test_gem_commands_setup_command.rb +11 -7
  37. data/test/rubygems/test_gem_commands_uninstall_command.rb +80 -1
  38. data/test/rubygems/test_gem_indexer.rb +8 -8
  39. data/test/rubygems/test_gem_installer.rb +78 -19
  40. data/test/rubygems/test_gem_package_tar_header.rb +41 -0
  41. data/test/rubygems/test_gem_remote_fetcher.rb +133 -14
  42. data/test/rubygems/test_gem_request.rb +4 -4
  43. data/test/rubygems/test_gem_request_set_gem_dependency_api.rb +20 -30
  44. data/test/rubygems/test_gem_specification.rb +29 -0
  45. data/test/rubygems/test_gem_util.rb +8 -0
  46. data/util/cops/deprecations.rb +52 -0
  47. data/util/create_certs.sh +27 -0
  48. data/util/update_bundled_ca_certificates.rb +1 -3
  49. metadata +12 -9
  50. data/lib/rubygems/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem +0 -23
  51. data/lib/rubygems/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem +0 -25
@@ -320,8 +320,11 @@ class Gem::Installer
320
320
  build_extensions
321
321
  write_build_info_file
322
322
  run_post_build_hooks
323
+ end
324
+
325
+ generate_bin
323
326
 
324
- generate_bin
327
+ unless @options[:install_as_default]
325
328
  write_spec
326
329
  write_cache_file
327
330
  end
@@ -799,7 +802,7 @@ TEXT
799
802
  # stub & ruby.exe withing same folder. Portable
800
803
  <<-TEXT
801
804
  @ECHO OFF
802
- @"%~dp0ruby.exe" "%~dpn0" %*
805
+ @"%~dp0#{ruby_exe}" "%~dpn0" %*
803
806
  TEXT
804
807
  elsif bindir.downcase.start_with? rb_topdir.downcase
805
808
  # stub within ruby folder, but not standard bin. Portable
@@ -809,14 +812,14 @@ TEXT
809
812
  rel = to.relative_path_from from
810
813
  <<-TEXT
811
814
  @ECHO OFF
812
- @"%~dp0#{rel}/ruby.exe" "%~dpn0" %*
815
+ @"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %*
813
816
  TEXT
814
817
  else
815
818
  # outside ruby folder, maybe -user-install or bundler. Portable, but ruby
816
819
  # is dependent on PATH
817
820
  <<-TEXT
818
821
  @ECHO OFF
819
- @ruby.exe "%~dpn0" %*
822
+ @#{ruby_exe} "%~dpn0" %*
820
823
  TEXT
821
824
  end
822
825
  end
@@ -857,7 +860,7 @@ TEXT
857
860
  # without the full gem installed.
858
861
 
859
862
  def extract_bin
860
- @package.extract_files gem_dir, "bin/*"
863
+ @package.extract_files gem_dir, "#{spec.bindir}/*"
861
864
  end
862
865
 
863
866
  ##
@@ -119,9 +119,9 @@ class Gem::InstallerTestCase < Gem::TestCase
119
119
  # The executable is also written to the bin dir in @tmpdir and the installed
120
120
  # gem directory for +spec+.
121
121
 
122
- def util_make_exec(spec = @spec, shebang = "#!/usr/bin/ruby")
122
+ def util_make_exec(spec = @spec, shebang = "#!/usr/bin/ruby", bindir = "bin")
123
123
  spec.executables = %w[executable]
124
- spec.files << 'bin/executable'
124
+ spec.bindir = bindir
125
125
 
126
126
  exec_path = spec.bin_file "executable"
127
127
  write_file exec_path do |io|
@@ -107,8 +107,8 @@ class Gem::Package::TarHeader
107
107
 
108
108
  new :name => fields.shift,
109
109
  :mode => strict_oct(fields.shift),
110
- :uid => strict_oct(fields.shift),
111
- :gid => strict_oct(fields.shift),
110
+ :uid => oct_or_256based(fields.shift),
111
+ :gid => oct_or_256based(fields.shift),
112
112
  :size => strict_oct(fields.shift),
113
113
  :mtime => strict_oct(fields.shift),
114
114
  :checksum => strict_oct(fields.shift),
@@ -130,6 +130,15 @@ class Gem::Package::TarHeader
130
130
  raise ArgumentError, "#{str.inspect} is not an octal string"
131
131
  end
132
132
 
133
+ def self.oct_or_256based(str)
134
+ # \x80 flags a positive 256-based number
135
+ # \ff flags a negative 256-based number
136
+ # In case we have a match, parse it as a signed binary value
137
+ # in big-endian order, except that the high-order bit is ignored.
138
+ return str.unpack('N2').last if str =~ /\A[\x80\xff]/n
139
+ strict_oct(str)
140
+ end
141
+
133
142
  ##
134
143
  # Creates a new TarHeader using +vals+
135
144
 
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  require 'rubygems'
3
3
  require 'rubygems/request'
4
+ require 'rubygems/request/connection_pools'
5
+ require 'rubygems/s3_uri_signer'
4
6
  require 'rubygems/uri_formatter'
5
7
  require 'rubygems/user_interaction'
6
- require 'rubygems/request/connection_pools'
7
8
  require 'resolv'
8
9
 
9
10
  ##
@@ -173,7 +174,7 @@ class Gem::RemoteFetcher
173
174
  path = source_uri.path
174
175
  path = File.dirname(path) if File.extname(path) == '.gem'
175
176
 
176
- remote_gem_path = correct_for_windows_path(File.join(path, 'gems', gem_file_name))
177
+ remote_gem_path = Gem::Util.correct_for_windows_path(File.join(path, 'gems', gem_file_name))
177
178
 
178
179
  FileUtils.cp(remote_gem_path, local_gem_path)
179
180
  rescue Errno::EACCES
@@ -210,7 +211,7 @@ class Gem::RemoteFetcher
210
211
  # File Fetcher. Dispatched by +fetch_path+. Use it instead.
211
212
 
212
213
  def fetch_file(uri, *_)
213
- Gem.read_binary correct_for_windows_path uri.path
214
+ Gem.read_binary Gem::Util.correct_for_windows_path uri.path
214
215
  end
215
216
 
216
217
  ##
@@ -275,7 +276,7 @@ class Gem::RemoteFetcher
275
276
  rescue Timeout::Error
276
277
  raise UnknownHostError.new('timed out', uri.to_s)
277
278
  rescue IOError, SocketError, SystemCallError,
278
- *(OpenSSL::SSL::SSLError if defined?(OpenSSL)) => e
279
+ *(OpenSSL::SSL::SSLError if defined?(OpenSSL)) => e
279
280
  if e.message =~ /getaddrinfo/
280
281
  raise UnknownHostError.new('no such name', uri.to_s)
281
282
  else
@@ -284,10 +285,19 @@ class Gem::RemoteFetcher
284
285
  end
285
286
 
286
287
  def fetch_s3(uri, mtime = nil, head = false)
287
- public_uri = sign_s3_url(uri)
288
+ begin
289
+ public_uri = s3_uri_signer(uri).sign
290
+ rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e
291
+ raise FetchError.new(e.message, "s3://#{uri.host}")
292
+ end
288
293
  fetch_https public_uri, mtime, head
289
294
  end
290
295
 
296
+ # we have our own signing code here to avoid a dependency on the aws-sdk gem
297
+ def s3_uri_signer(uri)
298
+ Gem::S3URISigner.new(uri)
299
+ end
300
+
291
301
  ##
292
302
  # Downloads +uri+ to +path+ if necessary. If no path is given, it just
293
303
  # passes the data.
@@ -317,14 +327,6 @@ class Gem::RemoteFetcher
317
327
  response['content-length'].to_i
318
328
  end
319
329
 
320
- def correct_for_windows_path(path)
321
- if path[0].chr == '/' && path[1].chr =~ /[a-z]/i && path[2].chr == ':'
322
- path[1..-1]
323
- else
324
- path
325
- end
326
- end
327
-
328
330
  ##
329
331
  # Performs a Net::HTTP request of type +request_class+ on +uri+ returning
330
332
  # a Net::HTTP response object. request maintains a table of persistent
@@ -349,31 +351,6 @@ class Gem::RemoteFetcher
349
351
  @pools.each_value {|pool| pool.close_all}
350
352
  end
351
353
 
352
- protected
353
-
354
- # we have our own signing code here to avoid a dependency on the aws-sdk gem
355
- # fortunately, a simple GET request isn't too complex to sign properly
356
- def sign_s3_url(uri, expiration = nil)
357
- require 'base64'
358
- require 'openssl'
359
-
360
- id, secret = s3_source_auth uri
361
-
362
- expiration ||= s3_expiration
363
- canonical_path = "/#{uri.host}#{uri.path}"
364
- payload = "GET\n\n\n#{expiration}\n#{canonical_path}"
365
- digest = OpenSSL::HMAC.digest('sha1', secret, payload)
366
- # URI.escape is deprecated, and there isn't yet a replacement that does quite what we want
367
- signature = Base64.encode64(digest).gsub("\n", '').gsub(/[\+\/=]/) { |c| BASE64_URI_TRANSLATE[c] }
368
- URI.parse("https://#{uri.host}.s3.amazonaws.com#{uri.path}?AWSAccessKeyId=#{id}&Expires=#{expiration}&Signature=#{signature}")
369
- end
370
-
371
- def s3_expiration
372
- (Time.now + 3600).to_i # one hour from now
373
- end
374
-
375
- BASE64_URI_TRANSLATE = { '+' => '%2B', '/' => '%2F', '=' => '%3D' }.freeze
376
-
377
354
  private
378
355
 
379
356
  def proxy_for(proxy, uri)
@@ -386,20 +363,4 @@ class Gem::RemoteFetcher
386
363
  end
387
364
  end
388
365
 
389
- def s3_source_auth(uri)
390
- return [uri.user, uri.password] if uri.user && uri.password
391
-
392
- s3_source = Gem.configuration[:s3_source] || Gem.configuration['s3_source']
393
- host = uri.host
394
- raise FetchError.new("no s3_source key exists in .gemrc", "s3://#{host}") unless s3_source
395
-
396
- auth = s3_source[host] || s3_source[host.to_sym]
397
- raise FetchError.new("no key for host #{host} in s3_source in .gemrc", "s3://#{host}") unless auth
398
-
399
- id = auth[:id] || auth['id']
400
- secret = auth[:secret] || auth['secret']
401
- raise FetchError.new("s3_source for #{host} missing id or secret", "s3://#{host}") unless id and secret
402
-
403
- [id, secret]
404
- end
405
366
  end
@@ -283,7 +283,7 @@ class Gem::Request
283
283
  end
284
284
  ua << ")"
285
285
 
286
- ua << " #{RUBY_ENGINE}" if defined?(RUBY_ENGINE) and RUBY_ENGINE != 'ruby'
286
+ ua << " #{RUBY_ENGINE}" if RUBY_ENGINE != 'ruby'
287
287
 
288
288
  ua
289
289
  end
@@ -782,7 +782,7 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
782
782
  # You may also provide +engine:+ and +engine_version:+ options to restrict
783
783
  # this gem dependencies file to a particular ruby engine and its engine
784
784
  # version. This matching is performed by using the RUBY_ENGINE and
785
- # engine_specific VERSION constants. (For JRuby, JRUBY_VERSION).
785
+ # RUBY_ENGINE_VERSION constants.
786
786
 
787
787
  def ruby(version, options = {})
788
788
  engine = options[:engine]
@@ -809,11 +809,9 @@ Gem dependencies file #{@path} includes git reference for both ref/branch and ta
809
809
  end
810
810
 
811
811
  if engine_version
812
- my_engine_version = Object.const_get "#{Gem.ruby_engine.upcase}_VERSION"
813
-
814
- if engine_version != my_engine_version
812
+ if engine_version != RUBY_ENGINE_VERSION
815
813
  message =
816
- "Your Ruby engine version is #{Gem.ruby_engine} #{my_engine_version}, " +
814
+ "Your Ruby engine version is #{Gem.ruby_engine} #{RUBY_ENGINE_VERSION}, " +
817
815
  "but your #{gem_deps_file} requires #{engine} #{engine_version}"
818
816
 
819
817
  raise Gem::RubyVersionMismatch, message
@@ -124,7 +124,10 @@ class Gem::Resolver
124
124
 
125
125
  data = yield
126
126
  $stderr.printf "%10s (%d entries)\n", stage.to_s.upcase, data.size
127
- PP.pp data, $stderr unless data.empty?
127
+ unless data.empty?
128
+ require 'pp'
129
+ PP.pp data, $stderr
130
+ end
128
131
  end
129
132
 
130
133
  ##
@@ -0,0 +1,183 @@
1
+ require 'base64'
2
+ require 'digest'
3
+ require 'openssl'
4
+
5
+ ##
6
+ # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems
7
+ # More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html
8
+ class Gem::S3URISigner
9
+
10
+ class ConfigurationError < Gem::Exception
11
+
12
+ def initialize(message)
13
+ super message
14
+ end
15
+
16
+ def to_s # :nodoc:
17
+ "#{super}"
18
+ end
19
+
20
+ end
21
+
22
+ class InstanceProfileError < Gem::Exception
23
+
24
+ def initialize(message)
25
+ super message
26
+ end
27
+
28
+ def to_s # :nodoc:
29
+ "#{super}"
30
+ end
31
+
32
+ end
33
+
34
+ attr_accessor :uri
35
+
36
+ def initialize(uri)
37
+ @uri = uri
38
+ end
39
+
40
+ ##
41
+ # Signs S3 URI using query-params according to the reference: https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
42
+ def sign(expiration = 86400)
43
+ s3_config = fetch_s3_config
44
+
45
+ current_time = Time.now.utc
46
+ date_time = current_time.strftime("%Y%m%dT%H%m%SZ")
47
+ date = date_time[0,8]
48
+
49
+ credential_info = "#{date}/#{s3_config.region}/s3/aws4_request"
50
+ canonical_host = "#{uri.host}.s3.#{s3_config.region}.amazonaws.com"
51
+
52
+ query_params = generate_canonical_query_params(s3_config, date_time, credential_info, expiration)
53
+ canonical_request = generate_canonical_request(canonical_host, query_params)
54
+ string_to_sign = generate_string_to_sign(date_time, credential_info, canonical_request)
55
+ signature = generate_signature(s3_config, date, string_to_sign)
56
+
57
+ URI.parse("https://#{canonical_host}#{uri.path}?#{query_params}&X-Amz-Signature=#{signature}")
58
+ end
59
+
60
+ private
61
+
62
+ S3Config = Struct.new :access_key_id, :secret_access_key, :security_token, :region
63
+
64
+ def generate_canonical_query_params(s3_config, date_time, credential_info, expiration)
65
+ canonical_params = {}
66
+ canonical_params["X-Amz-Algorithm"] = "AWS4-HMAC-SHA256"
67
+ canonical_params["X-Amz-Credential"] = "#{s3_config.access_key_id}/#{credential_info}"
68
+ canonical_params["X-Amz-Date"] = date_time
69
+ canonical_params["X-Amz-Expires"] = expiration.to_s
70
+ canonical_params["X-Amz-SignedHeaders"] = "host"
71
+ canonical_params["X-Amz-Security-Token"] = s3_config.security_token if s3_config.security_token
72
+
73
+ # Sorting is required to generate proper signature
74
+ canonical_params.sort.to_h.map do |key, value|
75
+ "#{base64_uri_escape(key)}=#{base64_uri_escape(value)}"
76
+ end.join("&")
77
+ end
78
+
79
+ def generate_canonical_request(canonical_host, query_params)
80
+ [
81
+ "GET",
82
+ uri.path,
83
+ query_params,
84
+ "host:#{canonical_host}",
85
+ "", # empty params
86
+ "host",
87
+ "UNSIGNED-PAYLOAD",
88
+ ].join("\n")
89
+ end
90
+
91
+ def generate_string_to_sign(date_time, credential_info, canonical_request)
92
+ [
93
+ "AWS4-HMAC-SHA256",
94
+ date_time,
95
+ credential_info,
96
+ Digest::SHA256.hexdigest(canonical_request)
97
+ ].join("\n")
98
+ end
99
+
100
+ def generate_signature(s3_config, date, string_to_sign)
101
+ date_key = OpenSSL::HMAC.digest("sha256", "AWS4" + s3_config.secret_access_key, date)
102
+ date_region_key = OpenSSL::HMAC.digest("sha256", date_key, s3_config.region)
103
+ date_region_service_key = OpenSSL::HMAC.digest("sha256", date_region_key, "s3")
104
+ signing_key = OpenSSL::HMAC.digest("sha256", date_region_service_key, "aws4_request")
105
+ OpenSSL::HMAC.hexdigest("sha256", signing_key, string_to_sign)
106
+ end
107
+
108
+ ##
109
+ # Extracts S3 configuration for S3 bucket
110
+ def fetch_s3_config
111
+ return S3Config.new(uri.user, uri.password, nil, "us-east-1") if uri.user && uri.password
112
+
113
+ s3_source = Gem.configuration[:s3_source] || Gem.configuration["s3_source"]
114
+ host = uri.host
115
+ raise ConfigurationError.new("no s3_source key exists in .gemrc") unless s3_source
116
+
117
+ auth = s3_source[host] || s3_source[host.to_sym]
118
+ raise ConfigurationError.new("no key for host #{host} in s3_source in .gemrc") unless auth
119
+
120
+ provider = auth[:provider] || auth["provider"]
121
+ case provider
122
+ when "env"
123
+ id = ENV["AWS_ACCESS_KEY_ID"]
124
+ secret = ENV["AWS_SECRET_ACCESS_KEY"]
125
+ security_token = ENV["AWS_SESSION_TOKEN"]
126
+ when "instance_profile"
127
+ credentials = ec2_metadata_credentials_json
128
+ id = credentials["AccessKeyId"]
129
+ secret = credentials["SecretAccessKey"]
130
+ security_token = credentials["Token"]
131
+ else
132
+ id = auth[:id] || auth["id"]
133
+ secret = auth[:secret] || auth["secret"]
134
+ security_token = auth[:security_token] || auth["security_token"]
135
+ end
136
+
137
+ raise ConfigurationError.new("s3_source for #{host} missing id or secret") unless id && secret
138
+
139
+ region = auth[:region] || auth["region"] || "us-east-1"
140
+ S3Config.new(id, secret, security_token, region)
141
+ end
142
+
143
+ def base64_uri_escape(str)
144
+ str.gsub(/[\+\/=\n]/, BASE64_URI_TRANSLATE)
145
+ end
146
+
147
+ def ec2_metadata_credentials_json
148
+ require 'net/http'
149
+ require 'rubygems/request'
150
+ require 'rubygems/request/connection_pools'
151
+ require 'json'
152
+
153
+ iam_info = ec2_metadata_request(EC2_IAM_INFO)
154
+ # Expected format: arn:aws:iam::<id>:instance-profile/<role_name>
155
+ role_name = iam_info['InstanceProfileArn'].split('/').last
156
+ ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name)
157
+ end
158
+
159
+ def ec2_metadata_request(url)
160
+ uri = URI(url)
161
+ @request_pool ||= create_request_pool(uri)
162
+ request = Gem::Request.new(uri, Net::HTTP::Get, nil, @request_pool)
163
+ response = request.fetch
164
+
165
+ case response
166
+ when Net::HTTPOK then
167
+ JSON.parse(response.body)
168
+ else
169
+ raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}")
170
+ end
171
+ end
172
+
173
+ def create_request_pool(uri)
174
+ proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme))
175
+ certs = Gem::Request.get_cert_files
176
+ Gem::Request::ConnectionPools.new(proxy_uri, certs).pool_for(uri)
177
+ end
178
+
179
+ BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze
180
+ EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info".freeze
181
+ EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/".freeze
182
+
183
+ end
@@ -19,7 +19,6 @@ end
19
19
 
20
20
  module Gem::SecurityOption
21
21
  def add_security_option
22
- # TODO: use @parser.accept
23
22
  OptionParser.accept Gem::Security::Policy do |value|
24
23
  require 'rubygems/security'
25
24