mirror of
https://github.com/elastic/logstash.git
synced 2025-04-24 22:57:16 -04:00
Add URI config validator/type
Often times plugins (like the Elasticsearch output) can naturally use URIs for their configuration. Unfortunately using the :string type here means that the password portion of the URI can easily be leaked. This wraps the URI class in a new LogStash::Util::SafeURI class that proxies all regular URI methods but masks the password when `#to_s` and `#inspect` are invoked. Fixes #5439
This commit is contained in:
parent
6ae54801da
commit
53ed1defd0
4 changed files with 140 additions and 0 deletions
15
docs/static/configuration.asciidoc
vendored
15
docs/static/configuration.asciidoc
vendored
|
@ -203,6 +203,21 @@ Example:
|
||||||
my_password => "password"
|
my_password => "password"
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
|
[[uri]]
|
||||||
|
[float]
|
||||||
|
==== URI
|
||||||
|
|
||||||
|
A URI can be anything from a full URL like 'http://elastic.co/' to a simple identifier
|
||||||
|
like 'foobar'. If the URI contains a password such as 'http://user:pass@example.net' the password
|
||||||
|
portion of the URI will not be logged or printed.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
[source,js]
|
||||||
|
----------------------------------
|
||||||
|
my_uri => "http://foo:bar@example.net"
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
|
||||||
[[path]]
|
[[path]]
|
||||||
[float]
|
[float]
|
||||||
==== Path
|
==== Path
|
||||||
|
|
|
@ -4,6 +4,7 @@ require "logstash/config/registry"
|
||||||
require "logstash/plugins/registry"
|
require "logstash/plugins/registry"
|
||||||
require "logstash/logging"
|
require "logstash/logging"
|
||||||
require "logstash/util/password"
|
require "logstash/util/password"
|
||||||
|
require "logstash/util/safe_uri"
|
||||||
require "logstash/version"
|
require "logstash/version"
|
||||||
require "logstash/environment"
|
require "logstash/environment"
|
||||||
require "logstash/util/plugin_version"
|
require "logstash/util/plugin_version"
|
||||||
|
@ -513,6 +514,12 @@ module LogStash::Config::Mixin
|
||||||
end
|
end
|
||||||
|
|
||||||
result = value.first.is_a?(::LogStash::Util::Password) ? value.first : ::LogStash::Util::Password.new(value.first)
|
result = value.first.is_a?(::LogStash::Util::Password) ? value.first : ::LogStash::Util::Password.new(value.first)
|
||||||
|
when :uri
|
||||||
|
if value.size > 1
|
||||||
|
return false, "Expected uri (one value), got #{value.size} values?"
|
||||||
|
end
|
||||||
|
|
||||||
|
result = value.first.is_a?(::LogStash::Util::SafeURI) ? value.first : ::LogStash::Util::SafeURI.new(value.first)
|
||||||
when :path
|
when :path
|
||||||
if value.size > 1 # Only 1 value wanted
|
if value.size > 1 # Only 1 value wanted
|
||||||
return false, "Expected path (one value), got #{value.size} values?"
|
return false, "Expected path (one value), got #{value.size} values?"
|
||||||
|
@ -551,6 +558,10 @@ module LogStash::Config::Mixin
|
||||||
if @config[key][:validate] == :password && !value.is_a?(::LogStash::Util::Password)
|
if @config[key][:validate] == :password && !value.is_a?(::LogStash::Util::Password)
|
||||||
params[key] = ::LogStash::Util::Password.new(value)
|
params[key] = ::LogStash::Util::Password.new(value)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if @config[key][:validate] == :uri && !value.is_a?(::LogStash::Util::SafeURI)
|
||||||
|
params[key] = ::LogStash::Util::SafeURI.new(value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
44
logstash-core/lib/logstash/util/safe_uri.rb
Normal file
44
logstash-core/lib/logstash/util/safe_uri.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
require "logstash/namespace"
|
||||||
|
require "logstash/util"
|
||||||
|
|
||||||
|
# This class exists to quietly wrap a password string so that, when printed or
|
||||||
|
# logged, you don't accidentally print the password itself.
|
||||||
|
class LogStash::Util::SafeURI
|
||||||
|
PASS_PLACEHOLDER = "xxxxxx".freeze
|
||||||
|
|
||||||
|
extend Forwardable
|
||||||
|
|
||||||
|
def_delegators :@uri, :coerce, :query=, :route_from, :port=, :default_port, :select, :normalize!, :absolute?, :registry=, :path, :password, :hostname, :merge, :normalize, :host, :component_ary, :userinfo=, :query, :set_opaque, :+, :merge!, :-, :password=, :parser, :port, :set_host, :set_path, :opaque=, :scheme, :fragment=, :set_query, :set_fragment, :userinfo, :hostname=, :set_port, :path=, :registry, :opaque, :route_to, :set_password, :hierarchical?, :set_user, :set_registry, :set_userinfo, :fragment, :component, :user=, :set_scheme, :absolute, :host=, :relative?, :scheme=, :user
|
||||||
|
|
||||||
|
attr_reader :uri
|
||||||
|
|
||||||
|
public
|
||||||
|
def initialize(arg)
|
||||||
|
@uri = case arg
|
||||||
|
when String
|
||||||
|
URI.parse(arg)
|
||||||
|
when URI
|
||||||
|
arg
|
||||||
|
else
|
||||||
|
raise ArgumentError, "Expected a string or URI, got a #{arg.class} creating a URL"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s
|
||||||
|
sanitized.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
sanitized.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def sanitized
|
||||||
|
return uri unless uri.password # nothing to sanitize here!
|
||||||
|
|
||||||
|
safe = uri.clone
|
||||||
|
safe.password = PASS_PLACEHOLDER
|
||||||
|
safe
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -102,6 +102,76 @@ describe LogStash::Config::Mixin do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when validating :uri" do
|
||||||
|
let(:klass) do
|
||||||
|
Class.new(LogStash::Filters::Base) do
|
||||||
|
config_name "fakeuri"
|
||||||
|
config :uri, :validate => :uri
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
shared_examples("safe URI") do
|
||||||
|
subject { klass.new("uri" => uri_str) }
|
||||||
|
|
||||||
|
it "should be a SafeURI object" do
|
||||||
|
expect(subject.uri).to(be_a(LogStash::Util::SafeURI))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should make password values hidden with #to_s" do
|
||||||
|
expect(subject.uri.to_s).to eql(uri_hidden)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should make password values hidden with #inspect" do
|
||||||
|
expect(subject.uri.inspect).to eql(uri_hidden)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should correctly copy URI types" do
|
||||||
|
clone = subject.class.new(subject.params)
|
||||||
|
expect(clone.uri.to_s).to eql(uri_hidden)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should make the real URI object availale under #uri" do
|
||||||
|
expect(subject.uri.uri).to be_a(::URI)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should obfuscate original_params" do
|
||||||
|
expect(subject.original_params['uri']).to(be_a(LogStash::Util::SafeURI))
|
||||||
|
end
|
||||||
|
|
||||||
|
context "attributes" do
|
||||||
|
[:scheme, :user, :password, :hostname, :path].each do |attr|
|
||||||
|
it "should make #{attr} available" do
|
||||||
|
expect(subject.uri.send(attr)).to eql(self.send(attr))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "with a username / password" do
|
||||||
|
let(:scheme) { "myscheme" }
|
||||||
|
let(:user) { "myuser" }
|
||||||
|
let(:password) { "fancypants" }
|
||||||
|
let(:hostname) { "myhostname" }
|
||||||
|
let(:path) { "/my/path" }
|
||||||
|
let(:uri_str) { "#{scheme}://#{user}:#{password}@#{hostname}#{path}" }
|
||||||
|
let(:uri_hidden) { "#{scheme}://#{user}:#{LogStash::Util::SafeURI::PASS_PLACEHOLDER}@#{hostname}#{path}" }
|
||||||
|
|
||||||
|
include_examples("safe URI")
|
||||||
|
end
|
||||||
|
|
||||||
|
context "without a username / password" do
|
||||||
|
let(:scheme) { "myscheme" }
|
||||||
|
let(:user) { nil }
|
||||||
|
let(:password) { nil }
|
||||||
|
let(:hostname) { "myhostname" }
|
||||||
|
let(:path) { "/my/path" }
|
||||||
|
let(:uri_str) { "#{scheme}://#{hostname}#{path}" }
|
||||||
|
let(:uri_hidden) { "#{scheme}://#{hostname}#{path}" }
|
||||||
|
|
||||||
|
include_examples("safe URI")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "obsolete settings" do
|
describe "obsolete settings" do
|
||||||
let(:plugin_class) do
|
let(:plugin_class) do
|
||||||
Class.new(LogStash::Inputs::Base) do
|
Class.new(LogStash::Inputs::Base) do
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue