# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one # or more contributor license agreements. Licensed under the Elastic License; # you may not use this file except in compliance with the Elastic License. require "spec_helper" require "logstash/json" require "logstash/runner" require 'helpers/elasticsearch_options' require "license_checker/license_manager" require 'monitoring/monitoring' shared_examples "elasticsearch options hash is populated without security" do it "with username, hosts and password" do expect(test_class.es_options_from_settings_or_modules('monitoring', system_settings)).to include( "hosts" => expected_url, "user" => expected_username, "password" => expected_password ) end end shared_examples 'elasticsearch options hash is populated with secure options' do context "with ca" do let(:elasticsearch_ca) { Stud::Temporary.file.path } let(:settings) { super.merge({ "xpack.monitoring.elasticsearch.ssl.certificate_authority" => elasticsearch_ca })} it "creates the elasticsearch output options hash" do expect(test_class.es_options_from_settings('monitoring', system_settings)).to include( "hosts" => elasticsearch_url, "user" => elasticsearch_username, "password" => elasticsearch_password, "ssl" => true, "cacert" => elasticsearch_ca ) end end context "with truststore" do let(:elasticsearch_truststore_path) { Stud::Temporary.file.path } let(:elasticsearch_truststore_password) { "truststore_password" } let(:settings) do super.merge({ "xpack.monitoring.elasticsearch.ssl.truststore.path" => elasticsearch_truststore_path, "xpack.monitoring.elasticsearch.ssl.truststore.password" => elasticsearch_truststore_password, }) end it "creates the elasticsearch output options hash" do expect(test_class.es_options_from_settings('monitoring', system_settings)).to include( "hosts" => elasticsearch_url, "user" => elasticsearch_username, "password" => elasticsearch_password, "ssl" => true, "truststore" => elasticsearch_truststore_path, "truststore_password" => elasticsearch_truststore_password ) end end context "with keystore" do let(:elasticsearch_keystore_path) { Stud::Temporary.file.path } let(:elasticsearch_keystore_password) { "keystore_password" } let(:settings) do super.merge({ "xpack.monitoring.elasticsearch.ssl.keystore.path" => elasticsearch_keystore_path, "xpack.monitoring.elasticsearch.ssl.keystore.password" => elasticsearch_keystore_password, }) end it "creates the elasticsearch output options hash" do expect(test_class.es_options_from_settings('monitoring', system_settings)).to include( "hosts" => elasticsearch_url, "user" => elasticsearch_username, "password" => elasticsearch_password, "ssl" => true, "keystore" => elasticsearch_keystore_path, "keystore_password" => elasticsearch_keystore_password ) end end end describe LogStash::Helpers::ElasticsearchOptions do let(:test_class) { Class.new { extend LogStash::Helpers::ElasticsearchOptions } } let(:elasticsearch_url) { ["https://localhost:9898"] } let(:elasticsearch_username) { "elastictest" } let(:elasticsearch_password) { "testchangeme" } let(:expected_url) { elasticsearch_url } let(:expected_username) { elasticsearch_username } let(:expected_password) { elasticsearch_password } let(:extension) { LogStash::MonitoringExtension.new } let(:system_settings) { LogStash::Runner::SYSTEM_SETTINGS.clone } before :each do extension.additionals_settings(system_settings) apply_settings(settings, system_settings) end describe "es_options_from_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' 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' ) 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/) end end 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.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") 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 end describe 'es_options_from_settings_or_modules' do context 'when only settings are set' 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' end context 'with modules set' do let(:modules_es_url) { ["https://localhost:9898", "https://localhost:9999"]} let(:modules_es_username) { "modules_user"} let(:modules_es_password) { "correcthorsebatterystaple"} context 'when only modules cli are set' do let(:expected_url) { modules_es_url } let(:expected_username) { modules_es_username } let(:expected_password) { modules_es_password } let(:settings) { {"modules.cli" => [{ "name" => "hello", 'var.elasticsearch.hosts' => modules_es_url, 'var.elasticsearch.username' => modules_es_username, 'var.elasticsearch.password' => modules_es_password}]} } it_behaves_like 'elasticsearch options hash is populated without security' end context 'when only modules yaml are set' do let(:expected_url) { modules_es_url } let(:expected_username) { modules_es_username } let(:expected_password) { modules_es_password } let(:settings) { {"modules" => [{ "name" => "hello", 'var.elasticsearch.hosts' => modules_es_url, 'var.elasticsearch.username' => modules_es_username, 'var.elasticsearch.password' => modules_es_password}]} } it_behaves_like 'elasticsearch options hash is populated without security' end context 'when cloud id and auth are set' do let(:cloud_name) { 'thebigone'} let(:cloud_domain) { 'elastic.co'} let(:base64_encoded) { Base64.urlsafe_encode64("#{cloud_domain}$#{cloud_name}$ignored")} let(:cloud_id) { "label:#{base64_encoded}" } let(:cloud_username) { 'cloudy' } let(:cloud_password) { 'cloud_password'} let(:expected_url) { ["https://#{cloud_name}.#{cloud_domain}:443"] } let(:expected_username) { cloud_username } let(:expected_password) { cloud_password } let(:settings) { { "cloud.id" => cloud_id, "cloud.auth" => "#{cloud_username}:#{cloud_password}", "modules" => [{ "name" => "hello", 'var.elasticsearch.hosts' => modules_es_url, 'var.elasticsearch.username' => modules_es_username, 'var.elasticsearch.password' => modules_es_password}]} } it_behaves_like 'elasticsearch options hash is populated without security' end context 'when only modules cli and yaml are set' do let(:modules_cli_url) { ['cli:9200']} let(:modules_cli_username) { 'cli_user' } let(:modules_cli_password) { 'cli_password'} let(:expected_url) { modules_cli_url } let(:expected_username) { modules_cli_username } let(:expected_password) { modules_cli_password } let(:settings) { {"modules.cli" => [{ "name" => "hello", 'var.elasticsearch.hosts' => modules_cli_url, 'var.elasticsearch.username' => modules_cli_username, 'var.elasticsearch.password' => modules_cli_password}], "modules" => [{ "name" => "hello", 'var.elasticsearch.hosts' => modules_es_url, 'var.elasticsearch.username' => modules_es_username, 'var.elasticsearch.password' => modules_es_password}]} } it_behaves_like 'elasticsearch options hash is populated without security' end context 'when everything is set' do let(:cloud_name) { 'thebigone'} let(:cloud_domain) { 'elastic.co'} let(:base64_encoded) { Base64.urlsafe_encode64("#{cloud_domain}$#{cloud_name}$ignored")} let(:cloud_id) { "label:#{base64_encoded}" } let(:cloud_username) { 'cloudy' } let(:cloud_password) { 'cloud_password'} let(:modules_cli_url) { ['cli:9200']} let(:modules_cli_username) { 'cli_user' } let(:modules_cli_password) { 'cli_password'} let(:settings) do { "modules" => [{ "name" => "hello", 'var.elasticsearch.hosts' => modules_es_url, 'var.elasticsearch.username' => modules_es_username, 'var.elasticsearch.password' => modules_es_password}], "modules.cli" => [{ "name" => "hello", 'var.elasticsearch.hosts' => modules_cli_url, 'var.elasticsearch.username' => modules_cli_username, 'var.elasticsearch.password' => modules_cli_password}], "cloud.id" => cloud_id, "cloud.auth" => "#{cloud_username}:#{cloud_password}", "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' end end end end