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 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
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class Padlock
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ # Author:: Josh Cronemeyer
2
+ # Copyright:: Copyright (c) 2013
3
+ # License:: MIT License (http://www.opensource.org/licenses/mit-license.php)
4
+ require 'rack/padlock'
@@ -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
@@ -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