Search code examples
puppet

Puppet Parse Order Explanation


I am building an auditd audit.rules file based on the the package-provided 30-stig.rules file.

The stig.rules file is located at /usr/share/doc/audit-<version>/rules/30-stig.rules. However, it's hard to know beforehand what version of auditd is installed therefore I wanted to use an exec resource to copy this file to a standard location:

exec { 'Copy stig.rules to /tmp/stig.rules':
  command  => 'cp $(rpm -qf auditd | grep stig.rules) /tmp/stig.rules',
  unless   => 'cmp /tmp/stig.rules $(rpm -qf auditd | grep stig.rules)',
}

file { '/etc/audit/audit.rules':
  ensure  => file,
  content => template('auditd/audit.rules.erb'),
}

The template (audit.rules.erb) contains:

scope.function_file(['/tmp/stig.rules'])

I initially got an error that the template could not locate /tmp/stig.rules. Therefore, I added:

Exec['Copy stig.rules to /tmp/stig.rules'] -> File['/etc/audit/audit.rules']

Even after this explicit ordering I get the same error that /tmp/stig.rules is not found. It seems that during parsing the file resource is doing some pre-validation even before it executes the 'exec' that it should be ordered after. Can anyone explain this behavior?


Solution

  • As mentioned in the comments, the problem you are having is that Puppet functions execute on the Puppet Master.

    Typically, this problem would be solved by creating a custom fact that returns the auditd version.

    Create a file in your module with something like:

    # lib/facter/auditd_version.rb
    
    Facter.add(:auditd_version) do
      confine :osfamily => 'RedHat'
      setcode do
        Facter::Core::Execution.exec('rpm -q --queryformat "%{VERSION}\n" audit')
      end
    end
    

    You can then reference your file path in the manifest using:

    $file_path = "/usr/share/doc/audit-${facts['auditd_version']}/rules/30-stig.rules"
    

    or (legacy):

    $file_path = "/usr/share/doc/audit-${::auditd_version}/rules/30-stig.rules"
    

    And you can access it in an ERB template using:

    /usr/share/doc/audit-<%= @auditd_version %>/rules/30-stig.rules
    

    If you then need the content of the stig rules file inside your manifest, you would typically transfer that content into your Puppet manifest, and allow Puppet to manage that file:

    $stig_rules = template('mymodule/stig_rules.erb')
    file { "/usr/share/doc/audit-${facts['auditd_version']}/rules/30-stig.rules":
      ensure  => file,
      content => $stig_rules,
    }
    

    And then inside your template you can now access that content as @stig_rules.

    In the event that the RPM occasionally changes the content of that file, you would then need to update your Puppet manifest - true, that is not ideal.

    Another approach would be to simply return the entire file contents in a custom fact.

    # lib/facter/stig_rules.rb
    
    Facter.add(:stig_rules) do
      confine :osfamily => 'RedHat'
      setcode do
        Facter::Core::Execution.exec("cat \
          /usr/share/doc/audit-#{Facter.value(:auditd_version)}/stig.rules")
      end
    end