Skip to content

Commit 24a0da4

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 24a0da4

File tree

6 files changed

+127
-9
lines changed

6 files changed

+127
-9
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: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
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
@@ -17,13 +24,6 @@ class { '::splunk::enterprise': }
1724
it { is_expected.to be_installed }
1825
end
1926

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-
2727
describe service(service_name) do
2828
it { is_expected.to be_enabled }
2929
it { is_expected.to be_running }
@@ -39,5 +39,21 @@ class { '::splunk::enterprise': }
3939
it { is_expected.to be_grouped_into 'root' }
4040
end
4141
end
42+
43+
# Uninstall so that splunkforwarder tests aren't affected by this set of tests
44+
context 'uninstalling splunk' do
45+
it do
46+
pp = <<-EOS
47+
service { '#{service_name}': ensure => stopped }
48+
package { 'splunk': ensure => purged }
49+
file { '/opt/splunk': ensure => absent, force => true, require => Package['splunk'] }
50+
file { '/etc/init.d/splunk': ensure => absent, require => Package['splunk'] }
51+
EOS
52+
apply_manifest(pp, catch_failures: true)
53+
end
54+
describe package('splunk') do
55+
it { is_expected.not_to be_installed }
56+
end
57+
end
4258
end
4359
end

spec/acceptance/splunk_forwarder_spec.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,27 @@
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

1518
# Run it twice and test for idempotency
1619
apply_manifest(pp, catch_failures: true)
1720
apply_manifest(pp, catch_changes: true)
1821
end
1922

23+
describe file('/opt/splunkforwarder/etc/system/local/outputs.conf') do
24+
it { is_expected.to be_file }
25+
its(:content) { is_expected.to match %r{^sslPassword} }
26+
its(:content) { is_expected.to match %r{^sslPassword = \$7\$} }
27+
end
28+
2029
describe package('splunkforwarder') do
2130
it { is_expected.to be_installed }
2231
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)