logstash-input-box_enterprise 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 46bc9c2e0e9ce97c7b006ef796c9b7c9eb9ff4c7
4
+ data.tar.gz: 4650dba0a79d54e40e36b6f5a4180171fc250c3e
5
+ SHA512:
6
+ metadata.gz: 7b75b18a4a08cc9792d69fa6058b5683ea5ae8156e3cf9a806a9e644b48d6ed1263363ffe54ccc948bfd9bace352f95ce7bc7880a9cff362a21063c1cc0f148c
7
+ data.tar.gz: 71bc121295ea12a04795ff8052ef047c23b65155937eb4f24e6373abedd73475edb07a4899be62b81155d3c6b081c6304319e2ddd350b76a1d8a1257e3493bca
data/CHANGELOG.md ADDED
@@ -0,0 +1,2 @@
1
+ ## 0.1.0
2
+ - Plugin created with the logstash plugin generator
data/CONTRIBUTORS ADDED
@@ -0,0 +1,10 @@
1
+ The following is a list of people who have contributed ideas, code, bug
2
+ reports, or in general have helped logstash along its way.
3
+
4
+ Contributors:
5
+ * -
6
+
7
+ Note: If you've sent us patches, bug reports, or otherwise contributed to
8
+ Logstash, and you aren't on the list above and want to be, please let us know
9
+ and we'll make sure you're here. Contributions from folks like you are what make
10
+ open source awesome.
data/DEVELOPER.md ADDED
@@ -0,0 +1,2 @@
1
+ # logstash-input-box_enterprise
2
+ Example input plugin. This should help bootstrap your effort to write your own input plugin!
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
data/LICENSE ADDED
@@ -0,0 +1,11 @@
1
+ Licensed under the Apache License, Version 2.0 (the "License");
2
+ you may not use this file except in compliance with the License.
3
+ You may obtain a copy of the License at
4
+
5
+ http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS,
9
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10
+ See the License for the specific language governing permissions and
11
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Logstash Plugin
2
+
3
+ This is a plugin for [Logstash](https://github.com/elastic/logstash).
4
+
5
+ It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
6
+
7
+ ## Documentation
8
+
9
+ Logstash provides infrastructure to automatically generate documentation for this plugin. We use the asciidoc format to write documentation so any comments in the source code will be first converted into asciidoc and then into html. All plugin documentation are placed under one [central location](http://www.elastic.co/guide/en/logstash/current/).
10
+
11
+ - For formatting code or config example, you can use the asciidoc `[source,ruby]` directive
12
+ - For more asciidoc formatting tips, see the excellent reference here https://github.com/elastic/docs#asciidoc-guide
13
+
14
+ ## Need Help?
15
+
16
+ Need help? Try #logstash on freenode IRC or the https://discuss.elastic.co/c/logstash discussion forum.
17
+
18
+ ## Developing
19
+
20
+ ### 1. Plugin Developement and Testing
21
+
22
+ #### Code
23
+ - To get started, you'll need JRuby with the Bundler gem installed.
24
+
25
+ - Create a new plugin or clone and existing from the GitHub [logstash-plugins](https://github.com/logstash-plugins) organization. We also provide [example plugins](https://github.com/logstash-plugins?query=example).
26
+
27
+ - Install dependencies
28
+ ```sh
29
+ bundle install
30
+ ```
31
+
32
+ #### Test
33
+
34
+ - Update your dependencies
35
+
36
+ ```sh
37
+ bundle install
38
+ ```
39
+
40
+ - Run tests
41
+
42
+ ```sh
43
+ bundle exec rspec
44
+ ```
45
+
46
+ ### 2. Running your unpublished Plugin in Logstash
47
+
48
+ #### 2.1 Run in a local Logstash clone
49
+
50
+ - Edit Logstash `Gemfile` and add the local plugin path, for example:
51
+ ```ruby
52
+ gem "logstash-filter-awesome", :path => "/your/local/logstash-filter-awesome"
53
+ ```
54
+ - Install plugin
55
+ ```sh
56
+ bin/logstash-plugin install --no-verify
57
+ ```
58
+ - Run Logstash with your plugin
59
+ ```sh
60
+ bin/logstash -e 'filter {awesome {}}'
61
+ ```
62
+ At this point any modifications to the plugin code will be applied to this local Logstash setup. After modifying the plugin, simply rerun Logstash.
63
+
64
+ #### 2.2 Run in an installed Logstash
65
+
66
+ You can use the same **2.1** method to run your plugin in an installed Logstash by editing its `Gemfile` and pointing the `:path` to your local plugin development directory or you can build the gem and install it using:
67
+
68
+ - Build your plugin gem
69
+ ```sh
70
+ gem build logstash-filter-awesome.gemspec
71
+ ```
72
+ - Install the plugin from the Logstash home
73
+ ```sh
74
+ bin/logstash-plugin install /your/local/plugin/logstash-filter-awesome.gem
75
+ ```
76
+ - Start Logstash and proceed to test the plugin
77
+
78
+ ## Contributing
79
+
80
+ All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
81
+
82
+ Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
83
+
84
+ It is more important to the community that you are able to contribute.
85
+
86
+ For more information about contributing, see the [CONTRIBUTING](https://github.com/elastic/logstash/blob/master/CONTRIBUTING.md) file.
@@ -0,0 +1,619 @@
1
+ # encoding: utf-8
2
+ require "logstash/inputs/base"
3
+ require "logstash/namespace"
4
+ require "rufus/scheduler"
5
+ require "socket" # for Socket.gethostname
6
+ require "logstash/plugin_mixins/http_client"
7
+ require "manticore"
8
+ require "openssl"
9
+ require "jwt"
10
+ require "json"
11
+ require "cgi"
12
+
13
+ MAX_FILE_SIZE = 10 * 2**20
14
+
15
+
16
+ class LogStash::Inputs::BoxEnterprise < LogStash::Inputs::Base
17
+ include LogStash::PluginMixins::HttpClient
18
+
19
+ config_name "box_enterprise"
20
+
21
+ # If undefined, Logstash will complain, even if codec is unused.
22
+ default :codec, "json"
23
+
24
+ # Set how many messages you want to pull with each request
25
+ #
26
+ # The default, `100`, means to fetch 100 events at a time.
27
+ # The maximum value is 500.
28
+ config :chunk_size, :validate => :number, :default => 100
29
+
30
+ # Schedule of when to periodically poll from the urls
31
+ # Format: A hash with
32
+ # + key: "cron" | "every" | "in" | "at"
33
+ # + value: string
34
+ # Examples:
35
+ # a) { "every" => "1h" }
36
+ # b) { "cron" => "* * * * * UTC" }
37
+ # See: rufus/scheduler for details about different schedule options and value string format
38
+ config :schedule, :validate => :hash, :required => true
39
+
40
+ # The Oauth2 Client ID used to access the Box.com API
41
+ #
42
+ # Format: A string wtih the client ID
43
+ config :client_id, :validate => :string, :required => true
44
+
45
+ # The Oauth2 Client secret to pull the events
46
+ # Here to allow it via enviornment variable
47
+ #
48
+ # Format: A string with the client secret to send
49
+ config :client_secret_env, :validate => :string
50
+
51
+ # The Oauth2 Client secret to pull events, stored in a file
52
+ #
53
+ # Format: A valid path containing the client_secret
54
+ config :client_secret_file, :validate => :path
55
+
56
+ # The Box.com Enterprise ID associated with the customer.
57
+ # Used in the JWT
58
+ #
59
+ # Format: String
60
+ config :enterprise_id, :validate => :string, :required => true
61
+
62
+ # The kid for the jwt that corresponds to the uploaded key.
63
+ # The corresponding private key must be accessible by this file.
64
+ #
65
+ # Format: String
66
+ config :kid, :validate => :string, :required => true
67
+
68
+ # The algorithim that the private/public key pair used
69
+ # Supported values: RS256, RS384, or RS512
70
+ #
71
+ # Format: String containing one of the above values
72
+ config :algo, :validate => :string, :default => "RS256"
73
+
74
+ # The file where the private key is stored.
75
+ #
76
+ # Format: Filepath
77
+ config :private_key_file, :validate => :path, :required => true
78
+
79
+ # The private key password stored in an envionrment.
80
+ # WARNING: Avoid storing the private key password directly in this file.
81
+ # This method is provided solely to add the key password via environment variable.
82
+ # This will contain the password for the private key for the Box.com instance.
83
+ #
84
+ # Format: File path
85
+ config :private_key_pass_env, :validate => :string
86
+
87
+ # The file in which the password for private key
88
+ # WARNING: This file should be VERY carefully monitored.
89
+ # This will contain the private_key which can have a lot access to your Box.com instance.
90
+ # It cannot be stressed enough how important it is to protect this file.
91
+ #
92
+ # Format: File path
93
+ config :private_key_pass_file, :validate => :path
94
+
95
+ # The base filename to store the pointer to the current location in the logs
96
+ # This file will be renamed with each new reference to limit loss of this data
97
+ # The location will need at least write and execute privs for the logstash user
98
+ # This parameter is not required, however on start logstash will ship all logs to your SIEM.
99
+ #
100
+ # Format: Filepath
101
+ # This is not the filepath of the file itself, but rather the base name of the file
102
+ config :state_file_base, :validate => :string
103
+
104
+ # The events to filter on using Box.com event filter
105
+ # This an API filter, not local, fetch only events that are needed
106
+ #
107
+ # Format: Comma separated string with the events to filter on
108
+ # Use: https://docs.box.com/reference#section-enterprise-events
109
+ config :event_type, :validate => :string
110
+
111
+ # The date and time after which to fetch events
112
+ #
113
+ # Format: string with a RFC 3339 formatted date (e.g. 2016-10-09T22:25:06-07:00)
114
+ config :created_after, :validate => :string
115
+
116
+ # The date and time before which to fetch events
117
+ #
118
+ # Format: string with a RFC 3339 formatted date (e.g. 2016-10-09T22:25:06-07:00)
119
+ config :created_before, :validate => :string
120
+
121
+ # Define the target field for placing the received data.
122
+ # If this setting is omitted, the data will be stored at the root (top level) of the event.
123
+ #
124
+ # Format: String
125
+ config :target, :validate => :string
126
+
127
+ # If you'd like to work with the request/response metadata.
128
+ # Set this value to the name of the field you'd like to store a nested
129
+ # hash of metadata.
130
+ #
131
+ # Format: String
132
+ config :metadata_target, :validate => :string, :default => '@metadata'
133
+
134
+ public
135
+ Schedule_types = %w(cron every at in)
136
+ def register
137
+
138
+ algo_types = %w(RS256 RS384 RS512)
139
+ msg_invalid_algo = "Invalid config. Algo string must contain " +
140
+ "exactly one of the following strings - RS256, RS384, or RS512"
141
+ raise LogStash::ConfigurationError, msg_invalid_algo unless algo_types.include?(@algo)
142
+
143
+ unless (@chunk_size > 0 and @chunk_size <= 500)
144
+ raise LogStash::ConfigurationError, "Chunk size must be between 1 and 500"
145
+ end
146
+
147
+ if (@created_after)
148
+ begin
149
+ @created_after = DateTime.parse(@created_after).rfc3339()
150
+ rescue ArgumentError => e
151
+ raise LogStash::ConfigurationError, "created_after must be of the form " +
152
+ "yyyy-MM-dd’‘T’‘HH:mm:ssZZ, e.g. 2013-01-01T12:00:00-07:00."
153
+ end
154
+ @created_after = CGI.escape(@created_after)
155
+ end
156
+
157
+ if (@created_before)
158
+ begin
159
+ @created_before = DateTime.parse(@created_before).rfc3339()
160
+ rescue ArgumentError => e
161
+ raise LogStash::ConfigurationError, "created_before must be of the form " +
162
+ "yyyy-MM-dd’‘T’‘HH:mm:ssZZ, e.g. 2013-01-01T12:00:00-07:00."
163
+ end
164
+ @created_before = CGI.escape(@created_before)
165
+ end
166
+
167
+ @event_type = CGI.escape(@event_type) if @event_type
168
+
169
+ if (@private_key_pass_env and @private_key_pass_file)
170
+ raise LogStash::ConfigurationError, "Both private_key_file and private_key_env cannot be set. Please select one for use."
171
+ end
172
+ unless (@private_key_pass_env or @private_key_pass_file)
173
+ raise LogStash::ConfigurationError, "Both private_key_file and private_key_env cannot be empty. Please select one for use."
174
+ end
175
+
176
+ if (@private_key_pass_file)
177
+ begin
178
+ if (File.size(@private_key_pass_file) > MAX_FILE_SIZE)
179
+ raise LogStash::ConfigurationError, "The private key password file is too large to map"
180
+ else
181
+ @private_key_pass = File.read(@private_key_pass_file).chomp
182
+ @logger.info("Successfully opened private_key_pass_file",:private_key_pass_file => @private_key_pass_file)
183
+ end
184
+ rescue LogStash::ConfigurationError
185
+ raise
186
+ # Some clean up magic to cover the stuff below.
187
+ # This will keep me from stomping on signal interrupts and ctrl+c
188
+ rescue SignalException, Interrupt, SyntaxError
189
+ raise
190
+ rescue Exception => e
191
+ # This is currently a bug in logstash, confirmed here:
192
+ # https://discuss.elastic.co/t/logstash-configurationerror-but-configurationok-logstash-2-4-0/65727/2
193
+ # Will need to determine the best way to handle this
194
+ # Rather than testing all error conditions, this can just display them.
195
+ # Should figure out a way to display this in a better fashion.
196
+ raise LogStash::ConfigurationError, e.inspect
197
+ end
198
+ else
199
+ @private_key_pass = @private_key_pass_env
200
+ end
201
+
202
+ if (@client_secret_env and @client_secret_file)
203
+ raise LogStash::ConfigurationError, "Both client_secret_file and client_secret_env cannot be set. Please select one for use."
204
+ end
205
+ unless (@client_secret_env or @client_secret_file)
206
+ raise LogStash::ConfigurationError, "Both client_secret_file and client_secret_env cannot be empty. Please select one for use."
207
+ end
208
+
209
+ if (@client_secret_file)
210
+ begin
211
+ if (File.size(@client_secret_file) > MAX_FILE_SIZE)
212
+ raise LogStash::ConfigurationError, "The client secret file is too large to map"
213
+ else
214
+ @client_secret = File.read(@client_secret_file).chomp
215
+ @logger.info("Successfully opened client_secret_file",:client_secret_file => @client_secret_file)
216
+ end
217
+ rescue LogStash::ConfigurationError
218
+ raise
219
+ # Some clean up magic to cover the stuff below.
220
+ # This will keep me from stomping on signal interrupts and ctrl+c
221
+ rescue SignalException, Interrupt, SyntaxError
222
+ raise
223
+ rescue Exception => e
224
+ # This is currently a bug in logstash, confirmed here:
225
+ # https://discuss.elastic.co/t/logstash-configurationerror-but-configurationok-logstash-2-4-0/65727/2
226
+ # Will need to determine the best way to handle this
227
+ # Rather than testing all error conditions, this can just display them.
228
+ # Should figure out a way to display this in a better fashion.
229
+ raise LogStash::ConfigurationError, e.inspect
230
+ end
231
+ else
232
+ @client_secret = @client_secret_env
233
+ end
234
+
235
+ if (File.size(@private_key_file) < MAX_FILE_SIZE)
236
+ begin
237
+ @private_key = OpenSSL::PKey::RSA.new(File.read(@private_key_file),@private_key_pass)
238
+ rescue SignalException, Interrupt, SyntaxError
239
+ raise
240
+ # This is currently a bug in logstash, confirmed here:
241
+ # https://discuss.elastic.co/t/logstash-configurationerror-but-configurationok-logstash-2-4-0/65727/2
242
+ # Will need to determine the best way to handle this
243
+ # Rather than testing all error conditions, this can just display them.
244
+ # Should figure out a way to display this in a better fashion.
245
+ rescue Exception => e
246
+ raise LogStash::ConfigurationError, e.inspect
247
+ end
248
+ else
249
+ raise LogStash::ConfigurationError, "The private key file appears to be too big to be mapped."
250
+ end
251
+
252
+
253
+
254
+ if (@state_file_base)
255
+ dir_name = File.dirname(@state_file_base)
256
+ ## Generally the state file directory will have the correct permissions
257
+ ## so check for that case first.
258
+ if (File.readable?(dir_name) and File.executable?(dir_name) and
259
+ File.writable?(dir_name))
260
+ @state_file = Dir[@state_file_base + "*"].sort.last
261
+ else
262
+ ## Build one message for the rest of the issues
263
+ access_message = "Could not access the state file dir" +
264
+ "#{dir_name} for the following reasons: "
265
+ unless (File.readable?(dir_name))
266
+ access_message << "Cannot read #{dir_name}."
267
+ end
268
+ unless (File.executable?(dir_name))
269
+ access_message << "Cannot list directory or perform special" +
270
+ "operations on #{dir_name}."
271
+ end
272
+ unless (File.writable?(dir_name))
273
+ access_message << "Cannot write to #{dir_name}."
274
+ end
275
+ access_message << "Please provide the appopriate permissions."
276
+ raise LogStash::ConfigurationError, access_message
277
+ end
278
+
279
+ # There is a state file so get the state data from it.
280
+ if (@state_file)
281
+ @next_stream_position = @state_file.slice(/(?<state_file>#{@state_file_base})(?<state>[0-9]+)/,'state')
282
+ # If not create the state file
283
+ else
284
+ begin
285
+ @state_file = @state_file_base + "start"
286
+ @logger.info("Created base state_file", :state_file => @state_file)
287
+ # 'touch' a file to keep the conditional from happening later
288
+ File.open(@state_file, "w") {}
289
+ # Some clean up magic to cover the stuff below.
290
+ # This will keep me from stomping on signal interrupts and ctrl+c
291
+ rescue SignalException, Interrupt, SyntaxError
292
+ raise
293
+ rescue Exception => e
294
+ raise LogStash::ConfigurationError, "Could not create #{@statefile}. " +
295
+ "Error: #{e.inspect}."
296
+ end
297
+ end
298
+ end
299
+
300
+
301
+ # The auth URL from box that leverages oauth
302
+ @auth_url = "https://api.box.com/oauth2/token"
303
+ @event_url = "https://api.box.com/2.0/events"
304
+ @host = Socket.gethostname
305
+
306
+ # Generate a random alpha-numeric string that is 128 chars long
307
+ jti = (0...128).map { (('a'..'z').to_a + ('A'..'Z').to_a)[rand(52)] }.join
308
+ @payload = { :iss => @client_id, # The Client ID of the service that created the JWT assertion.
309
+ :sub => @enterprise_id, # enterprise_id for a token specific to an enterprise when creating and managing app users.
310
+ :box_sub_type => 'enterprise', # “enterprise” or “user” depending on the type of token being requested in the sub claim.
311
+ :aud => @auth_url, # Always “https://api.box.com/oauth2/token” for OAuth2 token requests
312
+ :jti => jti, # A unique identifier specified by the client for this JWT. This is a unique string that is at least 16 characters and at most 128 characters.
313
+ :exp => 0 } # The unix time as to when this JWT will expire.
314
+ ## This can be set to a maximum value of 60 seconds beyond the issue time. Note: It is recommended to set this value to less than the maximum allowed 60 seconds.
315
+ ## Note: It is recommended to set this value to less than the maximum allowed 60 seconds.
316
+
317
+ @header = { :kid => @kid, # Public Key ID generated by Box and provided upon submission of a Public Key. Identifies which Public Key a client is using.
318
+ :alg => @algo, # The algorithm used to verify the signature. Values may only be set to: “RS256″, “RS384″, or “RS512.
319
+ :typ => "JWT" } # Type of token. Default is “JWT” to specify a JSON Web Token (JWT).
320
+
321
+ @logger.debug("JWT created", :jsot => @payload)
322
+
323
+ end # def register
324
+
325
+ def run(queue)
326
+
327
+ auth_token = "" ## Empty string to treat as a reference to pass around functions
328
+ msg_invalid_schedule = "Invalid config. schedule hash must contain " +
329
+ "exactly one of the following keys - cron, at, every or in"
330
+
331
+ raise LogStash::ConfigurationError, msg_invalid_schedule if @schedule.keys.length !=1
332
+ schedule_type = @schedule.keys.first
333
+ schedule_value = @schedule[schedule_type]
334
+ raise LogStash::ConfigurationError, msg_invalid_schedule unless Schedule_types.include?(schedule_type)
335
+
336
+ @scheduler = Rufus::Scheduler.new(:max_work_threads => 1)
337
+
338
+ #as of v3.0.9, :first_in => :now doesn't work. Use the following workaround instead
339
+ opts = schedule_type == "every" ? { :first_in => 0.01 } : {}
340
+ opts[:overlap] = false;
341
+
342
+ params_event = {:stream_type => "admin_logs"}
343
+ params_event[:limit] = @chunk_size
344
+ params_event[:created_after] = @created_after if @created_after
345
+ params_event[:created_before] = @created_before if @created_before
346
+ params_event[:event_type] = @event_type if @event_type
347
+
348
+ @scheduler.send(schedule_type, schedule_value, opts) { run_once(queue,auth_token,params_event) }
349
+
350
+ @scheduler.join
351
+
352
+ end # def run
353
+ private
354
+ def run_once(queue,auth_token,params_event)
355
+
356
+ run_fetcher(queue,auth_token,params_event)
357
+
358
+ end
359
+
360
+ def run_fetcher(queue,auth_token,params_event)
361
+
362
+ @continue = true
363
+
364
+ if auth_token.nil? or auth_token.empty?
365
+ handle_auth(queue, auth_token)
366
+ end
367
+
368
+ begin
369
+
370
+ #loop_count = 0
371
+ while @continue and !stop?
372
+
373
+ if @next_stream_position
374
+ params_event[:stream_position] = @next_stream_position
375
+ end
376
+
377
+ @logger.debug("Calling URL",
378
+ :event_url => @event_url,
379
+ :params => params_event,
380
+ :auth_set => auth_token.length > 0)
381
+
382
+
383
+ started = Time.now
384
+ client.async.get(@event_url, params: params_event, headers: {"Authorization" => "Bearer #{auth_token}"}).
385
+ on_success { |response | handle_success(queue, response, auth_token, @event_url, Time.now - started) }.
386
+ on_failure { |exception | handle_failure(queue, exception, @event_url, Time.now - started) }
387
+
388
+ client.execute!
389
+
390
+ #puts loop_count
391
+ #loop_count += 1
392
+
393
+ end
394
+
395
+ # Some clean up magic to cover the stuff below.
396
+ # This will keep me from stomping on signal interrupts and ctrl+c
397
+ rescue SignalException, Interrupt, SyntaxError
398
+ raise
399
+
400
+ rescue Exception => e
401
+ @logger.fatal("Could not call URL",
402
+ :url => @event_url,
403
+ :params => params_event,
404
+ :auth_set => auth_token.length > 0,
405
+ :exception => e.inspect)
406
+ raise
407
+ ensure
408
+ if (@state_file_base && @state_file != "#{@state_file_base}#{@next_stream_position ||= 'start'}" )
409
+ begin
410
+ #puts "Old state file: #{@state_file}"
411
+ File.rename(@state_file,@state_file_base + @next_stream_position)
412
+ rescue SignalException, Interrupt, SyntaxError
413
+ raise
414
+ rescue Exception => e
415
+ @logger.fatal("Could not rename file",
416
+ :old_file => @state_file,
417
+ :new_file => @state_file_base + @next_stream_position,
418
+ :exception => e.inspect)
419
+ raise
420
+ end
421
+ @state_file = @state_file_base + @next_stream_position
422
+ #puts "New state file: #{@state_file}"
423
+ end
424
+ end
425
+
426
+
427
+ end
428
+
429
+ private
430
+ def handle_auth(queue, auth_token)
431
+
432
+ @logger.debug("Authenticating to box.com")
433
+ ## clear out the old auth token if it exists
434
+ auth_token.clear
435
+
436
+ @payload[:exp] = Time.now.to_i + 30
437
+
438
+ @logger.debug("Created JWT json",
439
+ :json => @payload)
440
+ token = JWT.encode(@payload, @private_key, @algo, @header)
441
+
442
+ response = client.post(@auth_url, params: {grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", client_secret: @client_secret, client_id: @client_id, assertion: token}).
443
+ on_failure { |exception | handle_failure(queue, exception, @auth_url, nil) }
444
+
445
+ begin
446
+ if response.body.length > 0 and response.code == 200
447
+ @logger.info("Successfully authenticated to box.com")
448
+ auth_token << JSON.parse(response.body)["access_token"]
449
+ #puts auth_token ## TODO: Remove testing code
450
+ else
451
+ @continue = false
452
+ handle_unknown_error(queue, response, nil, nil)
453
+ end
454
+ rescue NoMethodError => e
455
+ @continue = false
456
+ end
457
+
458
+
459
+ end # end handle_auth
460
+
461
+ private
462
+ def handle_success(queue, response,auth_token, requested_url, exec_time)
463
+
464
+ case response.code
465
+ when 200
466
+ if response.body.length > 0
467
+ response_hash = JSON.parse(response.body)
468
+ #puts "current stream position #{@next_stream_position ||= 'nil'}"
469
+ #puts "Next stream position #{response_hash["next_stream_position"]}"
470
+ begin
471
+ if (@next_stream_position && @next_stream_position == response_hash.fetch("next_stream_position") )
472
+ @continue = false
473
+ response_hash["entries"] = []
474
+ end
475
+
476
+ @next_stream_position = response_hash.fetch("next_stream_position")
477
+ #puts response_hash["chunk_size"]
478
+ rescue KeyError
479
+ @logger.error("Could not parse next_stream_position out of the response",:response_body => response_hash)
480
+ @continue = false
481
+ end
482
+
483
+
484
+ else
485
+ @continue = false
486
+ response_hash = {"entries" => {} }
487
+ end
488
+
489
+ #response_hash["entries"].each do |entry|
490
+ @codec.decode(response_hash["entries"].to_json) do |decoded|
491
+ #event = @target ? LogStash::Event.new(@target => entry) : LogStash::Event.new(entry)
492
+ event = @target ? LogStash::Event.new(@target => decoded.to_hash) : decoded
493
+ apply_metadata(event,requested_url, response, exec_time)
494
+ decorate(event)
495
+ queue << event
496
+ end
497
+
498
+ when 401
499
+
500
+ @logger.warn("Auth failed, calling handle_auth to reauthenticate.")
501
+ handle_auth(queue, auth_token)
502
+
503
+ else
504
+
505
+ @continue = false
506
+ handle_unknown_error(queue,response,requested_url, exec_time)
507
+
508
+ end
509
+
510
+ end # end handle_success
511
+
512
+ def handle_unknown_error(queue,response, requested_url, exec_time)
513
+ @continue = false
514
+
515
+ event_hash = {
516
+ "Box-Plugin-Status" => "Box.com server error",
517
+ "Box-Error-Headers" => response.headers,
518
+ "Box-Error-Code" => response.code,
519
+ "Box=Error-Msg" => JSON.parse(response.body)["message"],
520
+ "Box-Error-Raw-Msg" => response.body
521
+ }
522
+
523
+ event = @target ? LogStash::Event.new(@target => event_hash) : LogStash::Event.new(event_hash)
524
+ event.tag("_box_response_failure")
525
+ apply_metadata(event,requested_url, response, exec_time)
526
+ decorate(event)
527
+ queue << event
528
+
529
+ return nil
530
+
531
+ end
532
+
533
+
534
+ def handle_failure(queue, exception, requested_url, exec_time)
535
+ @continue = false
536
+ @logger.warn("Client connection error",
537
+ :exception => exception.inspect)
538
+
539
+
540
+ event_message_hash = {
541
+ "Box-Plugin-Status" => "Client Connection error",
542
+ "Connection-Error" => exception.message,
543
+ "backtrace" => exception.backtrace
544
+ }
545
+
546
+ event_hash = {"http_request_failure" => event_message_hash }
547
+
548
+ event = @target ? LogStash::Event.new(@target => event_hash) : LogStash::Event.new(event_hash)
549
+ event.tag("_http_request_failure")
550
+ apply_metadata(event,requested_url, nil, exec_time)
551
+ decorate(event)
552
+ queue << event
553
+
554
+ return nil
555
+ end
556
+
557
+ private
558
+ def apply_metadata(event, requested_url, response=nil, exec_time=nil)
559
+ return unless @metadata_target
560
+
561
+ m = {}
562
+
563
+ m = {
564
+ "host" => @host,
565
+ "url" => requested_url,
566
+ "runtime_seconds" => exec_time
567
+ }
568
+
569
+ if response
570
+ m["code"] = response.code
571
+ m["response_headers"] = response.headers
572
+ m["response_message"] = response.message
573
+ m["retry_count"] = response.times_retried
574
+ end
575
+
576
+ event.set(@metadata_target,m)
577
+
578
+ end
579
+
580
+
581
+ public
582
+ def stop
583
+ # nothing to do in this case so it is not necessary to define stop
584
+ # examples of common "stop" tasks:
585
+ # * close sockets (unblocking blocking reads/accepts)
586
+ # * cleanup temporary files
587
+ # * terminate spawned threads
588
+ begin
589
+ @scheduler.stop
590
+ rescue NoMethodError => e
591
+ unless (e.message == "undefined method `stop' for nil:NilClass")
592
+ raise
593
+ end
594
+ rescue Exception => e
595
+ @logger.warn("Undefined error", :exception => e.inspect)
596
+ raise
597
+ ensure
598
+ if (@state_file_base && @state_file != "#{@state_file_base}#{@next_stream_position ||= 'start'}" )
599
+
600
+ begin
601
+ #puts "Old state file: #{@state_file}"
602
+ File.rename(@state_file,@state_file_base + @next_stream_position)
603
+ rescue SignalException, Interrupt, SyntaxError
604
+ raise
605
+ rescue Exception => e
606
+ @logger.fatal("Could not rename file",
607
+ :old_file => @state_file,
608
+ :new_file => @state_file_base + @next_stream_position,
609
+ :exception => e.inspect)
610
+ raise
611
+
612
+ end
613
+ @state_file = @state_file_base + @next_stream_position
614
+ #puts "New state file: #{@state_file}"
615
+
616
+ end
617
+ end
618
+ end
619
+ end # class LogStash::Inputs::BoxEnterprise