Skip to content

Commit 8417f2a

Browse files
committed
Compare decrypted values to see if they are insync
When splunk is started, it automatically encrypts certain values when it finds them in config files. To stop puppet reverting these changes, I've overriden `insync?` so that it performs the decryption before comparing. Currently only implemented for splunk >= 7.2 Based on description of algorithm in this python based project. https://github.com/HurricaneLabs/splunksecrets
1 parent d3312a2 commit 8417f2a

File tree

6 files changed

+131
-11
lines changed

6 files changed

+131
-11
lines changed

lib/puppet_x/puppetlabs/splunk/type.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require File.join(File.dirname(__FILE__), '..', '..', 'voxpupuli/splunk/util')
2+
13
module PuppetX
24
module Puppetlabs
35
module Splunk
@@ -26,6 +28,15 @@ def self.clone_type(type)
2628
munge do |v|
2729
v.to_s.strip
2830
end
31+
def insync?(is) # rubocop:disable Lint/NestedMethodDefinition
32+
secrets_file_path = File.join(provider.class.file_path, 'auth/splunk.secret')
33+
if File.file?(secrets_file_path)
34+
PuppetX::Voxpupuli::Splunk::Util.decrypt(secrets_file_path, is) == should
35+
else
36+
Puppet.warning('Secrets file NOT found')
37+
is == should
38+
end
39+
end
2940
end
3041
type.newparam(:setting) do
3142
desc 'The setting being defined.'
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
require 'openssl'
2+
require 'base64'
3+
4+
module PuppetX
5+
module Voxpupuli
6+
module Splunk
7+
class Util
8+
def self.decrypt(secrets_file, value)
9+
return value unless value.start_with?('$7$')
10+
11+
Puppet.debug "Decrypting splunk >= 7.2 data using secret from #{secrets_file}"
12+
value.slice!(0, 3)
13+
data = Base64.strict_decode64(value)
14+
splunk_secret = IO.binread(secrets_file).chomp
15+
16+
iv = data.bytes[0, 16].pack('c*')
17+
tag = data.bytes[-16..-1].pack('c*')
18+
ciphertext = data.bytes[16..-17].pack('c*')
19+
20+
decipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt
21+
decipher.key = OpenSSL::PKCS5.pbkdf2_hmac(splunk_secret, 'disk-encryption', 1, 32, OpenSSL::Digest::SHA256.new)
22+
decipher.iv_len = 16
23+
decipher.iv = iv
24+
decipher.auth_tag = tag
25+
decipher.auth_data = ''
26+
27+
decipher.update(ciphertext) + decipher.final
28+
end
29+
end
30+
end
31+
end
32+
end

spec/acceptance/splunk_enterprise_spec.rb

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
require 'spec_helper_acceptance'
22

33
describe 'splunk enterprise class' do
4+
init = shell('/bin/readlink /sbin/init', acceptable_exit_codes: [0, 1]).stdout
5+
service_name = if init.include? 'systemd'
6+
'Splunkd'
7+
else
8+
'splunk'
9+
end
10+
411
context 'default parameters' do
512
# Using puppet_apply as a helper
613
it 'works idempotently with no errors' do
714
pp = <<-EOS
815
class { '::splunk::enterprise': }
916
EOS
1017

11-
# Run it twice and test for idempotency
18+
# Run it three times and test for eventual idempotency (Needed for SELINUX limitations)
19+
apply_manifest(pp, catch_failures: true)
1220
apply_manifest(pp, catch_failures: true)
1321
apply_manifest(pp, catch_changes: true)
1422
end
@@ -17,13 +25,6 @@ class { '::splunk::enterprise': }
1725
it { is_expected.to be_installed }
1826
end
1927

