rack-allow-from 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -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
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack-allow-from.gemspec
4
+ gemspec
5
+ #gem 'railties', '~>3.1.0', require: 'rails'
6
+ #group :development do
7
+ #gem 'rspec'
8
+ #end
@@ -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
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,4 @@
1
+ require 'rails'
2
+ require "rack/allow-from/middleware"
3
+ require "rack/allow-from/railtie"
4
+ require "rack/allow-from/version"
@@ -0,0 +1,9 @@
1
+ require "rack/allow-from/middleware"
2
+ require "rack/allow-from/railtie"
3
+ require "rack/allow-from/version"
4
+
5
+ module Rack
6
+ module AllowFrom
7
+
8
+ end
9
+ end
@@ -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,5 @@
1
+ module Rack
2
+ module AllowFrom
3
+ VERSION = "0.0.2"
4
+ end
5
+ 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
@@ -0,0 +1,2 @@
1
+ require 'rails'
2
+ require 'rack/allow-from'
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