diff --git a/lib/puppet_x/puppetlabs/splunk/type.rb b/lib/puppet_x/puppetlabs/splunk/type.rb index 54cf7ee2..c576200d 100644 --- a/lib/puppet_x/puppetlabs/splunk/type.rb +++ b/lib/puppet_x/puppetlabs/splunk/type.rb @@ -1,3 +1,5 @@ +require File.join(File.dirname(__FILE__), '..', '..', 'voxpupuli/splunk/util') + module PuppetX module Puppetlabs module Splunk @@ -26,6 +28,15 @@ def self.clone_type(type) munge do |v| v.to_s.strip end + def insync?(is) # rubocop:disable Lint/NestedMethodDefinition + secrets_file_path = File.join(provider.class.file_path, 'auth/splunk.secret') + if File.file?(secrets_file_path) + PuppetX::Voxpupuli::Splunk::Util.decrypt(secrets_file_path, is) == should + else + Puppet.warning('Secrets file NOT found') + is == should + end + end end type.newparam(:setting) do desc 'The setting being defined.' diff --git a/lib/puppet_x/voxpupuli/splunk/util.rb b/lib/puppet_x/voxpupuli/splunk/util.rb new file mode 100644 index 00000000..1148cfd3 --- /dev/null +++ b/lib/puppet_x/voxpupuli/splunk/util.rb @@ -0,0 +1,32 @@ +require 'openssl' +require 'base64' + +module PuppetX + module Voxpupuli + module Splunk + class Util + def self.decrypt(secrets_file, value) + return value unless value.start_with?('$7$') + + Puppet.debug "Decrypting splunk >= 7.2 data using secret from #{secrets_file}" + value.slice!(0, 3) + data = Base64.strict_decode64(value) + splunk_secret = IO.binread(secrets_file).chomp + + iv = data.bytes[0, 16].pack('c*') + tag = data.bytes[-16..-1].pack('c*') + ciphertext = data.bytes[16..-17].pack('c*') + + decipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt + decipher.key = OpenSSL::PKCS5.pbkdf2_hmac(splunk_secret, 'disk-encryption', 1, 32, OpenSSL::Digest::SHA256.new) + decipher.iv_len = 16 + decipher.iv = iv + decipher.auth_tag = tag + decipher.auth_data = '' + + decipher.update(ciphertext) + decipher.final + end + end + end + end +end diff --git a/spec/acceptance/splunk_enterprise_spec.rb b/spec/acceptance/splunk_enterprise_spec.rb index db83ebb4..db497992 100644 --- a/spec/acceptance/splunk_enterprise_spec.rb +++ b/spec/acceptance/splunk_enterprise_spec.rb @@ -1,6 +1,13 @@ require 'spec_helper_acceptance' describe 'splunk enterprise class' do + init = shell('/bin/readlink /sbin/init', acceptable_exit_codes: [0, 1]).stdout + service_name = if init.include? 'systemd' + 'Splunkd' + else + 'splunk' + end + context 'default parameters' do # Using puppet_apply as a helper it 'works idempotently with no errors' do @@ -17,13 +24,6 @@ class { '::splunk::enterprise': } it { is_expected.to be_installed } end - init = shell('/bin/readlink /sbin/init', acceptable_exit_codes: [0, 1]).stdout - service_name = if init.include? 'systemd' - 'Splunkd' - else - 'splunk' - end - describe service(service_name) do it { is_expected.to be_enabled } it { is_expected.to be_running } @@ -39,5 +39,21 @@ class { '::splunk::enterprise': } it { is_expected.to be_grouped_into 'root' } end end + + # Uninstall so that splunkforwarder tests aren't affected by this set of tests + context 'uninstalling splunk' do + it do + pp = <<-EOS + service { '#{service_name}': ensure => stopped } + package { 'splunk': ensure => purged } + file { '/opt/splunk': ensure => absent, force => true, require => Package['splunk'] } + file { '/etc/init.d/splunk': ensure => absent, require => Package['splunk'] } + EOS + apply_manifest(pp, catch_failures: true) + end + describe package('splunk') do + it { is_expected.not_to be_installed } + end + end end end diff --git a/spec/acceptance/splunk_forwarder_spec.rb b/spec/acceptance/splunk_forwarder_spec.rb index f7a55652..074c8859 100644 --- a/spec/acceptance/splunk_forwarder_spec.rb +++ b/spec/acceptance/splunk_forwarder_spec.rb @@ -5,11 +5,14 @@ # Using puppet_apply as a helper it 'works idempotently with no errors' do pp = <<-EOS - class { '::splunk::params': + class { 'splunk::params': } - class { '::splunk::forwarder': + class { 'splunk::forwarder': splunkd_port => 8090, } + splunkforwarder_output { 'tcpout:splunkcloud/sslPassword': + value => 'super_secure_password', + } EOS # Run it twice and test for idempotency @@ -17,6 +20,12 @@ class { '::splunk::forwarder': apply_manifest(pp, catch_changes: true) end + describe file('/opt/splunkforwarder/etc/system/local/outputs.conf') do + it { is_expected.to be_file } + its(:content) { is_expected.to match %r{^sslPassword} } + its(:content) { is_expected.to match %r{^sslPassword = \$7\$} } + end + describe package('splunkforwarder') do it { is_expected.to be_installed } end diff --git a/spec/unit/puppet/type/splunk_types_spec.rb b/spec/unit/puppet/type/splunk_types_spec.rb index 36bbd3cd..76b15a90 100644 --- a/spec/unit/puppet/type/splunk_types_spec.rb +++ b/spec/unit/puppet/type/splunk_types_spec.rb @@ -71,5 +71,34 @@ expect(described_class.provider(:ini_setting).file_name).to eq(file_name) end end + + describe 'value property' do + it 'has a value property' do + expect(described_class.attrtype(:value)).to eq(:property) + end + context 'when testing value is insync' do + let(:resource) { described_class.new(title: 'foo/bar', value: 'value') } + let(:property) { resource.property(:value) } + + before do + Puppet::Type.type(:splunk_config).new( + name: 'config', + server_confdir: '/opt/splunk/etc', + forwarder_confdir: '/opt/splunkforwarder/etc' + ).generate + end + + it 'is insync if unencrypted `is` value matches `should` value' do + property.should = 'value' + expect(property).to be_safe_insync('value') + end + it 'is insync if encrypted `is` value matches `should` value after being decrypted' do + property.should = 'temp1234' + allow(File).to receive(:file?).with(%r{/opt/splunk(forwarder)?/etc/auth/splunk\.secret$}).and_return(true) + allow(IO).to receive(:binread).with(%r{/opt/splunk(forwarder)?/etc/auth/splunk\.secret$}).and_return('JX7cQAnH6Nznmild8MvfN8/BLQnGr8C3UYg3mqvc3ArFkaxj4gUt1RUCaRBD/r0CNn8xOA2oKX8/0uyyChyGRiFKhp6h2FA+ydNIRnN46N8rZov8QGkchmebZa5GAM5U50GbCCgzJFObPyWi5yT8CrSCYmv9cpRtpKyiX+wkhJwltoJzAxWbBERiLp+oXZnN3lsRn6YkljmYBqN9tZLTVVpsLvqvkezPgpv727Fd//5dRoWsWBv2zRp0mwDv3tj') + expect(property).to be_safe_insync('$7$aTVkS01HYVNJUk5wSnR5NIu4GXLhj2Qd49n2B6Y8qmA/u1CdL9JYxQ==') + end + end + end end end diff --git a/spec/unit/puppet_x/voxpupuli/splunk/util_spec.rb b/spec/unit/puppet_x/voxpupuli/splunk/util_spec.rb new file mode 100644 index 00000000..4f44a845 --- /dev/null +++ b/spec/unit/puppet_x/voxpupuli/splunk/util_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' +require 'puppet_x/voxpupuli/splunk/util' + +describe PuppetX::Voxpupuli::Splunk::Util do + describe '.decrypt' do + context 'when called with an unencrypted value' do + it 'returns the value unmodified' do + expect(described_class.decrypt('secrets_file', 'non_encrypted_value')).to eq 'non_encrypted_value' + end + end + context 'when called with splunk 7.2 encrypted value' do + let(:encrypted_value) { '$7$aTVkS01HYVNJUk5wSnR5NIu4GXLhj2Qd49n2B6Y8qmA/u1CdL9JYxQ==' } + let(:splunk_secret) { 'JX7cQAnH6Nznmild8MvfN8/BLQnGr8C3UYg3mqvc3ArFkaxj4gUt1RUCaRBD/r0CNn8xOA2oKX8/0uyyChyGRiFKhp6h2FA+ydNIRnN46N8rZov8QGkchmebZa5GAM5U50GbCCgzJFObPyWi5yT8CrSCYmv9cpRtpKyiX+wkhJwltoJzAxWbBERiLp+oXZnN3lsRn6YkljmYBqN9tZLTVVpsLvqvkezPgpv727Fd//5dRoWsWBv2zRp0mwDv3tj' } + + it 'returns decrypted value' do + allow(IO).to receive(:binread).with('secrets_file').and_return(splunk_secret) + expect(described_class.decrypt('secrets_file', encrypted_value)).to eq 'temp1234' + end + end + end +end