rack-allow-from 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/CHANGELOG +10 -0
- data/Gemfile +8 -0
- data/README.md +51 -0
- data/Rakefile +1 -0
- data/lib/rack-allow-from.rb +4 -0
- data/lib/rack/allow-from.rb +9 -0
- data/lib/rack/allow-from/middleware.rb +50 -0
- data/lib/rack/allow-from/railtie.rb +10 -0
- data/lib/rack/allow-from/version.rb +5 -0
- data/rack-allow-from.gemspec +24 -0
- data/spec/middleware_spec.rb +130 -0
- data/spec/spec_helper.rb +2 -0
- metadata +85 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/CHANGELOG
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
2012-02-25 Philip Champon <pchampon@gmail.com>
|
2
|
+
|
3
|
+
* Added rescue for TCPSocket failure
|
4
|
+
* Replaced env['X-Remote-Hostname'] with env['HTTP_X_REMOTE_HOSTNAME']
|
5
|
+
* Better tests
|
6
|
+
* Better README
|
7
|
+
|
8
|
+
2012-02-24 Philip Champon <pchampon@gmail.com>
|
9
|
+
|
10
|
+
* initial version, supported as Rails middleware
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# Description
|
2
|
+
|
3
|
+
Rack-allow-from is a simple piece of [Rack](https://github.com/rack/rack) middleware, which accepts connections from authorized hosts. Unauthorized hosts receive "403 Unauthorized access". Hosts are defined as IP addresses, host names, or simplistic regexes of either (ie 1.2.3.* or *.example.com).
|
4
|
+
|
5
|
+
White listed IPs are checked against `env['REMOTE_ADDR']`. White listed host names are validated against the client provided header, X-Remote-Hostname (env['HTTP_X_REMOTE_HOSTNAME']). When a host name is specified, its A record must match `env['REMOTE_ADDR']`, or the connection is rejected.
|
6
|
+
|
7
|
+
This version only supports Rails 3 applications.
|
8
|
+
|
9
|
+
# Configuration
|
10
|
+
|
11
|
+
* Add configuration options to config/application.rb or config/environments/*.rb
|
12
|
+
* `config.allow_from` is defined as an array of strings
|
13
|
+
* the default values are `["127.0.0.1", "localhost", "*.dev"]`
|
14
|
+
* add hosts using this idiom `config.allow_from += ["host.name"]`, unless you know what you're doing
|
15
|
+
* Configuring rack-allow-from with regex characters, other than *, is not supported. You can likely use most matchers with ease, but all periods are escaped, ie `*.example.com` becomes `.*\.example\.com`
|
16
|
+
* Acceptable array elements are IP addresses, hostnames or blobs of either
|
17
|
+
* `["127.0.0.1", "192.168.*", "example.com", "*.example.com"]`
|
18
|
+
* `Rails.configuration.allow_from.empty?` will reject all requests
|
19
|
+
* A nil `Rails.configuration.allow_from` accepts all requests
|
20
|
+
|
21
|
+
The middlware is inserted during boot, using [Rails::Railtie](http://api.rubyonrails.org/classes/Rails/Railtie.html) and should appear near the top of `rake middleware`.
|
22
|
+
|
23
|
+
# Examples
|
24
|
+
|
25
|
+
1. Allowing an entire class C to connect to your app
|
26
|
+
* Assume your private network is 192.168.1.0/24 and your dev machine's IP is 192.168.1.2
|
27
|
+
* config/application.rb would define allow_hosts as `config.allow_hosts += ["192.168.1.*"]
|
28
|
+
* Boot your app, `rails s`
|
29
|
+
* `curl http://192.168.1.2:3000/`
|
30
|
+
1. Allowing an entire domain to connect to your app
|
31
|
+
* Assume your domain is example.com, foo.example.com's A record is 192.168.1.2
|
32
|
+
* config/application.rb would define allow_hosts as `config.allow_hosts += ["*.example.com"]`
|
33
|
+
* Add `192.168.1.2 foo.example.com` to /etc/hosts
|
34
|
+
* Boot your app, `rails s`
|
35
|
+
* `curl -H "X-Remote-Hostname: foo.example.com" http://192.168.1.2:3000/`
|
36
|
+
|
37
|
+
# Contributing to rack-allow-from
|
38
|
+
|
39
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
40
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
41
|
+
* Fork the project.
|
42
|
+
* Start a feature/bugfix branch.
|
43
|
+
* Commit and push until you are happy with your contribution.
|
44
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
45
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
46
|
+
|
47
|
+
# Copyright
|
48
|
+
|
49
|
+
Copyright (c) 2012 Philip Champon. See LICENSE.txt for
|
50
|
+
further details.
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Rack
|
2
|
+
module AllowFrom
|
3
|
+
class Middleware
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
status, headers, body = @app.call(env)
|
10
|
+
if valid_client?(env)
|
11
|
+
[status, headers, body]
|
12
|
+
else
|
13
|
+
[403, {}, ["Unauthorized access"]]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def valid_client?(env)
|
20
|
+
return true unless Rails.configuration.allow_from
|
21
|
+
r = false
|
22
|
+
ip = env['REMOTE_ADDR']
|
23
|
+
host = env['HTTP_X_REMOTE_HOSTNAME']
|
24
|
+
Rails.configuration.allow_from.each do |rule|
|
25
|
+
if ip == rule || (host == rule && valid_ip?(ip, host))
|
26
|
+
r = true
|
27
|
+
elsif ip =~ %r{#{re_sub rule}} || (host =~ %r{#{re_sub rule}} && valid_ip?(ip, host))
|
28
|
+
r = true
|
29
|
+
end
|
30
|
+
break if r
|
31
|
+
end
|
32
|
+
r
|
33
|
+
end
|
34
|
+
|
35
|
+
def valid_ip?(ip, host)
|
36
|
+
require "socket"
|
37
|
+
ips = TCPSocket.gethostbyname(host)[3..-1]
|
38
|
+
ips.include?(ip)
|
39
|
+
rescue => e
|
40
|
+
#Rails.logger.debug("Rack::AllowFrom: Invalid hostname '#{host}'")
|
41
|
+
false
|
42
|
+
end
|
43
|
+
|
44
|
+
def re_sub(re)
|
45
|
+
re.gsub('.', '\\.').sub('*', '.*')
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Rack
|
2
|
+
module AllowFrom
|
3
|
+
class Railtie < Rails::Railtie
|
4
|
+
config.allow_from = %w(127.0.0.1 localhost *.dev)
|
5
|
+
initializer "rack-allow-from.configure_rails_initialization" do |app|
|
6
|
+
app.middleware.insert_before 0, "Rack::AllowFrom::Middleware"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rack/allow-from/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rack-allow-from"
|
7
|
+
s.version = Rack::AllowFrom::VERSION
|
8
|
+
s.authors = ["Philip Champon"]
|
9
|
+
s.email = ["pchampon@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Control which hosts connect to your Rails app}
|
12
|
+
s.description = %q{A Rack middlware which reads from a Rails configuration variable,
|
13
|
+
determining which hosts are allowed to connect to your Rails app.}
|
14
|
+
|
15
|
+
s.rubyforge_project = "rack-allow-from"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_runtime_dependency "rails", "~> 3.1.0"
|
24
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rack::AllowFrom::Middleware do
|
4
|
+
let(:app) {
|
5
|
+
a=double("app").as_null_object
|
6
|
+
a.stub(:call).and_return([200, {"Content-Type" => "text/plain", "Content-Length" => 0}, []])
|
7
|
+
a
|
8
|
+
}
|
9
|
+
let(:host_headers) { { 'REMOTE_ADDR' => '127.0.0.1', 'HTTP_X_REMOTE_HOSTNAME' => 'foo.example.com'} }
|
10
|
+
let(:ip_headers) { { 'REMOTE_ADDR' => '127.0.0.1'} }
|
11
|
+
subject { Rack::AllowFrom::Middleware.new(app) }
|
12
|
+
|
13
|
+
context "#call" do
|
14
|
+
before do
|
15
|
+
TCPSocket.stub(:gethostbyname).and_return(["foo.example.com", [], 30, "::1", "127.0.0.1", "fe80::1%lo0"])
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
TCPSocket.unstub(:gethostbyname)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "success" do
|
23
|
+
let(:_expected) { [200, {"Content-Type" => "text/plain", "Content-Length" => 0}, []] }
|
24
|
+
it "matching ips return 200" do
|
25
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(127.0.0.1))
|
26
|
+
subject.call(ip_headers).should == _expected
|
27
|
+
end
|
28
|
+
it "matching hostname return 200" do
|
29
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(127.0.0.1))
|
30
|
+
subject.call(host_headers).should == _expected
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "failure" do
|
35
|
+
let(:_expected) { [403, {}, ["Unauthorized access"]] }
|
36
|
+
|
37
|
+
it "mismatched hostname return 403" do
|
38
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(127.0.0.2))
|
39
|
+
subject.call(ip_headers).should == _expected
|
40
|
+
end
|
41
|
+
it "mismatched hostname return 403" do
|
42
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(127.0.0.2))
|
43
|
+
subject.call(host_headers).should == _expected
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "#valid_ip?" do
|
49
|
+
it "returns true for 127.0.0.1 localhost" do
|
50
|
+
subject.send(:valid_ip?, "127.0.0.1", "localhost").should be_true
|
51
|
+
end
|
52
|
+
it "returns false for 127.0.0.2 localhost" do
|
53
|
+
subject.send(:valid_ip?, "127.0.0.2", "localhost").should be_false
|
54
|
+
end
|
55
|
+
it "returns false for invalid host names" do
|
56
|
+
subject.send(:valid_ip?, "127.0.0.1", "_bad.com_").should be_false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "#valid_client?" do
|
61
|
+
before do
|
62
|
+
TCPSocket.stub(:gethostbyname).and_return(["foo.example.com", [], 30, "::1", "127.0.0.1", "fe80::1%lo0"])
|
63
|
+
end
|
64
|
+
|
65
|
+
after do
|
66
|
+
TCPSocket.unstub(:gethostbyname)
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "success" do
|
70
|
+
it "accepts a nil configuration" do
|
71
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(nil)
|
72
|
+
subject.send(:valid_client?, ip_headers).should be_true
|
73
|
+
end
|
74
|
+
|
75
|
+
it "accepts an ip address" do
|
76
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(127.0.0.1))
|
77
|
+
subject.send(:valid_client?, ip_headers).should be_true
|
78
|
+
end
|
79
|
+
|
80
|
+
it "accepts an ip wildcard at the end" do
|
81
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(127.*))
|
82
|
+
subject.send(:valid_client?, ip_headers).should be_true
|
83
|
+
end
|
84
|
+
|
85
|
+
it "accepts an ip wildcard in the middle" do
|
86
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(127.0.*.1))
|
87
|
+
subject.send(:valid_client?, ip_headers).should be_true
|
88
|
+
end
|
89
|
+
|
90
|
+
it "accepts a host name" do
|
91
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(foo.example.com))
|
92
|
+
subject.send(:valid_client?, host_headers).should be_true
|
93
|
+
end
|
94
|
+
|
95
|
+
it "accepts a host name wildcard at the beginning" do
|
96
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(*.example.com))
|
97
|
+
subject.send(:valid_client?, host_headers).should be_true
|
98
|
+
end
|
99
|
+
|
100
|
+
it "accepts a hostname wildcard in the middle" do
|
101
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(foo.*.com))
|
102
|
+
subject.send(:valid_client?, host_headers).should be_true
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "failure" do
|
108
|
+
it "rejects mismatched ip addresses" do
|
109
|
+
Rails.stub_chain(:configuration,:allow_from).and_return([])
|
110
|
+
subject.send(:valid_client?, ip_headers).should be_false
|
111
|
+
end
|
112
|
+
|
113
|
+
it "rejects mismatched ip addresses" do
|
114
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(127.0.0.2))
|
115
|
+
subject.send(:valid_client?, ip_headers).should be_false
|
116
|
+
end
|
117
|
+
|
118
|
+
it "rejects mismatched ip address wildcards" do
|
119
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(192.168.*))
|
120
|
+
subject.send(:valid_client?, ip_headers).should be_false
|
121
|
+
end
|
122
|
+
|
123
|
+
it "rejects mismatched hostname wildcards" do
|
124
|
+
Rails.stub_chain(:configuration,:allow_from).and_return(%w(*.foo.com))
|
125
|
+
subject.send(:valid_client?, host_headers).should be_false
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-allow-from
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Philip Champon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-02-26 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70275271308940 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70275271308940
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rails
|
27
|
+
requirement: &70275271308440 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 3.1.0
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70275271308440
|
36
|
+
description: ! "A Rack middlware which reads from a Rails configuration variable,
|
37
|
+
\n determining which hosts are allowed to connect to your Rails
|
38
|
+
app."
|
39
|
+
email:
|
40
|
+
- pchampon@gmail.com
|
41
|
+
executables: []
|
42
|
+
extensions: []
|
43
|
+
extra_rdoc_files: []
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- .rspec
|
47
|
+
- CHANGELOG
|
48
|
+
- Gemfile
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- lib/rack-allow-from.rb
|
52
|
+
- lib/rack/allow-from.rb
|
53
|
+
- lib/rack/allow-from/middleware.rb
|
54
|
+
- lib/rack/allow-from/railtie.rb
|
55
|
+
- lib/rack/allow-from/version.rb
|
56
|
+
- rack-allow-from.gemspec
|
57
|
+
- spec/middleware_spec.rb
|
58
|
+
- spec/spec_helper.rb
|
59
|
+
homepage: ''
|
60
|
+
licenses: []
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
requirements: []
|
78
|
+
rubyforge_project: rack-allow-from
|
79
|
+
rubygems_version: 1.8.15
|
80
|
+
signing_key:
|
81
|
+
specification_version: 3
|
82
|
+
summary: Control which hosts connect to your Rails app
|
83
|
+
test_files:
|
84
|
+
- spec/middleware_spec.rb
|
85
|
+
- spec/spec_helper.rb
|