20-
init = shell('/bin/readlink /sbin/init', acceptable_exit_codes: [0, 1]).stdout
21-
service_name = if init.include? 'systemd'
22-
'Splunkd'
23-
else
24-
'splunk'
25-
end
26-
2728
describe service(service_name) do
2829
it { is_expected.to be_enabled }
2930
it { is_expected.to be_running }
@@ -39,5 +40,21 @@ class { '::splunk::enterprise': }
3940
it { is_expected.to be_grouped_into 'root' }
4041
end
4142
end
43+
44+
# Uninstall so that splunkforwarder tests aren't affected by this set of tests
45+
context 'uninstalling splunk' do
46+
it do
47+
pp = <<-EOS
48+
service { #{service_name}: ensure => stopped }
49+
package { 'splunk': ensure => purged }
50+
file { '/opt/splunk': ensure => absent, force => true, require => Package['splunk'] }
51+
file { '/etc/init.d/splunk': ensure => absent, require => Package['splunk'] }
52+
EOS
53+
apply_manifest(pp, catch_failures: true)
54+
end
55+
describe package('splunk') do
56+
it { is_expected.not_to be_installed }
57+
end
58+
end
4259
end
4360
end

spec/acceptance/splunk_forwarder_spec.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,28 @@
55
# Using puppet_apply as a helper
66
it 'works idempotently with no errors' do
77
pp = <<-EOS
8-
class { '::splunk::params':
8+
class { 'splunk::params':
99
}
10-
class { '::splunk::forwarder':
10+
class { 'splunk::forwarder':
1111
splunkd_port => 8090,
1212
}
13+
splunkforwarder_output { 'tcpout:splunkcloud/sslPassword':
14+
value => 'super_secure_password',
15+
}
1316
EOS
1417

15-
# Run it twice and test for idempotency
18+
# Run it three times and test for eventual idempotency (Needed for SELINUX limitations)
19+
apply_manifest(pp, catch_failures: true)
1620
apply_manifest(pp, catch_failures: true)
1721
apply_manifest(pp, catch_changes: true)
1822
end
1923

24+
describe file('/opt/splunkforwarder/etc/system/local/outputs.conf') do
25+
it { is_expected.to be_file }
26+
its(:content) { is_expected.to match %r{^sslPassword} }
27+
its(:content) { is_expected.to match %r{^sslPassword = \$7\$} }
28+
end
29+
2030
describe package('splunkforwarder') do
2131
it { is_expected.to be_installed }
2232
end

spec/unit/puppet/type/splunk_types_spec.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,34 @@
7171
expect(described_class.provider(:ini_setting).file_name).to eq(file_name)
7272
end
7373
end
74+
75+
describe 'value property' do
76+
it 'has a value property' do
77+
expect(described_class.attrtype(:value)).to eq(:property)
78+
end
79+
context 'when testing value is insync' do
80+
let(:resource) { described_class.new(title: 'foo/bar', value: 'value') }
81+
let(:property) { resource.property(:value) }
82+
83+
before do
84+
Puppet::Type.type(:splunk_config).new(
85+
name: 'config',
86+
server_confdir: '/opt/splunk/etc',
87+
forwarder_confdir: '/opt/splunkforwarder/etc'
88+
).generate
89+
end
90+
91+
it 'is insync if unencrypted `is` value matches `should` value' do
92+
property.should = 'value'
93+
expect(property).to be_safe_insync('value')
94+
end
95+
it 'is insync if encrypted `is` value matches `should` value after being decrypted' do
96+
property.should = 'temp1234'
97+
allow(File).to receive(:file?).with(%r{/opt/splunk(forwarder)?/etc/auth/splunk\.secret$}).and_return(true)
98+
allow(IO).to receive(:binread).with(%r{/opt/splunk(forwarder)?/etc/auth/splunk\.secret$}).and_return('JX7cQAnH6Nznmild8MvfN8/BLQnGr8C3UYg3mqvc3ArFkaxj4gUt1RUCaRBD/r0CNn8xOA2oKX8/0uyyChyGRiFKhp6h2FA+ydNIRnN46N8rZov8QGkchmebZa5GAM5U50GbCCgzJFObPyWi5yT8CrSCYmv9cpRtpKyiX+wkhJwltoJzAxWbBERiLp+oXZnN3lsRn6YkljmYBqN9tZLTVVpsLvqvkezPgpv727Fd//5dRoWsWBv2zRp0mwDv3tj')
99+
expect(property).to be_safe_insync('$7$aTVkS01HYVNJUk5wSnR5NIu4GXLhj2Qd49n2B6Y8qmA/u1CdL9JYxQ==')
100+
end
101+
end
102+
end
74103
end
75104
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
require 'spec_helper'
2+
require 'puppet_x/voxpupuli/splunk/util'
3+
4+
describe PuppetX::Voxpupuli::Splunk::Util do
5+
describe '.decrypt' do
6+
context 'when called with an unencrypted value' do
7+
it 'returns the value unmodified' do
8+
expect(described_class.decrypt('secrets_file', 'non_encrypted_value')).to eq 'non_encrypted_value'
9+
end
10+
end
11+
context 'when called with splunk 7.2 encrypted value' do
12+
let(:encrypted_value) { '$7$aTVkS01HYVNJUk5wSnR5NIu4GXLhj2Qd49n2B6Y8qmA/u1CdL9JYxQ==' }
13+
let(:splunk_secret) { 'JX7cQAnH6Nznmild8MvfN8/BLQnGr8C3UYg3mqvc3ArFkaxj4gUt1RUCaRBD/r0CNn8xOA2oKX8/0uyyChyGRiFKhp6h2FA+ydNIRnN46N8rZov8QGkchmebZa5GAM5U50GbCCgzJFObPyWi5yT8CrSCYmv9cpRtpKyiX+wkhJwltoJzAxWbBERiLp+oXZnN3lsRn6YkljmYBqN9tZLTVVpsLvqvkezPgpv727Fd//5dRoWsWBv2zRp0mwDv3tj' }
14+
15+
it 'returns decrypted value' do
16+
allow(IO).to receive(:binread).with('secrets_file').and_return(splunk_secret)
17+
expect(described_class.decrypt('secrets_file', encrypted_value)).to eq 'temp1234'
18+
end
19+
end
20+
end
21+
end

0 commit comments

Comments
 (0)