Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compare decrypted values to see if they are insync #221

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/puppet_x/puppetlabs/splunk/type.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require File.join(File.dirname(__FILE__), '..', '..', 'voxpupuli/splunk/util')

module PuppetX
module Puppetlabs
module Splunk
Expand Down Expand Up @@ -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.'
Expand Down
32 changes: 32 additions & 0 deletions lib/puppet_x/voxpupuli/splunk/util.rb
Original file line number Diff line number Diff line change
@@ -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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dunno if I should catch exceptions here, log a warning, and return something so that puppet can continue??

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO things this deep shouldn't rescue exceptions because they're too deep to know what to do about them. That should be done higher up. IMHO only rescue exceptions here if you watch to toss your own customized exception like Spunk::Util::DecodeException blah blah blah IMHO YMMV

Copy link
Member Author

@alexjfisher alexjfisher Apr 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thought was if decryption did fail, it could be because of something like the splunk.secrets file being replaced. If decryption does fail (for whatever reason), it's maybe better for insync? to return false instead of a hard error being thrown and not handled.

I like your advice on rethrowing a custom exception and catching it higher up. I'll leave this as is in this PR, but might work on an enhancement in a separate PR later. Thanks for the tip.

end
end
end
end
end
30 changes: 23 additions & 7 deletions spec/acceptance/splunk_enterprise_spec.rb
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 }
Expand All @@ -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
13 changes: 11 additions & 2 deletions spec/acceptance/splunk_forwarder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,27 @@
# 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
apply_manifest(pp, catch_failures: true)
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
Expand Down
29 changes: 29 additions & 0 deletions spec/unit/puppet/type/splunk_types_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
21 changes: 21 additions & 0 deletions spec/unit/puppet_x/voxpupuli/splunk/util_spec.rb
Original file line number Diff line number Diff line change
@@ -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