Skip to content

Commit ca66225

Browse files
authored
Merge pull request #11 from logicmonitor/DEV-135160_Bearer_Token
Add bearer token support for auth
2 parents 414ba3b + 83a1367 commit ca66225

File tree

4 files changed

+112
-35
lines changed

4 files changed

+112
-35
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,14 @@
11
## 1.0.0
22
- First version of the plugin
3+
## 1.0.1
4+
- Proxy support
5+
## 1.0.2
6+
- Update debug logs
7+
## 1.1.0
8+
- Disable mfa for gem push
9+
## 1.2.0
10+
- Add metadata by default to every log
11+
## 1.2.1
12+
- Fix ensuring metadata exists before deleting the original while forwarding to lm-logs
13+
## 1.3.0
14+
- Add bearer token support for authentication with Logicmonitor

lib/logstash/outputs/lmlogs.rb

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class InvalidHTTPConfigError < StandardError; end
3030

3131
# Keep logstash timestamp
3232
config :keep_timestamp, :validate => :boolean, :default => true
33-
33+
3434
# Use a configured message key for timestamp values
3535
# Valid timestamp formats are ISO8601 strings or epoch in seconds, milliseconds or nanoseconds
3636
config :timestamp_is_key, :validate => :boolean, :default => false
@@ -91,13 +91,16 @@ class InvalidHTTPConfigError < StandardError; end
9191
config :portal_name, :validate => :string, :required => true
9292

9393
# Username to use for HTTP auth.
94-
config :access_id, :validate => :string, :required => true
94+
config :access_id, :validate => :string, :required => false, :default => nil
9595

9696
# Include/Exclude metadata from sending to LM Logs
9797
config :include_metadata, :validate => :boolean, :default => true
9898

9999
# Password to use for HTTP auth
100-
config :access_key, :validate => :password, :required => true
100+
config :access_key, :validate => :password, :required => false, :default => nil
101+
102+
# Use bearer token instead of access key/id for authentication.
103+
config :bearer_token, :validate => :password, :required => false, :default => nil
101104

102105
@@MAX_PAYLOAD_SIZE = 8*1024*1024
103106

