rabbitcage 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.markdown +61 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/rabbitcage +66 -0
- data/lib/rabbitcage/client_connection.rb +95 -0
- data/lib/rabbitcage/core_extensions.rb +20 -0
- data/lib/rabbitcage/filter.rb +92 -0
- data/lib/rabbitcage/server_connection.rb +19 -0
- data/lib/rabbitcage.rb +137 -0
- data/spec/rabbitcage_spec.rb +7 -0
- data/spec/spec_helper.rb +8 -0
- metadata +99 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Dominik Sander
|
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.markdown
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# RabbitCage
|
2
|
+
|
3
|
+
**WARNING: This project is at a very early stage of development. The command line options and the config file format will most likely change in future versions.**
|
4
|
+
|
5
|
+
RabbitCage is a AMQP application firewall build on EventMachine. The code has been heavily inspired by mojombo's awesome [ProxyMachine](http://github.com/mojombo/proxymachine/).
|
6
|
+
|
7
|
+
RabbitCage was written because RabbitMQ's access control capabilities are rather limited.
|
8
|
+
|
9
|
+
RabbitCage works as a transparent, content aware proxy between the connecting client and a AMQP broker (currently only tested with RabbitMQ). Based on configured ACL-like rules RabbitCage will either forward or reject the message. Messages sent from the broker are forwarded directly to the client using EventMachine's [proxy incoming to](http://eventmachine.rubyforge.org/EventMachine/Connection.html#M000275), though it will just affect the client -> server performance.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
sudo gem install rabbitcage
|
14
|
+
|
15
|
+
## Running
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
rabbitcage -c <config file> [-h <host>] [-p <port>]
|
19
|
+
|
20
|
+
Options:
|
21
|
+
-c, --config CONFIG Configuration file
|
22
|
+
-h, --host HOST Hostname to bind. Default 0.0.0.0
|
23
|
+
-p, --port PORT Port to listen on. Default 5672
|
24
|
+
-r, --remote-host HOST Hostname of the RabbitMQ server to connect to. Default 'localhost'
|
25
|
+
-x, --remote-port PORT Port of the RabbitMQ server to connect to. Default 5673
|
26
|
+
-v Verbose output (denied requests).
|
27
|
+
-V Very verbose output (denied requests/allowed requests).
|
28
|
+
-D Debug output (denied requests/allowed requests/debug info).
|
29
|
+
|
30
|
+
## Example config file
|
31
|
+
# Basic syntax:
|
32
|
+
# allow|deny 'username'|:all, AMQP method|:all, AMQP class|:all, Hash of AMQP method properties
|
33
|
+
#
|
34
|
+
# This example will allow the admin user to perform any action on the broker.
|
35
|
+
# A guest is allowed to consume every exchange which name does not start with 'private_' and
|
36
|
+
# register every queue which name does not start with 'reserved_'
|
37
|
+
include RabbitCageACL
|
38
|
+
config do
|
39
|
+
allow 'admin', :all, :all
|
40
|
+
allow 'guest', :all, :queue, :name => /^(?!reserved_)/
|
41
|
+
allow 'guest', :all, :exchange, :name => /^(?!private_)/
|
42
|
+
allow 'guest', [:consume, :get], :basic
|
43
|
+
allow 'guest', :all, :connection
|
44
|
+
allow 'guest', :all, :channel
|
45
|
+
allow 'guest', :all, :access
|
46
|
+
default :deny
|
47
|
+
end
|
48
|
+
|
49
|
+
## Note on Patches/Pull Requests
|
50
|
+
|
51
|
+
* Fork the project.
|
52
|
+
* Make your feature addition or bug fix.
|
53
|
+
* Add tests for it. This is important so I don't break it in a
|
54
|
+
future version unintentionally.
|
55
|
+
* Commit, do not mess with rakefile, version, or history.
|
56
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
57
|
+
* Send me a pull request. Bonus points for topic branches.
|
58
|
+
|
59
|
+
## Copyright
|
60
|
+
|
61
|
+
Copyright (c) 2009 Dominik Sander. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "rabbitcage"
|
8
|
+
gem.summary = %Q{A AMQP firewall which allows to restrict user access to RabbitMQ using ACLs.}
|
9
|
+
gem.description = %Q{RabbitMQ's access control capabilities are rather limited. RabbitCage enables fine-grated permission setups, you define which user can perform which AMQP method on which class.}
|
10
|
+
gem.email = "git@dsander.de"
|
11
|
+
gem.homepage = "http://github.com/dsander/rabbitcage"
|
12
|
+
gem.authors = ["Dominik Sander"]
|
13
|
+
gem.add_development_dependency "bacon", ">= 0"
|
14
|
+
gem.add_dependency "amqp", ">= 0.6.5"
|
15
|
+
gem.add_dependency "eventmachine", ">= 0.12.10"
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
Jeweler::GemcutterTasks.new
|
19
|
+
rescue LoadError
|
20
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'rake/testtask'
|
24
|
+
Rake::TestTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
27
|
+
spec.verbose = true
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'rcov/rcovtask'
|
32
|
+
Rcov::RcovTask.new do |spec|
|
33
|
+
spec.libs << 'spec'
|
34
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
35
|
+
spec.verbose = true
|
36
|
+
end
|
37
|
+
rescue LoadError
|
38
|
+
task :rcov do
|
39
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
task :spec => :check_dependencies
|
44
|
+
|
45
|
+
task :default => :spec
|
46
|
+
|
47
|
+
require 'rake/rdoctask'
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "rabbitcage #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/bin/rabbitcage
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
3
|
+
require 'rabbitcage'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
begin
|
9
|
+
options = {:host => "0.0.0.0", :port => 5672, :remote_host => 'localhost', :remote_port => 5673, :log_level => Logger::ERROR}
|
10
|
+
|
11
|
+
opts = OptionParser.new do |opts|
|
12
|
+
opts.banner = <<-EOF
|
13
|
+
Usage:
|
14
|
+
rabbitcage -c <config file> [-h <host>] [-p <port>]
|
15
|
+
|
16
|
+
Options:
|
17
|
+
EOF
|
18
|
+
opts.on("-cCONFIG", "--config CONFIG", "Configuration file") do |x|
|
19
|
+
options[:config] = x
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on("-hHOST", "--host HOST", "Hostname to bind. Default 0.0.0.0") do |x|
|
23
|
+
options[:host] = x
|
24
|
+
end
|
25
|
+
|
26
|
+
opts.on("-pPORT", "--port PORT", "Port to listen on. Default 5672") do |x|
|
27
|
+
options[:port] = x
|
28
|
+
end
|
29
|
+
|
30
|
+
opts.on("-rHOST", "--remote-host HOST", "Hostname of the RabbitMQ server to connect to. Default 'localhost'") do |x|
|
31
|
+
options[:host] = x
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
opts.on("-xPORT", "--remote-port PORT", "Port of the RabbitMQ server to connect to. Default 5673") do |x|
|
36
|
+
options[:remote_port] = x
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("-v", "Verbose output (denied requests).") do |x|
|
40
|
+
options[:log_level] = Logger::WARN
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("-V", "Very verbose output (denied requests/allowed requests).") do |x|
|
44
|
+
options[:log_level] = Logger::INFO
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("-D", "Debug output (denied requests/allowed requests/debug info).") do |x|
|
48
|
+
options[:log_level] = Logger::DEBUG
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.parse!
|
54
|
+
|
55
|
+
load(options[:config])
|
56
|
+
name = options[:config].split('/').last.chomp('.rb')
|
57
|
+
RabbitCage.run('config', options[:host], options[:port], options[:remote_host], options[:remote_port], options[:log_level])
|
58
|
+
rescue Exception => e
|
59
|
+
if e.instance_of?(SystemExit)
|
60
|
+
raise
|
61
|
+
else
|
62
|
+
LOGGER.fatal 'Uncaught exception'
|
63
|
+
LOGGER.fatal e.message
|
64
|
+
LOGGER.fatal "\n"+e.backtrace.join("\n")
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
class RabbitCage
|
2
|
+
class ClientConnection < EventMachine::Connection
|
3
|
+
def self.start(host, port)
|
4
|
+
$server = EM.start_server(host, port, self)
|
5
|
+
LOGGER.info "Listening on #{host}:#{port}"
|
6
|
+
LOGGER.info "Send QUIT to quit after waiting for all connections to finish."
|
7
|
+
LOGGER.info "Send TERM or INT to quit after waiting for up to 10 seconds for connections to finish."
|
8
|
+
end
|
9
|
+
|
10
|
+
def post_init
|
11
|
+
LOGGER.info "Accepted #{peer}"
|
12
|
+
@buffer = []
|
13
|
+
@tries = 0
|
14
|
+
RabbitCage.incr
|
15
|
+
end
|
16
|
+
|
17
|
+
def peer
|
18
|
+
@peer ||=
|
19
|
+
begin
|
20
|
+
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
21
|
+
"#{ip}:#{port}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def receive_data(data)
|
26
|
+
handle_data data
|
27
|
+
rescue => e
|
28
|
+
close_connection
|
29
|
+
LOGGER.error "#{e.class} - #{e.message}"
|
30
|
+
LOGGER.debug "\n#{e.backtrace.join("\n")}"
|
31
|
+
end
|
32
|
+
|
33
|
+
def handle_data(data)
|
34
|
+
@timer.cancel if @timer
|
35
|
+
data2 = data.dup
|
36
|
+
|
37
|
+
while frame = AMQP::Frame.parse(data2)
|
38
|
+
LOGGER.debug "Got frame: " + frame.payload.inspect
|
39
|
+
case frame.payload
|
40
|
+
when AMQP::Protocol::Connection::Open
|
41
|
+
@vhost = frame.payload.virtual_host
|
42
|
+
when AMQP::Protocol::Connection::StartOk
|
43
|
+
length = frame.payload.response[10].unpack('c').first
|
44
|
+
@user = frame.payload.response[11..10+length]
|
45
|
+
end
|
46
|
+
|
47
|
+
command = self.filter(frame.payload)
|
48
|
+
if command == :deny
|
49
|
+
LOGGER.warn generate_log_line(frame.payload, command) if frame.payload.class != AMQP::Protocol::Basic::Get
|
50
|
+
resp = AMQP::Protocol::Channel::Close.new(:reply_code => 403,
|
51
|
+
:reply_text => "ACCESS_REFUSED - access to '#{frame.payload.queue || frame.payload.exchange rescue 'the server'}' in vhost '#{@vhost}' refused for user '#{@user}' by rabbitcage",
|
52
|
+
:method_id => 10,
|
53
|
+
:class_id => 50,
|
54
|
+
).to_frame
|
55
|
+
resp.channel = frame.channel
|
56
|
+
self.send_data resp.to_s
|
57
|
+
return
|
58
|
+
else
|
59
|
+
LOGGER.info generate_log_line(frame.payload, command) if frame.payload.class != AMQP::Protocol::Basic::Get
|
60
|
+
end
|
61
|
+
end
|
62
|
+
if @server_side || try_server_connect(RabbitCage.rabbit_host, RabbitCage.rabbit_port)
|
63
|
+
@server_side.send_data data
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def try_server_connect(host, port)
|
68
|
+
@server_side = ServerConnection.request(host, port, self)
|
69
|
+
LOGGER.info "Successful connection to #{host}:#{port}."
|
70
|
+
true
|
71
|
+
rescue => e
|
72
|
+
@server_side = nil
|
73
|
+
if @tries < 10
|
74
|
+
@tries += 1
|
75
|
+
LOGGER.error "Failed on server connect attempt #{@tries}. Trying again..."
|
76
|
+
@timer.cancel if @timer
|
77
|
+
@timer = EventMachine::Timer.new(0.1) do
|
78
|
+
self.handle_data
|
79
|
+
end
|
80
|
+
else
|
81
|
+
LOGGER.error "Failed after ten connection attempts."
|
82
|
+
end
|
83
|
+
false
|
84
|
+
end
|
85
|
+
|
86
|
+
def unbind
|
87
|
+
@server_side.close_connection_after_writing if @server_side
|
88
|
+
RabbitCage.decr
|
89
|
+
end
|
90
|
+
|
91
|
+
def generate_log_line(payload, command)
|
92
|
+
"#{peer} #{command} #{payload.class.to_s[16..-1]}\tuser:#{@user} vh:#{@vhost} q:#{payload.respond_to?(:queue) ? payload.queue : 'nil'} ex:#{payload.respond_to?(:exchange) ? payload.exchange : 'nil'}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
# ripped from active_record
|
3
|
+
class String
|
4
|
+
def camelize(first_letter_in_uppercase = true)
|
5
|
+
if first_letter_in_uppercase
|
6
|
+
self.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
7
|
+
else
|
8
|
+
self.first + self.camelize[1..-1]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
class Symbol
|
13
|
+
def camelize(first_letter_in_uppercase = true)
|
14
|
+
if first_letter_in_uppercase
|
15
|
+
self.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
16
|
+
else
|
17
|
+
self.first + self.camelize[1..-1]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module RabbitCageACL
|
4
|
+
def allow user, action, object, properties = {}
|
5
|
+
RabbitCage::Filter.register :allow, user, action, object, properties
|
6
|
+
end
|
7
|
+
def deny user, action, object, properties = {}
|
8
|
+
RabbitCage::Filter.register :deny, user, action, object, properties
|
9
|
+
end
|
10
|
+
|
11
|
+
def default action
|
12
|
+
RabbitCage::Filter.set_default(action)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class RabbitCage
|
17
|
+
class Filter
|
18
|
+
@rules = {}
|
19
|
+
|
20
|
+
def self.register permission, user, action, object, properties
|
21
|
+
available_actions = {:queue => ['Declare', 'Bind', 'Unbind', 'Delete', 'Purge'],
|
22
|
+
:connection => ['Start', 'StartOk', 'Open', 'Tune', 'TuneOk', 'Close', 'Redirect', 'Secure'],
|
23
|
+
:channel => ['Open', 'Flow', 'Alert', 'Close', 'CloseOk'],
|
24
|
+
:exchange => ['Declare', 'Delete'],
|
25
|
+
:access => ['Request'],
|
26
|
+
:basic => ['Qos', 'Consume', 'Cancel', 'Publish', 'Deliver', 'Get', 'Ack', 'Reject']
|
27
|
+
}
|
28
|
+
klass = []
|
29
|
+
if action.is_a? Array
|
30
|
+
klass = action.collect { |a| "AMQP::Protocol::#{object.camelize}::#{a.camelize}" }.join(',')
|
31
|
+
else
|
32
|
+
if action == :all && object != :all
|
33
|
+
klass = available_actions[object].collect { |x| "AMQP::Protocol::#{object.camelize}::#{x}" }
|
34
|
+
elsif action == :all && object == :all
|
35
|
+
klass = :any
|
36
|
+
elsif action != :all && object == :all
|
37
|
+
raise "This acl format is currently not supported"
|
38
|
+
exit
|
39
|
+
else
|
40
|
+
klass = "AMQP::Protocol::#{object.camelize}::#{action.camelize}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
klass = klass.join(', ') if klass.class == Array
|
44
|
+
@rules[klass] = [] unless @rules[klass].class == Array
|
45
|
+
|
46
|
+
name = properties.delete(:name)
|
47
|
+
properties[object] = name if name
|
48
|
+
cond = []
|
49
|
+
cond << ('@user =' << (user.is_a?(Regexp) ? "~ #{user.inspect}" : "= '#{user}'")) if user != :all
|
50
|
+
|
51
|
+
properties.each_pair do |p, value|
|
52
|
+
cond << ("frame.#{p} =" << (value.is_a?(Regexp) ? "~ #{value.inspect}" : "= '#{value}'"))
|
53
|
+
end
|
54
|
+
|
55
|
+
@rules[klass] << {:permission => permission, :user => user, :properties => cond.join(' and ') }
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.generate_properties(properties, object)
|
59
|
+
properties
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.set_default action
|
63
|
+
@default = action
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.build
|
67
|
+
require 'erb'
|
68
|
+
method = ERB.new(%q[
|
69
|
+
def filter(frame)
|
70
|
+
<%- if @rules.any? -%>
|
71
|
+
<%- @rules[:any].each do |rule| -%>
|
72
|
+
return :<%= rule[:permission] %> if <%= rule[:properties] %>
|
73
|
+
<%- end -%>
|
74
|
+
case frame # 4
|
75
|
+
<%- @rules.each_pair do |klass, r|-%>
|
76
|
+
<%- next if klass == :any -%>
|
77
|
+
when <%= klass %>
|
78
|
+
<%- r.each do |rule| -%>
|
79
|
+
return :<%= rule[:permission] %> if <%= rule[:properties] %>
|
80
|
+
<%- end -%>
|
81
|
+
<%- end -%>
|
82
|
+
end
|
83
|
+
<%- end -%>
|
84
|
+
:<%= @default %>
|
85
|
+
end
|
86
|
+
].gsub!(/^ /,''), nil, '>-%').result(binding)
|
87
|
+
LOGGER.debug "Generated filter method:\n#{method}"
|
88
|
+
RabbitCage::ClientConnection.class_eval method
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class RabbitCage
|
2
|
+
class ServerConnection < EventMachine::Connection
|
3
|
+
def self.request(host, port, client_side)
|
4
|
+
EventMachine.connect(host, port, self, client_side)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(conn)
|
8
|
+
@client_side = conn
|
9
|
+
end
|
10
|
+
|
11
|
+
def post_init
|
12
|
+
proxy_incoming_to(@client_side, 10240)
|
13
|
+
end
|
14
|
+
|
15
|
+
def unbind
|
16
|
+
@client_side.close_connection_after_writing
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/rabbitcage.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'amqp'
|
4
|
+
require 'logger'
|
5
|
+
require 'socket'
|
6
|
+
|
7
|
+
require 'rabbitcage/client_connection'
|
8
|
+
require 'rabbitcage/server_connection'
|
9
|
+
require 'rabbitcage/filter'
|
10
|
+
require 'rabbitcage/core_extensions'
|
11
|
+
LOGGER = Logger.new(STDOUT)
|
12
|
+
LOGGER.datetime_format = "%m-%d %H:%M:%S "
|
13
|
+
|
14
|
+
class RabbitCage
|
15
|
+
MAX_FAST_SHUTDOWN_SECONDS = 10
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def update_procline
|
19
|
+
$0 = "rabbitcage #{VERSION} - #{@@name} #{@@listen} - #{self.stats} cur/max/tot conns"
|
20
|
+
end
|
21
|
+
|
22
|
+
def stats
|
23
|
+
"#{@@counter}/#{@@maxcounter}/#{@@totalcounter}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def count
|
27
|
+
@@counter
|
28
|
+
end
|
29
|
+
|
30
|
+
def incr
|
31
|
+
@@totalcounter += 1
|
32
|
+
@@counter += 1
|
33
|
+
@@maxcounter = @@counter if @@counter > @@maxcounter
|
34
|
+
self.update_procline
|
35
|
+
@@counter
|
36
|
+
end
|
37
|
+
|
38
|
+
def decr
|
39
|
+
@@counter -= 1
|
40
|
+
if $server.nil?
|
41
|
+
LOGGER.info "Waiting for #{@@counter} connections to finish."
|
42
|
+
end
|
43
|
+
self.update_procline
|
44
|
+
EM.stop if $server.nil? and @@counter == 0
|
45
|
+
@@counter
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_config(block)
|
49
|
+
@@config = block
|
50
|
+
end
|
51
|
+
|
52
|
+
def config
|
53
|
+
@@config
|
54
|
+
end
|
55
|
+
|
56
|
+
def rabbit_host
|
57
|
+
@@remote_host
|
58
|
+
end
|
59
|
+
|
60
|
+
def rabbit_port
|
61
|
+
@@remote_port
|
62
|
+
end
|
63
|
+
def build_filter
|
64
|
+
RabbitCage.config.call
|
65
|
+
@@filter = Filter.build
|
66
|
+
end
|
67
|
+
|
68
|
+
def filter frame
|
69
|
+
Filter.filter frame
|
70
|
+
end
|
71
|
+
|
72
|
+
def graceful_shutdown(signal)
|
73
|
+
EM.stop_server($server) if $server
|
74
|
+
LOGGER.info "Received #{signal} signal. No longer accepting new connections."
|
75
|
+
LOGGER.info "Waiting for #{RabbitCage.count} connections to finish."
|
76
|
+
$server = nil
|
77
|
+
EM.stop if RabbitCage.count == 0
|
78
|
+
end
|
79
|
+
|
80
|
+
def fast_shutdown(signal)
|
81
|
+
EM.stop_server($server) if $server
|
82
|
+
LOGGER.info "Received #{signal} signal. No longer accepting new connections."
|
83
|
+
LOGGER.info "Maximum time to wait for connections is #{MAX_FAST_SHUTDOWN_SECONDS} seconds."
|
84
|
+
LOGGER.info "Waiting for #{RabbitCage.count} connections to finish."
|
85
|
+
$server = nil
|
86
|
+
EM.stop if RabbitCage.count == 0
|
87
|
+
Thread.new do
|
88
|
+
sleep MAX_FAST_SHUTDOWN_SECONDS
|
89
|
+
exit!
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def run(name, host, port, rhost, rport, log_level)
|
94
|
+
@@totalcounter = 0
|
95
|
+
@@maxcounter = 0
|
96
|
+
@@counter = 0
|
97
|
+
@@name = name
|
98
|
+
@@listen = "#{host}:#{port}"
|
99
|
+
@@remote_host = rhost
|
100
|
+
@@remote_port = rport
|
101
|
+
LOGGER.level = log_level
|
102
|
+
|
103
|
+
self.update_procline
|
104
|
+
EM.epoll
|
105
|
+
|
106
|
+
RabbitCage.build_filter
|
107
|
+
|
108
|
+
EM.run do
|
109
|
+
RabbitCage::ClientConnection.start(host, port)
|
110
|
+
trap('QUIT') do
|
111
|
+
self.graceful_shutdown('QUIT')
|
112
|
+
end
|
113
|
+
trap('TERM') do
|
114
|
+
self.fast_shutdown('TERM')
|
115
|
+
end
|
116
|
+
trap('INT') do
|
117
|
+
self.fast_shutdown('INT')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def version
|
123
|
+
yml = YAML.load(File.read(File.join(File.dirname(__FILE__), *%w[.. VERSION.yml])))
|
124
|
+
"#{yml[:major]}.#{yml[:minor]}.#{yml[:patch]}"
|
125
|
+
rescue
|
126
|
+
'unknown'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
VERSION = self.version
|
131
|
+
end
|
132
|
+
|
133
|
+
module Kernel
|
134
|
+
def config(&block)
|
135
|
+
RabbitCage.set_config(block)
|
136
|
+
end
|
137
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rabbitcage
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dominik Sander
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-20 00:00:00 +01:00
|
13
|
+
default_executable: rabbitcage
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: bacon
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: amqp
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.5
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: eventmachine
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.12.10
|
44
|
+
version:
|
45
|
+
description: RabbitMQ's access control capabilities are rather limited. RabbitCage enables fine-grated permission setups, you define which user can perform which AMQP method on which class.
|
46
|
+
email: git@dsander.de
|
47
|
+
executables:
|
48
|
+
- rabbitcage
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- LICENSE
|
53
|
+
- README.markdown
|
54
|
+
files:
|
55
|
+
- .document
|
56
|
+
- .gitignore
|
57
|
+
- LICENSE
|
58
|
+
- README.markdown
|
59
|
+
- Rakefile
|
60
|
+
- VERSION
|
61
|
+
- bin/rabbitcage
|
62
|
+
- lib/rabbitcage.rb
|
63
|
+
- lib/rabbitcage/client_connection.rb
|
64
|
+
- lib/rabbitcage/core_extensions.rb
|
65
|
+
- lib/rabbitcage/filter.rb
|
66
|
+
- lib/rabbitcage/server_connection.rb
|
67
|
+
- spec/rabbitcage_spec.rb
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: http://github.com/dsander/rabbitcage
|
71
|
+
licenses: []
|
72
|
+
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options:
|
75
|
+
- --charset=UTF-8
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "0"
|
89
|
+
version:
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.3.5
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: A AMQP firewall which allows to restrict user access to RabbitMQ using ACLs.
|
97
|
+
test_files:
|
98
|
+
- spec/rabbitcage_spec.rb
|
99
|
+
- spec/spec_helper.rb
|