tcell_agent 0.2.8 → 0.2.9
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.
- checksums.yaml +4 -4
- data/lib/tcell_agent/agent/event_processor.rb +14 -0
- data/lib/tcell_agent/agent/policy_manager.rb +48 -17
- data/lib/tcell_agent/configuration.rb +20 -7
- data/lib/tcell_agent/policies/http_redirect_policy.rb +2 -2
- data/lib/tcell_agent/rails.rb +0 -3
- data/lib/tcell_agent/rails/auth/authlogic.rb +46 -41
- data/lib/tcell_agent/rails/auth/devise.rb +45 -39
- data/lib/tcell_agent/rails/dlp.rb +126 -84
- data/lib/tcell_agent/rails/middleware/body_filter_middleware.rb +26 -25
- data/lib/tcell_agent/rails/middleware/context_middleware.rb +13 -9
- data/lib/tcell_agent/rails/middleware/global_middleware.rb +24 -22
- data/lib/tcell_agent/rails/middleware/headers_middleware.rb +17 -12
- data/lib/tcell_agent/rails/on_start.rb +52 -48
- data/lib/tcell_agent/rails/routes.rb +74 -75
- data/lib/tcell_agent/sensor_events/sensor.rb +4 -1
- data/lib/tcell_agent/servers/thin.rb +1 -0
- data/lib/tcell_agent/servers/unicorn.rb +82 -6
- data/lib/tcell_agent/start_background_thread.rb +24 -19
- data/lib/tcell_agent/version.rb +1 -1
- data/spec/lib/tcell_agent/agent/policy_manager_spec.rb +218 -0
- data/spec/lib/tcell_agent/policies/http_redirect_policy_spec.rb +2 -2
- data/spec/lib/tcell_agent/rails/middleware/redirect_middleware_spec.rb +89 -0
- data/spec/spec_helper.rb +9 -0
- metadata +30 -26
@@ -44,13 +44,16 @@ module TCellAgent
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
class TCellRedirectSensorEvent < TCellSensorEvent
|
47
|
-
def initialize(redirect_domain, original_domain, original_url, method, status_code, remote_addr,
|
47
|
+
def initialize(redirect_domain, original_domain, original_url, method, route_id, status_code, remote_addr, session_id=nil, user_id=nil)
|
48
48
|
super("redirect")
|
49
49
|
@raw_original_url = original_url
|
50
50
|
self["method"] = method
|
51
51
|
self["from_domain"] = original_domain
|
52
52
|
self["status_code"] = status_code
|
53
53
|
self["remote_addr"] = remote_addr
|
54
|
+
if route_id
|
55
|
+
self["rid"] = route_id
|
56
|
+
end
|
54
57
|
@raw_redirect_domain = redirect_domain
|
55
58
|
@user_id = user_id
|
56
59
|
@raw_session_id = session_id
|
@@ -18,6 +18,80 @@ Unicorn::HttpServer.class_eval do
|
|
18
18
|
original_join
|
19
19
|
end
|
20
20
|
|
21
|
+
# This gets called when unicorn receives the HUP signal to reload its config.
|
22
|
+
# Tcell also needs to ensure its config is reloaded and services are started
|
23
|
+
# or stopped accordingly
|
24
|
+
alias_method :original_load_config!, :load_config!
|
25
|
+
def load_config!
|
26
|
+
original_load_config!
|
27
|
+
|
28
|
+
TCellAgent::Instrumentation.safe_block("Reloading Tcell Config") do
|
29
|
+
new_config = TCellAgent::Configuration.new
|
30
|
+
TCellAgent.logger.debug("Reloading config")
|
31
|
+
TCellAgent.logger.debug(
|
32
|
+
"ENABLED:#{new_config.enabled}" +
|
33
|
+
"|ENABLE_EVENT_MANAGER:#{new_config.enable_event_manager}" +
|
34
|
+
"|ENABLE_EVENT_CONSUMER:#{new_config.enable_event_consumer}" +
|
35
|
+
"|ENABLE_POLICY_POLLING:#{new_config.enable_policy_polling}" +
|
36
|
+
"|ENABLE_INSTRUMENTATION:#{new_config.enable_instrumentation}" +
|
37
|
+
"|ENABLE_INTERCEPT_REQUESTS:#{new_config.enable_intercept_requests}"
|
38
|
+
)
|
39
|
+
old_config = TCellAgent.configuration
|
40
|
+
|
41
|
+
TCellAgent.configuration = new_config
|
42
|
+
|
43
|
+
if new_config.enabled ^ old_config.enabled
|
44
|
+
if new_config.enabled
|
45
|
+
TCellAgent.run_instrumentation("Unicorn")
|
46
|
+
|
47
|
+
else
|
48
|
+
TCellAgent.thread_agent.stop_event_processor
|
49
|
+
TCellAgent.thread_agent.stop_metrics_event_thread
|
50
|
+
TCellAgent.thread_agent.stop_policy_polling
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
if new_config.enable_event_manager ^ old_config.enable_event_manager
|
55
|
+
if new_config.enable_event_manager
|
56
|
+
TCellAgent.run_instrumentation("Unicorn Restart")
|
57
|
+
else
|
58
|
+
TCellAgent.thread_agent.stop_event_processor
|
59
|
+
end
|
60
|
+
else
|
61
|
+
# Just in case
|
62
|
+
if new_config.enable_event_manager
|
63
|
+
TCellAgent.thread_agent.ensure_event_processor_running
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if new_config.enable_event_consumer ^ old_config.enable_event_consumer
|
68
|
+
if new_config.enable_event_consumer
|
69
|
+
TCellAgent.thread_agent.ensure_metrics_event_thread_running
|
70
|
+
else
|
71
|
+
TCellAgent.thread_agent.stop_metrics_event_thread
|
72
|
+
end
|
73
|
+
else
|
74
|
+
# Just in case
|
75
|
+
if new_config.enable_event_consumer
|
76
|
+
TCellAgent.thread_agent.ensure_metrics_event_thread_running
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if new_config.enable_policy_polling ^ old_config.enable_policy_polling
|
81
|
+
if new_config.enable_policy_polling
|
82
|
+
TCellAgent.thread_agent.ensure_policy_polling_running
|
83
|
+
else
|
84
|
+
TCellAgent.thread_agent.stop_policy_polling
|
85
|
+
end
|
86
|
+
else
|
87
|
+
# Just in case
|
88
|
+
if new_config.enable_policy_polling
|
89
|
+
TCellAgent.thread_agent.ensure_policy_polling_running
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
21
95
|
# this only runs when preload_app=true because when preload_app=false
|
22
96
|
# the gems aren't loaded early enough for tcell to override
|
23
97
|
# the class definitions
|
@@ -25,13 +99,15 @@ Unicorn::HttpServer.class_eval do
|
|
25
99
|
def init_worker_process(work)
|
26
100
|
start_process = original_init_worker_process(work)
|
27
101
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
102
|
+
if TCellAgent.configuration.enabled && TCellAgent.configuration.should_instrument?
|
103
|
+
begin
|
104
|
+
TCellAgent.thread_agent.policy_polling_worker_mutex = Mutex.new
|
105
|
+
TCellAgent.thread_agent.policy_polling_thread = nil
|
106
|
+
TCellAgent.thread_agent.start
|
32
107
|
|
33
|
-
|
34
|
-
|
108
|
+
rescue Exception => e
|
109
|
+
TCellAgent.logger.error("Could not start thread agent. #{e.message}")
|
110
|
+
end
|
35
111
|
end
|
36
112
|
|
37
113
|
start_process
|
@@ -5,22 +5,22 @@ require 'tcell_agent/agent'
|
|
5
5
|
require 'tcell_agent/configuration'
|
6
6
|
require 'thread'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
require 'tcell_agent/rails' if defined?(Rails)
|
8
|
+
module TCellAgent
|
9
|
+
#require 'tcell_agent/sinatra' if defined?(Sinatra)
|
10
|
+
require 'tcell_agent/rails' if defined?(Rails)
|
12
11
|
|
13
|
-
|
12
|
+
def self.run_instrumentation(server_name)
|
14
13
|
|
15
|
-
|
14
|
+
require 'tcell_agent/rails/on_start' if defined?(Rails)
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
begin
|
17
|
+
TCellAgent.logger.debug("Instrumenting: #{server_name}")
|
18
|
+
TCellAgent.thread_agent.start
|
19
|
+
rescue Exception => e
|
20
|
+
TCellAgent.logger.error("Could not start thread agent. #{e.message}")
|
21
|
+
end
|
23
22
|
|
23
|
+
if TCellAgent.configuration.should_instrument?
|
24
24
|
Thread.abort_on_exception = TCellAgent.configuration.raise_exceptions
|
25
25
|
Thread.new do
|
26
26
|
|
@@ -34,36 +34,34 @@ if TCellAgent.configuration.enabled
|
|
34
34
|
TCellAgent.send_event(event)
|
35
35
|
end
|
36
36
|
|
37
|
-
if
|
37
|
+
if defined?(Rails)
|
38
38
|
TCellAgent::Instrumentation.safe_block("Instrumenting routes") do
|
39
39
|
TCellAgent::Instrumentation::Rails.instrument_routes
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
43
42
|
end
|
44
43
|
|
45
44
|
end
|
46
45
|
|
47
46
|
end
|
48
47
|
|
49
|
-
|
48
|
+
end
|
49
|
+
|
50
|
+
tcell_server = ENV["TCELL_AGENT_SERVER"]
|
50
51
|
|
52
|
+
if TCellAgent.configuration.should_instrument?
|
51
53
|
if (!(tcell_server && tcell_server == "mock"))
|
52
54
|
|
53
55
|
if (tcell_server && tcell_server == "webrick") || defined?(Rails::Server)
|
54
|
-
|
55
56
|
require("tcell_agent/servers/rails_server")
|
56
57
|
|
57
58
|
elsif (tcell_server && tcell_server == "thin") || defined?(Thin)
|
58
|
-
|
59
59
|
require("tcell_agent/servers/thin")
|
60
60
|
|
61
61
|
elsif (tcell_server && tcell_server == "puma") || defined?(Puma)
|
62
|
-
|
63
62
|
require("tcell_agent/servers/puma")
|
64
63
|
|
65
64
|
elsif (tcell_server && tcell_server == "unicorn") || defined?(Unicorn)
|
66
|
-
|
67
65
|
require("tcell_agent/servers/unicorn")
|
68
66
|
|
69
67
|
else
|
@@ -74,4 +72,11 @@ if TCellAgent.configuration.enabled
|
|
74
72
|
puts "[tCell.io] **********************************************************************"
|
75
73
|
end
|
76
74
|
end
|
75
|
+
|
76
|
+
else
|
77
|
+
|
78
|
+
# unicorn is always instrumented to support rolling restarts
|
79
|
+
if (tcell_server && tcell_server == "unicorn") || defined?(Unicorn)
|
80
|
+
require("tcell_agent/servers/unicorn")
|
81
|
+
end
|
77
82
|
end
|
data/lib/tcell_agent/version.rb
CHANGED
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module TCellAgent
|
4
|
+
module SensorEvents
|
5
|
+
|
6
|
+
describe Agent do
|
7
|
+
|
8
|
+
describe "#cache" do
|
9
|
+
context "with an existing cached file" do
|
10
|
+
|
11
|
+
context "with two processes" do
|
12
|
+
context "while one process is writing to the cached file" do
|
13
|
+
before(:each) do
|
14
|
+
TCellAgent.thread_agent.cache(
|
15
|
+
"http-redirect",
|
16
|
+
{
|
17
|
+
"app_id"=>"raftest-EyJZR",
|
18
|
+
"policy_id"=>"363b8b60-a9a8-11e5-bb10-a9372a56b8a7",
|
19
|
+
"data"=> {
|
20
|
+
"enabled"=>true,
|
21
|
+
"block"=>false,
|
22
|
+
"whitelist"=>[]
|
23
|
+
}
|
24
|
+
}
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should raise a timeout exception when the other process tries to write to it" do
|
29
|
+
file_lock_holder = ForkBreak::Process.new do |breakpoints|
|
30
|
+
|
31
|
+
original_dump = JSON.method(:dump)
|
32
|
+
JSON.stub(:dump) do |*args|
|
33
|
+
breakpoints << :before_dump
|
34
|
+
original_dump.call(*args)
|
35
|
+
end
|
36
|
+
|
37
|
+
TCellAgent.thread_agent.cache(
|
38
|
+
"http-redirect",
|
39
|
+
{
|
40
|
+
"app_id"=>"raftest-EyJZR",
|
41
|
+
"policy_id"=>"363b8b60-a9a8-11e5-bb10-a9372a56b8a7",
|
42
|
+
"data"=> {
|
43
|
+
"enabled"=>true,
|
44
|
+
"block"=>false,
|
45
|
+
"whitelist"=>[]
|
46
|
+
}
|
47
|
+
}
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
file_lock_holder.run_until(:before_dump).wait
|
52
|
+
|
53
|
+
logger = double("logger")
|
54
|
+
expect(TCellAgent).to receive(:logger).and_return(logger)
|
55
|
+
expect(logger).to receive(:error).with("execution expired")
|
56
|
+
|
57
|
+
TCellAgent.thread_agent.cache(
|
58
|
+
"http-redirect",
|
59
|
+
{
|
60
|
+
"app_id"=>"raftest-EyJZR",
|
61
|
+
"policy_id"=>"363b8b60-a9a8-11e5-bb10-a9372a56b8a7",
|
62
|
+
"data"=> {
|
63
|
+
"enabled"=>true,
|
64
|
+
"block"=>false,
|
65
|
+
"whitelist"=>[]
|
66
|
+
}
|
67
|
+
}
|
68
|
+
)
|
69
|
+
|
70
|
+
file_lock_holder.finish.wait
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
after(:each) do
|
75
|
+
File.delete(TCellAgent.configuration.cache_filename_with_app_id)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "with 10 processes updating the cached file" do
|
82
|
+
it "should update the cached file with all updates" do
|
83
|
+
processes = 5.times.map do |process_number|
|
84
|
+
ForkBreak::Process.new do |breakpoints|
|
85
|
+
original_dump = JSON.method(:dump)
|
86
|
+
JSON.stub(:dump) do |*args|
|
87
|
+
breakpoints << :before_dump
|
88
|
+
original_dump.call(*args)
|
89
|
+
end
|
90
|
+
|
91
|
+
TCellAgent.thread_agent.cache(
|
92
|
+
"process_#{process_number}",
|
93
|
+
{
|
94
|
+
"app_id"=>"raftest-EyJZR",
|
95
|
+
"policy_id"=>"policy_id",
|
96
|
+
"data"=> { "enabled"=>true }
|
97
|
+
}
|
98
|
+
)
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
processes.map(&:finish).map(&:wait)
|
104
|
+
|
105
|
+
policies = JSON.parse(open(TCellAgent.configuration.cache_filename_with_app_id).read)
|
106
|
+
|
107
|
+
File.delete(TCellAgent.configuration.cache_filename_with_app_id)
|
108
|
+
|
109
|
+
expect(policies).to eq({
|
110
|
+
"process_0"=>{"app_id"=>"raftest-EyJZR", "policy_id"=>"policy_id", "data"=>{"enabled"=>true}},
|
111
|
+
"process_1"=>{"app_id"=>"raftest-EyJZR", "policy_id"=>"policy_id", "data"=>{"enabled"=>true}},
|
112
|
+
"process_2"=>{"app_id"=>"raftest-EyJZR", "policy_id"=>"policy_id", "data"=>{"enabled"=>true}},
|
113
|
+
"process_3"=>{"app_id"=>"raftest-EyJZR", "policy_id"=>"policy_id", "data"=>{"enabled"=>true}},
|
114
|
+
"process_4"=>{"app_id"=>"raftest-EyJZR", "policy_id"=>"policy_id", "data"=>{"enabled"=>true}}
|
115
|
+
})
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe "#policies_from_cachefile" do
|
121
|
+
context "with no cache file" do
|
122
|
+
it "should return nil" do
|
123
|
+
|
124
|
+
expect(File).to receive(:exist?).with(/tcell\/cache\/tcell_agent.cache/).and_return(false)
|
125
|
+
expect_any_instance_of(TCellAgent::Agent).to_not receive(:processPolicyJson)
|
126
|
+
agent = TCellAgent::Agent.new(Process.pid)
|
127
|
+
|
128
|
+
expect(agent.policies).to eq({})
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
context "with a cache file present" do
|
133
|
+
context "that is empty" do
|
134
|
+
it "should raise a json error" do
|
135
|
+
cache_file = double("cache_file")
|
136
|
+
expect(File).to receive(:exist?).with(/tcell\/cache\/tcell_agent.cache/).and_return(true)
|
137
|
+
expect(File).to receive(:open).and_return(cache_file)
|
138
|
+
expect(cache_file).to receive(:flock).and_return(true)
|
139
|
+
expect(cache_file).to receive(:read).and_return("")
|
140
|
+
expect(cache_file).to receive(:close)
|
141
|
+
|
142
|
+
logger = double("logger")
|
143
|
+
expect(TCellAgent).to receive(:logger).and_return(logger)
|
144
|
+
expect(logger).to receive(:error).with("A JSON text must at least contain two octets!")
|
145
|
+
|
146
|
+
expect_any_instance_of(TCellAgent::Agent).to_not receive(:processPolicyJson)
|
147
|
+
|
148
|
+
agent = TCellAgent::Agent.new(Process.pid)
|
149
|
+
|
150
|
+
expect(agent.policies).to eq({})
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "that has content" do
|
155
|
+
context "that is malformed" do
|
156
|
+
it "should raise a json error" do
|
157
|
+
cache_file = double("cache_file")
|
158
|
+
expect(File).to receive(:exist?).with(/tcell\/cache\/tcell_agent.cache/).and_return(true)
|
159
|
+
expect(File).to receive(:open).and_return(cache_file)
|
160
|
+
expect(cache_file).to receive(:flock).and_return(true)
|
161
|
+
expect(cache_file).to receive(:read).and_return("bad_json")
|
162
|
+
expect(cache_file).to receive(:close)
|
163
|
+
|
164
|
+
logger = double("logger")
|
165
|
+
expect(TCellAgent).to receive(:logger).and_return(logger)
|
166
|
+
expect(logger).to receive(:error).with("757: unexpected token at 'bad_json'")
|
167
|
+
expect_any_instance_of(TCellAgent::Agent).to_not receive(:processPolicyJson)
|
168
|
+
|
169
|
+
agent = TCellAgent::Agent.new(Process.pid)
|
170
|
+
|
171
|
+
expect(agent.policies).to eq({})
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "that is an empty json object" do
|
176
|
+
it "should raise a json error" do
|
177
|
+
cache_file = double("cache_file")
|
178
|
+
expect(File).to receive(:exist?).with(/tcell\/cache\/tcell_agent.cache/).and_return(true)
|
179
|
+
expect(File).to receive(:open).and_return(cache_file)
|
180
|
+
expect(cache_file).to receive(:flock).and_return(true)
|
181
|
+
expect(cache_file).to receive(:read).and_return("{}")
|
182
|
+
expect(cache_file).to receive(:close)
|
183
|
+
|
184
|
+
expect(TCellAgent).to_not receive(:logger)
|
185
|
+
|
186
|
+
expect_any_instance_of(TCellAgent::Agent).to receive(:processPolicyJson).with({}, false)
|
187
|
+
|
188
|
+
agent = TCellAgent::Agent.new(Process.pid)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context "that is a valid policy" do
|
193
|
+
it "should set the policies" do
|
194
|
+
cache_file = double("cache_file")
|
195
|
+
expect(File).to receive(:exist?).with(/tcell\/cache\/tcell_agent.cache/).and_return(true)
|
196
|
+
expect(File).to receive(:open).and_return(cache_file)
|
197
|
+
expect(cache_file).to receive(:flock).and_return(true)
|
198
|
+
expect(cache_file).to receive(:read).and_return(
|
199
|
+
{
|
200
|
+
process_0: {app_id: "raftest-EyJZR", policy_id: "policy_id", data: {enabled: true}}
|
201
|
+
}.to_json
|
202
|
+
)
|
203
|
+
expect(cache_file).to receive(:close)
|
204
|
+
expect_any_instance_of(TCellAgent::Agent).to receive(:processPolicyJson).with(
|
205
|
+
{"process_0"=>{"app_id"=>"raftest-EyJZR", "policy_id"=>"policy_id", "data"=>{"enabled"=>true}}},
|
206
|
+
false
|
207
|
+
)
|
208
|
+
|
209
|
+
TCellAgent::Agent.new(Process.pid)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
end
|
@@ -46,14 +46,14 @@ module TCellAgent
|
|
46
46
|
http_redirect_from_json.enabled = false
|
47
47
|
http_redirect_from_json.block = true
|
48
48
|
http_redirect_from_json.whitelist = ["*.google.com"]
|
49
|
-
result = http_redirect_from_json.enforce("https://test.google.com", "www.test.com", "/path/a", "GET", "1.1.1.1", 400)
|
49
|
+
result = http_redirect_from_json.enforce("https://test.google.com", "www.test.com", "/path/a", "GET", "routex", "1.1.1.1", 400)
|
50
50
|
expect(result).to eq(nil)
|
51
51
|
end
|
52
52
|
it "domain enforce enabled true, block true" do
|
53
53
|
http_redirect_from_json.enabled = true
|
54
54
|
http_redirect_from_json.block = true
|
55
55
|
http_redirect_from_json.whitelist = ["good.com"]
|
56
|
-
result = http_redirect_from_json.enforce("https://www.google.com/abc/def", "localhost", "/path/a", "GET", "1.1.1.1", 400)
|
56
|
+
result = http_redirect_from_json.enforce("https://www.google.com/abc/def", "localhost", "/path/a", "GET", "routey", "1.1.1.1", 400)
|
57
57
|
expect(result).to eq("/")
|
58
58
|
end
|
59
59
|
end
|