codeclimate-services 1.8.0 → 1.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 77816c3d07a72abbd79d831e8852592b4b30b654
4
- data.tar.gz: f8696a4c43abd9b325dc9d8baadf5033c4c8246f
3
+ metadata.gz: 4df2ee48a6422a56813233250049c38979fc3cb8
4
+ data.tar.gz: 7e58584424589e2c0b2ca337a853a4ea37e7b0fa
5
5
  SHA512:
6
- metadata.gz: d6ae01e70ac2bdc798333a52500b11778b47c256c444c7cceb76316c348c532fca4bd405864d4ba12310aac91f81d1a3ae4498a3a986ceb2ef4e0959f888f1e0
7
- data.tar.gz: 1237a7918f78505f03e166fdd34f0efe21596dc39837d41a25abe1d8b0867e5a1531205f757cf179b20efba0153f7144064604eccbff01252d1a6118b6885ff3
6
+ metadata.gz: ebb9f1f93b763a3858d6c66283ad1b27ff6507e57612301b71eeaf2077a1a034e808f98e787bbd9d9a9b2091f7f2e0bb397f38b3710c726bcd6907d5e37d51ad
7
+ data.tar.gz: db694bb54c853bf76dab733883350b38e1c3123706104e3b2a7730ebb41618b65badd9b6c7afc4ccade4ecc26db5997935cd8c5392a052987a06c156f75ab541
data/.codeclimate.yml CHANGED
@@ -10,10 +10,8 @@ engines:
10
10
  rubocop:
11
11
  enabled: true
12
12
  exclude_fingerprints:
13
- # Using #=== intentionally in SafeWebhook
13
+ # Using #=== intentionally to do subnet masking
14
14
  - d1afe90be49c43e85a76bfa58f637804
15
- # High complexity in http method due to SafeWebhook check
16
- - f05cea2d219c0f8119eb826067a18eda
17
15
  ratings:
18
16
  paths:
19
17
  - "**.rb"
data/Gemfile CHANGED
@@ -4,3 +4,8 @@ source "https://rubygems.org"
4
4
  gemspec
5
5
 
6
6
  gem "pry"
7
+
8
+ group :test do
9
+ gem "simplecov"
10
+ gem "codeclimate-test-reporter", "1.0.0.pre.rc2"
11
+ end
data/circle.yml ADDED
@@ -0,0 +1,4 @@
1
+ test:
2
+ override:
3
+ - bundle exec rake
4
+ - bundle exec codeclimate-test-reporter
@@ -23,7 +23,6 @@ Gem::Specification.new do |spec|
23
23
  spec.add_dependency "activemodel", ">= 3.0"
24
24
  spec.add_dependency "activesupport", ">= 3.0"
25
25
  spec.add_development_dependency "bundler", ">= 1.6.2"
26
- spec.add_development_dependency "codeclimate-test-reporter"
27
26
  spec.add_development_dependency "rake"
28
27
  spec.add_development_dependency "rspec"
29
28
  end
@@ -0,0 +1,29 @@
1
+ require "resolv-replace"
2
+
3
+ module CC
4
+ class FixedResolv < Resolv::DNS
5
+ def self.enable!
6
+ new.tap do |instance|
7
+ Resolv::DefaultResolver.replace_resolvers([instance])
8
+ end
9
+ end
10
+
11
+ def initialize
12
+ @addresses = {}
13
+ end
14
+
15
+ def setaddress(name, address)
16
+ addresses[name] = address
17
+ end
18
+
19
+ def each_address(name)
20
+ if addresses.key?(name)
21
+ yield addresses.fetch(name)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :addresses
28
+ end
29
+ end
@@ -53,16 +53,13 @@ module CC::Service::HTTP
53
53
  def http_method(method, url = nil, body = nil, headers = nil)
54
54
  block = Proc.new if block_given?
55
55
 
56
+ CC::Service::SafeWebhook.ensure_safe!(url)
57
+
56
58
  http.send(method) do |req|
57
59
  req.url(url) if url
58
60
  req.headers.update(headers) if headers
59
61
  req.body = body if body
60
62
  block.call req if block
61
-
62
- unless allow_internal_webhooks?
63
- safe_webhook = CC::Service::SafeWebhook.new(url)
64
- safe_webhook.validate!(req)
65
- end
66
63
  end
67
64
  end
68
65
 
@@ -103,9 +100,4 @@ module CC::Service::HTTP
103
100
  message: "Success",
104
101
  }
105
102
  end
