bundler-checksum 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|