Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added support for redis caching database #65

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
1 change: 1 addition & 0 deletions Gemfile_test
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ gem 'httpx'
gem 'typhoeus'
gem 'async-http'
gem 'ethon'
gem 'redis'
if RUBY_ENGINE == 'jruby'
gem 'activerecord-jdbc-adapter'
gem 'jdbc-sqlite3'
Expand Down
2 changes: 2 additions & 0 deletions lib/newrelic_security/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ module NewRelic::Security
HTTP_REQUEST = 'HTTP_REQUEST'
XPATH = 'XPATH'
LDAP = 'LDAP'
CACHING_DATA_STORE = 'CACHING_DATA_STORE'
REDIS = 'REDIS'
MONGO = 'MONGO'
SQLITE = 'SQLITE'
MYSQL = 'MYSQL'
Expand Down
35 changes: 35 additions & 0 deletions lib/newrelic_security/instrumentation-security/redis/chain.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module NewRelic::Security
module Instrumentation
module Redis
module Client
module Chain

def self.instrument!
::Redis::Client.class_eval do
include NewRelic::Security::Instrumentation::Redis::Client

if ::Redis::VERSION < '5'
alias_method :call_without_security, :call

def call(command, &block)
retval = nil
event = call_v_on_enter(command) { retval = call_without_security(command, &block) }
call_v_on_exit(event) { return retval }
end
else
alias_method :call_v_without_security, :call_v

def call_v(command, &block)
retval = nil
event = call_v_on_enter(command) { retval = call_v_without_security(command, &block) }
call_v_on_exit(event) { return retval }
end
end

end
end
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
require_relative 'prepend'
require_relative 'chain'

module NewRelic::Security
module Instrumentation
module Redis::Client

READ_MODES = %i[get getrange mapped_mget mget strlen].freeze
WRITE_MODES = %i[set mapped_mset mapped_msetnx mset msetnx psetex setex setnx].freeze
DELETE_MODES = %i[getdel del].freeze
UPDATE_MODES = %i[append decr decrby getex getset incr incrby incrbyfloat setrange].freeze

def call_v_on_enter(command)
event = nil
NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
hash = {}
hash[:type] = command[0]
hash[:arguments] = command[1..-1]
if READ_MODES.include?(command[0])
hash[:mode] = :read
elsif WRITE_MODES.include?(command[0])
hash[:mode] = :write
elsif DELETE_MODES.include?(command[0])
hash[:mode] = :delete
elsif UPDATE_MODES.include?(command[0])
hash[:mode] = :update
end
event = NewRelic::Security::Agent::Control::Collector.collect(CACHING_DATA_STORE, [hash], REDIS)
rescue => exception
NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
ensure
yield
return event
end

def call_v_on_exit(event)
NewRelic::Security::Agent.logger.debug "OnExit : #{self.class}.#{__method__}"
NewRelic::Security::Agent::Utils.create_exit_event(event)
rescue => exception
NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
ensure
yield
end

end
end
end

NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:redis, ::Redis::Client, ::NewRelic::Security::Instrumentation::Redis::Client)
25 changes: 25 additions & 0 deletions lib/newrelic_security/instrumentation-security/redis/prepend.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module NewRelic::Security
module Instrumentation
module Redis
module Client
module Prepend
include NewRelic::Security::Instrumentation::Redis::Client

if ::Redis::VERSION <= '5'
def call(command, &block)
retval = nil
event = call_v_on_enter(command) { retval = super }
call_v_on_exit(event) { return retval }
end
else
def call_v(command, &block)
retval = nil
event = call_v_on_enter(command) { retval = super }
call_v_on_exit(event) { return retval }
end
end
end
end
end
end
end
32 changes: 32 additions & 0 deletions test/helpers/database_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ module Test
POSTGRESQL_USER = 'postgres'
POSTGRESQL_DATABASE = 'postgres'

REDIS_HOST = 'localhost'
REDIS_PORT = '6380'

module DatabaseHelper
extend self

Expand Down Expand Up @@ -110,6 +113,35 @@ def remove_postgresql_container
end
end

REDIS_CONFIG = {
'Image' => 'redis:latest',
'name' => 'redis_test',
'HostConfig' => {
'PortBindings' => {
'6379/tcp' => [{ 'HostPort' => REDIS_PORT }]
}
}
}

def create_redis_container
image = Docker::Image.create('fromImage' => 'redis:latest')
image.refresh!
begin
Docker::Container.get('redis_test').remove(force: true)
rescue
end
container = Docker::Container.create(REDIS_CONFIG)
container.start
sleep 10
end

def remove_redis_container
begin
Docker::Container.get('redis_test').remove(force: true)
rescue
end
end

end
end
end
120 changes: 120 additions & 0 deletions test/newrelic_security/instrumentation-security/redis/redis_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
require 'redis'
require_relative '../../../test_helper'
require 'newrelic_security/instrumentation-security/redis/instrumentation'

module NewRelic::Security
module Test
module Instrumentation
class TestREDIS < Minitest::Test
@@before_all_flag = false

def setup
$event_list.clear()
NewRelic::Security::Agent::Control::HTTPContext.set_context({})
unless @@before_all_flag
NewRelic::Security::Test::DatabaseHelper.create_redis_container
@@before_all_flag = true
end
end

