Patric
2009-May-08 08:11 UTC
[Backgroundrb-devel] Backgroundrb in shared hosting environment
Hi all, I am running a linux shared hosting environment, and one of my users would like to make use of an app (Coupra Express) which uses backgroundrb to process certain tasks. This requires that I run backgroundrb as a service so that it is always available to the client. What I am trying to assess is if there are potential security risks in running this in a shared hosting environment. I assume that this service would be available to other users on the same server as well - if so are there any security issues that I should be aware of there? I read on http://www.ruby-forum.com/topic/69440 that backgroundrb can be configured to only accept connections from localhost (I believe this was the default behaviour)? Any information would be very much appreciated. Unfortunately I am completely unfamiliar with ruby, rake or backgroundrb and am attempting to pick up as much as possible as I research this topic, so if I have misspoken please feel free to correct me. Many thanks Patric
Gaël SECHAUD
2009-May-25 16:10 UTC
[Backgroundrb-devel] Backgroundrb in shared hosting environment
Hi,> I am running a linux shared hosting environment, and one of my users > would like to make use of an app (Coupra Express) which uses > backgroundrb to process certain tasks. This requires that I run > backgroundrb as a service so that it is always available to the client.I recently had the same problem: at first I thought of setting iptables rules, but it comes to be a pain, as I needed to set one rule by user running on my environment. An other solution was to patch the kernel with GRSec, but I''m not fond of this solution. So I come with a third solution: patching backgroundrb with a modification of mine (you will find the patch and the revision to which its applied), adding a password support. It''s quite a temporary solution, as I haven''t figured an other solution.> What I am trying to assess is if there are potential security risks in > running this in a shared hosting environment. I assume that this service > would be available to other users on the same server as well - if so are > there any security issues that I should be aware of there? I read on > http://www.ruby-forum.com/topic/69440 that backgroundrb can be > configured to only accept connections from localhost (I believe this was > the default behaviour)?You can configure backgroundrb to only accept connections from localhost, but I think it won''t solve your problem if you have multiples clients running on your host: even if backgroundrb is binded to localhost, other client who have solution on your host can still access other instance of backgroundrb, by tweaking their backgroundrb.yml (which I consider to be a security issue). Regards. -- SECHAUD Ga?l ----- How To ----- To add password support, just patch backgroundrb and add the following entry in your backgrounrb config (commonly RAILS_ROOT/config/backgroundrb.yml) :backgroundrb: [..] :password: Your_Password ----- revision info ----- svn info Path: . URL: http://svn.devjavu.com/backgroundrb/trunk Repository Root: http://svn.devjavu.com/backgroundrb Repository UUID: 69d54aea-511f-0410-a924-81c4482807e4 Revision: 331 Node Kind: directory Schedule: normal Last Changed Author: gethemant at gmail.com Last Changed Rev: 330 Last Changed Date: 2008-10-14 12:51:23 +0200 (Tue, 14 Oct 2008) ----- patch ----- diff -crB backgroundrb/lib/backgroundrb/bdrb_connection.rb backgroundrb-patched/lib/backgroundrb/bdrb_connection.rb *** backgroundrb/lib/backgroundrb/bdrb_connection.rb 2009-05-25 17:18:35.000000000 +0200 --- backgroundrb-patched/lib/backgroundrb/bdrb_connection.rb 2009-05-25 16:48:48.000000000 +0200 *************** *** 8,13 **** --- 8,14 ---- @server_port = port @cluster_conn = cluster_conn @connection_status = true + @password = BDRB_CONFIG[:backgroundrb][:password].nil? ? false : BDRB_CONFIG[:backgroundrb][:password] end *************** *** 66,71 **** --- 67,73 ---- end def dump_object data + data[:password] = @password establish_connection raise BackgrounDRb::BdrbConnError.new("Error while connecting to the backgroundrb server #{server_info}") unless @connection_status diff -crB backgroundrb/server/lib/master_worker.rb backgroundrb-patched/server/lib/master_worker.rb *** backgroundrb/server/lib/master_worker.rb 2009-05-25 17:18:35.000000000 +0200 --- backgroundrb-patched/server/lib/master_worker.rb 2009-05-25 16:50:53.000000000 +0200 *************** *** 25,31 **** end class MasterWorker ! attr_accessor :debug_logger include BackgrounDRb::BdrbServerHelper # receives requests from rails and based on request type invoke appropriate method def receive_data p_data --- 25,31 ---- end class MasterWorker ! attr_accessor :debug_logger,:password include BackgrounDRb::BdrbServerHelper # receives requests from rails and based on request type invoke appropriate method def receive_data p_data *************** *** 33,38 **** --- 33,45 ---- begin t_data = load_data b_data if t_data + # check password + if @password && t_data[:password] != @password + debug_logger.info("Invalid password : #{t_data.inspect}") + error_password(t_data) + return + end + case t_data[:type] # async method invocation when :async_invoke: async_method_invoke(t_data) *************** *** 55,60 **** --- 62,76 ---- end end + # Send password require info to the user + def error_password(t_data) + worker_name_key gen_worker_key(t_data[:worker],t_data[:worker_key]) + worker_instance = reactor.live_workers[worker_name_key] + info_response = { :error => "Password required / Wrong password" } + worker_instance ? (info_response[:status] = :running) : (info_response[:status] = :stopped) + send_object(info_response) + end + # Send worker info to the user def pass_worker_info(t_data) worker_name_key gen_worker_key(t_data[:worker],t_data[:worker_key]) *************** *** 163,168 **** --- 179,185 ---- # called whenever a new connection is made.Initializes binary data parser def post_init @tokenizer = Packet::BinParser.new + @password = BDRB_CONFIG[:backgroundrb][:password].nil? ? false : BDRB_CONFIG[:backgroundrb][:password] end def connection_completed; end end
Gaël SECHAUD
2009-May-28 15:16 UTC
[Backgroundrb-devel] Backgroundrb in shared hosting environment
Hi, Considering the problem of backgroundrb in shared hosting environment, I''ve come with another temporary patch, which allow encrypted communication between the MiddleMan and the server. I still haven''t found a better way to secure backgroundrb in shared hosting environment, but at least, it''s better than nothing. Regards. ----- revision info ----- svn info Path: . URL: http://svn.devjavu.com/backgroundrb/trunk Repository Root: http://svn.devjavu.com/backgroundrb Repository UUID: 69d54aea-511f-0410-a924-81c4482807e4 Revision: 331 Node Kind: directory Schedule: normal Last Changed Author: gethemant at gmail.com Last Changed Rev: 330 Last Changed Date: 2008-10-14 12:51:23 +0200 (Tue, 14 Oct 2008) ----- patch ----- diff -crB backgroundrb/lib/backgroundrb/bdrb_connection.rb backgroundrb-patched/lib/backgroundrb/bdrb_connection.rb *** backgroundrb/lib/backgroundrb/bdrb_connection.rb 2009-05-25 17:18:35.000000000 +0200 --- backgroundrb-patched/lib/backgroundrb/bdrb_connection.rb 2009-05-28 16:38:44.000000000 +0200 *************** *** 8,13 **** --- 8,15 ---- @server_port = port @cluster_conn = cluster_conn @connection_status = true + @password = BDRB_CONFIG[:backgroundrb][:password].nil? ? false : BDRB_CONFIG[:backgroundrb][:password] + @key = Digest::MD5.hexdigest(File.read(File.join(RAILS_ROOT, "config", "backgroundrb.yml"))) end *************** *** 65,74 **** end end def dump_object data establish_connection raise BackgrounDRb::BdrbConnError.new("Error while connecting to the backgroundrb server #{server_info}") unless @connection_status ! object_dump = Marshal.dump(data) dump_length = object_dump.length.to_s length_str = dump_length.rjust(9,''0'') --- 67,121 ---- end end + def decrypt_data(t_data) + if @key && t_data + data = Base64.decode64(t_data) + res = OpenSSL::Cipher::Cipher.new("aes-256-cbc") + res.decrypt + res.key = Digest::SHA2.hexdigest(@key) + decrypt_data = res.update(data) + decrypt_data << res.final + decrypt_data = Base64.decode64(decrypt_data) + tdata = decrypt_data.split("\b") + + t_data = [] + tdata.each do |t| + data = {} + t.split("\a").each do |e| + key_data = e.split("\r") + if key_data[1] + data[key_data[0].to_sym] = key_data[1].to_sym + else + data[key_data[0].to_sym] = "" + end + end + t_data << data + end + end + return t_data + end + def encrypt_data(data) + if @key + tdata = "" + data.each do |key, d| + tdata << "#{key}\r#{d}\a" + end + tdata = Base64.encode64(tdata) + res = OpenSSL::Cipher::Cipher.new("aes-256-cbc") + res.encrypt + res.key = Digest::SHA2.hexdigest(@key) + encrypt_data = res.update(tdata) + encrypt_data << res.final + data = Base64.encode64(encrypt_data) + end + return data + end + def dump_object data + data[:password] = @password establish_connection raise BackgrounDRb::BdrbConnError.new("Error while connecting to the backgroundrb server #{server_info}") unless @connection_status ! data = encrypt_data(data) object_dump = Marshal.dump(data) dump_length = object_dump.length.to_s length_str = dump_length.rjust(9,''0'') *************** *** 100,106 **** bdrb_response = nil @mutex.synchronize { bdrb_response = read_from_bdrb() } close_connection ! bdrb_response end def all_worker_info --- 147,153 ---- bdrb_response = nil @mutex.synchronize { bdrb_response = read_from_bdrb() } close_connection ! decrypt_data(bdrb_response) end def all_worker_info *************** *** 110,116 **** bdrb_response = nil @mutex.synchronize { bdrb_response = read_from_bdrb() } close_connection ! bdrb_response end def delete_worker p_data --- 157,163 ---- bdrb_response = nil @mutex.synchronize { bdrb_response = read_from_bdrb() } close_connection ! decrypt_data(bdrb_response) end def delete_worker p_data *************** *** 148,153 **** --- 195,201 ---- bdrb_response = nil @mutex.synchronize { bdrb_response = read_from_bdrb() } close_connection + bdrb_response = decrypt_data(bdrb_response) bdrb_response ? bdrb_response[:data] : nil end end *************** *** 170,175 **** --- 218,224 ---- bdrb_response = nil @mutex.synchronize { bdrb_response = read_from_bdrb(nil) } close_connection + bdrb_response = decrypt_data(bdrb_response) bdrb_response ? bdrb_response[:data] : nil end end diff -crB backgroundrb/server/lib/master_worker.rb backgroundrb-patched/server/lib/master_worker.rb *** backgroundrb/server/lib/master_worker.rb 2009-05-25 17:18:35.000000000 +0200 --- backgroundrb-patched/server/lib/master_worker.rb 2009-05-28 16:38:39.000000000 +0200 *************** *** 25,38 **** end class MasterWorker ! attr_accessor :debug_logger include BackgrounDRb::BdrbServerHelper # receives requests from rails and based on request type invoke appropriate method def receive_data p_data @tokenizer.extract(p_data) do |b_data| begin t_data = load_data b_data if t_data case t_data[:type] # async method invocation when :async_invoke: async_method_invoke(t_data) --- 25,94 ---- end class MasterWorker ! attr_accessor :debug_logger,:password, :key include BackgrounDRb::BdrbServerHelper + + def decrypt_data(t_data) + if @key && t_data + data = Base64.decode64(t_data) + res = OpenSSL::Cipher::Cipher.new("aes-256-cbc") + res.decrypt + res.key = Digest::SHA2.hexdigest(@key) + decrypt_data = res.update(data) + decrypt_data << res.final + decrypt_data = Base64.decode64(decrypt_data) + t_data = {} + tdata = decrypt_data.split("\a") + tdata.each do |e| + key_data = e.split("\r") + t_data[key_data[0].to_sym] = key_data[1].to_sym + end + end + return t_data + end + def encrypt_data(data) + if @key && data + if data.is_a?(Array) + tdata = [] + data.each do |e| + sdata = "" + e.each do |key, d| + sdata << "#{key}\r#{d}\a" + end + tdata << sdata + end + tdata = tdata.join("\b") + else + tdata = "" + data.each do |key, d| + tdata << "#{key}\r#{d}\a" + end + end + tdata = Base64.encode64(tdata) + res = OpenSSL::Cipher::Cipher.new("aes-256-cbc") + res.encrypt + res.key = Digest::SHA2.hexdigest(@key) + encrypt_data = res.update(tdata) + encrypt_data << res.final + data = Base64.encode64(encrypt_data) + end + return data + end + # receives requests from rails and based on request type invoke appropriate method def receive_data p_data @tokenizer.extract(p_data) do |b_data| begin t_data = load_data b_data if t_data + t_data = decrypt_data(t_data) + # check password + if @password && t_data[:password].to_s != @password + debug_logger.info("Invalid password : #{t_data.inspect}") + error_password(t_data) + return + end + case t_data[:type] # async method invocation when :async_invoke: async_method_invoke(t_data) *************** *** 55,67 **** end end # Send worker info to the user def pass_worker_info(t_data) worker_name_key = gen_worker_key(t_data[:worker],t_data[:worker_key]) worker_instance = reactor.live_workers[worker_name_key] info_response = { :worker => t_data[:worker],:worker_key => t_data[:worker_key]} worker_instance ? (info_response[:status] = :running) : (info_response[:status] = :stopped) ! send_object(info_response) end # collect all worker info in an array and send to the user --- 111,132 ---- end end + # Send password require info to the user + def error_password(t_data) + worker_name_key = gen_worker_key(t_data[:worker],t_data[:worker_key]) + worker_instance = reactor.live_workers[worker_name_key] + info_response = { :error => "Password required / Wrong password" } + worker_instance ? (info_response[:status] = :running) : (info_response[:status] = :stopped) + send_object(encrypt_data(info_response)) + end + # Send worker info to the user def pass_worker_info(t_data) worker_name_key = gen_worker_key(t_data[:worker],t_data[:worker_key]) worker_instance = reactor.live_workers[worker_name_key] info_response = { :worker => t_data[:worker],:worker_key => t_data[:worker_key]} worker_instance ? (info_response[:status] = :running) : (info_response[:status] = :stopped) ! send_object(encrypt_data(info_response)) end # collect all worker info in an array and send to the user *************** *** 71,77 **** worker_key = (value.worker_key.to_s).gsub(/#{value.worker_name}_?/,"") info_response << { :worker => value.worker_name,:worker_key => worker_key,:status => :running } end ! send_object(info_response) end # Delete the worker. Sends TERM signal to the worker process and removes --- 136,142 ---- worker_key = (value.worker_key.to_s).gsub(/#{value.worker_name}_?/,"") info_response << { :worker => value.worker_name,:worker_key => worker_key,:status => :running } end ! send_object(encrypt_data(info_response)) end # Delete the worker. Sends TERM signal to the worker process and removes *************** *** 155,161 **** # Receieve responses from workers and dispatch them back to the client def worker_receive p_data ! send_object(p_data) end def unbind; end --- 220,226 ---- # Receieve responses from workers and dispatch them back to the client def worker_receive p_data ! send_object(encrypt_data(p_data)) end def unbind; end *************** *** 163,168 **** --- 228,235 ---- # called whenever a new connection is made.Initializes binary data parser def post_init @tokenizer = Packet::BinParser.new + @password = BDRB_CONFIG[:backgroundrb][:password].nil? ? false : BDRB_CONFIG[:backgroundrb][:password] + @key = Digest::MD5.hexdigest(File.read(File.join(RAILS_ROOT, "config", "backgroundrb.yml"))) end def connection_completed; end end -- SECHAUD Ga?l -------------- next part -------------- A non-text attachment was scrubbed... Name: backgroundrb.patch Type: text/x-patch Size: 10506 bytes Desc: not available URL: <http://rubyforge.org/pipermail/backgroundrb-devel/attachments/20090528/ddd4a7fe/attachment.bin>