codeclimate-services 1.7.0 → 1.8.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
- data/.codeclimate.yml +22 -0
- data/Rakefile +1 -3
- data/lib/cc/service/http.rb +12 -3
- data/lib/cc/service/safe_webhook.rb +71 -0
- data/lib/cc/services/version.rb +1 -1
- data/spec/cc/service/github_issues_spec.rb +3 -1
- data/spec/cc/service/github_pull_requests_spec.rb +3 -1
- data/spec/cc/service/gitlab_merge_requests_spec.rb +31 -1
- data/spec/cc/service/safe_webhook_spec.rb +61 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/support/resolv_helper.rb +10 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77816c3d07a72abbd79d831e8852592b4b30b654
|
4
|
+
data.tar.gz: f8696a4c43abd9b325dc9d8baadf5033c4c8246f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6ae01e70ac2bdc798333a52500b11778b47c256c444c7cceb76316c348c532fca4bd405864d4ba12310aac91f81d1a3ae4498a3a986ceb2ef4e0959f888f1e0
|
7
|
+
data.tar.gz: 1237a7918f78505f03e166fdd34f0efe21596dc39837d41a25abe1d8b0867e5a1531205f757cf179b20efba0153f7144064604eccbff01252d1a6118b6885ff3
|
data/.codeclimate.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
---
|
2
|
+
engines:
|
3
|
+
duplication:
|
4
|
+
enabled: true
|
5
|
+
config:
|
6
|
+
languages:
|
7
|
+
- ruby
|
8
|
+
fixme:
|
9
|
+
enabled: true
|
10
|
+
rubocop:
|
11
|
+
enabled: true
|
12
|
+
exclude_fingerprints:
|
13
|
+
# Using #=== intentionally in SafeWebhook
|
14
|
+
- d1afe90be49c43e85a76bfa58f637804
|
15
|
+
# High complexity in http method due to SafeWebhook check
|
16
|
+
- f05cea2d219c0f8119eb826067a18eda
|
17
|
+
ratings:
|
18
|
+
paths:
|
19
|
+
- "**.rb"
|
20
|
+
exclude_paths:
|
21
|
+
- config/
|
22
|
+
- spec/
|
data/Rakefile
CHANGED
data/lib/cc/service/http.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "active_support/concern"
|
2
2
|
require "cc/service/response_check"
|
3
|
+
require "cc/service/safe_webhook"
|
3
4
|
|
4
5
|
module CC::Service::HTTP
|
5
6
|
extend ActiveSupport::Concern
|
@@ -38,10 +39,8 @@ module CC::Service::HTTP
|
|
38
39
|
end
|
39
40
|
|
40
41
|
def raw_get(url = nil, params = nil, headers = nil)
|
41
|
-
|
42
|
-
req.url(url) if url
|
42
|
+
http_method(:get, url, nil, headers) do |req|
|
43
43
|
req.params.update(params) if params
|
44
|
-
req.headers.update(headers) if headers
|
45
44
|
yield req if block_given?
|
46
45
|
end
|
47
46
|
end
|
@@ -59,6 +58,11 @@ module CC::Service::HTTP
|
|
59
58
|
req.headers.update(headers) if headers
|
60
59
|
req.body = body if body
|
61
60
|
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
|
62
66
|
end
|
63
67
|
end
|
64
68
|
|
@@ -99,4 +103,9 @@ module CC::Service::HTTP
|
|
99
103
|
message: "Success",
|
100
104
|
}
|
101
105
|
end
|
106
|
+
|
107
|
+
def allow_internal_webhooks?
|
108
|
+
var = ENV["CODECLIMATE_ALLOW_INTERNAL_WEBHOOKS"] || ""
|
109
|
+
var == "1" || var == "true"
|
110
|
+
end
|
102
111
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "ipaddr"
|
2
|
+
require "resolv"
|
3
|
+
|
4
|
+
module CC
|
5
|
+
class Service
|
6
|
+
class SafeWebhook
|
7
|
+
InvalidWebhookURL = Class.new(StandardError)
|
8
|
+
|
9
|
+
# https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
|
10
|
+
# https://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses
|
11
|
+
PRIVATE_ADDRESS_SUBNETS = [
|
12
|
+
IPAddr.new("10.0.0.0/8"),
|
13
|
+
IPAddr.new("172.16.0.0/12"),
|
14
|
+
IPAddr.new("192.168.0.0/16"),
|
15
|
+
IPAddr.new("fd00::/8"),
|
16
|
+
IPAddr.new("127.0.0.1"),
|
17
|
+
IPAddr.new("0:0:0:0:0:0:0:1"),
|
18
|
+
].freeze
|
19
|
+
|
20
|
+
def self.getaddress(host)
|
21
|
+
@resolv ||= Resolv::DNS.new
|
22
|
+
@resolv.getaddress(host).to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(url)
|
26
|
+
@url = url
|
27
|
+
end
|
28
|
+
|
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)
|
35
|
+
uri = URI.parse(url)
|
36
|
+
address = self.class.getaddress(uri.host)
|
37
|
+
|
38
|
+
if internal?(address)
|
39
|
+
raise_invalid("resolves to a private IP address")
|
40
|
+
end
|
41
|
+
|
42
|
+
alter_request(request, uri, address)
|
43
|
+
rescue URI::InvalidURIError, Resolv::ResolvError, Resolv::ResolvTimeout => ex
|
44
|
+
raise_invalid(ex.message)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :url
|
50
|
+
|
51
|
+
def internal?(address)
|
52
|
+
ip_addr = IPAddr.new(address)
|
53
|
+
|
54
|
+
PRIVATE_ADDRESS_SUBNETS.any? do |subnet|
|
55
|
+
subnet === ip_addr
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
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}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/cc/services/version.rb
CHANGED
@@ -67,8 +67,10 @@ 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
|
+
|
70
72
|
http_stubs.post request_url do |env|
|
71
|
-
expect(env[:url].to_s).to eq("http://
|
73
|
+
expect(env[:url].to_s).to eq("http://1.1.1.2/#{request_url}")
|
72
74
|
[200, {}, '{"number": 2, "html_url": "http://foo.bar"}']
|
73
75
|
end
|
74
76
|
|
@@ -107,8 +107,10 @@ 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
|
+
|
110
112
|
http_stubs.post("/repos/pbrisbin/foo/statuses/#{"0" * 40}") do |env|
|
111
|
-
expect(env[:url].to_s).to eq("http://
|
113
|
+
expect(env[:url].to_s).to eq("http://1.1.1.2/repos/pbrisbin/foo/statuses/#{"0" * 40}")
|
112
114
|
[422, { "x-oauth-scopes" => "gist, user, repo" }, ""]
|
113
115
|
end
|
114
116
|
|
@@ -134,14 +134,44 @@ 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
|
+
|
137
139
|
http_stubs.post("api/v3/projects/hal%2Fhal9000/statuses/#{"0" * 40}") do |env|
|
138
|
-
expect(env[:url].to_s).to eq("https://
|
140
|
+
expect(env[:url].to_s).to eq("https://1.1.1.2/api/v3/projects/hal%2Fhal9000/statuses/#{"0" * 40}")
|
139
141
|
[404, {}, ""]
|
140
142
|
end
|
141
143
|
|
142
144
|
expect(receive_test({ base_url: "https://gitlab.hal.org" }, git_url: "ssh://git@gitlab.com/hal/hal9000.git")[:ok]).to eq(true)
|
143
145
|
end
|
144
146
|
|
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
|
+
|
145
175
|
private
|
146
176
|
|
147
177
|
def expect_status_update(repo, commit_sha, params)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class CC::Service
|
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)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
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
|
34
|
+
|
35
|
+
it "raises for un-resolvable URL" do
|
36
|
+
allow(SafeWebhook).to receive(:getaddress).and_raise(Resolv::ResolvError)
|
37
|
+
|
38
|
+
expect { validate("http://example.com") }.to raise_error(SafeWebhook::InvalidWebhookURL)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "raises for localhost URLs" do
|
42
|
+
stub_resolv("example.com", "127.0.0.1")
|
43
|
+
|
44
|
+
expect { validate("http://example.com") }.to raise_error(SafeWebhook::InvalidWebhookURL)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "raises for internal URLs" do
|
48
|
+
stub_resolv("example.com", "10.0.0.1")
|
49
|
+
|
50
|
+
expect { validate("http://example.com") }.to raise_error(SafeWebhook::InvalidWebhookURL)
|
51
|
+
end
|
52
|
+
|
53
|
+
def validate(url)
|
54
|
+
request = double
|
55
|
+
safe_webhook = SafeWebhook.new(url)
|
56
|
+
safe_webhook.validate!(request)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/spec_helper.rb
CHANGED
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.
|
4
|
+
version: 1.8.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-
|
11
|
+
date: 2016-09-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -143,6 +143,7 @@ executables: []
|
|
143
143
|
extensions: []
|
144
144
|
extra_rdoc_files: []
|
145
145
|
files:
|
146
|
+
- ".codeclimate.yml"
|
146
147
|
- ".gitignore"
|
147
148
|
- ".rspec"
|
148
149
|
- ".rubocop.yml"
|
@@ -183,6 +184,7 @@ files:
|
|
183
184
|
- lib/cc/service/invocation/with_retries.rb
|
184
185
|
- lib/cc/service/invocation/with_return_values.rb
|
185
186
|
- lib/cc/service/response_check.rb
|
187
|
+
- lib/cc/service/safe_webhook.rb
|
186
188
|
- lib/cc/services.rb
|
187
189
|
- lib/cc/services/asana.rb
|
188
190
|
- lib/cc/services/campfire.rb
|
@@ -216,12 +218,14 @@ files:
|
|
216
218
|
- spec/cc/service/jira_spec.rb
|
217
219
|
- spec/cc/service/lighthouse_spec.rb
|
218
220
|
- spec/cc/service/pivotal_tracker_spec.rb
|
221
|
+
- spec/cc/service/safe_webhook_spec.rb
|
219
222
|
- spec/cc/service/slack_spec.rb
|
220
223
|
- spec/cc/service/stash_pull_requests_spec.rb
|
221
224
|
- spec/cc/service_spec.rb
|
222
225
|
- spec/fixtures.rb
|
223
226
|
- spec/spec_helper.rb
|
224
227
|
- spec/support/fake_logger.rb
|
228
|
+
- spec/support/resolv_helper.rb
|
225
229
|
- spec/support/service_context.rb
|
226
230
|
homepage: ''
|
227
231
|
licenses:
|
@@ -243,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
243
247
|
version: '0'
|
244
248
|
requirements: []
|
245
249
|
rubyforge_project:
|
246
|
-
rubygems_version: 2.5
|
250
|
+
rubygems_version: 2.4.5
|
247
251
|
signing_key:
|
248
252
|
specification_version: 4
|
249
253
|
summary: Service classes for Code Climate
|
@@ -265,10 +269,12 @@ test_files:
|
|
265
269
|
- spec/cc/service/jira_spec.rb
|
266
270
|
- spec/cc/service/lighthouse_spec.rb
|
267
271
|
- spec/cc/service/pivotal_tracker_spec.rb
|
272
|
+
- spec/cc/service/safe_webhook_spec.rb
|
268
273
|
- spec/cc/service/slack_spec.rb
|
269
274
|
- spec/cc/service/stash_pull_requests_spec.rb
|
270
275
|
- spec/cc/service_spec.rb
|
271
276
|
- spec/fixtures.rb
|
272
277
|
- spec/spec_helper.rb
|
273
278
|
- spec/support/fake_logger.rb
|
279
|
+
- spec/support/resolv_helper.rb
|
274
280
|
- spec/support/service_context.rb
|