add support for api_key authentication in xpack management and monitoring. (#11953)

7.x backport of #11864
This commit is contained in:
Colin Surprenant 2020-06-03 10:57:36 -04:00 committed by GitHub
parent fa75ac2780
commit 5e62c96c19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 437 additions and 136 deletions

View file

@ -99,3 +99,10 @@ and `xpack.management.elasticsearch.password`. If `cloud_auth` is configured,
those settings should not be used.
The credentials you specify here should be for a user with the `logstash_admin` role, which
provides access to `.logstash-*` indices for managing configurations.
`xpack.management.elasticsearch.api_key`::
Authenticate using an Elasticsearch API key. Note that this option also requires using SSL.
The API key Format is `id:api_key` where `id` and `api_key` are as returned by the Elasticsearch
https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html[Create API key API].

View file

@ -104,3 +104,10 @@ If you're using {es} in {ecloud}, you can set your auth credentials here.
This setting is an alternative to both `xpack.monitoring.elasticsearch.username`
and `xpack.monitoring.elasticsearch.password`. If `cloud_auth` is configured,
those settings should not be used.
`xpack.monitoring.elasticsearch.api_key`::
Authenticate using an Elasticsearch API key. Note that this option also requires using SSL.
The API key Format is `id:api_key` where `id` and `api_key` are as returned by the Elasticsearch
https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html[Create API key API].

View file

@ -35,22 +35,9 @@ module LogStash
def initialize(settings)
super(settings)
if @settings.get("xpack.management.enabled")
if @settings.get_setting("xpack.management.elasticsearch.cloud_id").set?
if !@settings.get_setting("xpack.management.elasticsearch.cloud_auth").set?
raise ArgumentError.new("You must set credentials using \"xpack.management.elasticsearch.cloud_auth\", " +
"when using \"xpack.management.elasticsearch.cloud_id\" in logstash.yml")
end
else
if !@settings.get_setting("xpack.management.elasticsearch.password").set?
raise ArgumentError.new("You must set the password using \"xpack.management.elasticsearch.password\" in logstash.yml")
end
end
end
@es_options = es_options_from_settings('management', settings)
if enabled?
@es_options = es_options_from_settings('management', settings)
setup_license_checker(FEATURE_INTERNAL)
license_check(true)
end

View file

@ -29,6 +29,7 @@ module LogStash
settings.register(LogStash::Setting::ArrayCoercible.new("xpack.management.elasticsearch.hosts", String, [ "https://localhost:9200" ] ))
settings.register(LogStash::Setting::NullableString.new("xpack.management.elasticsearch.cloud_id"))
settings.register(LogStash::Setting::NullableString.new("xpack.management.elasticsearch.cloud_auth"))
settings.register(LogStash::Setting::NullableString.new("xpack.management.elasticsearch.api_key"))
settings.register(LogStash::Setting::NullableString.new("xpack.management.elasticsearch.proxy"))
settings.register(LogStash::Setting::NullableString.new("xpack.management.elasticsearch.ssl.certificate_authority"))
settings.register(LogStash::Setting::NullableString.new("xpack.management.elasticsearch.ssl.truststore.path"))

View file

@ -6,17 +6,34 @@ module LogStash module Helpers
module ElasticsearchOptions
extend self
ES_SETTINGS =%w(
ssl.certificate_authority
ssl.truststore.path
ssl.keystore.path
hosts
username
password
cloud_id
cloud_auth
proxy
)
ES_SETTINGS = %w(
ssl.certificate_authority
ssl.truststore.path
ssl.keystore.path
hosts
username
password
cloud_id
cloud_auth
api_key
proxy
)
# xpack setting to ES output setting
SETTINGS_MAPPINGS = {
"cloud_id" => "cloud_id",
"cloud_auth" => "cloud_auth",
"username" => "user",
"password" => "password",
"api_key" => "api_key",
"proxy" => "proxy",
"sniffing" => "sniffing",
"ssl.certificate_authority" => "cacert",
"ssl.truststore.path" => "truststore",
"ssl.truststore.password" => "truststore_password",
"ssl.keystore.path" => "keystore",
"ssl.keystore.password" => "keystore_password",
}
# Retrieve elasticsearch options from either specific settings, or modules if the setting is not there and the
# feature supports falling back to modules if the feature is not specified in logstash.yml
@ -27,53 +44,53 @@ module LogStash module Helpers
# Populate the Elasticsearch options from LogStashSettings file, based on the feature that is being used.
# @return Hash
def es_options_from_settings(feature, settings)
prefix = if feature == "monitoring" &&
LogStash::MonitoringExtension.use_direct_shipping?(settings)
""
else
"xpack."
end
prefix = (feature == "monitoring" && LogStash::MonitoringExtension.use_direct_shipping?(settings)) ? "" : "xpack."
opts = {}
if cloud_id = settings.get("#{prefix}#{feature}.elasticsearch.cloud_id")
opts['cloud_id'] = cloud_id
check_cloud_id_configuration!(feature, settings, prefix)
else
validate_authentication!(feature, settings, prefix)
# transpose all directly mappable settings
SETTINGS_MAPPINGS.each do |xpack_setting, es_setting|
v = settings.get("#{prefix}#{feature}.elasticsearch.#{xpack_setting}")
opts[es_setting] = v unless v.nil?
end
# process remaining settings
unless settings.get("#{prefix}#{feature}.elasticsearch.cloud_id")
opts['hosts'] = settings.get("#{prefix}#{feature}.elasticsearch.hosts")
end
if cloud_auth = settings.get("#{prefix}#{feature}.elasticsearch.cloud_auth")
opts['cloud_auth'] = cloud_auth
check_cloud_auth_configuration!(feature, settings, prefix)
else
opts['user'] = settings.get("#{prefix}#{feature}.elasticsearch.username")
opts['password'] = settings.get("#{prefix}#{feature}.elasticsearch.password")
end
if proxysetting = settings.get("#{prefix}#{feature}.elasticsearch.proxy")
opts['proxy'] = proxysetting
end
opts['sniffing'] = settings.get("#{prefix}#{feature}.elasticsearch.sniffing")
opts['ssl_certificate_verification'] = settings.get("#{prefix}#{feature}.elasticsearch.ssl.verification_mode") == 'certificate'
if cacert = settings.get("#{prefix}#{feature}.elasticsearch.ssl.certificate_authority")
opts['cacert'] = cacert
# if all hosts are using https or any of the ssl related settings are set
if ssl?(feature, settings, prefix)
opts['ssl'] = true
end
if truststore = settings.get("#{prefix}#{feature}.elasticsearch.ssl.truststore.path")
opts['truststore'] = truststore
opts['truststore_password'] = settings.get("#{prefix}#{feature}.elasticsearch.ssl.truststore.password")
opts['ssl'] = true
# the username setting has a default value and should not be included when using another authentication
# it is safe to silently remove here since all authentication verifications have been validated at this point.
if settings.set?("#{prefix}#{feature}.elasticsearch.cloud_auth") || settings.set?("#{prefix}#{feature}.elasticsearch.api_key")
opts.delete('user')
end
if keystore = settings.get("#{prefix}#{feature}.elasticsearch.ssl.keystore.path")
opts['keystore'] = keystore
opts['keystore_password']= settings.get("#{prefix}#{feature}.elasticsearch.ssl.keystore.password")
opts['ssl'] = true
end
opts
end
def ssl?(feature, settings, prefix)
return true if verify_https_scheme(feature, settings, prefix)
return true if settings.set?("#{prefix}#{feature}.elasticsearch.cloud_id") # cloud_id always resolves to https hosts
return true if settings.set?("#{prefix}#{feature}.elasticsearch.ssl.certificate_authority")
return true if settings.set?("#{prefix}#{feature}.elasticsearch.ssl.truststore.path") && settings.set?("#{prefix}#{feature}.elasticsearch.ssl.truststore.password")
return true if settings.set?("#{prefix}#{feature}.elasticsearch.ssl.keystore.path") && settings.set?("#{prefix}#{feature}.elasticsearch.ssl.keystore.password")
return false
end
HTTPS_SCHEME = /^https:\/\/.+/
def verify_https_scheme(feature, settings, prefix)
hosts = Array(settings.get("#{prefix}#{feature}.elasticsearch.hosts"))
hosts.all? {|host| host.match?(HTTPS_SCHEME)}
end
# Elasticsearch settings can be extracted from the modules settings inside the configuration.
# Few options will be supported, however - the modules security configuration is
@ -113,9 +130,6 @@ module LogStash module Helpers
end
# If no settings are configured, then assume that the feature has not been configured.
# The assumption is that with security setup, at least one setting (password or certificates)
# should be configured. If security is not setup, and defaults 'just work' for monitoring, then
# this will need to be reconsidered.
def feature_configured?(feature, settings)
ES_SETTINGS.each do |option|
return true if settings.set?("xpack.#{feature}.elasticsearch.#{option}")
@ -146,20 +160,62 @@ module LogStash module Helpers
private
def check_cloud_id_configuration!(feature, settings, prefix)
return if !settings.set?("#{prefix}#{feature}.elasticsearch.hosts")
def validate_authentication!(feature, settings, prefix)
provided_cloud_id = settings.set?("#{prefix}#{feature}.elasticsearch.cloud_id")
provided_hosts = settings.set?("#{prefix}#{feature}.elasticsearch.hosts")
provided_cloud_auth = settings.set?("#{prefix}#{feature}.elasticsearch.cloud_auth")
provided_api_key = settings.set?("#{prefix}#{feature}.elasticsearch.api_key")
provided_username = settings.set?("#{prefix}#{feature}.elasticsearch.username")
provided_password = settings.set?("#{prefix}#{feature}.elasticsearch.password")
raise ArgumentError.new("Both \"#{prefix}#{feature}.elasticsearch.cloud_id\" and " +
"\"#{prefix}#{feature}.elasticsearch.hosts\" specified, please only use one of those.")
# note that the username setting has a default value and in the verifications below
# we can test on the password option being set as a proxy to using basic auth because
# if the username is not explicitly set it will use its default value.
if provided_cloud_auth && (provided_username || provided_password)
raise ArgumentError.new(
"Both #{prefix}#{feature}.elasticsearch.cloud_auth and " +
"#{prefix}#{feature}.elasticsearch.username/password " +
"specified, please only use one of those"
)
end
if provided_username && !provided_password
raise(ArgumentError,
"When using #{prefix}#{feature}.elasticsearch.username, " +
"#{prefix}#{feature}.elasticsearch.password must also be set"
)
end
if provided_cloud_id
if provided_hosts
raise(ArgumentError,
"Both #{prefix}#{feature}.elasticsearch.cloud_id and " +
"#{prefix}#{feature}.elasticsearch.hosts specified, please only use one of those"
)
end
end
authentication_count = 0
authentication_count += 1 if provided_cloud_auth
authentication_count += 1 if provided_password
authentication_count += 1 if provided_api_key
if authentication_count == 0
# when no explicit authentication is set it is relying on default username
# but without and explicit password set
raise(ArgumentError,
"With the default #{prefix}#{feature}.elasticsearch.username, " +
"#{prefix}#{feature}.elasticsearch.password must be set"
)
end
if authentication_count > 1
raise(ArgumentError, "Multiple authentication options are specified, please only use one of #{prefix}#{feature}.elasticsearch.username/password, #{prefix}#{feature}.elasticsearch.cloud_auth or #{prefix}#{feature}.elasticsearch.api_key")
end
if provided_api_key && !ssl?(feature, settings, prefix)
raise(ArgumentError, "Using api_key authentication requires SSL/TLS secured communication")
end
end
def check_cloud_auth_configuration!(feature, settings, prefix)
return if !settings.set?("#{prefix}#{feature}.elasticsearch.username") &&
!settings.set?("#{prefix}#{feature}.elasticsearch.password")
raise ArgumentError.new("Both \"#{prefix}#{feature}.elasticsearch.cloud_auth\" and " +
"\"#{prefix}#{feature}.elasticsearch.username\"/\"#{prefix}#{feature}.elasticsearch.password\" " +
"specified, please only use one of those.")
end
end end end

View file

@ -13,10 +13,10 @@ module LogStash module Monitoring
include LogStash::Util::Loggable
FEATURE = 'monitoring'
def initialize(pipeline_config, agent)
def initialize(pipeline_config, agent, settings)
super(pipeline_config.settings)
@pipeline_config = pipeline_config
@settings = LogStash::SETTINGS.clone
@settings = settings
@agent = agent
@es_options = es_options_from_settings_or_modules(FEATURE, @settings)
setup_license_checker(FEATURE)

View file

@ -32,7 +32,9 @@ module LogStash
@password = es_settings['password']
@cloud_id = es_settings['cloud_id']
@cloud_auth = es_settings['cloud_auth']
@api_key = es_settings['api_key']
@proxy = es_settings['proxy']
@ssl = es_settings['ssl']
@ca_path = es_settings['cacert']
@truststore_path = es_settings['truststore']
@truststore_password = es_settings['truststore_password']
@ -42,8 +44,8 @@ module LogStash
@ssl_certificate_verification = (es_settings['verification_mode'] == 'certificate')
end
attr_accessor :system_api_version, :es_hosts, :user, :password, :node_uuid, :cloud_id, :cloud_auth, :proxy
attr_accessor :ca_path, :truststore_path, :truststore_password
attr_accessor :system_api_version, :es_hosts, :user, :password, :node_uuid, :cloud_id, :cloud_auth, :api_key
attr_accessor :proxy, :ssl, :ca_path, :truststore_path, :truststore_password
attr_accessor :keystore_path, :keystore_password, :sniffing, :ssl_certificate_verification
def collection_interval
@ -70,8 +72,12 @@ module LogStash
user && password
end
def api_key?
api_key
end
def ssl?
ca_path || (truststore_path && truststore_password) || (keystore_path && keystore_password)
ssl || ca_path || (truststore_path && truststore_password) || (keystore_path && keystore_password)
end
def truststore?
@ -133,7 +139,7 @@ module LogStash
logger.trace("registering the metrics pipeline")
LogStash::SETTINGS.set("node.uuid", runner.agent.id)
internal_pipeline_source = LogStash::Monitoring::InternalPipelineSource.new(setup_metrics_pipeline, runner.agent)
internal_pipeline_source = LogStash::Monitoring::InternalPipelineSource.new(setup_metrics_pipeline, runner.agent, LogStash::SETTINGS.clone)
runner.source_loader.add_source(internal_pipeline_source)
rescue => e
logger.error("Failed to set up the metrics pipeline", :message => e.message, :backtrace => e.backtrace)
@ -260,6 +266,7 @@ module LogStash
settings.register(LogStash::Setting::NullableString.new("#{prefix}monitoring.elasticsearch.proxy"))
settings.register(LogStash::Setting::NullableString.new("#{prefix}monitoring.elasticsearch.cloud_id"))
settings.register(LogStash::Setting::NullableString.new("#{prefix}monitoring.elasticsearch.cloud_auth"))
settings.register(LogStash::Setting::NullableString.new("#{prefix}monitoring.elasticsearch.api_key"))
settings.register(LogStash::Setting::NullableString.new("#{prefix}monitoring.elasticsearch.ssl.certificate_authority"))
settings.register(LogStash::Setting::NullableString.new("#{prefix}monitoring.elasticsearch.ssl.truststore.path"))
settings.register(LogStash::Setting::NullableString.new("#{prefix}monitoring.elasticsearch.ssl.truststore.password"))

View file

@ -12,13 +12,20 @@ input {
}
output {
elasticsearch_monitoring {
<% if auth? %>
user => "<%= user %>"
password => "<%= password %>"
<% end %>
<% if api_key? %>
api_key => "<%= api_key %>"
<% end %>
<% if cloud_id? %>
cloud_id => "<%= cloud_id %>"
<% if cloud_auth %>
cloud_auth => "<%= cloud_auth %>"
<% end %>
<% else %>
hosts => <%= es_hosts %>
<% end %>
<% if cloud_auth %>
cloud_auth => "<%= cloud_auth %>"
<% end %>
bulk_path => "<%= monitoring_endpoint %>"
manage_template => false
@ -28,10 +35,6 @@ output {
<% if proxy? %>
proxy => "<%= proxy %>"
<% end %>
<% if auth? && !cloud_auth? %>
user => "<%= user %>"
password => "<%= password %>"
<% end %>
<% if ssl? %>
ssl => true
<% if ca_path %>

View file

@ -4,6 +4,7 @@
require "spec_helper"
require "logstash/json"
require "logstash/runner"
require "config_management/elasticsearch_source"
require "config_management/extension"
require "license_checker/license_manager"
@ -164,9 +165,12 @@ describe LogStash::ConfigManagement::ElasticsearchSource do
end
let(:pipeline_id) { "foobar" }
let(:settings) { { "xpack.management.pipeline.id" => pipeline_id,
"xpack.management.elasticsearch.password" => "testpassword"
} }
let(:settings) do
{
"xpack.management.pipeline.id" => pipeline_id,
"xpack.management.elasticsearch.password" => "testpassword"
}
end
it "generates the path to get the configuration" do
expect(subject.config_path).to eq("#{described_class::PIPELINE_INDEX}/_mget")
@ -182,10 +186,13 @@ describe LogStash::ConfigManagement::ElasticsearchSource do
end
context "when enabled" do
let(:settings) { {
"xpack.management.enabled" => true,
"xpack.management.elasticsearch.password" => "testpassword"
} }
let(:settings) do
{
"xpack.management.enabled" => true,
"xpack.management.elasticsearch.username" => "testuser",
"xpack.management.elasticsearch.password" => "testpassword"
}
end
it "returns true" do
expect(subject.match?).to be_truthy
@ -357,7 +364,8 @@ describe LogStash::ConfigManagement::ElasticsearchSource do
context 'when security is enabled in Elasticsearch' do
let(:security_enabled) { true }
it 'should not raise an error' do
expect { subject.pipeline_configs }.not_to raise_error(LogStash::LicenseChecker::LicenseError)
expect_any_instance_of(described_class).to receive(:build_client).and_return(mock_client)
expect { subject.pipeline_configs }.not_to raise_error
end
end
end

View file

@ -4,6 +4,7 @@
require "spec_helper"
require "logstash/json"
require "logstash/runner"
require 'helpers/elasticsearch_options'
require "license_checker/license_manager"
require 'monitoring/monitoring'
@ -97,65 +98,267 @@ describe LogStash::Helpers::ElasticsearchOptions do
end
describe "es_options_from_settings" do
let(:settings) do
{
context "with implicit username" do
let(:settings) do
{
"xpack.monitoring.enabled" => true,
"xpack.monitoring.elasticsearch.hosts" => elasticsearch_url,
}
end
it "fails without password" do
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /password must be set/)
end
context "with cloud_auth" do
let(:cloud_username) { 'elastic' }
let(:cloud_password) { 'passw0rd'}
let(:cloud_auth) { "#{cloud_username}:#{cloud_password}" }
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.cloud_auth" => cloud_auth,
)
end
it "silently ignores the default username" do
es_options = test_class.es_options_from_settings_or_modules('monitoring', system_settings)
expect(es_options).to include("cloud_auth")
expect(es_options).to_not include("user")
end
end
context "with api_key" do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.api_key" => 'foo:bar'
)
end
it "silently ignores the default username" do
es_options = test_class.es_options_from_settings_or_modules('monitoring', system_settings)
expect(es_options).to include("api_key")
expect(es_options).to_not include("user")
end
context "and explicit password" do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.password" => elasticsearch_password
)
end
it "fails for multiple authentications" do
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /Multiple authentication options are specified/)
end
end
end
end
context "with explicit username" do
let(:settings) do
{
"xpack.monitoring.enabled" => true,
"xpack.monitoring.elasticsearch.hosts" => elasticsearch_url,
"xpack.monitoring.elasticsearch.username" => "foo",
}
end
it "fails without password" do
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /password must also be set/)
end
context "with cloud_auth" do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.password" => "bar",
"xpack.monitoring.elasticsearch.cloud_auth" => "foo:bar",
)
end
it "fails for multiple authentications" do
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /Both.*?cloud_auth.*?and.*?username.*?specified/)
end
end
context "with api_key" do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.password" => "bar",
"xpack.monitoring.elasticsearch.api_key" => 'foo:bar'
)
end
it "fails for multiple authentications" do
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /Multiple authentication options are specified/)
end
end
end
context "with username and password" do
let(:settings) do
{
"xpack.monitoring.enabled" => true,
"xpack.monitoring.elasticsearch.hosts" => elasticsearch_url,
"xpack.monitoring.elasticsearch.username" => elasticsearch_username,
"xpack.monitoring.elasticsearch.password" => elasticsearch_password,
}
end
it_behaves_like 'elasticsearch options hash is populated without security'
it_behaves_like 'elasticsearch options hash is populated with secure options'
context 'when cloud id and auth are set' do
let(:cloud_name) { 'thebigone'}
let(:cloud_domain) { 'elastic.co'}
let(:cloud_id) { "monitoring:#{Base64.urlsafe_encode64("#{cloud_domain}$#{cloud_name}$ignored")}" }
let(:cloud_username) { 'elastic' }
let(:cloud_password) { 'passw0rd'}
let(:cloud_auth) { "#{cloud_username}:#{cloud_password}" }
let(:expected_url) { ["https://#{cloud_name}.#{cloud_domain}:443"] }
let(:settings) do
{
"xpack.monitoring.enabled" => true,
"xpack.monitoring.elasticsearch.cloud_id" => cloud_id,
"xpack.monitoring.elasticsearch.cloud_auth" => cloud_auth,
}
end
it "creates the elasticsearch output options hash" do
es_options = test_class.es_options_from_settings_or_modules('monitoring', system_settings)
expect(es_options).to include("cloud_id" => cloud_id, "cloud_auth" => cloud_auth)
expect(es_options.keys).to_not include("hosts")
expect(es_options.keys).to_not include("username")
expect(es_options.keys).to_not include("password")
it_behaves_like 'elasticsearch options hash is populated without security'
it_behaves_like 'elasticsearch options hash is populated with secure options'
end
context 'when cloud_id' do
let(:cloud_name) { 'thebigone'}
let(:cloud_domain) { 'elastic.co'}
let(:cloud_id) { "monitoring:#{Base64.urlsafe_encode64("#{cloud_domain}$#{cloud_name}$ignored")}" }
let(:expected_url) { ["https://#{cloud_name}.#{cloud_domain}:443"] }
let(:settings) do
{
"xpack.monitoring.enabled" => true,
"xpack.monitoring.elasticsearch.cloud_id" => cloud_id,
}
end
context 'hosts also set' do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.hosts" => 'https://localhost:9200'
"xpack.monitoring.elasticsearch.hosts" => 'https://localhost:9200'
)
end
it "raises due invalid configuration" do
expect { test_class.es_options_from_settings_or_modules('monitoring', system_settings) }.
to raise_error(ArgumentError, /Both.*?cloud_id.*?and.*?hosts.*?specified/)
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /Both.*?cloud_id.*?and.*?hosts.*?specified/)
end
end
context 'username also set' do
context "when cloud_auth is set" do
let(:cloud_username) { 'elastic' }
let(:cloud_password) { 'passw0rd'}
let(:cloud_auth) { "#{cloud_username}:#{cloud_password}" }
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.username" => 'elastic'
"xpack.monitoring.elasticsearch.cloud_auth" => cloud_auth,
)
end
it "raises due invalid configuration" do
expect { test_class.es_options_from_settings_or_modules('monitoring', system_settings) }.
to raise_error(ArgumentError, /Both.*?cloud_auth.*?and.*?username.*?specified/)
it "creates the elasticsearch output options hash" do
es_options = test_class.es_options_from_settings_or_modules('monitoring', system_settings)
expect(es_options).to include("cloud_id" => cloud_id, "cloud_auth" => cloud_auth)
expect(es_options.keys).to_not include("hosts")
expect(es_options.keys).to_not include("username")
expect(es_options.keys).to_not include("password")
end
context 'username also set' do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.username" => 'elastic'
)
end
it "raises for invalid configuration" do
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /Both.*?cloud_auth.*?and.*?username.*?specified/)
end
end
context 'api_key also set' do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.api_key" => 'foo:bar',
)
end
it "raises for invalid configuration" do
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /Multiple authentication options are specified/)
end
end
end
context "when cloud_auth is not set" do
it "raises for invalid configuration" do
# if not other authn is provided it will assume basic auth using the default username
# but the password is missing.
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /With the default.*?username,.*?password must be set/)
end
context 'username and password set' do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.username" => 'foo',
"xpack.monitoring.elasticsearch.password" => 'bar'
)
end
it "creates the elasticsearch output options hash" do
es_options = test_class.es_options_from_settings_or_modules('monitoring', system_settings)
expect(es_options).to include("cloud_id", "user", "password")
expect(es_options.keys).to_not include("hosts")
end
end
context 'api_key set' do
let(:settings) do
super.merge(
"xpack.monitoring.elasticsearch.api_key" => 'foo:bar'
)
end
it "creates the elasticsearch output options hash" do
es_options = test_class.es_options_from_settings_or_modules('monitoring', system_settings)
expect(es_options).to include("cloud_id", "api_key")
expect(es_options.keys).to_not include("hosts")
end
end
end
end
context 'when api_key is set' do
let(:api_key) { 'foo:bar'}
let(:settings) do
{
"xpack.monitoring.enabled" => true,
"xpack.monitoring.elasticsearch.hosts" => elasticsearch_url,
"xpack.monitoring.elasticsearch.api_key" => api_key,
}
end
it "creates the elasticsearch output options hash" do
es_options = test_class.es_options_from_settings_or_modules('monitoring', system_settings)
expect(es_options).to include("api_key" => api_key)
expect(es_options.keys).to include("hosts")
end
context "with a non https host" do
let(:elasticsearch_url) { ["https://host1", "http://host2"] }
it "fails at options validation" do
expect {
test_class.es_options_from_settings_or_modules('monitoring', system_settings)
}.to raise_error(ArgumentError, /api_key authentication requires SSL\/TLS/)
end
end
end