def test_set_get_fetch_delete
cache = Redis.new(host: REDIS_HOST, port: REDIS_PORT)
$event_list.clear()
res = cache.set 'greet', 'hello'
args = [{:type=>:set, :arguments=>["greet", "hello"], :mode=>:write}]
expected_event = NewRelic::Security::Agent::Control::Event.new(CACHING_DATA_STORE, args, REDIS)
assert_equal 'OK', res
assert_equal 1, NewRelic::Security::Agent::Control::Collector.get_event_count(CACHING_DATA_STORE)
assert_equal expected_event.caseType, $event_list[0].caseType
assert_equal expected_event.parameters, $event_list[0].parameters
assert_equal expected_event.eventCategory, $event_list[0].eventCategory
$event_list.clear()

res = cache.get 'greet'
args = [{:type=>:get, :arguments=>["greet"], :mode=>:read}]
expected_event = NewRelic::Security::Agent::Control::Event.new(CACHING_DATA_STORE, args, REDIS)
assert_equal 'hello', res
assert_equal 1, NewRelic::Security::Agent::Control::Collector.get_event_count(CACHING_DATA_STORE)
assert_equal expected_event.caseType, $event_list[0].caseType
assert_equal expected_event.parameters, $event_list[0].parameters
assert_equal expected_event.eventCategory, $event_list[0].eventCategory
$event_list.clear()

res = cache.getdel 'greet'
args = [{:type=>:getdel, :arguments=>["greet"], :mode=>:delete}]
expected_event = NewRelic::Security::Agent::Control::Event.new(CACHING_DATA_STORE, args, REDIS)
assert_equal 1, NewRelic::Security::Agent::Control::Collector.get_event_count(CACHING_DATA_STORE)
assert_equal "hello", res
assert_equal expected_event.caseType, $event_list[0].caseType
assert_equal expected_event.parameters, $event_list[0].parameters
assert_equal expected_event.eventCategory, $event_list[0].eventCategory
$event_list.clear()
end

def test_set_incr_get_flush
cache = Redis.new(host: REDIS_HOST, port: REDIS_PORT)
$event_list.clear()
res = cache.set 'counter', 0
args = [{:type=>:set, :arguments=>["counter", "0"], :mode=>:write}]
expected_event = NewRelic::Security::Agent::Control::Event.new(CACHING_DATA_STORE, args, REDIS)
assert_equal 1, NewRelic::Security::Agent::Control::Collector.get_event_count(CACHING_DATA_STORE)
assert_equal expected_event.caseType, $event_list[0].caseType
assert_equal expected_event.parameters, $event_list[0].parameters
assert_equal expected_event.eventCategory, $event_list[0].eventCategory
$event_list.clear()

res = cache.incr 'counter'
args = [{:type=>:incr, :arguments=>["counter"], :mode=>:update}]
expected_event = NewRelic::Security::Agent::Control::Event.new(CACHING_DATA_STORE, args, REDIS)
assert_equal 1, res
assert_equal 1, NewRelic::Security::Agent::Control::Collector.get_event_count(CACHING_DATA_STORE)
assert_equal expected_event.caseType, $event_list[0].caseType
assert_equal expected_event.parameters, $event_list[0].parameters
assert_equal expected_event.eventCategory, $event_list[0].eventCategory
$event_list.clear()

res = cache.get 'counter'
args = [{:type=>:get, :arguments=>["counter"], :mode=>:read}]
expected_event = NewRelic::Security::Agent::Control::Event.new(CACHING_DATA_STORE, args, REDIS)
assert_equal "1", res
assert_equal 1, NewRelic::Security::Agent::Control::Collector.get_event_count(CACHING_DATA_STORE)
assert_equal expected_event.caseType, $event_list[0].caseType
assert_equal expected_event.parameters, $event_list[0].parameters
assert_equal expected_event.eventCategory, $event_list[0].eventCategory
$event_list.clear()

res = cache.decr 'counter'
args = [{:type=>:decr, :arguments=>["counter"], :mode=>:update}]
expected_event = NewRelic::Security::Agent::Control::Event.new(CACHING_DATA_STORE, args, REDIS)
assert_equal 0, res
assert_equal 1, NewRelic::Security::Agent::Control::Collector.get_event_count(CACHING_DATA_STORE)
assert_equal expected_event.caseType, $event_list[0].caseType
assert_equal expected_event.parameters, $event_list[0].parameters
assert_equal expected_event.eventCategory, $event_list[0].eventCategory
$event_list.clear()

res = cache.del 'greet'
args = [{:type=>:del, :arguments=>["greet"], :mode=>:delete}]
expected_event = NewRelic::Security::Agent::Control::Event.new(CACHING_DATA_STORE, args, REDIS)
assert_equal 1, NewRelic::Security::Agent::Control::Collector.get_event_count(CACHING_DATA_STORE)
assert_equal 0, res
assert_equal expected_event.caseType, $event_list[0].caseType
assert_equal expected_event.parameters, $event_list[0].parameters
assert_equal expected_event.eventCategory, $event_list[0].eventCategory
$event_list.clear()
end

def teardown
$event_list.clear()
NewRelic::Security::Agent::Control::HTTPContext.reset_context
end

Minitest.after_run do
NewRelic::Security::Test::DatabaseHelper.remove_redis_container
end

end
end
end
end

Loading