106
-
107
- def allow_internal_webhooks?
108
- var = ENV["CODECLIMATE_ALLOW_INTERNAL_WEBHOOKS"] || ""
109
- var == "1" || var == "true"
110
- end
111
103
  end
@@ -1,13 +1,13 @@
1
1
  require "ipaddr"
2
- require "resolv"
2
+ require "uri"
3
+
4
+ require "cc/fixed_resolv"
3
5
 
4
6
  module CC
5
7
  class Service
6
8
  class SafeWebhook
7
- InvalidWebhookURL = Class.new(StandardError)
9
+ InternalWebhookError = Class.new(StandardError)
8
10
 
9
- # https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
10
- # https://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses
11
11
  PRIVATE_ADDRESS_SUBNETS = [
12
12
  IPAddr.new("10.0.0.0/8"),
13
13
  IPAddr.new("172.16.0.0/12"),
@@ -17,54 +17,52 @@ module CC
17
17
  IPAddr.new("0:0:0:0:0:0:0:1"),
18
18
  ].freeze
19
19
 
20
+ def self.ensure_safe!(url)
21
+ instance = new(url)
22
+ instance.ensure_safe!
23
+ end
24
+
20
25
  def self.getaddress(host)
21
- @resolv ||= Resolv::DNS.new
22
- @resolv.getaddress(host).to_s
26
+ @dns ||= Resolv::DNS.new
27
+ @dns.getaddress(host)
28
+ end
29
+
30
+ def self.setaddress(host, address)
31
+ @fixed_resolv ||= CC::FixedResolv.enable!
32
+ @fixed_resolv.setaddress(host, address)
23
33
  end
24
34
 
25
35
  def initialize(url)
26
36
  @url = url
27
37
  end
28
38
 
29
- # Resolve the Host to an IP address, validate that it doesn't point to
30
- # anything internal, then alter the request to be for the IP directly with
31
- # an explicit Host header given.
32
- #
33
- # See http://blog.fanout.io/2014/01/27/how-to-safely-invoke-webhooks/#ip-address-blacklisting
34
- def validate!(request)
39
+ def ensure_safe!
35
40
  uri = URI.parse(url)
36
- address = self.class.getaddress(uri.host)
37
41
 
38
- if internal?(address)
39
- raise_invalid("resolves to a private IP address")
42
+ if !allow_internal_webhooks? && internal?(uri.host)
43
+ raise InternalWebhookError, "#{url.inspect} maps to an internal address"
40
44
  end
41
-
42
- alter_request(request, uri, address)
43
- rescue URI::InvalidURIError, Resolv::ResolvError, Resolv::ResolvTimeout => ex
44
- raise_invalid(ex.message)
45
45
  end
46
46
 
47
47
  private
48
48
 
49
49
  attr_reader :url
50
50
 
51
- def internal?(address)
52
- ip_addr = IPAddr.new(address)
51
+ def internal?(host)
52
+ address = self.class.getaddress(host)
53
+
54
+ self.class.setaddress(host, address)
53
55
 
54
56
  PRIVATE_ADDRESS_SUBNETS.any? do |subnet|
55
- subnet === ip_addr
57
+ subnet === IPAddr.new(address.to_s)
56
58
  end
59
+ rescue Resolv::ResolvError
60
+ true # localhost
57
61
  end
58
62
 
59
- def alter_request(request, uri, address)
60
- address_uri = uri.dup
61
- address_uri.host = address
62
- request.url(address_uri.to_s)
63
- request.headers.update(Host: uri.host)
64
- end
65
-
66
- def raise_invalid(message)
67
- raise InvalidWebhookURL, "The Webhook URL #{url} is invalid: #{message}"
63
+ def allow_internal_webhooks?
64
+ var = ENV["CODECLIMATE_ALLOW_INTERNAL_WEBHOOKS"] || ""
65
+ var == "1" || var == "true"
68
66
  end
69
67
  end
70
68
  end
@@ -1,5 +1,5 @@
1
1
  module CC
2
2
  module Services
3
- VERSION = "1.8.0".freeze
3
+ VERSION = "1.9.0".freeze
4
4
  end
5
5
  end
@@ -67,10 +67,8 @@ describe CC::Service::GitHubIssues, type: :service do
67
67
  end
68
68
 
69
69
  it "different base url" do
70
- stub_resolv("example.com", "1.1.1.2")
71
-
72
70
  http_stubs.post request_url do |env|
