From 5c485b8cdde48746a27598c9ae1e84e87c79707f Mon Sep 17 00:00:00 2001 From: Colleen Murphy Date: Thu, 18 Dec 2014 11:39:03 -0800 Subject: [PATCH] Replace erlang cookie fact with type and provider Keeping secrets as facts is a potential security risk since facts are generally not protected as secrets. This change removes the rabbitmq_erlang_cookie fact. In order to make this feasible, the exec that wipes out the rabbitmq db and the file resource managing the cookie are replaced by a type and provider to reproduce this functionality. --- lib/facter/rabbitmq_erlang_cookie.rb | 16 ------- .../provider/rabbitmq_erlang_cookie/ruby.rb | 38 ++++++++++++++++ lib/puppet/type/rabbitmq_erlang_cookie.rb | 36 +++++++++++++++ manifests/config.pp | 35 ++++----------- spec/acceptance/clustering_spec.rb | 30 ++++++++++++- spec/classes/rabbitmq_spec.rb | 31 +++---------- .../unit/facts/rabbitmq_erlang_cookie_spec.rb | 19 -------- .../type/rabbitmq_erlang_cookie_spec.rb | 45 +++++++++++++++++++ 8 files changed, 161 insertions(+), 89 deletions(-) delete mode 100644 lib/facter/rabbitmq_erlang_cookie.rb create mode 100644 lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb create mode 100644 lib/puppet/type/rabbitmq_erlang_cookie.rb delete mode 100644 spec/unit/facts/rabbitmq_erlang_cookie_spec.rb create mode 100644 spec/unit/puppet/type/rabbitmq_erlang_cookie_spec.rb diff --git a/lib/facter/rabbitmq_erlang_cookie.rb b/lib/facter/rabbitmq_erlang_cookie.rb deleted file mode 100644 index 474e12885..000000000 --- a/lib/facter/rabbitmq_erlang_cookie.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Fact: rabbitmq_erlang_cookie -# -# Purpose: To determine the current erlang cookie value. -# -# Resolution: Returns the cookie. -Facter.add(:rabbitmq_erlang_cookie) do - confine :osfamily => %w[Debian RedHat Suse] - - setcode do - if File.exists?('/var/lib/rabbitmq/.erlang.cookie') - File.read('/var/lib/rabbitmq/.erlang.cookie') - else - nil - end - end -end diff --git a/lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb b/lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb new file mode 100644 index 000000000..6e05b7811 --- /dev/null +++ b/lib/puppet/provider/rabbitmq_erlang_cookie/ruby.rb @@ -0,0 +1,38 @@ +require 'puppet' +require 'set' +Puppet::Type.type(:rabbitmq_erlang_cookie).provide(:ruby) do + + defaultfor :feature => :posix + has_command(:puppet, 'puppet') do + environment :PATH => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin' + end + + def exists? + # Hack to prevent the create method from being called. + # We never need to create or destroy this resource, only change its value + true + end + + def content=(value) + if resource[:force] == :true # Danger! + puppet('resource', 'service', resource[:service_name], 'ensure=stopped') + FileUtils.rm_rf('/var/lib/rabbitmq/mnesia') + File.open(resource[:path], 'w') do |cookie| + cookie.chmod(0400) + cookie.write(value) + end + FileUtils.chown('rabbitmq', 'rabbitmq', resource[:path]) + else + fail("The current erlang cookie needs to change. In order to do this the RabbitMQ database needs to be wiped. Please set force => true to allow this to happen automatically.") + end + end + + def content + if File.exists?(resource[:path]) + File.read(resource[:path]) + else + '' + end + end + +end diff --git a/lib/puppet/type/rabbitmq_erlang_cookie.rb b/lib/puppet/type/rabbitmq_erlang_cookie.rb new file mode 100644 index 000000000..fc56575a9 --- /dev/null +++ b/lib/puppet/type/rabbitmq_erlang_cookie.rb @@ -0,0 +1,36 @@ +Puppet::Type.newtype(:rabbitmq_erlang_cookie) do + desc 'Type to manage the rabbitmq erlang cookie securely' + + newparam(:path, :namevar => true) + + validate do + # This does pre-validation on the content property and force parameter. + # The intent is to simulate the prior behavior to the invention of this + # type (see https://github.com/puppetlabs/puppetlabs-rabbitmq/blob/4.1.0/manifests/config.pp#L87-L117) + # where validation occurs before the catalog starts being applied. + # This prevents other resources from failing after attempting to apply + # this resource and having it fail due to the force parameter being + # set to false. + is = (File.read(self[:path]) if File.exists?(self[:path])) || '' + should = self[:content] + failstring = 'The current erlang cookie needs to change. In order to do this the RabbitMQ database needs to be wiped. Please set force => true to allow this tohappen automatically.' + fail(failstring) if (is != should && self[:force] != :true) + end + + newproperty(:content) do + desc 'Content of cookie' + newvalues(/^\S+$/) + def change_to_s(current, desired) + "The rabbitmq erlang cookie was changed" + end + end + + newparam(:force) do + defaultto(:false) + newvalues(:true, :false) + end + + newparam(:service_name) do + newvalues(/^\S+$/) + end +end diff --git a/manifests/config.pp b/manifests/config.pp index d07861a03..3f65bb5f7 100644 --- a/manifests/config.pp +++ b/manifests/config.pp @@ -100,34 +100,17 @@ if $config_cluster { - file { 'erlang_cookie': - ensure => 'present', - path => '/var/lib/rabbitmq/.erlang.cookie', - owner => 'rabbitmq', - group => 'rabbitmq', - mode => '0400', - content => $erlang_cookie, - replace => true, - before => File['rabbitmq.config'], - notify => Class['rabbitmq::service'], - } - - # rabbitmq_erlang_cookie is a fact in this module. - if $erlang_cookie != $::rabbitmq_erlang_cookie { - # Safety check. - if $wipe_db_on_cookie_change { - exec { 'wipe_db': - command => "puppet resource service ${service_name} ensure=stopped; rm -rf /var/lib/rabbitmq/mnesia", - path => '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin', - } - File['erlang_cookie'] { - require => Exec['wipe_db'], - } - } else { - fail("ERROR: The current erlang cookie is ${::rabbitmq_erlang_cookie} and needs to change to ${erlang_cookie}. In order to do this the RabbitMQ database needs to be wiped. Please set the parameter called wipe_db_on_cookie_change to true to allow this to happen automatically.") + if $erlang_cookie == undef { + fail('You must set the $erlang_cookie value in order to configure clustering.') + } else { + rabbitmq_erlang_cookie { '/var/lib/rabbitmq/.erlang.cookie': + content => $erlang_cookie, + force => $wipe_db_on_cookie_change, + service_name => $service_name, + before => File['rabbitmq.config'], + notify => Class['rabbitmq::service'], } } - } diff --git a/spec/acceptance/clustering_spec.rb b/spec/acceptance/clustering_spec.rb index 04627fa6f..438c65ba1 100644 --- a/spec/acceptance/clustering_spec.rb +++ b/spec/acceptance/clustering_spec.rb @@ -1,13 +1,38 @@ require 'spec_helper_acceptance' describe 'rabbitmq clustering' do - context 'rabbitmq::config_cluster => true' do + context 'rabbitmq::wipe_db_on_cookie_change => false' do it 'should run successfully' do pp = <<-EOS class { 'rabbitmq': config_cluster => true, cluster_nodes => ['rabbit1', 'rabbit2'], cluster_node_type => 'ram', + erlang_cookie => 'TESTCOOKIE', + wipe_db_on_cookie_change => false, + } + if $::osfamily == 'RedHat' { + class { 'erlang': epel_enable => true} + Class['erlang'] -> Class['rabbitmq'] + } + EOS + + apply_manifest(pp, :expect_failures => true) + end + + describe file('/var/lib/rabbitmq/.erlang.cookie') do + it { should_not contain 'TESTCOOKIE' } + end + + end + context 'rabbitmq::wipe_db_on_cookie_change => true' do + it 'should run successfully' do + pp = <<-EOS + class { 'rabbitmq': + config_cluster => true, + cluster_nodes => ['rabbit1', 'rabbit2'], + cluster_node_type => 'ram', + erlang_cookie => 'TESTCOOKIE', wipe_db_on_cookie_change => true, } if $::osfamily == 'RedHat' { @@ -15,7 +40,7 @@ class { 'erlang': epel_enable => true} Class['erlang'] -> Class['rabbitmq'] } EOS - + apply_manifest(pp, :catch_failures => true) end @@ -29,6 +54,7 @@ class { 'erlang': epel_enable => true} describe file('/var/lib/rabbitmq/.erlang.cookie') do it { should be_file } + it { should contain 'TESTCOOKIE' } end end end diff --git a/spec/classes/rabbitmq_spec.rb b/spec/classes/rabbitmq_spec.rb index f8595093b..a24c01ff8 100644 --- a/spec/classes/rabbitmq_spec.rb +++ b/spec/classes/rabbitmq_spec.rb @@ -223,7 +223,6 @@ context "on #{distro}" do let(:facts) {{ :osfamily => distro, - :rabbitmq_erlang_cookie => 'EOKOWXQREETZSHFNTPEY', :lsbdistcodename => 'squeeze', :lsbdistid => 'Debian' }} @@ -278,22 +277,21 @@ end context 'configures config_cluster' do - let(:facts) {{ :osfamily => distro, :rabbitmq_erlang_cookie => 'ORIGINAL', :lsbdistid => 'Debian' }} + let(:facts) {{ :osfamily => distro, :lsbdistid => 'Debian' }} let(:params) {{ :config_cluster => true, :cluster_nodes => ['hare-1', 'hare-2'], :cluster_node_type => 'ram', - :erlang_cookie => 'TESTCOOKIE', :wipe_db_on_cookie_change => false }} describe 'with defaults' do it 'fails' do - expect{subject}.to raise_error(/^ERROR: The current erlang cookie is ORIGINAL/) + expect{subject}.to raise_error(/^You must set the \$erlang_cookie value/) end end - describe 'with wipe_db_on_cookie_change set' do + describe 'with erlang_cookie set' do let(:params) {{ :config_cluster => true, :cluster_nodes => ['hare-1', 'hare-2'], @@ -301,22 +299,8 @@ :erlang_cookie => 'TESTCOOKIE', :wipe_db_on_cookie_change => true }} - it 'wipes the database' do - should contain_exec('wipe_db') - should contain_file('erlang_cookie') - end - end - - describe 'correctly when cookies match' do - let(:params) {{ - :config_cluster => true, - :cluster_nodes => ['hare-1', 'hare-2'], - :cluster_node_type => 'ram', - :erlang_cookie => 'ORIGINAL', - :wipe_db_on_cookie_change => true - }} - it 'and doesnt wipe anything' do - should contain_file('erlang_cookie') + it 'contains the rabbitmq_erlang_cookie' do + should contain_rabbitmq_erlang_cookie('/var/lib/rabbitmq/.erlang.cookie') end end @@ -334,11 +318,6 @@ }) end - it 'for erlang_cookie' do - should contain_file('erlang_cookie').with({ - 'content' => 'ORIGINAL', - }) - end end end diff --git a/spec/unit/facts/rabbitmq_erlang_cookie_spec.rb b/spec/unit/facts/rabbitmq_erlang_cookie_spec.rb deleted file mode 100644 index f4daf79bd..000000000 --- a/spec/unit/facts/rabbitmq_erlang_cookie_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe 'rabbitmq_erlang_cookie', :type => :fact do - before(:each) { Facter.clear } - - it 'works correctly' do - Facter.fact(:osfamily).stubs(:value).returns('RedHat') - File.stubs(:exists?).with('/var/lib/rabbitmq/.erlang.cookie').returns(true) - File.stubs(:read).with('/var/lib/rabbitmq/.erlang.cookie').returns('THISISACOOKIE') - Facter.fact(:rabbitmq_erlang_cookie).value.should == 'THISISACOOKIE' - end - - it 'fails if file doesnt exist' do - Facter.fact(:osfamily).stubs(:value).returns('RedHat') - File.stubs(:exists?).with('/var/lib/rabbitmq/.erlang.cookie').returns(false) - Facter.fact(:rabbitmq_erlang_cookie).value.should == nil - end - -end diff --git a/spec/unit/puppet/type/rabbitmq_erlang_cookie_spec.rb b/spec/unit/puppet/type/rabbitmq_erlang_cookie_spec.rb new file mode 100644 index 000000000..c35548351 --- /dev/null +++ b/spec/unit/puppet/type/rabbitmq_erlang_cookie_spec.rb @@ -0,0 +1,45 @@ +require 'mocha' +require 'puppet' +require 'puppet/type/rabbitmq_exchange' +RSpec.configure do |config| + config.mock_with :mocha +end +describe Puppet::Type.type(:rabbitmq_erlang_cookie) do + context 'when content needs to change and force is unset' do + it 'fails' do + File.expects(:read).with('/var/lib/rabbitmq/.erlang.cookie').returns('OLDCOOKIE') + File.expects(:exists?).returns(true) + expect { + Puppet::Type.type(:rabbitmq_erlang_cookie).new( + :name => '/var/lib/rabbitmq/.erlang.cookie', + :content => 'NEWCOOKIE', + ) + }.to raise_error(Puppet::Error, /The current erlang cookie needs to change/) + end + end + + context 'when content needs to change and force is true' do + it 'sets the cookie' do + File.expects(:read).with('/var/lib/rabbitmq/.erlang.cookie').returns('OLDCOOKIE') + File.expects(:exists?).returns(true) + cookie = Puppet::Type.type(:rabbitmq_erlang_cookie).new( + :name => '/var/lib/rabbitmq/.erlang.cookie', + :content => 'NEWCOOKIE', + :force => true, + ) + expect(cookie[:content]).to eq('NEWCOOKIE') + end + end + + context 'when content does not need to change' do + it 'still sets the cookie' do + File.expects(:read).with('/var/lib/rabbitmq/.erlang.cookie').returns('NEWCOOKIE') + File.expects(:exists?).returns(true) + cookie = Puppet::Type.type(:rabbitmq_erlang_cookie).new( + :name => '/var/lib/rabbitmq/.erlang.cookie', + :content => 'NEWCOOKIE', + ) + expect(cookie[:content]).to eq('NEWCOOKIE') + end + end +end