View file

@ -7,6 +7,7 @@ require 'support/helpers'
require "license_checker/license_reader"
require "helpers/elasticsearch_options"
require "monitoring/monitoring"
require "logstash/runner"
describe LogStash::LicenseChecker::LicenseReader do
let(:elasticsearch_url) { "https://localhost:9898" }
@ -23,7 +24,7 @@ describe LogStash::LicenseChecker::LicenseReader do
let(:settings) do
{
"xpack.monitoring.enabled" => true,
"xpack.monitoring.elasticsearch.hosts" => [ elasticsearch_url ],
"xpack.monitoring.elasticsearch.hosts" => [ elasticsearch_url],
"xpack.monitoring.elasticsearch.username" => elasticsearch_username,
"xpack.monitoring.elasticsearch.password" => elasticsearch_password,
}
@ -128,4 +129,19 @@ describe LogStash::LicenseChecker::LicenseReader do
expect( subject.client.options ).to include(:user => 'elastic', :password => 'LnWMLeK3EQPTf3G3F1IBdFvO')
end
end
context 'with api_key' do
let(:api_key) { "foo:bar" }
let(:settings) do
{
"xpack.monitoring.enabled" => true,
"xpack.monitoring.elasticsearch.hosts" => [elasticsearch_url],
"xpack.monitoring.elasticsearch.api_key" => api_key,
}
end
it "builds ES client" do
expect( subject.client.options[:client_settings][:headers] ).to include("Authorization" => "ApiKey Zm9vOmJhcg==")
end
end
end

