* [Patch] Rack::Access - Limit access based on IP address
@ 2010-01-14 23:05 Pirmin Kalberer
2010-01-15 12:34 ` Ryan Tomayko
0 siblings, 1 reply; 4+ messages in thread
From: Pirmin Kalberer @ 2010-01-14 23:05 UTC (permalink / raw)
To: rack-devel
[-- Attachment #1: Type: text/plain, Size: 378 bytes --]
Hi all,
For implementing a monitoring rack middleware I had to limit access to certain
URL's based on IP adresses. The attached patch for rack-contrib implements
this. It borrows some ideas from rack-bug and code from Rack::URLMap...
-- Pull URL:
git://github.com/pka/rack-contrib.git
Pirmin Kalberer
Sourcepole - Linux & Open Source Solutions
http://www.sourcepole.com
[-- Attachment #2: rack_access.patch --]
[-- Type: text/x-patch, Size: 10111 bytes --]
diff --git a/README.rdoc b/README.rdoc
index 3583555..ca015ae 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -45,6 +45,7 @@ interface:
* Rack::AcceptFormat - Adds a format extension at the end of the URI when there is none, corresponding to the mime-type given in the Accept HTTP header.
* Rack::HostMeta - Configures /host-meta using a block
* Rack::Cookies - Adds simple cookie jar hash to env
+* Rack::Access - Limit access based on IP address
=== Use
diff --git a/lib/rack/contrib.rb b/lib/rack/contrib.rb
index 6252b26..8905437 100644
--- a/lib/rack/contrib.rb
+++ b/lib/rack/contrib.rb
@@ -8,6 +8,7 @@ module Rack
end
autoload :AcceptFormat, "rack/contrib/accept_format"
+ autoload :Access, "rack/contrib/access"
autoload :BounceFavicon, "rack/contrib/bounce_favicon"
autoload :Cookies, "rack/contrib/cookies"
autoload :CSSHTTPRequest, "rack/contrib/csshttprequest"
diff --git a/lib/rack/contrib/access.rb b/lib/rack/contrib/access.rb
new file mode 100644
index 0000000..2f12065
--- /dev/null
+++ b/lib/rack/contrib/access.rb
@@ -0,0 +1,85 @@
+require "ipaddr"
+
+module Rack
+
+ ##
+ # Rack middleware for limiting access based on IP address
+ #
+ #
+ # === Options:
+ #
+ # path => ipmasks ipmasks: Array of remote addresses which are allowed to access
+ #
+ # === Examples:
+ #
+ # use Rack::Access, '/backend' => [ '127.0.0.1', '192.168.1.0/24' ]
+ #
+ #
+
+ class Access
+
+ attr_reader :options
+
+ def initialize(app, options = {})
+ @app = app
+ mapping = options.empty? ? {"/" => ["127.0.0.1"]} : options
+ @mapping = remap(mapping)
+ end
+
+ def remap(mapping)
+ mapping.map { |location, ipmasks|
+ if location =~ %r{\Ahttps?://(.*?)(/.*)}
+ host, location = $1, $2
+ else
+ host = nil
+ end
+
+ unless location[0] == ?/
+ raise ArgumentError, "paths need to start with /"
+ end
+ location = location.chomp('/')
+ match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
+
+ ipmasks.collect! do |ipmask|
+ ipmask.is_a?(IPAddr) ? ipmask : IPAddr.new(ipmask)
+ end
+ [host, location, match, ipmasks]
+ }.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
+ end
+
+ def call(env)
+ @original_request = Request.new(env)
+ ipmasks = ipmasks_for_path(env)
+ return forbidden! unless ip_authorized?(ipmasks)
+ status, headers, body = @app.call(env)
+ [status, headers, body]
+ end
+
+ def ipmasks_for_path(env)
+ path = env["PATH_INFO"].to_s
+ hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
+ @mapping.each do |host, location, match, ipmasks|
+ next unless (hHost == host || sName == host \
+ || (host.nil? && (hHost == sName || hHost == sName+':'+sPort)))
+ next unless path =~ match && rest = $1
+ next unless rest.empty? || rest[0] == ?/
+
+ return ipmasks
+ end
+ nil
+ end
+
+ def forbidden!
+ [403, { 'Content-Type' => 'text/html', 'Content-Length' => '0' }, '']
+ end
+
+ def ip_authorized?(ipmasks)
+ return true if ipmasks.nil?
+
+ ipmasks.any? do |ip_mask|
+ ip_mask.include?(IPAddr.new(@original_request.ip))
+ end
+ end
+
+ end
+end
diff --git a/rack-contrib.gemspec b/rack-contrib.gemspec
index c21fa67..498b8d4 100644
--- a/rack-contrib.gemspec
+++ b/rack-contrib.gemspec
@@ -3,8 +3,8 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.name = 'rack-contrib'
- s.version = '0.9.2'
- s.date = '2009-03-07'
+ s.version = '0.9.3'
+ s.date = '2010-01-10'
s.description = "Contributed Rack Middleware and Utilities"
s.summary = "Contributed Rack Middleware and Utilities"
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
Rakefile
lib/rack/contrib.rb
lib/rack/contrib/accept_format.rb
+ lib/rack/contrib/access.rb
lib/rack/contrib/backstage.rb
lib/rack/contrib/bounce_favicon.rb
lib/rack/contrib/callbacks.rb
@@ -49,6 +50,7 @@ Gem::Specification.new do |s|
test/Maintenance.html
test/mail_settings.rb
test/spec_rack_accept_format.rb
+ test/spec_rack_access.rb
test/spec_rack_backstage.rb
test/spec_rack_callbacks.rb
test/spec_rack_config.rb
diff --git a/test/spec_rack_access.rb b/test/spec_rack_access.rb
new file mode 100644
index 0000000..53a80a8
--- /dev/null
+++ b/test/spec_rack_access.rb
@@ -0,0 +1,154 @@
+require 'test/spec'
+require 'rack/mock'
+require 'rack/contrib/access'
+
+context "Rack::Access" do
+
+ setup do
+ @app = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, 'hello'] }
+ @mock_addr_1 = '111.111.111.111'
+ @mock_addr_2 = '192.168.1.222'
+ @mock_addr_localhost = '127.0.0.1'
+ @mock_addr_range = '192.168.1.0/24'
+ end
+
+ def mock_env(remote_addr, path = '/')
+ Rack::MockRequest.env_for(path, { 'REMOTE_ADDR' => remote_addr })
+ end
+
+ def middleware(options = {})
+ Rack::Access.new(@app, options)
+ end
+
+ specify "default configuration should deny non-local requests" do
+ app = middleware
+ status, headers, body = app.call(mock_env(@mock_addr_1))
+ status.should.equal 403
+ body.should.equal ''
+ end
+
+ specify "default configuration should allow requests from 127.0.0.1" do
+ app = middleware
+ status, headers, body = app.call(mock_env(@mock_addr_localhost))
+ status.should.equal 200
+ body.should.equal 'hello'
+ end
+
+ specify "should allow remote addresses in allow_ipmasking" do
+ app = middleware('/' => [@mock_addr_1])
+ status, headers, body = app.call(mock_env(@mock_addr_1))
+ status.should.equal 200
+ body.should.equal 'hello'
+ end
+
+ specify "should deny remote addresses not in allow_ipmasks" do
+ app = middleware('/' => [@mock_addr_1])
+ status, headers, body = app.call(mock_env(@mock_addr_2))
+ status.should.equal 403
+ body.should.equal ''
+ end
+
+ specify "should allow remote addresses in allow_ipmasks range" do
+ app = middleware('/' => [@mock_addr_range])
+ status, headers, body = app.call(mock_env(@mock_addr_2))
+ status.should.equal 200
+ body.should.equal 'hello'
+ end
+
+ specify "should deny remote addresses not in allow_ipmasks range" do
+ app = middleware('/' => [@mock_addr_range])
+ status, headers, body = app.call(mock_env(@mock_addr_1))
+ status.should.equal 403
+ body.should.equal ''
+ end
+
+ specify "should allow remote addresses in one of allow_ipmasking" do
+ app = middleware('/' => [@mock_addr_range, @mock_addr_localhost])
+
+ status, headers, body = app.call(mock_env(@mock_addr_2))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_localhost))
+ status.should.equal 200
+ body.should.equal 'hello'
+ end
+
+ specify "should deny remote addresses not in one of allow_ipmasks" do
+ app = middleware('/' => [@mock_addr_range, @mock_addr_localhost])
+ status, headers, body = app.call(mock_env(@mock_addr_1))
+ status.should.equal 403
+ body.should.equal ''
+ end
+
+ specify "handles paths correctly" do
+ app = middleware({
+ 'http://foo.org/bar' => [@mock_addr_localhost],
+ '/foo' => [@mock_addr_localhost],
+ '/foo/bar' => [@mock_addr_range, @mock_addr_localhost]
+ })
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/"))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/qux"))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/foo"))
+ status.should.equal 403
+ body.should.equal ''
+ status, headers, body = app.call(mock_env(@mock_addr_localhost, "/foo"))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/foo/"))
+ status.should.equal 403
+ body.should.equal ''
+ status, headers, body = app.call(mock_env(@mock_addr_localhost, "/foo/"))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/foo/bar"))
+ status.should.equal 403
+ body.should.equal ''
+ status, headers, body = app.call(mock_env(@mock_addr_localhost, "/foo/bar"))
+ status.should.equal 200
+ body.should.equal 'hello'
+ status, headers, body = app.call(mock_env(@mock_addr_2, "/foo/bar"))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/foo/bar/"))
+ status.should.equal 403
+ body.should.equal ''
+ status, headers, body = app.call(mock_env(@mock_addr_localhost, "/foo/bar/"))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/foo///bar//quux"))
+ status.should.equal 403
+ body.should.equal ''
+ status, headers, body = app.call(mock_env(@mock_addr_localhost, "/foo///bar//quux"))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/foo/quux"))
+ status.should.equal 403
+ body.should.equal ''
+ status, headers, body = app.call(mock_env(@mock_addr_localhost, "/foo/quux"))
+ status.should.equal 200
+ body.should.equal 'hello'
+
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/bar"))
+ status.should.equal 200
+ body.should.equal 'hello'
+ status, headers, body = app.call(mock_env(@mock_addr_1, "/bar").merge('HTTP_HOST' => 'foo.org'))
+ status.should.equal 403
+ body.should.equal ''
+ status, headers, body = app.call(mock_env(@mock_addr_localhost, "/bar").merge('HTTP_HOST' => 'foo.org'))
+ status.should.equal 200
+ body.should.equal 'hello'
+ end
+end
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [Patch] Rack::Access - Limit access based on IP address
2010-01-14 23:05 [Patch] Rack::Access - Limit access based on IP address Pirmin Kalberer
@ 2010-01-15 12:34 ` Ryan Tomayko
2010-01-19 18:58 ` yonghui
0 siblings, 1 reply; 4+ messages in thread
From: Ryan Tomayko @ 2010-01-15 12:34 UTC (permalink / raw)
To: rack-devel
On Thu, Jan 14, 2010 at 3:05 PM, Pirmin Kalberer
<pirmin.kalberer@gmail.com> wrote:
> Hi all,
> For implementing a monitoring rack middleware I had to limit access to certain
> URL's based on IP adresses. The attached patch for rack-contrib implements
> this. It borrows some ideas from rack-bug and code from Rack::URLMap...
>
> -- Pull URL:
> git://github.com/pka/rack-contrib.git
Merged.
Thanks,
Ryan
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Rack::Access - Limit access based on IP address
2010-01-15 12:34 ` Ryan Tomayko
@ 2010-01-19 18:58 ` yonghui
2010-01-19 20:08 ` Gaius
0 siblings, 1 reply; 4+ messages in thread
From: yonghui @ 2010-01-19 18:58 UTC (permalink / raw)
To: Rack Development
Apache 2.2 renamed mod_access to mod_authz_host. Do we need a more
descriptive name for this middleware?
On Jan 15, 7:34 am, Ryan Tomayko <r...@tomayko.com> wrote:
> On Thu, Jan 14, 2010 at 3:05 PM, Pirmin Kalberer
>
> <pirmin.kalbe...@gmail.com> wrote:
> > Hi all,
> > For implementing a monitoring rack middleware I had to limit access to certain
> > URL's based on IP adresses. The attached patch for rack-contrib implements
> > this. It borrows some ideas from rack-bug and code from Rack::URLMap...
>
> > -- Pull URL:
> > git://github.com/pka/rack-contrib.git
>
> Merged.
>
> Thanks,
> Ryan
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: Rack::Access - Limit access based on IP address
2010-01-19 18:58 ` yonghui
@ 2010-01-19 20:08 ` Gaius
0 siblings, 0 replies; 4+ messages in thread
From: Gaius @ 2010-01-19 20:08 UTC (permalink / raw)
To: Rack Development
As implemented, it seems Rack::HostWhiteList or
Rack::Auth::HostWhiteList might be a good name. It would be possible
to modify the code to support both white- and black-lists. In that
case, Rack::Auth::HostAccess might work.
On Jan 19, 1:58 pm, yonghui <yonghui....@gmail.com> wrote:
> Apache 2.2 renamed mod_access to mod_authz_host. Do we need a more
> descriptive name for this middleware?
>
> On Jan 15, 7:34 am, Ryan Tomayko <r...@tomayko.com> wrote:
>
> > On Thu, Jan 14, 2010 at 3:05 PM, Pirmin Kalberer
>
> > <pirmin.kalbe...@gmail.com> wrote:
> > > Hi all,
> > > For implementing a monitoring rack middleware I had to limit access to certain
> > > URL's based on IP adresses. The attached patch for rack-contrib implements
> > > this. It borrows some ideas from rack-bug and code from Rack::URLMap...
>
> > > -- Pull URL:
> > > git://github.com/pka/rack-contrib.git
>
> > Merged.
>
> > Thanks,
> > Ryan
>
>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2010-01-19 20:08 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-01-14 23:05 [Patch] Rack::Access - Limit access based on IP address Pirmin Kalberer
2010-01-15 12:34 ` Ryan Tomayko
2010-01-19 18:58 ` yonghui
2010-01-19 20:08 ` Gaius
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).