rabbitcage 0.1.0
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/.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
|