Skip to content

Commit 7fec6b0

Browse files
authored
add ECS support (#138)
This PR maps `header.*` to structured ECS fields `http`, `url`, `user_agent` and `host`
1 parent a36964f commit 7fec6b0

File tree

6 files changed

+283
-79
lines changed

6 files changed

+283
-79
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## 3.4.0
2+
- Add ECS support, mapping Http header to ECS compatible fields [#137](https://github.com/logstash-plugins/logstash-input-http/pull/137)
3+
14
## 3.3.7
25
- Feat: improved error handling/logging/unwraping [#133](https://github.com/logstash-plugins/logstash-input-http/pull/133)
36

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.3.7
1+
3.4.0

docs/index.asciidoc

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,28 @@ This input can also be used to receive webhook requests to integrate with other
3232
and applications. By taking advantage of the vast plugin ecosystem available in Logstash
3333
you can trigger actionable events right from your application.
3434

35+
[id="plugins-{type}s-{plugin}-ecs_metadata"]
36+
==== Event Metadata and the Elastic Common Schema (ECS)
37+
In addition to decoding the events, this input will add HTTP headers containing connection information to each event.
38+
When ECS compatibility is disabled, the headers are stored in the `headers` field, which has the potential to create confusion and schema conflicts downstream.
39+
When ECS is enabled, we can ensure a pipeline maintains access to this metadata throughout the event's lifecycle without polluting the top-level namespace.
40+
41+
Here’s how ECS compatibility mode affects output.
42+
[cols="<l,<l,e,<e"]
43+
|=======================================================================
44+
| ECS disabled | ECS v1 | Availability | Description
45+
46+
| [host] | [host][ip] | Always | Host IP address
47+
| [headers] | [@metadata][input][http][request][headers] | Always | Complete HTTP headers
48+
| [headers][http_version] | [http][version] | Always | HTTP version
49+
| [headers][http_user_agent] | [user_agent][original] | Always | client user agent
50+
| [headers][http_host] | [url][domain] and [url][port] | Always | host domain and port
51+
| [headers][request_method] | [http][method] | Always | HTTP method
52+
| [headers][request_path] | [url][path] | Always | Query path
53+
| [headers][content_length] | [http][request][body][bytes] | Always | Request content length
54+
| [headers][content_type] | [http][request][mime_type] | Always | Request mime type
55+
|=======================================================================
56+
3557
==== Blocking Behavior
3658

3759
The HTTP protocol doesn't deal well with long running requests. This plugin will either return
@@ -70,6 +92,7 @@ This plugin supports the following configuration options plus the <<plugins-{typ
7092
|Setting |Input type|Required
7193
| <<plugins-{type}s-{plugin}-additional_codecs>> |<<hash,hash>>|No
7294
| <<plugins-{type}s-{plugin}-cipher_suites>> |<<array,array>>|No
95+
| <<plugins-{type}s-{plugin}-ecs_compatibility>> | <<string,string>>|No
7396
| <<plugins-{type}s-{plugin}-host>> |<<string,string>>|No
7497
| <<plugins-{type}s-{plugin}-keystore>> |<<path,path>>|No
7598
| <<plugins-{type}s-{plugin}-keystore_password>> |<<password,password>>|No
@@ -115,6 +138,72 @@ and no codec for the request's content-type is found
115138

116139
The list of ciphers suite to use, listed by priorities.
117140

141+
[id="plugins-{type}s-{plugin}-ecs_compatibility"]
142+
===== `ecs_compatibility`
143+
144+
* Value type is <<string,string>>
145+
* Supported values are:
146+
** `disabled`: unstructured connection metadata added at root level
147+
** `v1`: headers added under `[@metadata][http][header]`. Some are copied to structured ECS fields `http`, `url`, `user_agent` and `host`
148+
149+
Controls this plugin's compatibility with the
150+
{ecs-ref}[Elastic Common Schema (ECS)].
151+
See <<plugins-{type}s-{plugin}-ecs_metadata>> for detailed information.
152+
153+
Example output:
154+
155+
**Sample output: ECS disabled**
156+
[source,text]
157+
-----
158+
{
159+
"@version" => "1",
160+
"headers" => {
161+
"request_path" => "/twitter/tweet/1",
162+
"http_accept" => "*/*",
163+
"http_version" => "HTTP/1.1",
164+
"request_method" => "PUT",
165+
"http_host" => "localhost:8080",
166+
"http_user_agent" => "curl/7.64.1",
167+
"content_length" => "5",
168+
"content_type" => "application/x-www-form-urlencoded"
169+
},
170+
"@timestamp" => 2021-05-28T19:27:28.609Z,
171+
"host" => "127.0.0.1",
172+
"message" => "hello"
173+
}
174+
-----
175+
176+
**Sample output: ECS enabled**
177+
[source,text]
178+
-----
179+
{
180+
"@version" => "1",
181+
"user_agent" => {
182+
"original" => "curl/7.64.1"
183+
},
184+
"http" => {
185+
"method" => "PUT",
186+
"request" => {
187+
"mime_type" => "application/x-www-form-urlencoded",
188+
"body" => {
189+
"bytes" => "5"
190+
}
191+
},
192+
"version" => "HTTP/1.1"
193+
},
194+
"url" => {
195+
"port" => "8080",
196+
"domain" => "snmp1",
197+
"path" => "/twitter/tweet/1"
198+
},
199+
"@timestamp" => 2021-05-28T23:32:38.222Z,
200+
"host" => {
201+
"ip" => "127.0.0.1"
202+
},
203+
"message" => "hello",
204+
}
205+
-----
206+
118207
[id="plugins-{type}s-{plugin}-host"]
119208
===== `host`
120209

@@ -209,15 +298,17 @@ specify a custom set of response headers
209298
===== `remote_host_target_field`
210299

211300
* Value type is <<string,string>>
212-
* Default value is `"host"`
301+
* Default value is `"host"` when ECS is disabled
302+
* Default value is `[host][ip]` when ECS is enabled
213303

214304
specify a target field for the client host of the http request
215305

216306
[id="plugins-{type}s-{plugin}-request_headers_target_field"]
217307
===== `request_headers_target_field`
218308

219309
* Value type is <<string,string>>
220-
* Default value is `"headers"`
310+
* Default value is `"headers"` when ECS is disabled
311+
* Default value is `[@metadata][http][header]` when ECS is enabled
221312

222313
specify target field for the client host of the http request
223314

lib/logstash/inputs/http.rb

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "logstash/namespace"
44
require "stud/interval"
55
require "logstash-input-http_jars"
6+
require "logstash/plugin_mixins/ecs_compatibility_support"
67

78
# Using this input you can receive single or multiline events over http(s).
89
# Applications can send a HTTP POST request with a body to the endpoint started by this
@@ -25,6 +26,7 @@
2526
# format]
2627
#
2728
class LogStash::Inputs::Http < LogStash::Inputs::Base
29+
include LogStash::PluginMixins::ECSCompatibilitySupport(:disabled, :v1, :v8 => :v1)
2830
require "logstash/inputs/http/tls"
2931

3032
java_import "io.netty.handler.codec.http.HttpUtil"
@@ -104,10 +106,10 @@ class LogStash::Inputs::Http < LogStash::Inputs::Base
104106
config :response_headers, :validate => :hash, :default => { 'Content-Type' => 'text/plain' }
105107

106108
# target field for the client host of the http request
107-
config :remote_host_target_field, :validate => :string, :default => "host"
109+
config :remote_host_target_field, :validate => :string
108110

109111
# target field for the client host of the http request
110-
config :request_headers_target_field, :validate => :string, :default => "headers"
112+
config :request_headers_target_field, :validate => :string
111113

112114
config :threads, :validate => :number, :required => false, :default => ::LogStash::Config::CpuCoreStrategy.maximum
113115

@@ -130,7 +132,7 @@ def register
130132

131133
validate_ssl_settings!
132134

133-
if @user && @password then
135+
if @user && @password
134136
token = Base64.strict_encode64("#{@user}:#{@password.value}")
135137
@auth_token = "Basic #{token}"
136138
end
@@ -144,6 +146,9 @@ def register
144146
require "logstash/inputs/http/message_handler"
145147
message_handler = MessageHandler.new(self, @codec, @codecs, @auth_token)
146148
@http_server = create_http_server(message_handler)
149+
150+
@remote_host_target_field ||= ecs_select[disabled: "host", v1: "[host][ip]"]
151+
@request_headers_target_field ||= ecs_select[disabled: "headers", v1: "[@metadata][input][http][request][headers]"]
147152
end # def register
148153

149154
def run(queue)
@@ -177,12 +182,50 @@ def decode_body(headers, remote_address, body, default_codec, additional_codecs)
177182
end
178183

179184
def push_decoded_event(headers, remote_address, event)
185+
add_ecs_fields(headers, event)
180186
event.set(@request_headers_target_field, headers)
181187
event.set(@remote_host_target_field, remote_address)
182188
decorate(event)
183189
@queue << event
184190
end
185191

192+
def add_ecs_fields(headers, event)
193+
return if ecs_compatibility == :disabled
194+
195+
http_version = headers.get("http_version")
196+
event.set("[http][version]", http_version) if http_version
197+
198+
http_user_agent = headers.get("http_user_agent")
199+
event.set("[user_agent][original]", http_user_agent) if http_user_agent
200+
201+
http_host = headers.get("http_host")
202+
domain, port = self.class.get_domain_port(http_host)
203+
event.set("[url][domain]", domain) if domain
204+
event.set("[url][port]", port) if port
205+
206+
request_method = headers.get("request_method")
207+
event.set("[http][method]", request_method) if request_method
208+
209+
request_path = headers.get("request_path")
210+
event.set("[url][path]", request_path) if request_path
211+
212+
content_length = headers.get("content_length")
213+
event.set("[http][request][body][bytes]", content_length) if content_length
214+
215+
content_type = headers.get("content_type")
216+
event.set("[http][request][mime_type]", content_type) if content_type
217+
end
218+
219+
# match the domain and port in either IPV4, "127.0.0.1:8080", or IPV6, "[2001:db8::8a2e:370:7334]:8080", style
220+
# return [domain, port]
221+
def self.get_domain_port(http_host)
222+
if /^(([^:]+)|\[(.*)\])\:([\d]+)$/ =~ http_host
223+
["#{$2 || $3}", $4.to_i]
224+
else
225+
[http_host, nil]
226+
end
227+
end
228+
186229
def validate_ssl_settings!
187230
if !@ssl
188231
@logger.warn("SSL Certificate will not be used") if @ssl_certificate

logstash-input-http.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
2323
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
2424
s.add_runtime_dependency 'logstash-codec-plain'
2525
s.add_runtime_dependency 'jar-dependencies', '~> 0.3', '>= 0.3.4'
26+
s.add_runtime_dependency 'logstash-mixin-ecs_compatibility_support', '~>1.2'
2627

2728
s.add_development_dependency 'logstash-devutils'
2829
s.add_development_dependency 'logstash-codec-json'

0 commit comments

Comments
 (0)