View file

@ -4,6 +4,7 @@
require 'spec_helper'
require "logstash-core"
require "logstash/runner"
require "logstash/agent"
require "monitoring/inputs/metrics"
require "rspec/wait"

View file

@ -4,7 +4,7 @@
require "logstash-core"
require "logstash/agent"
require "logstash/agent"
require "logstash/runner"
require "monitoring/inputs/metrics"
require "logstash/config/pipeline_config"
require "logstash/config/source/local"
@ -23,11 +23,17 @@ describe LogStash::Monitoring::InternalPipelineSource do
let(:options) { { "collection_interval" => xpack_monitoring_interval,
"collection_timeout_interval" => 600 } }
subject { described_class.new(pipeline_config, mock_agent) }
subject { described_class.new(pipeline_config, mock_agent, system_settings) }
let(:mock_agent) { double("agent")}
let(:mock_license_client) { double("es_client")}
let(:license_reader) { LogStash::LicenseChecker::LicenseReader.new(system_settings, 'monitoring', es_options)}
let(:system_settings) { LogStash::Runner::SYSTEM_SETTINGS.clone }
let(:extension) { LogStash::MonitoringExtension.new }
let(:system_settings) do
LogStash::Runner::SYSTEM_SETTINGS.clone.tap do |system_settings|
extension.additionals_settings(system_settings) # register defaults from extension
apply_settings(settings, system_settings) # apply `settings`
end
end
let(:license_status) { 'active'}
let(:license_type) { 'trial' }
let(:license_expiry_date) { Time.now + (60 * 60 * 24)}
@ -70,7 +76,6 @@ describe LogStash::Monitoring::InternalPipelineSource do
end
before :each do
allow(subject).to receive(:es_options_from_settings_or_modules).and_return(es_options)
allow(subject).to receive(:license_reader).and_return(license_reader)
allow(license_reader).to receive(:build_client).and_return(mock_license_client)
end