1

Is there a way to get the ipaddress from a >>puppet custom function<< ?

I'm trying use Puppet::FileServing::Content.indirection.find on a custom endpoint like this:

[secret]
path /var/lib/puppet/secret/%H
allow *

when using Puppet::FileServing::Content.indirection.find('puppet:///secret/file'), the %H portion of the path is replaced with the puppetmaster hostname instead of the client. After hacking the code, I found that you have to pass the node hostname, the ipaddress and the environment like this.

content = Puppet::FileServing::Content.indirection.find(
  'puppet:///secret/file',
  :node => lookupvar('fqdn'),
  :ip => lookupvar('ipaddress'),
  :environment => environment
)
return content.full_path() # this will be /var/lib/puppet/secret/clientnode

However, I found that using lookupvar('fqdn') and lookupvar('ipaddress') is not reliable because a node can easily overwrite these values with export FACTER_fqdn=whatever.

Using lookupvar('clientcert') seems to be the way to go for the hostname because this is inserted by puppet itself.

EDIT:

The function I'm building is to translate puppet: url that point to an encrypted file to a another puppet: url that point at the decrypted file.

Use case:

file{ '/tmp/test'
  source => eyaml_decrypt('puppet:///modules/profile/some_file')
}

In the puppet files, there could be some encrypted file at modules/profile/files/some_file.enc which represent the encrypted version of the given file. This way, we can share our puppet files with everyone without risking a leak of sensitive information. The function will then return a private puppet url like puppet:///private/some_file which will resolve to /var/lib/puppet/private/FQDN/some_file.

This is my decrypt function :

module Puppet::Parser::Functions
  newfunction(:eyaml_decrypt, :type => :rvalue, :doc => <<-EOS
Decrypt the given file with `eyaml decrypt -f filepath.enc > filepath`\n
If the target file already exist, the filepath is directly returned. No decryption occurs.\n
 [0] - filepath\n
returns filepath\n
EOS
  ) do |args|

    require 'puppet/file_serving'
    require 'puppet/file_serving/content'
    require 'puppet/file_serving/configuration'
    require 'fileutils'
    require 'digest'

    # Check if file is a puppet url.
    if not args[0].start_with?('puppet:///')
      # This does not mean that there is an error. It could be because in dev, we use a direct path from a node instead of a puppetmaster path.
      Puppet::notice("eyaml_decrypt: The file '#{args[0]}' does not look like a puppet url therefore, no decrypt occurs.")
      return args[0]
    end

    # Check if we already have the unencrypted form of the file in the fileserver
    unencrypted_content = Puppet::FileServing::Content.indirection.find(args[0], :node => lookupvar('clientcert'), :ip => lookupvar('ipaddress'), :environment => environment)
    if unencrypted_content
      return args[0]
    end

    # Check if we have the encrypted form of the file in the fileserver
    encrypted_content = Puppet::FileServing::Content.indirection.find(args[0] + '.enc', :node => lookupvar('clientcert'), :ip => lookupvar('ipaddress'), :environment => environment)
    if not encrypted_content
      # This happens when the unencrypted file is not present AND the encrypted file is not present aswell.
      # This is most likely a mistake from a fully qualified puppetlord and will probably result in a puppet error because the file args[0] won't exist.
      return args[0]
    end
    encrypted_filepath = encrypted_content.full_path()

    # If the hiera specify the box-is-vagrant, no decryption can occurs because the private key is not accessible.
    # If a vagrant box (most likely dev or intg) tries to decrypt a file, it is most propbably a mistake because encrypted data are restricted to prod and accp
    use_hiera_eyaml = function_hiera(['use_hiera_eyaml', false])
    if not use_hiera_eyaml
      Puppet::notice("eyaml_decrypt: no decryption occured for '#{args[0]}.enc' because use_hiera_eyaml is set to false")
      Puppet::notice("eyaml_decrypt: this most likely mean that there is a mistake in the puppet logic. A vagrant box should not have to access private encrypted file")
      return args[0] + '.enc'
    end

    # Check that the private mount does indeed exist.
    private_mount = Puppet::FileServing::Configuration.configuration.find_mount('private', environment)
    if not private_mount
      Puppet::err("eyaml_decrypt: The mount point 'private' is unavailable or does not exist. Check your fileserver.conf")
      return args[0]
    end

    # Prepare the private unencrypted file
    private_filename = Digest::MD5.hexdigest(args[0]) + '-' + File::basename(args[0])
    private_filepath = private_mount.path(lookupvar('clientcert')) + '/' + private_filename
    private_directory_path = File.dirname(private_filepath)
    if not File.directory?(private_directory_path)
      FileUtils.mkdir_p(private_directory_path)
    end

    # use eyaml to decrypt the encrypted file to the private file
    `eyaml decrypt -f #{encrypted_filepath} > #{private_filepath}`
    Puppet::notice("eyaml_decrypt: file '#{encrypted_filepath}' decrypted to '#{private_filepath}'")

    # return the private url for the unencrypted file
    return 'puppet:///private/' + private_filename
  end
end

I wish puppet had an API to send me reliable information about the node that is connecting like the ssl certificate and ip address.

Nico
  • 6,395
  • 4
  • 25
  • 34
  • 1
    puppet instances are built from facter, if you can't trust facter, then how can you work on puppet? – BMW Jan 20 '16 at 01:45
  • 2
    Is there a reason you don't want to use the native facter tools to get the ipaddress or fqdn? If it's a security issue, I think you might want to use trusted facts: https://docs.puppetlabs.com/puppet/latest/reference/lang_facts_and_builtin_vars.html#trusted-facts which can't be spoofed in the same way as the example you give. – Peter Souter Jan 20 '16 at 12:32
  • @BMW we use puppet for our development environment. Dev machine are not allowed to be provisionned as a production machine because of password and other sensitive data. This is why I cannot trust the facter ipaddress and fqdn. Because the api I'm using require me to pass the node name and the ipaddress, I want them to be as reliable as possible. See my edit as to why. – Nico Jan 20 '16 at 14:59
  • From what I have read from the code, the ipaddress is used to validate the allow directive from the fileserver.conf and the auth.conf so this value should not come front facter. – Nico Jan 20 '16 at 15:20

1 Answers1

0

Maybe you need to remove your sensitive information from the dev environment completely. Perhaps you could use the "environment" (puppet config environment defaults to production) as part of your string to look up the secrets file. r10k is good for making this sort of thing happen automagically

If in a dev environment the production secrets then just would not be available

Vorsprung
  • 32,923
  • 5
  • 39
  • 63
  • Are you suggesting to make another puppetmaster that would provision only dev boxes ? How do you suggest to use the environment to resolve puppet url ? Right now, we have one puppetmaster that provisions dev and production boxes. Any dev box can fake their fqdn and ipaddress with facter (`export FACTER_fqdn=foo && export FACTER_ipaddress=bar`) and provision with fqdn=foo and ipaddress=bar. While this will just obviously fail, the fact that the puppetmaster might decrypt some file on the behalf of the wrong server just annoys me. – Nico Jan 20 '16 at 15:46
  • 1
    no I am suggesting that you use the "environment" feature of the puppet server to isolate the dev and production environments. See https://puppetlabs.com/blog/git-workflow-and-puppet-environments – Vorsprung Jan 20 '16 at 16:44