73
- expect(env[:url].to_s).to eq("http://1.1.1.2/#{request_url}")
71
+ expect(env[:url].to_s).to eq("http://example.com/#{request_url}")
74
72
  [200, {}, '{"number": 2, "html_url": "http://foo.bar"}']
75
73
  end
76
74
 
@@ -107,10 +107,8 @@ describe CC::Service::GitHubPullRequests, type: :service do
107
107
  end
108
108
 
109
109
  it "different base url" do
110
- stub_resolv("example.com", "1.1.1.2")
111
-
112
110
  http_stubs.post("/repos/pbrisbin/foo/statuses/#{"0" * 40}") do |env|
113
- expect(env[:url].to_s).to eq("http://1.1.1.2/repos/pbrisbin/foo/statuses/#{"0" * 40}")
111
+ expect(env[:url].to_s).to eq("http://example.com/repos/pbrisbin/foo/statuses/#{"0" * 40}")
114
112
  [422, { "x-oauth-scopes" => "gist, user, repo" }, ""]
115
113
  end
116
114
 
@@ -134,44 +134,14 @@ describe CC::Service::GitlabMergeRequests, type: :service do
134
134
  end
135
135
 
136
136
  it "different base url" do
137
- stub_resolv("gitlab.hal.org", "1.1.1.2")
138
-
139
137
  http_stubs.post("api/v3/projects/hal%2Fhal9000/statuses/#{"0" * 40}") do |env|
140
- expect(env[:url].to_s).to eq("https://1.1.1.2/api/v3/projects/hal%2Fhal9000/statuses/#{"0" * 40}")
138
+ expect(env[:url].to_s).to eq("https://gitlab.hal.org/api/v3/projects/hal%2Fhal9000/statuses/#{"0" * 40}")
141
139
  [404, {}, ""]
142
140
  end
143
141
 
144
142
  expect(receive_test({ base_url: "https://gitlab.hal.org" }, git_url: "ssh://git@gitlab.com/hal/hal9000.git")[:ok]).to eq(true)
145
143
  end
146
144
 
147
- context "SafeWebhook" do
148
- it "rewrites the request to be for the resolved IP" do
149
- stub_resolv("my.gitlab.com", "1.1.1.2")
150
-
151
- http_stubs.post("api/v3/projects/hal%2Fhal9000/statuses/#{"0" * 40}") do |env|
152
- expect(env[:url].to_s).to eq("https://1.1.1.2/api/v3/projects/hal%2Fhal9000/statuses/#{"0" * 40}")
153
- expect(env[:request_headers][:Host]).to eq("my.gitlab.com")
154
- [404, {}, ""]
155
- end
156
-
157
- expect(receive_test({ base_url: "https://my.gitlab.com" }, git_url: "ssh://git@gitlab.com/hal/hal9000.git")[:ok]).to eq(true)
158
- end
159
-
160
- it "validates that the host doesn't resolve to something internal" do
161
- stub_resolv("my.gitlab.com", "127.0.0.1")
162
-
163
- expect do
164
- receive_test({ base_url: "https://my.gitlab.com" }, git_url: "")
165
- end.to raise_error(CC::Service::SafeWebhook::InvalidWebhookURL)
166
-
167
- stub_resolv("my.gitlab.com", "10.0.0.9")
168
-
169
- expect do
170
- receive_test({ base_url: "https://my.gitlab.com" }, git_url: "")
171
- end.to raise_error(CC::Service::SafeWebhook::InvalidWebhookURL)
172
- end
173
- end
174
-
175
145
  private
176
146
 
177
147
  def expect_status_update(repo, commit_sha, params)
@@ -2,59 +2,39 @@ require "spec_helper"
2
2
 
3
3
  class CC::Service
4
4
  describe SafeWebhook do
5
- describe ".getaddress" do
6
- it "resolves the dns name to a string" do
7
- address = SafeWebhook.getaddress("codeclimate.com")
8
-
9
- expect(address).to be_present
10
- expect(address).to respond_to(:start_with?)
11
- end
12
- end
13
-
14
- describe "#validate!" do
15
- context "valid webhook URLs" do
16
- it "rewrites the request to be safe" do
17
- stub_resolv("example.com", "2.2.2.2")
18
-
19
- request = double(headers: double)
20
- expect(request).to receive(:url).with("https://2.2.2.2:3000/foo")
21
- expect(request.headers).to receive(:update).with(Host: "example.com")
22
-
23
- safe_webhook = SafeWebhook.new("https://example.com:3000/foo")
24
- safe_webhook.validate!(request)
5
+ describe ".ensure_safe!" do
6
+ it "does not allow internal URLs" do
7
+ %w[ 127.0.0.1 192.168.0.1 10.0.1.18 ].each do |address|
8
+ stub_resolv("github.com", address)
9
+
10
+ expect do
11
+ SafeWebhook.ensure_safe!("https://github.com/api/v1/user")
12
+ end.to raise_error(SafeWebhook::InternalWebhookError)
25
13
  end
