rubygems-update 3.0.4 → 3.0.9

Sign up to get free protection for your applications and to get access to all the features.
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