bundler-security 0.0.1 → 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 +4 -4
- checksums.yaml.gz.sig +1 -0
- data.tar.gz.sig +0 -0
- data/.circleci/config.yml +25 -0
- data/.coditsu/ci.yml +3 -0
- data/.gitignore +47 -4
- data/.ruby-version +1 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +20 -0
- data/LICENSE +165 -0
- data/README.md +6 -26
- data/bundler-security.gemspec +27 -19
- data/certs/mensfeld.pem +25 -0
- data/lib/bundler/security.rb +64 -3
- data/lib/bundler/security/commands.rb +13 -0
- data/lib/bundler/security/config/fetcher.rb +29 -0
- data/lib/bundler/security/config/file_finder.rb +43 -0
- data/lib/bundler/security/errors.rb +15 -0
- data/lib/bundler/security/version.rb +6 -1
- data/lib/bundler/security/voting.rb +72 -0
- data/lib/bundler/security/voting/build_failure.rb +59 -0
- data/lib/bundler/security/voting/build_success.rb +55 -0
- data/lib/bundler/security/voting/build_unsafe_gem.rb +73 -0
- data/lib/bundler/security/voting/gem_policy.rb +67 -0
- data/lib/bundler/security/voting/remote_policy.rb +24 -0
- data/lib/bundler/security/voting/request.rb +88 -0
- data/lib/bundler/security/voting/versions/local.rb +102 -0
- data/lib/bundler/security/voting/versions/remote.rb +60 -0
- data/plugins.rb +5 -0
- metadata +64 -22
- metadata.gz.sig +0 -0
- data/LICENSE.txt +0 -21
- data/Rakefile +0 -2
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bundler
|
4
|
+
module Security
|
5
|
+
module Voting
|
6
|
+
# Remote policy settings from Coditsu
|
7
|
+
RemotePolicy = Struct.new(:type, :threshold) do
|
8
|
+
# How many time gem was marked as safe
|
9
|
+
#
|
10
|
+
# @return [Integer]
|
11
|
+
def approved
|
12
|
+
threshold['up'].to_i
|
13
|
+
end
|
14
|
+
|
15
|
+
# How many time gem was marked as malicious
|
16
|
+
#
|
17
|
+
# @return [Integer]
|
18
|
+
def rejected
|
19
|
+
threshold['down'].to_i
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Bundler
|
8
|
+
module Security
|
9
|
+
module Voting
|
10
|
+
# Module responsible for doing request to Coditsu differ
|
11
|
+
module Request
|
12
|
+
# Differ endpoint url
|
13
|
+
ENDPOINT_URL = 'https://diff.coditsu.io/api/bundler.json'
|
14
|
+
# Request headers
|
15
|
+
HEADERS = { 'Content-Type': 'application/json' }.freeze
|
16
|
+
|
17
|
+
private_constant :ENDPOINT_URL, :HEADERS
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Execute request to the differ
|
21
|
+
#
|
22
|
+
# @param config [OpenStruct] Coditsu config
|
23
|
+
# @param payload [Hash] with versions to check
|
24
|
+
#
|
25
|
+
# @return [Net::HTTPResponse] response from Coditsu differ
|
26
|
+
def call(config, payload)
|
27
|
+
build_http do |http, uri|
|
28
|
+
http.request(build_request(uri, config, payload))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Builds http connection object
|
33
|
+
def build_http
|
34
|
+
uri = URI(differ_url)
|
35
|
+
|
36
|
+
Net::HTTP.start(
|
37
|
+
uri.host,
|
38
|
+
uri.port,
|
39
|
+
use_ssl: uri.scheme == 'https',
|
40
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE
|
41
|
+
) { |http| yield(http, uri) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Build http post request and assigns headers and payload
|
45
|
+
#
|
46
|
+
# @param uri [URI::HTTPS]
|
47
|
+
# @param config [OpenStruct] Coditsu config
|
48
|
+
# @param payload [Hash] with versions to check
|
49
|
+
#
|
50
|
+
# @return [Net::HTTP::Post]
|
51
|
+
def build_request(uri, config, payload)
|
52
|
+
Net::HTTP::Post
|
53
|
+
.new(uri.request_uri, HEADERS)
|
54
|
+
.tap { |request| assign_auth(request, config) }
|
55
|
+
.tap { |request| assign_payload(request, payload) }
|
56
|
+
end
|
57
|
+
|
58
|
+
# Assigns basic authorization if provided in the config
|
59
|
+
#
|
60
|
+
# @param request [Net::HTTP::Post] prepared http post
|
61
|
+
# @param config [OpenStruct] Coditsu config
|
62
|
+
def assign_auth(request, config)
|
63
|
+
return unless config
|
64
|
+
return unless config.api_key
|
65
|
+
return unless config.api_secret
|
66
|
+
|
67
|
+
request.basic_auth(config.api_key, config.api_secret)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Assigns payload as json
|
71
|
+
#
|
72
|
+
# @param request [Net::HTTP::Post] prepared http post
|
73
|
+
# @param payload [Hash] with versions to check
|
74
|
+
def assign_payload(request, payload)
|
75
|
+
request.body = JSON.dump(payload)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Provides differ endpoint url
|
79
|
+
#
|
80
|
+
# @return [String]
|
81
|
+
def differ_url
|
82
|
+
ENV['CODITSU_DIFFER_URL'] || ENDPOINT_URL
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bundler
|
4
|
+
module Security
|
5
|
+
module Voting
|
6
|
+
# Module responsible for handling both local and remote gem versions
|
7
|
+
module Versions
|
8
|
+
# Module responsible for preparing current or current/new versions of gems
|
9
|
+
module Local
|
10
|
+
class << self
|
11
|
+
# Definition of a local path, if it matches it means that we are the source
|
12
|
+
ME_PATH = '.'
|
13
|
+
# Sources that we expect to match ourselves too
|
14
|
+
ME_SOURCES = [
|
15
|
+
Bundler::Source::Gemspec,
|
16
|
+
Bundler::Source::Path
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
# @param command [String] either install or update
|
20
|
+
# @param definition [Bundler::Definition] definition for your source
|
21
|
+
def call(command, definition)
|
22
|
+
Bundler.ui.silence { definition.resolve_remotely! }
|
23
|
+
|
24
|
+
case command
|
25
|
+
when Commands::INSTALL then build_install(definition)
|
26
|
+
when Commands::UPDATE then build_update(definition)
|
27
|
+
else
|
28
|
+
raise ArgumentError, "invalid command: #{command}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# @param definition [Bundler::Definition] definition for your source
|
35
|
+
def build_install(definition)
|
36
|
+
requested_specs = definition.requested_specs
|
37
|
+
locked_specs = definition.locked_gems.specs
|
38
|
+
introduced = requested_specs.map(&:name) - locked_specs.map(&:name)
|
39
|
+
introduced_specs = requested_specs.select { |spec| introduced.include?(spec.name) }
|
40
|
+
introduced_specs.concat(locked_specs)
|
41
|
+
|
42
|
+
introduced_specs.each_with_object({}) do |spec, hash|
|
43
|
+
next if skip?(spec.source)
|
44
|
+
|
45
|
+
hash[spec.name] = ['', spec.version.to_s]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# @param definition [Bundler::Definition] definition for your source
|
50
|
+
def build_update(definition)
|
51
|
+
locked_specs = definition.locked_gems.specs
|
52
|
+
|
53
|
+
definition.requested_specs.each_with_object({}) do |spec, hash|
|
54
|
+
next if skip?(spec.source)
|
55
|
+
|
56
|
+
locked_spec = locked_specs.find { |s| s.name == spec.name }
|
57
|
+
|
58
|
+
hash[spec.name] = if locked_spec
|
59
|
+
[locked_spec.version.to_s, spec.version.to_s]
|
60
|
+
else
|
61
|
+
['', spec.version.to_s]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Checks if we should skip a source
|
67
|
+
#
|
68
|
+
# @param source [Bundler::Source::Git, Bundler::Source::Rubygems]
|
69
|
+
#
|
70
|
+
# @return [Boolean] true if we should skip this source, false otherwise
|
71
|
+
def skip?(source)
|
72
|
+
return true if git?(source)
|
73
|
+
return true if me?(source)
|
74
|
+
|
75
|
+
false
|
76
|
+
end
|
77
|
+
|
78
|
+
# Checks if it's a git source
|
79
|
+
#
|
80
|
+
# @param source [Bundler::Source::Git, Bundler::Source::Rubygems]
|
81
|
+
#
|
82
|
+
# @return [Boolean] true if it's a git source, false otherwise
|
83
|
+
def git?(source)
|
84
|
+
source.instance_of?(Bundler::Source::Git)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Checks if it's a self source, this happens for repositories that are a gem
|
88
|
+
#
|
89
|
+
# @param source [Bundler::Source::Path,Bundler::Source::Git,Bundler::Source::Rubygems]
|
90
|
+
#
|
91
|
+
# @return [Boolean] true if it's a self source, false otherwise
|
92
|
+
def me?(source)
|
93
|
+
return false unless ME_SOURCES.include?(source.class)
|
94
|
+
|
95
|
+
source.path.to_s == ME_PATH
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Bundler
|
6
|
+
module Security
|
7
|
+
module Voting
|
8
|
+
# Module responsible for handling both local and remote gem versions
|
9
|
+
module Versions
|
10
|
+
# Module responsible for fetching safe/malicious votes
|
11
|
+
# for current or current/new versions of gems
|
12
|
+
module Remote
|
13
|
+
# Differ bundler url
|
14
|
+
ENDPOINT_URL = 'https://diff.coditsu.io/api/bundler.json'
|
15
|
+
|
16
|
+
private_constant :ENDPOINT_URL
|
17
|
+
|
18
|
+
class << self
|
19
|
+
# @param command [String] either install or update
|
20
|
+
# @param definition [Bundler::Definition] definition for your source
|
21
|
+
def call(command, definition)
|
22
|
+
config = fetch_config
|
23
|
+
|
24
|
+
Request
|
25
|
+
.call(config, payload(command, config&.repository_id, definition))
|
26
|
+
.then { |response| JSON.parse(response.body) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param command [String] either install or update
|
30
|
+
# @param repository_id [String] coditsu repository_id
|
31
|
+
# @param definition [Bundler::Definition] definition for your source
|
32
|
+
#
|
33
|
+
# @return [Hash] payload for differ bundler endpoint
|
34
|
+
def payload(command, repository_id, definition)
|
35
|
+
Local.call(command, definition).each_with_object({}) do |(name, versions), hash|
|
36
|
+
hash[:data] ||= {}
|
37
|
+
hash[:data][:repository_id] = repository_id if repository_id
|
38
|
+
hash[:data][:gems] ||= {}
|
39
|
+
hash[:data][:gems][name] = versions
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Fetch coditsu config file
|
44
|
+
#
|
45
|
+
# @return [OpenStruct, nil] configuration object
|
46
|
+
#
|
47
|
+
# @raise [Errors::MissingConfigurationFile] when no config file
|
48
|
+
def fetch_config
|
49
|
+
Config::Fetcher.call(
|
50
|
+
File.expand_path('..', Bundler.bin_path)
|
51
|
+
)
|
52
|
+
rescue Errors::MissingConfigurationFile
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/plugins.rb
ADDED
metadata
CHANGED
@@ -1,65 +1,107 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundler-security
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Tomasz Pajor
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
10
|
-
cert_chain:
|
11
|
-
|
9
|
+
bindir: bin
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIEODCCAqCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAjMSEwHwYDVQQDDBhtYWNp
|
14
|
+
ZWovREM9bWVuc2ZlbGQvREM9cGwwHhcNMTkwNzMwMTQ1NDU0WhcNMjAwNzI5MTQ1
|
15
|
+
NDU0WjAjMSEwHwYDVQQDDBhtYWNpZWovREM9bWVuc2ZlbGQvREM9cGwwggGiMA0G
|
16
|
+
CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQC9fCwtaHZG2SyyNXiH8r0QbJQx/xxl
|
17
|
+
dkvwWz9QGJO+O8rEx20FB1Ab+MVkfOscwIv5jWpmk1U9whzDPl1uFtIbgu+sk+Zb
|
18
|
+
uQlZyK/DPN6c+/BbBL+RryTBRyvkPLoCVwm7uxc/JZ1n4AI6eF4cCZ2ieZ9QgQbU
|
19
|
+
MQs2QPqs9hT50Ez/40GnOdadVfiDDGz+NME2C4ms0BriXwZ1tcRTfJIHe2xjIbbb
|
20
|
+
y5qRGfsLKcgMzvLQR24olixyX1MR0s4+Wveq3QL/gBhL4veUcv+UABJA8IJR0kyB
|
21
|
+
seHHutusiwZ1v3SjjjW1xLLrc2ARV0mgCb0WaK2T4iA3oFTGLh6Ydz8LNl31KQFv
|
22
|
+
94nRd8IhmJxrhQ6dQ/WT9IXoa5S9lfT5lPJeINemH4/6QPABzf9W2IZlCdI9wCdB
|
23
|
+
TBaw57MKneGAYZiKjw6OALSy2ltQUCl3RqFl3VP7n8uFy1U987Q5VIIQ3O1UUsQD
|
24
|
+
Oe/h+r7GUU4RSPKgPlrwvW9bD/UQ+zF51v8CAwEAAaN3MHUwCQYDVR0TBAIwADAL
|
25
|
+
BgNVHQ8EBAMCBLAwHQYDVR0OBBYEFJNIBHdfEUD7TqHqIer2YhWaWhwcMB0GA1Ud
|
26
|
+
EQQWMBSBEm1hY2llakBtZW5zZmVsZC5wbDAdBgNVHRIEFjAUgRJtYWNpZWpAbWVu
|
27
|
+
c2ZlbGQucGwwDQYJKoZIhvcNAQELBQADggGBAKA4eqko6BTNhlysip6rfBkVTGri
|
28
|
+
ZXsL+kRb2hLvsQJS/kLyM21oMlu+LN0aPj3qEFR8mE/YeDD8rLAfruBRTltPNbR7
|
29
|
+
xA5eE1gkxY5LfExUtK3b2wPqfmo7mZgfcsMwfYg/tUXw1WpBCnrhAJodpGH6SXmp
|
30
|
+
A40qFUZst0vjiOoO+aTblIHPmMJXoZ3K42dTlNKlEiDKUWMRKSgpjjYGEYalFNWI
|
31
|
+
hHfCz2r8L2t+dYdMZg1JGbEkq4ADGsAA8ioZIpJd7V4hI17u5TCdi7X5wh/0gN0E
|
32
|
+
CgP+nLox3D+l2q0QuQEkayr+auFYkzTCkF+BmEk1D0Ru4mcf3F4CJvEmW4Pzbjqt
|
33
|
+
i1tsCWPtJ4E/UUKnKaWKqGbjrjHJ0MuShYzHkodox5IOiCXIQg+1+YSzfXUV6WEK
|
34
|
+
KJG/fhg1JV5vVDdVy6x+tv5SQ5ctU0feCsVfESi3rE3zRd+nvzE9HcZ5aXeL1UtJ
|
35
|
+
nT5Xrioegu2w1jPyVEgyZgTZC5rvD0nNS5sFNQ==
|
36
|
+
-----END CERTIFICATE-----
|
37
|
+
date: 2019-11-11 00:00:00.000000000 Z
|
12
38
|
dependencies:
|
13
39
|
- !ruby/object:Gem::Dependency
|
14
40
|
name: bundler
|
15
41
|
requirement: !ruby/object:Gem::Requirement
|
16
42
|
requirements:
|
17
|
-
- - "
|
43
|
+
- - ">="
|
18
44
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
45
|
+
version: '0'
|
20
46
|
type: :development
|
21
47
|
prerelease: false
|
22
48
|
version_requirements: !ruby/object:Gem::Requirement
|
23
49
|
requirements:
|
24
|
-
- - "
|
50
|
+
- - ">="
|
25
51
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
52
|
+
version: '0'
|
27
53
|
- !ruby/object:Gem::Dependency
|
28
54
|
name: rake
|
29
55
|
requirement: !ruby/object:Gem::Requirement
|
30
56
|
requirements:
|
31
|
-
- - "
|
57
|
+
- - ">="
|
32
58
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
59
|
+
version: '0'
|
34
60
|
type: :development
|
35
61
|
prerelease: false
|
36
62
|
version_requirements: !ruby/object:Gem::Requirement
|
37
63
|
requirements:
|
38
|
-
- - "
|
64
|
+
- - ">="
|
39
65
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
41
|
-
description:
|
66
|
+
version: '0'
|
67
|
+
description: Bundler Security
|
42
68
|
email:
|
43
|
-
-
|
69
|
+
- tomek@coditsu.io
|
44
70
|
executables: []
|
45
71
|
extensions: []
|
46
72
|
extra_rdoc_files: []
|
47
73
|
files:
|
74
|
+
- ".circleci/config.yml"
|
75
|
+
- ".coditsu/ci.yml"
|
48
76
|
- ".gitignore"
|
77
|
+
- ".ruby-version"
|
49
78
|
- Gemfile
|
50
|
-
-
|
79
|
+
- Gemfile.lock
|
80
|
+
- LICENSE
|
51
81
|
- README.md
|
52
|
-
- Rakefile
|
53
|
-
- bin/console
|
54
|
-
- bin/setup
|
55
82
|
- bundler-security.gemspec
|
83
|
+
- certs/mensfeld.pem
|
56
84
|
- lib/bundler/security.rb
|
85
|
+
- lib/bundler/security/commands.rb
|
86
|
+
- lib/bundler/security/config/fetcher.rb
|
87
|
+
- lib/bundler/security/config/file_finder.rb
|
88
|
+
- lib/bundler/security/errors.rb
|
57
89
|
- lib/bundler/security/version.rb
|
90
|
+
- lib/bundler/security/voting.rb
|
91
|
+
- lib/bundler/security/voting/build_failure.rb
|
92
|
+
- lib/bundler/security/voting/build_success.rb
|
93
|
+
- lib/bundler/security/voting/build_unsafe_gem.rb
|
94
|
+
- lib/bundler/security/voting/gem_policy.rb
|
95
|
+
- lib/bundler/security/voting/remote_policy.rb
|
96
|
+
- lib/bundler/security/voting/request.rb
|
97
|
+
- lib/bundler/security/voting/versions/local.rb
|
98
|
+
- lib/bundler/security/voting/versions/remote.rb
|
99
|
+
- plugins.rb
|
58
100
|
homepage: https://diff.coditsu.io
|
59
101
|
licenses:
|
60
|
-
-
|
102
|
+
- MIT
|
61
103
|
metadata:
|
62
|
-
|
104
|
+
allowed_push_host: https://rubygems.org
|
63
105
|
post_install_message:
|
64
106
|
rdoc_options: []
|
65
107
|
require_paths:
|
@@ -78,5 +120,5 @@ requirements: []
|
|
78
120
|
rubygems_version: 3.0.3
|
79
121
|
signing_key:
|
80
122
|
specification_version: 4
|
81
|
-
summary:
|
123
|
+
summary: Bundler Security
|
82
124
|
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|
data/LICENSE.txt
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
The MIT License (MIT)
|
2
|
-
|
3
|
-
Copyright (c) 2019 Maciej Mensfeld
|
4
|
-
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
7
|
-
in the Software without restriction, including without limitation the rights
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
10
|
-
furnished to do so, subject to the following conditions:
|
11
|
-
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
13
|
-
all copies or substantial portions of the Software.
|
14
|
-
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
-
THE SOFTWARE.
|
data/Rakefile
DELETED