26
14
  end
27
15
 
28
- context "invalid Webhook URLs" do
29
- it "raises for invalid URL" do
30
- allow(URI).to receive(:parse).and_raise(URI::InvalidURIError)
31
-
32
- expect { validate("http://example.com") }.to raise_error(SafeWebhook::InvalidWebhookURL)
33
- end
16
+ it "allows internal URLs when configured to do so" do
17
+ allow(ENV).to receive(:[]).
18
+ with("CODECLIMATE_ALLOW_INTERNAL_WEBHOOKS").
19
+ and_return("1")
34
20
 
35
- it "raises for un-resolvable URL" do
36
- allow(SafeWebhook).to receive(:getaddress).and_raise(Resolv::ResolvError)
21
+ stub_resolv("github.com", "10.0.1.18")
37
22
 
38
- expect { validate("http://example.com") }.to raise_error(SafeWebhook::InvalidWebhookURL)
39
- end
23
+ SafeWebhook.ensure_safe!("https://github.com/api/v1/user")
24
+ end
40
25
 
41
- it "raises for localhost URLs" do
42
- stub_resolv("example.com", "127.0.0.1")
26
+ it "allows non-internal URLs" do
27
+ stub_resolv("github.com", "1.1.1.2")
43
28
 
44
- expect { validate("http://example.com") }.to raise_error(SafeWebhook::InvalidWebhookURL)
45
- end
29
+ SafeWebhook.ensure_safe!("https://github.com/api/v1/user")
30
+ end
46
31
 
47
- it "raises for internal URLs" do
48
- stub_resolv("example.com", "10.0.0.1")
32
+ it "ensures future dns queries get the same answer" do
33
+ stub_resolv("github.com", "1.1.1.3")
49
34
 
50
- expect { validate("http://example.com") }.to raise_error(SafeWebhook::InvalidWebhookURL)
51
- end
35
+ SafeWebhook.ensure_safe!("https://github.com/api/v1/user")
52
36
 
53
- def validate(url)
54
- request = double
55
- safe_webhook = SafeWebhook.new(url)
56
- safe_webhook.validate!(request)
57
- end
37
+ expect(Resolv.getaddress("github.com").to_s).to eq "1.1.1.3"
58
38
  end
59
39
  end
60
40
  end
@@ -32,42 +32,42 @@ describe CC::Service, type: :service do
32
32
  it "post success" do
33
33
  stub_http("/my/test/url", [200, {}, '{"ok": true, "thing": "123"}'])
34
34
 
35
- response = service_post("/my/test/url", { token: "1234" }.to_json, {}) do |inner_response|
35
+ response = service_post("http://example.com/my/test/url", { token: "1234" }.to_json, {}) do |inner_response|
36
36
  body = JSON.parse(inner_response.body)
37
37
  { thing: body["thing"] }
38
38
  end
39
39
 
40
40
  expect(response[:ok]).to eq(true)
41
41
  expect(response[:params]).to eq('{"token":"1234"}')
42
- expect(response[:endpoint_url]).to eq("/my/test/url")
42
+ expect(response[:endpoint_url]).to eq("http://example.com/my/test/url")
43
43
  expect(response[:status]).to eq(200)
44
44
  end
45
45
 
46
46
  it "post redirect success" do
47
- stub_http("/my/test/url", [307, { "Location" => "/my/redirect/url" }, '{"ok": false, "redirect": true}'])
47
+ stub_http("/my/test/url", [307, { "Location" => "http://example.com/my/redirect/url" }, '{"ok": false, "redirect": true}'])
48
48
  stub_http("/my/redirect/url", [200, {}, '{"ok": true, "thing": "123"}'])
49
49
 
50
- response = service_post_with_redirects("/my/test/url", { token: "1234" }.to_json, {}) do |inner_response|
50
+ response = service_post_with_redirects("http://example.com/my/test/url", { token: "1234" }.to_json, {}) do |inner_response|
51
51
  body = JSON.parse(inner_response.body)
52
52
  { thing: body["thing"] }
53
53
  end
54
54
 
55
55
  expect(response[:ok]).to eq(true)
