bundler-checksum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f30c2035a02d0175319ae3da8ba7c6559bf70c081dc8d26381cfdeabab9d2d54
4
+ data.tar.gz: db6bab7b4479f87a36496f0d6f49b05562b3aa4b7a5f03784fd51d02d1b3a9e3
5
+ SHA512:
6
+ metadata.gz: c8c8697de49be3491b0a9a3486a1630a3e758d2338b328c782ca39588bc0da4a03d11df27816f5c6972fc71a754cae822796ff0079ed22993e82411d4fb227ae
7
+ data.tar.gz: 7d0f2bffafdd409f8fd8084a27bbdc472dab4b6fcde79599485a227b8a3329204af1049a6b8207846711eeff152a92a84090b4e535b0ed917721f7590b8eb672
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler-checksum'
4
+ require 'bundler/checksum/command'
5
+
6
+ Bundler::Checksum::Command.execute(ARGV)
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+
6
+ module Bundler::Checksum::Command
7
+ module Helper
8
+ extend self
9
+
10
+ def remote_checksums_for_gem(gem_name, gem_version)
11
+ response = Net::HTTP.get_response(URI(
12
+ "https://rubygems.org/api/v1/versions/#{gem_name}.json"
13
+ ))
14
+
15
+ return [] unless response.code == '200'
16
+
17
+ gem_candidates = JSON.parse(response.body, symbolize_names: true)
18
+ gem_candidates.select! { |g| g[:number] == gem_version.to_s }
19
+
20
+ gem_candidates.map {
21
+ |g| {:name => gem_name, :version => gem_version, :platform => g[:platform], :checksum => g[:sha]}
22
+ }
23
+
24
+ rescue JSON::ParserError
25
+ []
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module Bundler::Checksum::Command
6
+ module Init
7
+ extend self
8
+
9
+ def execute
10
+ $stderr.puts "Initializing checksum file #{checksum_file}"
11
+
12
+ checksums = []
13
+
14
+ compact_index_cache = Bundler::Fetcher::CompactIndex
15
+ .new(nil, Bundler::Source::Rubygems::Remote.new(Bundler::URI("https://rubygems.org")), nil)
16
+ .send(:compact_index_client)
17
+ .instance_variable_get(:@cache)
18
+
19
+ seen = []
20
+ Bundler.definition.resolve.sort_by(&:name).each do |spec|
21
+ next unless spec.source.is_a?(Bundler::Source::Rubygems)
22
+
23
+ next if seen.include?(spec.name)
24
+ seen << spec.name
25
+
26
+ $stderr.puts "Adding #{spec.name}==#{spec.version}"
27
+
28
+ compact_index_dependencies = compact_index_cache.dependencies(spec.name).select { |item| item.first == spec.version.to_s }
29
+
30
+ if !compact_index_dependencies.empty?
31
+ compact_index_checksums = compact_index_dependencies.map do |version, platform, dependencies, requirements|
32
+ {
33
+ name: spec.name,
34
+ version: spec.version.to_s,
35
+ platform: Gem::Platform.new(platform).to_s,
36
+ checksum: requirements.detect { |requirement| requirement.first == 'checksum' }.flatten[1]
37
+ }
38
+ end
39
+
40
+ checksums += compact_index_checksums.sort_by { |hash| hash.values }
41
+ else
42
+ remote_checksum = Helper.remote_checksums_for_gem(spec.name, spec.version)
43
+
44
+ if remote_checksum.empty?
45
+ raise "#{spec.name} #{spec.version} not found on Rubygems!"
46
+ end
47
+
48
+ checksums += remote_checksum.sort_by { |hash| hash.values }
49
+ end
50
+ end
51
+
52
+ File.write(checksum_file, JSON.generate(checksums, array_nl: "\n") + "\n")
53
+ end
54
+
55
+ private
56
+
57
+ def checksum_file
58
+ ::Bundler::Checksum.checksum_file
59
+ end
60
+
61
+ def lockfile
62
+ lockfile_path = Bundler.default_lockfile
63
+ lockfile = Bundler::LockfileParser.new(Bundler.read_file(lockfile_path))
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler::Checksum::Command
4
+ module Verify
5
+ extend self
6
+
7
+ def execute
8
+ $stderr.puts 'Verifying bundle checksums'
9
+
10
+ verified = true
11
+
12
+ local_checksums.each do |gem|
13
+ name = gem.fetch(:name)
14
+ version = gem.fetch(:version)
15
+ platform = gem.fetch(:platform)
16
+ checksum = gem.fetch(:checksum)
17
+
18
+ $stderr.puts "Verifying #{name}==#{version} #{platform}"
19
+ unless validate_gem_checksum(name, version, platform, checksum)
20
+ verified = false
21
+ end
22
+ end
23
+
24
+ verified
25
+ end
26
+
27
+ private
28
+
29
+ def local_checksums
30
+ ::Bundler::Checksum.checksums_from_file
31
+ end
32
+
33
+ def validate_gem_checksum(gem_name, gem_version, gem_platform, local_checksum)
34
+ remote_checksums = Helper.remote_checksums_for_gem(gem_name, gem_version)
35
+ if remote_checksums.empty?
36
+ $stderr.puts "#{gem_name} #{gem_version} not found on Rubygems, skipping"
37
+ return false
38
+ end
39
+
40
+ remote_platform_checksum = remote_checksums.find { |g| g[:name] == gem_name && g[:platform] == gem_platform.to_s }
41
+
42
+ if local_checksum == remote_platform_checksum[:checksum]
43
+ true
44
+ else
45
+ $stderr.puts "Gem #{gem_name} #{gem_version} #{gem_platform} failed checksum verification"
46
+ $stderr.puts "LOCAL: #{local_checksum}"
47
+ $stderr.puts "REMOTE: #{remote_platform_checksum[:checksum]}"
48
+ return false
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler::Checksum
4
+ module Command
5
+ autoload :Init, File.expand_path("command/init", __dir__)
6
+ autoload :Verify, File.expand_path("command/verify", __dir__)
7
+ autoload :Helper, File.expand_path("command/helper", __dir__)
8
+
9
+ def self.execute(args)
10
+ if args.empty?
11
+ $stderr.puts 'A command must be given [init,update,verify]'
12
+ end
13
+
14
+ if args.first == 'init'
15
+ Init.execute
16
+ elsif args.first == 'update'
17
+ $stderr.puts 'Not implemented, please use init'
18
+ elsif args.first == 'verify'
19
+ verified = Verify.execute
20
+
21
+ unless verified
22
+ exit 1
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ module Checksum
5
+ # bundler-checksum version
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
4
+ require 'bundler/checksum/version'
5
+ require 'json'
6
+
7
+ module Bundler
8
+ module Patches
9
+ # This module monkey-patches Bundler to check Gemfile.checksum
10
+ # when installing gems that are from RubyGems
11
+ module RubyGemsInstallerPatch
12
+ def pre_install_checks
13
+ super && validate_local_package_checksum
14
+ end
15
+
16
+ private
17
+
18
+ def validate_local_package_checksum
19
+ cached_checksum = fetch_checksum_from_file(spec)
20
+
21
+ if cached_checksum.nil?
22
+ raise SecurityError, "Cached checksum for #{spec.full_name} not found. Please (re-)generate Gemfile.checksum"
23
+ end
24
+
25
+ validate_file_checksum(cached_checksum)
26
+ end
27
+
28
+ def fetch_checksum_from_file(spec)
29
+ ::Bundler::Checksum.checksum_for(spec.name, spec.version.to_s, spec.platform.to_s)
30
+ end
31
+
32
+ # Modified from
33
+ # https://github.com/rubygems/rubygems/blob/243173279e79a38f03e318eea8825d1c8824e119/bundler/lib/bundler/rubygems_gem_installer.rb#L116
34
+ def validate_file_checksum(checksum)
35
+ return true if Bundler.settings[:disable_checksum_validation]
36
+
37
+ source = @package.instance_variable_get(:@gem)
38
+
39
+ # Contary to upstream, we raise instead of silently returning
40
+ raise "#{@package.inspect} does not have :@gem" unless source
41
+ raise "#{source.inspect} does not respond to :with_read_io" unless source.respond_to?(:with_read_io)
42
+
43
+ digest = source.with_read_io do |io|
44
+ digest = SharedHelpers.digest(:SHA256).new
45
+ digest << io.read(16_384) until io.eof?
46
+ io.rewind
47
+ send(checksum_type(checksum), digest)
48
+ end
49
+ unless digest == checksum
50
+ raise SecurityError, <<-MESSAGE
51
+ Bundler cannot continue installing #{spec.name} (#{spec.version}).
52
+ The checksum for the downloaded `#{spec.full_name}.gem` does not match \
53
+ the checksum from the checksum file. This means the contents of the downloaded \
54
+ gem is different from what was recorded in the checksum file, and could be potential security issue.
55
+ gem is different from what was uploaded to the server, and could be a potential security issue.
56
+
57
+ To resolve this issue:
58
+ 1. delete the downloaded gem located at: `#{spec.gem_dir}/#{spec.full_name}.gem`
59
+ 2. run `bundle install`
60
+
61
+ If you wish to continue installing the downloaded gem, and are certain it does not pose a \
62
+ security issue despite the mismatching checksum, do the following:
63
+ 1. run `bundle config set --local disable_checksum_validation true` to turn off checksum verification
64
+ 2. run `bundle install`
65
+
66
+ (More info: The expected SHA256 checksum was #{checksum.inspect}, but the \
67
+ checksum for the downloaded gem was #{digest.inspect}.)
68
+ MESSAGE
69
+ end
70
+ true
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ module Bundler
77
+ module Checksum
78
+ class << self
79
+ def checksum_file
80
+ @checksum_file ||= File.join(File.dirname(Bundler.default_gemfile), 'Gemfile.checksum')
81
+ end
82
+
83
+ def checksums_from_file
84
+ @checksums_from_file ||= JSON.parse(File.open(checksum_file).read, symbolize_names: true)
85
+ rescue JSON::ParserError => e
86
+ raise "Invalid checksum file: #{e.message}"
87
+ end
88
+
89
+ def checksum_for(gem_name, gem_version, gem_platform)
90
+ item = checksums_from_file.detect do |item|
91
+ item[:name] == gem_name &&
92
+ item[:platform] == gem_platform &&
93
+ item[:version] == gem_version
94
+ end
95
+
96
+ item&.fetch(:checksum)
97
+ end
98
+
99
+ def patch!
100
+ return if defined?(@patched) && @patched
101
+ @patched = true
102
+
103
+ Bundler.ui.info "Patching bundler with bundler-checksum..."
104
+ require 'bundler/rubygems_gem_installer'
105
+ ::Bundler::RubyGemsGemInstaller.prepend(Bundler::Patches::RubyGemsInstallerPatch)
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1 @@
1
+ require 'bundler/checksum'
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bundler-checksum
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - dustinmm80
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-09-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Track checksums locally with Bundler
28
+ email:
29
+ - dcollins@gitlab.com
30
+ executables:
31
+ - bundler-checksum
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - bin/bundler-checksum
36
+ - lib/bundler-checksum.rb
37
+ - lib/bundler/checksum.rb
38
+ - lib/bundler/checksum/command.rb
39
+ - lib/bundler/checksum/command/helper.rb
40
+ - lib/bundler/checksum/command/init.rb
41
+ - lib/bundler/checksum/command/verify.rb
42
+ - lib/bundler/checksum/version.rb
43
+ homepage: https://gitlab.com/gitlab-org/gitlab/-/tree/master/vendor/gems/bundler-checksum
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubygems_version: 3.3.16
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: Track checksums locally with Bundler
66
+ test_files: []