honkster-rack-ssl-enforcer 0.2.2.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/LICENSE +20 -0
- data/README.rdoc +111 -0
- data/lib/rack-ssl-enforcer.rb +1 -0
- data/lib/rack/ssl-enforcer.rb +174 -0
- data/lib/rack/ssl-enforcer/version.rb +5 -0
- metadata +139 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Tobias Matthies
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
= Rack::SslEnforcer
|
2
|
+
|
3
|
+
Rack::SslEnforcer is a simple Rack middleware to enforce SSL connections. As of Version 0.2.0, Rack::SslEnforcer marks
|
4
|
+
Cookies as secure by default (HSTS must be set manually).
|
5
|
+
|
6
|
+
Tested on Ruby 1.8.7, 1.9.2, REE, JRuby and Rubinius.
|
7
|
+
|
8
|
+
== Installation
|
9
|
+
|
10
|
+
gem install rack-ssl-enforcer
|
11
|
+
|
12
|
+
|
13
|
+
== Basic Usage
|
14
|
+
|
15
|
+
require 'rack/ssl-enforcer'
|
16
|
+
use Rack::SslEnforcer
|
17
|
+
|
18
|
+
Or, if you are using Bundler, just add this to your Gemfile:
|
19
|
+
|
20
|
+
gem 'rack-ssl-enforcer'
|
21
|
+
|
22
|
+
To use Rack::SslEnforcer in your Rails application, add the following line to your application
|
23
|
+
config file (<tt>config/application.rb</tt> for Rails 3, <tt>config/environment.rb</tt> for Rails 2):
|
24
|
+
|
25
|
+
config.middleware.use Rack::SslEnforcer
|
26
|
+
|
27
|
+
If all you want is SSL for your whole application, you are done! However, you can specify some
|
28
|
+
|
29
|
+
|
30
|
+
== Options
|
31
|
+
|
32
|
+
You might need the :redirect_to option if the requested URL can't be determined (e.g. if using a proxy).
|
33
|
+
|
34
|
+
config.middleware.use Rack::SslEnforcer, :redirect_to => 'https://example.org'
|
35
|
+
|
36
|
+
You can also define specific regex patterns or paths or hosts to redirect.
|
37
|
+
|
38
|
+
config.middleware.use Rack::SslEnforcer, :only => /^\/admin\//
|
39
|
+
config.middleware.use Rack::SslEnforcer, :only => "/login"
|
40
|
+
config.middleware.use Rack::SslEnforcer, :only => ["/login", /\.xml$/]
|
41
|
+
config.middleware.use Rack::SslEnforcer, :only_hosts => 'api.example.com'
|
42
|
+
config.middleware.use Rack::SslEnforcer, :only_hosts => [/[www|api]\.example\.org$/, 'example.com']
|
43
|
+
config.middleware.use Rack::SslEnforcer, :except_hosts => 'help.example.com'
|
44
|
+
config.middleware.use Rack::SslEnforcer, :except_hosts => /[help|blog]\.example\.com$/
|
45
|
+
|
46
|
+
Note: hosts options take precedence over the path options. See tests for examples.
|
47
|
+
|
48
|
+
Use the :strict option to force http for all requests not matching your :only specification
|
49
|
+
|
50
|
+
config.middleware.use Rack::SslEnforcer, :only => ["/login", /\.xml$/], :strict => true
|
51
|
+
config.middleware.use Rack::SslEnforcer, :only_hosts => 'api.example.com', :strict => true
|
52
|
+
|
53
|
+
Or in the case where you have matching urls with different methods (Rails RESTful routes: get#users post#users || get#user/:id put#user/:id) you may need to post and put to secure but redirect to http on get.
|
54
|
+
|
55
|
+
config.middleware.use Rack::SslEnforcer, :only => [%r{^/users/}], :mixed => true
|
56
|
+
|
57
|
+
The above will allow you to post/put from the secure/non-secure urls keeping the original schema.
|
58
|
+
|
59
|
+
To set HSTS expiry and subdomain inclusion (defaults: one year, true). Strict option disables HSTS.
|
60
|
+
|
61
|
+
config.middleware.use Rack::SslEnforcer, :hsts => { :expires => 500, :subdomains => false }
|
62
|
+
config.middleware.use Rack::SslEnforcer, :hsts => true # equivalent to { :expires => 31536000, :subdomains => true }
|
63
|
+
|
64
|
+
Finally you might want to share a cookie based session between http and https.
|
65
|
+
This is not possible by default with Rack::SslEnforcer for security reasons.
|
66
|
+
See: [http://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking]
|
67
|
+
|
68
|
+
Nevertheless, you can set the option :force_secure_cookies to false in order to be able to share a cookie based session between http and https:
|
69
|
+
|
70
|
+
config.middleware.use Rack::SslEnforcer, :only => "/login", :force_secure_cookies => false
|
71
|
+
|
72
|
+
But be aware that if you do so, you have to make sure that the content of you cookie is encoded.
|
73
|
+
This can be done using a coder with Rack::Session::Cookie.
|
74
|
+
See: [https://github.com/rack/rack/blob/master/lib/rack/session/cookie.rb#L28-42]
|
75
|
+
|
76
|
+
|
77
|
+
== TODO
|
78
|
+
|
79
|
+
* Add configuration option to specify local http / https ports
|
80
|
+
* Cleanup tests
|
81
|
+
|
82
|
+
|
83
|
+
== Contributors
|
84
|
+
|
85
|
+
* {Dan Mayer}[http://github.com/danmayer]
|
86
|
+
* {Rémy Coutable}[http://github.com/rymai]
|
87
|
+
* {Thibaud Guillaume-Gentil}[http://github.com/thibaudgg]
|
88
|
+
* {Paul Annesley}[https://github.com/pda]
|
89
|
+
* {Saimon Moore}[https://github.com/saimonmoore]
|
90
|
+
|
91
|
+
|
92
|
+
== Credits
|
93
|
+
|
94
|
+
Flagging cookies as secure functionality and HSTS support is greatly inspired by {Joshua Peek's Rack::SSL}[https://github.com/josh/rack-ssl]
|
95
|
+
|
96
|
+
|
97
|
+
== Note on Patches/Pull Requests
|
98
|
+
|
99
|
+
* Fork the project.
|
100
|
+
* Make your feature addition or bug fix.
|
101
|
+
* Add tests for it. This is important so I don't break it in a
|
102
|
+
future version unintentionally.
|
103
|
+
* Commit, do not mess with rakefile, version, or history.
|
104
|
+
(if you want to have your own version,
|
105
|
+
that is fine but bump version in a commit by itself I can ignore when I pull)
|
106
|
+
* Send me a pull request. Bonus points for topic branches.
|
107
|
+
|
108
|
+
|
109
|
+
== Copyright
|
110
|
+
|
111
|
+
Copyright (c) 2010 Tobias Matthies. See LICENSE for details.
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rack/ssl-enforcer'
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module Rack
|
2
|
+
class SslEnforcer
|
3
|
+
|
4
|
+
# Warning: If you set the option force_secure_cookies to false, make sure that your cookies
|
5
|
+
# are encoded and that you understand the consequences (see documentation)
|
6
|
+
def initialize(app, options={})
|
7
|
+
default_options = {
|
8
|
+
:redirect_to => nil,
|
9
|
+
:only => nil,
|
10
|
+
:only_hosts => nil,
|
11
|
+
:except => nil,
|
12
|
+
:except_hosts => nil,
|
13
|
+
:strict => false,
|
14
|
+
:mixed => false,
|
15
|
+
:hsts => nil,
|
16
|
+
:http_port => nil,
|
17
|
+
:https_port => nil,
|
18
|
+
:force_secure_cookies => true
|
19
|
+
}
|
20
|
+
@app, @options = app, default_options.merge(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
@req = Rack::Request.new(env)
|
25
|
+
if enforce_ssl?(@req)
|
26
|
+
scheme = 'https' unless ssl_request?(env)
|
27
|
+
elsif ssl_request?(env) && enforcement_non_ssl?(env)
|
28
|
+
scheme = 'http'
|
29
|
+
end
|
30
|
+
|
31
|
+
if scheme
|
32
|
+
location = replace_scheme(@req, scheme).url
|
33
|
+
body = "<html><body>You are being <a href=\"#{location}\">redirected</a>.</body></html>"
|
34
|
+
[301, { 'Content-Type' => 'text/html', 'Location' => location }, [body]]
|
35
|
+
elsif ssl_request?(env)
|
36
|
+
status, headers, body = @app.call(env)
|
37
|
+
flag_cookies_as_secure!(headers) if @options[:force_secure_cookies]
|
38
|
+
set_hsts_headers!(headers) if @options[:hsts] && !@options[:strict]
|
39
|
+
[status, headers, body]
|
40
|
+
else
|
41
|
+
@app.call(env)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def enforcement_non_ssl?(env)
|
48
|
+
true if @options[:strict] || @options[:mixed] && !(env['REQUEST_METHOD'] == 'PUT' || env['REQUEST_METHOD'] == 'POST')
|
49
|
+
end
|
50
|
+
|
51
|
+
def ssl_request?(env)
|
52
|
+
scheme(env) == 'https'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Fixed in rack >= 1.3
|
56
|
+
def scheme(env)
|
57
|
+
if env['HTTPS'] == 'on'
|
58
|
+
'https'
|
59
|
+
elsif env['HTTP_X_FORWARDED_PROTO']
|
60
|
+
env['HTTP_X_FORWARDED_PROTO'].split(',')[0]
|
61
|
+
else
|
62
|
+
env['rack.url_scheme']
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def matches?(key, pattern, req)
|
67
|
+
if pattern.is_a?(Regexp)
|
68
|
+
case key
|
69
|
+
when :only
|
70
|
+
req.path =~ pattern
|
71
|
+
when :except
|
72
|
+
req.path !~ pattern
|
73
|
+
when :only_hosts
|
74
|
+
req.host =~ pattern
|
75
|
+
when :except_hosts
|
76
|
+
req.host !~ pattern
|
77
|
+
end
|
78
|
+
else
|
79
|
+
case key
|
80
|
+
when :only
|
81
|
+
req.path[0,pattern.length] == pattern
|
82
|
+
when :except
|
83
|
+
req.path[0,pattern.length] != pattern
|
84
|
+
when :only_hosts
|
85
|
+
req.host == pattern
|
86
|
+
when :except_hosts
|
87
|
+
req.host != pattern
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def enforce_ssl_for?(keys, req)
|
93
|
+
if keys.any? { |option| @options[option] }
|
94
|
+
keys.any? do |key|
|
95
|
+
rules = [@options[key]].flatten.compact
|
96
|
+
unless rules.empty?
|
97
|
+
rules.send(key == :except_hosts || key == :except ? "all?" : "any?") do |pattern|
|
98
|
+
matches?(key, pattern, req)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
else
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def enforce_ssl?(req)
|
108
|
+
path_keys = [:only, :except]
|
109
|
+
hosts_keys = [:only_hosts, :except_hosts]
|
110
|
+
if hosts_keys.any? { |option| @options[option] }
|
111
|
+
if enforce_ssl_for?(hosts_keys, req)
|
112
|
+
if path_keys.any? { |option| @options[option] }
|
113
|
+
enforce_ssl_for?(path_keys, req)
|
114
|
+
else
|
115
|
+
true
|
116
|
+
end
|
117
|
+
else
|
118
|
+
false
|
119
|
+
end
|
120
|
+
elsif path_keys.any? { |option| @options[option] }
|
121
|
+
enforce_ssl_for?(path_keys, req)
|
122
|
+
else
|
123
|
+
true
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def replace_scheme(req, scheme)
|
128
|
+
if @options[:redirect_to]
|
129
|
+
uri = URI.split(@options[:redirect_to])
|
130
|
+
uri = uri[2] || uri[5]
|
131
|
+
else
|
132
|
+
uri = nil
|
133
|
+
end
|
134
|
+
Rack::Request.new(req.env.merge(
|
135
|
+
'rack.url_scheme' => scheme,
|
136
|
+
'HTTP_X_FORWARDED_PROTO' => scheme,
|
137
|
+
'HTTP_X_FORWARDED_PORT' => port_for(scheme).to_s,
|
138
|
+
'SERVER_PORT' => port_for(scheme).to_s
|
139
|
+
).merge(uri ? {'HTTP_HOST' => uri} : {}))
|
140
|
+
end
|
141
|
+
|
142
|
+
def port_for(scheme)
|
143
|
+
if scheme == 'https'
|
144
|
+
@options[:https_port] || 443
|
145
|
+
else
|
146
|
+
@options[:http_port] || 80
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# see http://en.wikipedia.org/wiki/HTTP_cookie#Cookie_theft_and_session_hijacking
|
151
|
+
def flag_cookies_as_secure!(headers)
|
152
|
+
if cookies = headers['Set-Cookie']
|
153
|
+
# Support Rails 2.3 / Rack 1.1 arrays as headers
|
154
|
+
unless cookies.is_a?(Array)
|
155
|
+
cookies = cookies.split("\n")
|
156
|
+
end
|
157
|
+
|
158
|
+
headers['Set-Cookie'] = cookies.map do |cookie|
|
159
|
+
cookie !~ / secure;/ ? "#{cookie}; secure" : cookie
|
160
|
+
end.join("\n")
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# see http://en.wikipedia.org/wiki/Strict_Transport_Security
|
165
|
+
def set_hsts_headers!(headers)
|
166
|
+
opts = { :expires => 31536000, :subdomains => true }
|
167
|
+
opts.merge!(@options[:hsts])
|
168
|
+
value = "max-age=#{opts[:expires]}"
|
169
|
+
value += "; includeSubDomains" if opts[:subdomains]
|
170
|
+
headers.merge!({ 'Strict-Transport-Security' => value })
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: honkster-rack-ssl-enforcer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.2.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tobias Matthies
|
9
|
+
- Thibaud Guillaume-Gentil
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2011-07-27 00:00:00 -07:00
|
15
|
+
default_executable:
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: bundler
|
19
|
+
prerelease: false
|
20
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ~>
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: "1.0"
|
26
|
+
type: :development
|
27
|
+
version_requirements: *id001
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: guard
|
30
|
+
prerelease: false
|
31
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
32
|
+
none: false
|
33
|
+
requirements:
|
34
|
+
- - ~>
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 0.3.3
|
37
|
+
type: :development
|
38
|
+
version_requirements: *id002
|
39
|
+
- !ruby/object:Gem::Dependency
|
40
|
+
name: guard-test
|
41
|
+
prerelease: false
|
42
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: "0.2"
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id003
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: test-unit
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ~>
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: "2.2"
|
59
|
+
type: :development
|
60
|
+
version_requirements: *id004
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: shoulda
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.11.3
|
70
|
+
type: :development
|
71
|
+
version_requirements: *id005
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: rack
|
74
|
+
prerelease: false
|
75
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ~>
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 1.2.0
|
81
|
+
type: :development
|
82
|
+
version_requirements: *id006
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rack-test
|
85
|
+
prerelease: false
|
86
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ~>
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 0.5.4
|
92
|
+
type: :development
|
93
|
+
version_requirements: *id007
|
94
|
+
description: Rack::SslEnforcer is a simple Rack middleware to enforce ssl connections
|
95
|
+
email:
|
96
|
+
- tm@mit2m.de
|
97
|
+
- thibaud@thibaud.me
|
98
|
+
executables: []
|
99
|
+
|
100
|
+
extensions: []
|
101
|
+
|
102
|
+
extra_rdoc_files: []
|
103
|
+
|
104
|
+
files:
|
105
|
+
- lib/rack-ssl-enforcer.rb
|
106
|
+
- lib/rack/ssl-enforcer/version.rb
|
107
|
+
- lib/rack/ssl-enforcer.rb
|
108
|
+
- LICENSE
|
109
|
+
- README.rdoc
|
110
|
+
has_rdoc: true
|
111
|
+
homepage: http://github.com/tobmatth/rack-ssl-enforcer
|
112
|
+
licenses: []
|
113
|
+
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: "0"
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ">="
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 1.3.6
|
131
|
+
requirements: []
|
132
|
+
|
133
|
+
rubyforge_project: rack-ssl-enforcer
|
134
|
+
rubygems_version: 1.5.2
|
135
|
+
signing_key:
|
136
|
+
specification_version: 3
|
137
|
+
summary: A simple Rack middleware to enforce SSL
|
138
|
+
test_files: []
|
139
|
+
|