Puppet Function: ssh::global_known_hosts

Defined in:
lib/puppet/functions/ssh/global_known_hosts.rb
Function type:
Ruby 4.x API

Summary

Update the ssh_known_hosts files for all hosts, purging old files,

Overview

ssh::global_known_hosts(Optional[Integer] $expire_days)None

removing duplicates, and creating catalog resources that are found

Note: This function if marked as an InternalFunction because it
changes the state of the system by adding/removing files and
adding catalog resources.

Parameters:

  • expire_days (Optional[Integer])

    expire time in days; defaults to 7; value of 0 means never purge

Returns:

  • (None)


8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/puppet/functions/ssh/global_known_hosts.rb', line 8

Puppet::Functions.create_function(:'ssh::global_known_hosts', Puppet::Functions::InternalFunction) do

  # @param expire_days expire time in days; defaults to 7; value of 0
  #   means never purge
  #
  # @return [None]
  dispatch :global_known_hosts do
    optional_param 'Integer', :expire_days
  end

  def global_known_hosts(expire_days = 7)
    require 'yaml'
    require 'find'
    require 'fileutils'

    env = closure_scope.lookupvar('::environment')
    basedir = "#{Puppet[:vardir]}/simp/environments/#{env}/simp_autofiles/ssh_global_known_hosts"

    # First, write out my key.
    write_this_host_key_file(basedir)

    # Now read all keys, resolve duplicates, and update the catalogue.
    update_catalog(basedir, expire_days)

    # Finally, purge any keys past the expire date. If the date is set to 0,
    # then don't purge.
    if expire_days != 0 then
      Dir.glob("#{basedir}/*").each do |file|
        if (Time.now - File.stat(file).mtime)/86400 > expire_days then
          FileUtils.rm(file)
        end
      end
    end
  end

  def write_this_host_key_file(basedir)
    #FIXME accessing facts per the documentation doesn't work in unit tests
    # fqdn = closure_scope['facts']['networking']['fqdn']
    # rsakey = closure_scope['facts']['sshrsakey']
    fqdn = closure_scope.lookupvar('::fqdn')
    rsakey = closure_scope.lookupvar('::sshrsakey')

    begin
      if not FileTest.directory?(basedir) then
        FileUtils.mkdir_p(basedir, mode: 0750)
      end

      if not File.stat(basedir).writable? then
        fail("ssh::global_known_hosts: Error, can't write #{basedir}")
      end
    rescue
      fail("ssh::global_known_hosts: Error, #{basedir} must be writable by #{Process.uid}")
    end

    hostkey = File.open("#{basedir}/#{fqdn}",'w+',0640)
    hostkey.puts(rsakey)
    hostkey.close
  end

  def collect_hostnames(basedir)
    hnames = {
      :longnames => [],
      :shortnames => []
    }

    # Collect current list of hostnames
    Find.find(basedir) do |file|
      if ( not FileTest.directory?(file) ) and FileTest.readable?(file)

        hname = File.basename(file).strip

        if hname.include?('.') then
          hnames[:longnames] << hname
        else
          hnames[:shortnames] << hname
        end
      end
    end

    # Remove any old files that exist that have newer conflicts.
    hnames[:shortnames].dup.each do |short_name|
      long_dup = hnames[:longnames].find{|x| x =~ /^#{short_name}\..*$/ }
      if long_dup then
        to_del = nil
        if File.stat("#{basedir}/#{short_name}").mtime < File.stat("#{basedir}/#{long_dup}").mtime then
          hnames[:shortnames].delete(short_name)
          to_del = "#{basedir}/#{short_name}"
        else
          hnames[:longnames].delete(long_dup)
          to_del = "#{basedir}/#{long_dup}"
        end

        #FIXME Shouldn't we remove/modify to ensure-absent any existing catalog entry for
        # the file we will be deleting?
        Puppet.notice("ssh::global_known_hosts is removing '#{to_del}' due to a conflict.")
        FileUtils.rm_f(to_del)
      end
    end
    (hnames[:longnames] + hnames[:shortnames])
  end

  def update_catalog(basedir, expire_days)
    hnames = collect_hostnames(basedir)
    hnames.each do |hname|

      file = "#{basedir}/#{hname}"

      ssh_ensure = 'present'
      if expire_days != 0 then
        if (Time.now - File.stat(file).mtime)/86400 > expire_days then
          ssh_ensure = 'absent'
        end
      end

      sshkey_resource_hash =  {
         hname=> {
          :type         => 'ssh-rsa',
          :host_aliases => hname.split('.').first,
          :key          => File.open(file,'r').read.strip,
          :ensure       => ssh_ensure
        }
      }
     begin
       call_function('create_resources', 'sshkey', sshkey_resource_hash)
     rescue  Puppet::Resource::Catalog::DuplicateResourceError => e
       # FIXME  This will fail if the resource exists.  We should either
       # remove any existing resource before creating it, or modify it in
       # case any of the existing parameters are wrong (notably ensure).
     end
    end
  end
end