rabbitcage 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ rabbitcage.gemspec
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
@@ -0,0 +1,7 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Rabbitcage" do
4
+ it "fails" do
5
+ should.flunk "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bacon'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'rabbitcage'
7
+
8
+ Bacon.summary_on_exit
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