@@ -110,9 +113,9 @@ def register
110113
@total_failed = 0
111114
logger.info("Initialized LogicMonitor output plugin with configuration",
112115
:host => @host)
113-
logger.info("Max Payload Size: ",
116+
logger.info("Max Payload Size: ",
114117
:size => @@MAX_PAYLOAD_SIZE)
115-
118+
configure_auth
116119
end # def register
117120

118121
def client_config
@@ -137,19 +140,7 @@ def client_config
137140
@proxy
138141
end
139142

140-
if @access_id
141-
if !@access_key || !@access_key.value
142-
raise ::LogStash::ConfigurationError, "access_id '#{@access_id}' specified without access_key!"
143-
end
144-
145-
# Symbolize keys if necessary
146-
# c[:auth] = {
147-
# :user => @access_id,
148-
# :password => @access_key.value,
149-
# :eager => true
150-
# }
151-
end
152-
log_debug("manticore client config: ", :client => c)
143+
log_debug("manticore client config: ", :client => c)
153144
return c
154145
end
155146

@@ -168,21 +159,35 @@ def close
168159
@client.close
169160
end
170161

171-
162+
def configure_auth
163+
@use_bearer_instead_of_lmv1 = false
164+
if @access_id == nil || @access_key.value == nil
165+
@logger.info "Access Id or access key null. Using bearer token for authentication."
166+
@use_bearer_instead_of_lmv1 = true
167+
end
168+
if @use_bearer_instead_of_lmv1 && @bearer_token.value == nil
169+
@logger.error "Bearer token not specified. Either access_id and access_key both or bearer_token must be specified for authentication with Logicmonitor."
170+
raise LogStash::ConfigurationError, 'No valid authentication specified. Either access_id and access_key both or bearer_token must be specified for authentication with Logicmonitor.'
171+
end
172+
end
172173
def generate_auth_string(body)
173-
timestamp = DateTime.now.strftime('%Q')
174-
hash_this = "POST#{timestamp}#{body}/log/ingest"
175-
sign_this = OpenSSL::HMAC.hexdigest(
176-
OpenSSL::Digest.new('sha256'),
177-
"#{@access_key.value}",
178-
hash_this
179-
)
180-
signature = Base64.strict_encode64(sign_this)
181-
"LMv1 #{@access_id}:#{signature}:#{timestamp}"
174+
if @use_bearer_instead_of_lmv1
175+
return "Bearer #{@bearer_token.value}"
176+
else
177+
timestamp = DateTime.now.strftime('%Q')
178+
hash_this = "POST#{timestamp}#{body}/log/ingest"
179+
sign_this = OpenSSL::HMAC.hexdigest(
180+
OpenSSL::Digest.new('sha256'),
181+
"#{@access_key.value}",
182+
hash_this
183+
)
184+
signature = Base64.strict_encode64(sign_this)
185+
return "LMv1 #{@access_id}:#{signature}:#{timestamp}"
186+
end
182187
end
183188

184189
def send_batch(events)
185-
log_debug("Started sending logs to LM: ",
190+
log_debug("Started sending logs to LM: ",
186191
:time => Time::now.utc)
187192
url = "https://" + @portal_name + ".logicmonitor.com/rest/log/ingest"
188193
body = events.to_json
@@ -249,7 +254,7 @@ def log_debug(message, *opts)
249254
elsif debug
250255
@logger.debug(message, *opts)
251256
end
252-
end
257+
end
253258

254259
public
255260
def multi_receive(events)
@@ -268,7 +273,7 @@ def multi_receive(events)
268273
lmlogs_event.delete("@timestamp") # remove redundant timestamp field
269274
if lmlogs_event.dig("event", "original") != nil
270275
lmlogs_event["event"].delete("original") # remove redundant log field
271-
end
276+
end
272277
end
273278

274279
lmlogs_event["message"] = event.get(@message_key).to_s
@@ -278,7 +283,7 @@ def multi_receive(events)
278283
if @keep_timestamp
279284
lmlogs_event["timestamp"] = event.get("@timestamp")
280285
end
281-
286+
282287
if @timestamp_is_key
283288
lmlogs_event["timestamp"] = event.get(@timestamp_key.to_s)
284289
end
@@ -295,10 +300,10 @@ def log_failure(message, opts)
295300
end
296301

297302
def isValidPayloadSize(documents,lmlogs_event,max_payload_size)
298-
if (documents.to_json.bytesize + lmlogs_event.to_json.bytesize) > max_payload_size
303+
if (documents.to_json.bytesize + lmlogs_event.to_json.bytesize) > max_payload_size
299304
send_batch(documents)
300305
documents = []
301-
306+
302307
end
303308
documents.push(lmlogs_event)
304309
return documents

logstash-output-lmlogs.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Gem::Specification.new do |s|
22
s.name = 'logstash-output-lmlogs'
3-
s.version = '1.2.1'
3+
s.version = '1.3.0'
44
s.licenses = ['Apache-2.0']
55
s.summary = "Logstash output plugin for LM Logs"
66
s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"

spec/outputs/auth_spec.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# encoding: utf-8
2+
require "logstash/devutils/rspec/spec_helper"
3+
require "logstash/outputs/lmlogs"
4+
require "logstash/event"
5+
6+
describe LogStash::Outputs::LMLogs do
7+
8+
let(:sample_lm_logs_event){{"message" => "hello this is log 1", "_lm.resourceId" => {"test.property" => "host1"}, "timestamp" => "2021-03-22T04:28:55.907121106Z"}}
9+
10+
def create_output_plugin_with_conf(conf)
11+
return LogStash::Outputs::LMLogs.new(conf)
12+
end
13+
14+
it "with no auth specified" do
15+
puts "auth test"
16+
plugin = create_output_plugin_with_conf({
17+
"portal_name" => "localhost"
18+
})
19+
expect { plugin.configure_auth() }.to raise_error(LogStash::ConfigurationError)
20+
end
21+
22+
it "access_key id is specified with no bearer" do
23+
puts "auth test"
24+
plugin = create_output_plugin_with_conf({
25+
"portal_name" => "localhost",
26+
"access_id" => "abcd",
27+
"access_key" => "abcd"
28+
})
29+
plugin.configure_auth()
30+
auth_string = plugin.generate_auth_string([sample_lm_logs_event])
31+
32+
expect(auth_string).to start_with("LMv1 abcd:")
33+
end
34+
35+
it "when access id /key not specified but bearer specified" do
36+
plugin = create_output_plugin_with_conf({
37+
"portal_name" => "localhost",
38+
"access_id" => "abcd",
39+
"bearer_token" => "abcd"
40+
})
41+
plugin.configure_auth()
42+
auth_string = plugin.generate_auth_string([sample_lm_logs_event])
43+
44+
expect(auth_string).to eq("Bearer abcd")
45+
end
46+
47+
it "when access id /key bearer all specified, use lmv1" do
48+
puts "auth test"
49+
plugin = create_output_plugin_with_conf({
50+
"portal_name" => "localhost",
51+
"access_id" => "abcd",
52+
"access_key" => "abcd",
53+
"bearer_token" => "abcd"
54+
})
55+
plugin.configure_auth()
56+
auth_string = plugin.generate_auth_string([sample_lm_logs_event])
57+
58+
expect(auth_string).to start_with("LMv1 abcd:")
59+
end
60+
end

0 commit comments

Comments
 (0)