56
56
  expect(response[:params]).to eq('{"token":"1234"}')
57
- expect(response[:endpoint_url]).to eq("/my/test/url")
57
+ expect(response[:endpoint_url]).to eq("http://example.com/my/test/url")
58
58
  expect(response[:status]).to eq(200)
59
59
  end
60
60
 
61
61
  it "post http failure" do
62
62
  stub_http("/my/wrong/url", [404, {}, ""])
63
63
 
64
- expect { service_post("/my/wrong/url", { token: "1234" }.to_json, {}) }.to raise_error(CC::Service::HTTPError)
64
+ expect { service_post("http://example.com/my/wrong/url", { token: "1234" }.to_json, {}) }.to raise_error(CC::Service::HTTPError)
65
65
  end
66
66
 
67
67
  it "post some other failure" do
68
68
  stub_http("/my/wrong/url") { raise ArgumentError, "lol" }
69
69
 
70
- expect { service_post("/my/wrong/url", { token: "1234" }.to_json, {}) }.to raise_error(ArgumentError)
70
+ expect { service_post("http://example.com/my/wrong/url", { token: "1234" }.to_json, {}) }.to raise_error(ArgumentError)
71
71
  end
72
72
 
73
73
  it "services" do
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,7 @@
1
- require "codeclimate-test-reporter"
2
- CodeClimate::TestReporter.start
1
+ require "simplecov"
2
+ SimpleCov.start do
3
+ add_filter "/spec/"
4
+ end
3
5
 
4
6
  cwd = File.expand_path(File.dirname(__FILE__))
5
7
  require "#{cwd}/../config/load"
@@ -34,8 +36,8 @@ RSpec.configure do |config|
34
36
  # This setting enables warnings. It's recommended, but in some cases may
35
37
  # be too noisy due to issues in dependencies.
36
38
  config.warnings = true
37
-
38
- config.before(:each) do
39
+ config.before do
40
+ # Disable actual DNS resolution during specs by default
39
41
  stub_resolv(anything, "1.1.1.1")
40
42
  end
41
43
  end
@@ -0,0 +1,10 @@
1
+ module ResolvHelpers
2
+ def stub_resolv(name, address)
3
+ allow(CC::Service::SafeWebhook).to receive(:getaddress).
4
+ with(name).and_return(Resolv::IPv4.create(address))
5
+ end
6
+ end
7
+
8
+ RSpec.configure do |conf|
9
+ conf.include(ResolvHelpers)
10
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codeclimate-services
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Helmkamp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-26 00:00:00.000000000 Z
11
+ date: 2016-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -94,20 +94,6 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: 1.6.2
97
- - !ruby/object:Gem::Dependency
98
- name: codeclimate-test-reporter
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
97
  - !ruby/object:Gem::Dependency
112
98
  name: rake
113
99
  requirement: !ruby/object:Gem::Requirement
@@ -158,10 +144,12 @@ files:
158
144
  - bin/nokogiri
159
145
  - bin/pry
160
146
  - bin/rake
147
+ - circle.yml
161
148
  - codeclimate-services.gemspec
162
149
  - config/cacert.pem
163
150
  - config/load.rb
164
151
  - lib/axiom/types/password.rb
152
+ - lib/cc/fixed_resolv.rb
165
153
  - lib/cc/formatters/linked_formatter.rb
166
154
  - lib/cc/formatters/plain_formatter.rb
167
155
  - lib/cc/formatters/snapshot_formatter.rb
@@ -225,7 +213,7 @@ files:
225
213
  - spec/fixtures.rb
226
214
  - spec/spec_helper.rb
227
215
  - spec/support/fake_logger.rb
228
- - spec/support/resolv_helper.rb
216
+ - spec/support/resolv_helpers.rb
229
217
  - spec/support/service_context.rb
230
218
  homepage: ''
231
219
  licenses:
@@ -276,5 +264,5 @@ test_files:
276
264
  - spec/fixtures.rb
277
265
  - spec/spec_helper.rb
278
266
  - spec/support/fake_logger.rb
279
- - spec/support/resolv_helper.rb
267
+ - spec/support/resolv_helpers.rb
280
268
  - spec/support/service_context.rb
@@ -1,10 +0,0 @@
1
- module ResolvHelper
2
- def stub_resolv(host, address)
3
- allow(CC::Service::SafeWebhook).to receive(:getaddress).
4
- with(host).and_return(address)
5
- end
6
- end
7
-
8
- RSpec.configure do |conf|
9
- conf.include ResolvHelper
10
- end