rack-padlock 0.0.1
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.
- data/README.md +36 -0
- data/lib/rack/padlock/version.rb +5 -0
- data/lib/rack/padlock.rb +48 -0
- data/lib/rack-padlock.rb +4 -0
- data/lib/tasks/rack-padlock.rake +23 -0
- data/test/rack_padlock_test.rb +70 -0
- data/test/test_helper.rb +24 -0
- metadata +120 -0
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
rack-padlock
|
2
|
+
=======
|
3
|
+
|
4
|
+
A toolkit for writing tests against rack applications that ensure all traffic on a page is secure.
|
5
|
+
|
6
|
+
###Prerequisites
|
7
|
+
|
8
|
+
1. You need to have a rack based application
|
9
|
+
2. The application must have a browser based integration test suite. I recommend capybara.
|
10
|
+
3. Your integration tests must use HTTPS
|
11
|
+
|
12
|
+
## Setup
|
13
|
+
|
14
|
+
Add rack-padlock gem to your test group
|
15
|
+
|
16
|
+
```bash
|
17
|
+
group :test do
|
18
|
+
gem 'rack-padlock'
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
Add rack-padlock middleware to your app
|
23
|
+
|
24
|
+
```bash
|
25
|
+
group :test do
|
26
|
+
gem 'rack-padlock'
|
27
|
+
end
|
28
|
+
```
|
29
|
+
|
30
|
+
Please see the example application at https://github.com/joshuacronemeyer/rack-padlock-example-app
|
31
|
+
|
32
|
+
how it works
|
33
|
+
=======
|
34
|
+
|
35
|
+
|
36
|
+
Please see https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html
|
data/lib/rack/padlock.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module Rack
|
2
|
+
class Padlock
|
3
|
+
POST_BODY = 'rack.input'.freeze
|
4
|
+
|
5
|
+
def initialize(app, options={})
|
6
|
+
default_options = {
|
7
|
+
:log_file => "tmp/padlock.log"
|
8
|
+
}
|
9
|
+
@app = app
|
10
|
+
@options = default_options.merge(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
return capture_violation(env) if csp_policy_violation?(env)
|
15
|
+
status, headers, body = @app.call(env)
|
16
|
+
headers.merge!(csp_headers(env))
|
17
|
+
[status, headers, body]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def capture_violation(env)
|
23
|
+
violation = env[POST_BODY].read
|
24
|
+
PadlockFile.write(@options[:log_file], violation)
|
25
|
+
[200, {}, []]
|
26
|
+
end
|
27
|
+
|
28
|
+
def csp_policy_violation?(env)
|
29
|
+
env['PATH_INFO'] =~ /padlock_middleware\/report$/
|
30
|
+
end
|
31
|
+
|
32
|
+
def csp_headers(env)
|
33
|
+
host = env["HTTP_HOST"]
|
34
|
+
report_uri = "#{host}/padlock_middleware/report"
|
35
|
+
csp_header_names = %w(Content-Security-Policy-Report-Only X-Content-Security-Policy-Report-Only X-WebKit-CSP-Report-Only)
|
36
|
+
csp_headers = {}
|
37
|
+
csp_header_names.each{|name| csp_headers[name] = "default-src https: 'unsafe-inline' 'unsafe-eval'; report-uri http://#{report_uri}"}
|
38
|
+
csp_headers
|
39
|
+
end
|
40
|
+
|
41
|
+
class PadlockFile
|
42
|
+
def self.write(file, violation)
|
43
|
+
::File.open(file, 'a+') { |file| file.puts violation.strip }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/rack-padlock.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rainbow'
|
2
|
+
PADLOCK_LOG = 'tmp/padlock.log'
|
3
|
+
|
4
|
+
desc "Test for padlock"
|
5
|
+
task :padlock_test => [:padlock_clean, :test] do
|
6
|
+
contents = File.open(PADLOCK_LOG, 'r') { |f| f.readlines }
|
7
|
+
if File.size? PADLOCK_LOG
|
8
|
+
puts "Padlock test failure: Insecure content is being loaded on the page.".foreground(:red)
|
9
|
+
contents.each do |line|
|
10
|
+
blocked_uri = line.match(/blocked-uri":"([^"]+)/)[1].foreground(:yellow)
|
11
|
+
violated_directive = line.match(/violated-directive":"([^"]+)/)[1].foreground(:magenta)
|
12
|
+
puts "Request for #{blocked_uri} has violated directive #{violated_directive}"
|
13
|
+
end
|
14
|
+
exit 1
|
15
|
+
else
|
16
|
+
puts "Padlock test success: page is secure.".foreground(:green)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "Clean padlock logs"
|
21
|
+
task :padlock_clean do
|
22
|
+
File.truncate('tmp/padlock.log', 0) if File.exist?(PADLOCK_LOG)
|
23
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class RackPadlockTest < MiniTest::Unit::TestCase
|
4
|
+
|
5
|
+
describe "Padlock middleware headers" do
|
6
|
+
before do
|
7
|
+
mock_app
|
8
|
+
end
|
9
|
+
|
10
|
+
it "sets CSP report only" do
|
11
|
+
get 'http://oregonhero.com/wagons/1'
|
12
|
+
last_response.headers['Content-Security-Policy-Report-Only'].wont_be_nil
|
13
|
+
last_response.headers['X-Content-Security-Policy-Report-Only'].wont_be_nil
|
14
|
+
last_response.headers['X-WebKit-CSP-Report-Only'].wont_be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
it "sets the report-uri to a value that will be intercepted by padlock middleware" do
|
18
|
+
get 'http://oregonhero.com/wagons/1', {}, 'HTTP_HOST' => "http://oregonhero.com"
|
19
|
+
last_response.headers['Content-Security-Policy-Report-Only'].must_match "http://oregonhero.com/padlock_middleware/report"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "Padlock captures policy violation reports" do
|
24
|
+
before do
|
25
|
+
mock_app
|
26
|
+
end
|
27
|
+
|
28
|
+
it "prevents the policy violations from going to the app" do
|
29
|
+
post 'http://oregonhero.com/padlock_middleware/report' do
|
30
|
+
"Knock, Knock"
|
31
|
+
end
|
32
|
+
last_response.status.must_equal 200
|
33
|
+
last_response.body.must_equal ""
|
34
|
+
end
|
35
|
+
|
36
|
+
it "doesn't interfere with other traffic" do
|
37
|
+
get 'http://oregonhero.com/wagons/1'
|
38
|
+
last_response.body.must_equal "Hello world!"
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO figure out how to set the post data so we can test we log it.
|
42
|
+
# it "logs policy violations to padlock logfile" do
|
43
|
+
# post 'http://oregonhero.com/padlock_middleware/report', {}, {"RAW_POST_DATA" => "Knock"}
|
44
|
+
# last_response.status.must_equal 200
|
45
|
+
# last_response.body.must_equal ""
|
46
|
+
# Rack::Padlock::PadlockFile.last_output.must_match /Knock/
|
47
|
+
# end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Rack::Padlock::PadlockFile
|
51
|
+
@@last_output = ""
|
52
|
+
def self.write(data)
|
53
|
+
@@last_output = data
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.last_output
|
57
|
+
@@last_output
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.last_output=(data)
|
61
|
+
@@last_output = data
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class DummyApp
|
66
|
+
def call(env)
|
67
|
+
return [{}, {}, {}]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'rack/mock'
|
3
|
+
require 'rack/test'
|
4
|
+
require 'rack/padlock'
|
5
|
+
|
6
|
+
class MiniTest::Unit::TestCase
|
7
|
+
include Rack::Test::Methods
|
8
|
+
|
9
|
+
def app; Rack::Lint.new(@app); end
|
10
|
+
|
11
|
+
def mock_app(options = {})
|
12
|
+
main_app = lambda { |env|
|
13
|
+
request = Rack::Request.new(env)
|
14
|
+
headers = {'Content-Type' => "text/html"}
|
15
|
+
[200, headers, ['Hello world!']]
|
16
|
+
}
|
17
|
+
|
18
|
+
builder = Rack::Builder.new
|
19
|
+
builder.use Rack::Padlock, options
|
20
|
+
builder.run main_app
|
21
|
+
@app = builder.to_app
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-padlock
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Josh Cronemeyer
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rack
|
16
|
+
requirement: &70300117186540 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70300117186540
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rainbow
|
27
|
+
requirement: &70300117186000 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70300117186000
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
requirement: &70300117185120 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70300117185120
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rake
|
49
|
+
requirement: &70300117183780 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70300117183780
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: minitest
|
60
|
+
requirement: &70300117183120 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70300117183120
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rack-test
|
71
|
+
requirement: &70300117182600 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70300117182600
|
80
|
+
description: A Gem for testing web applications don't generate mixed secure/insecure
|
81
|
+
traffic. Keep that browser padlock locked!
|
82
|
+
email: joshuacronemeyer@gmail.com
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- lib/rack/padlock/version.rb
|
88
|
+
- lib/rack/padlock.rb
|
89
|
+
- lib/rack-padlock.rb
|
90
|
+
- lib/tasks/rack-padlock.rake
|
91
|
+
- README.md
|
92
|
+
- test/rack_padlock_test.rb
|
93
|
+
- test/test_helper.rb
|
94
|
+
homepage: https://github.com/joshuacronemeyer/rack-padlock
|
95
|
+
licenses: []
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
none: false
|
108
|
+
requirements:
|
109
|
+
- - ! '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 1.8.10
|
115
|
+
signing_key:
|
116
|
+
specification_version: 3
|
117
|
+
summary: A Toolkit for writing tests that ensure all traffic on a page is secure.
|
118
|
+
test_files:
|
119
|
+
- test/rack_padlock_test.rb
|
120
|
+
- test/test_helper.rb
|