bundler-checksum 0.1.0
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.
- checksums.yaml +7 -0
- data/bin/bundler-checksum +6 -0
- data/lib/bundler/checksum/command/helper.rb +28 -0
- data/lib/bundler/checksum/command/init.rb +66 -0
- data/lib/bundler/checksum/command/verify.rb +52 -0
- data/lib/bundler/checksum/command.rb +27 -0
- data/lib/bundler/checksum/version.rb +8 -0
- data/lib/bundler/checksum.rb +109 -0
- data/lib/bundler-checksum.rb +1 -0
- metadata +66 -0
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,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,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: []
|