diff --git a/lib/puppet/functions/ssh/ipaddresses.rb b/lib/puppet/functions/ssh/ipaddresses.rb new file mode 100644 index 0000000..466647a --- /dev/null +++ b/lib/puppet/functions/ssh/ipaddresses.rb @@ -0,0 +1,40 @@ +# @summary Returns ip addresses of network interfaces (except lo) found by facter. +# @api private +# +# Returns all ip addresses of network interfaces (except lo) found by facter. +# Special network interfaces (e.g. docker0) can be excluded by an exclude list. +Puppet::Functions.create_function(:'ssh::ipaddresses') do + dispatch :ipaddresses do + # @param excluded_interfaces An array of interface names to be excluded. + # @return The IP addresses found. + optional_param 'Array[String[1]]', :excluded_interfaces + return_type 'Array[Stdlib::IP::Address]' + end + + def ipaddresses(excluded_interfaces = []) + facts = closure_scope['facts'] + + # always exclude loopback interface + excluded_interfaces += ['lo'] + + result = [] + facts['networking']['interfaces'].each do |iface, data| + # skip excluded interfaces + next if excluded_interfaces.include?(iface) + + %w[bindings bindings6].each do |binding_type| + next unless data.key?(binding_type) + data[binding_type].each do |binding| + next unless binding.key?('address') + result << binding['address'] + end + end + end + + # Throw away any v6 link-local addresses + fe8064 = IPAddr.new('fe80::/64') + result.delete_if { |ip| fe8064.include? IPAddr.new(ip) } + + result.uniq + end +end diff --git a/lib/puppet/parser/functions/ipaddresses.rb b/lib/puppet/parser/functions/ipaddresses.rb deleted file mode 100644 index 432871d..0000000 --- a/lib/puppet/parser/functions/ipaddresses.rb +++ /dev/null @@ -1,41 +0,0 @@ -module Puppet::Parser::Functions - newfunction(:ipaddresses, type: :rvalue, doc: <<-EOS - Returns all ip addresses of network interfaces (except lo) found by facter. - Special network interfaces (e.g. docker0) can be excluded by an exclude list as - first argument for this function. -EOS - ) do |args| - networking = lookupvar('networking') - - # always exclude loopback interface - interfaces_exclude = ['lo'] - if args.size == 1 - unless args[0].is_a?(Array) - raise(Puppet::ParseError, 'ipaddresses(): Requires first argument to be an Array') - end - interfaces_exclude << args[0] - end - - return false unless networking.include?('interfaces') - - result = [] - networking['interfaces'].each do |iface, data| - # skip excluded interfaces - next if interfaces_exclude.include?(iface) - - %w[bindings bindings6].each do |binding_type| - next unless data.key?(binding_type) - data[binding_type].each do |binding| - next unless binding.key?('address') - result << binding['address'] - end - end - end - - # Throw away any v6 link-local addresses - fe8064 = IPAddr.new('fe80::/64') - result.delete_if { |ip| fe8064.include? IPAddr.new(ip) } - - return result.uniq - end -end diff --git a/manifests/hostkeys.pp b/manifests/hostkeys.pp index f766281..5271681 100644 --- a/manifests/hostkeys.pp +++ b/manifests/hostkeys.pp @@ -1,45 +1,45 @@ # Class ssh::hostkeys class ssh::hostkeys( Boolean $export_ipaddresses = true, Optional[String] $storeconfigs_group = undef, Array $extra_aliases = [], Array $exclude_interfaces = [], Array $exclude_ipaddresses = [], ) { if $export_ipaddresses == true { - $ipaddresses = ipaddresses($exclude_interfaces) + $ipaddresses = ssh::ipaddresses($exclude_interfaces) $ipaddresses_real = $ipaddresses - $exclude_ipaddresses $host_aliases = unique(flatten([ $::fqdn, $::hostname, $extra_aliases, $ipaddresses_real ])) } else { $host_aliases = unique(flatten([ $::fqdn, $::hostname, $extra_aliases])) } if $storeconfigs_group { tag 'hostkey_all', "hostkey_${storeconfigs_group}" } ['dsa', 'rsa', 'ecdsa', 'ed25519'].each |String $key_type| { # can be removed as soon as we drop support for puppet 4 # see https://tickets.puppetlabs.com/browse/FACT-1377?jql=project%20%3D%20FACT%20AND%20fixVersion%20%3D%20%22FACT%203.12.0%22 if $key_type == 'ecdsa' { $key_type_real = 'ecdsa-sha2-nistp256' } else { $key_type_real = $key_type } if $key_type in $facts['ssh'] { @@sshkey { "${::fqdn}_${key_type}": ensure => present, host_aliases => $host_aliases, type => $key_type_real, key => $facts['ssh'][$key_type]['key'], } } else { @@sshkey { "${::fqdn}_${key_type}": ensure => absent, type => $key_type_real, } } } } diff --git a/metadata.json b/metadata.json index 6475f3b..0c8331c 100644 --- a/metadata.json +++ b/metadata.json @@ -1,64 +1,64 @@ { "name": "saz-ssh", "version": "5.0.0", "author": "saz", "summary": "Manage SSH client and server via Puppet.", "description": "Manage SSH client and server via puppet", "license": "Apache-2.0", "source": "git://github.com/saz/puppet-ssh.git", "project_page": "https://github.com/saz/puppet-ssh", "dependencies": [ { "name": "puppetlabs/stdlib", - "version_requirement": ">= 4.24.0 < 7.0.0" + "version_requirement": ">= 4.25.0 < 7.0.0" }, { "name": "puppetlabs/concat", "version_requirement": ">= 2.2.0 < 7.0.0" } ], "operatingsystem_support": [ { "operatingsystem": "RedHat" }, { "operatingsystem": "CentOS" }, { "operatingsystem": "OracleLinux" }, { "operatingsystem": "Scientific" }, { "operatingsystem": "Debian" }, { "operatingsystem": "Ubuntu" }, { "operatingsystem": "FreeBSD" }, { "operatingsystem": "DragonFly" }, { "operatingsystem": "OpenBSD" }, { "operatingsystem": "Gentoo" }, { "operatingsystem": "Solaris" }, { "operatingsystem": "ArchLinux" } ], "requirements": [ { "name": "puppet", "version_requirement": ">= 4.10.10 < 7.0.0" } ] } diff --git a/spec/fixtures/.gitignore b/spec/fixtures/.gitignore index 0616a13..8182073 100644 --- a/spec/fixtures/.gitignore +++ b/spec/fixtures/.gitignore @@ -1,5 +1,6 @@ # Ignore everything in this directory * -# Except this file +# Except these files !.gitignore !site.pp +!mock-interface-fact.json diff --git a/spec/fixtures/mock-interface-fact.json b/spec/fixtures/mock-interface-fact.json new file mode 100644 index 0000000..b6ed4cc --- /dev/null +++ b/spec/fixtures/mock-interface-fact.json @@ -0,0 +1,89 @@ +{ + "networking": { + "interfaces": { + "docker0": { + "bindings": [ + { + "address": "172.17.0.1", + "netmask": "255.255.0.0", + "network": "172.17.0.0" + } + ], + "bindings6": [ + { + "address": "fe80::42:2fff:fea3:f2b7", + "netmask": "ffff:ffff:ffff:ffff::", + "network": "fe80::" + } + ], + "ip": "172.17.0.1", + "ip6": "fe80::42:2fff:fea3:f2b7", + "mac": "02:42:2f:a3:f2:b7", + "mtu": 1500, + "netmask": "255.255.0.0", + "netmask6": "ffff:ffff:ffff:ffff::", + "network": "172.17.0.0", + "network6": "fe80::" + }, + "eno1": { + "bindings": [ + { + "address": "10.13.42.61", + "netmask": "255.255.255.0", + "network": "10.13.42.0" + }, + { + "address": "10.0.0.110", + "netmask": "255.255.255.255", + "network": "10.0.0.110" + }, + { + "address": "10.0.0.104" + }, + { + "address": "10.0.0.109" + } + ], + "bindings6": [ + { + "address": "fe80::6544:473a:6ea4:c385", + "netmask": "ffff:ffff:ffff:ffff::", + "network": "fe80::" + } + ], + "dhcp": "10.13.42.1", + "ip": "10.13.42.61", + "ip6": "fe80::6544:473a:6ea4:c385", + "mac": "08:00:20:97:23:d1", + "mtu": 1500, + "netmask": "255.255.255.0", + "netmask6": "ffff:ffff:ffff:ffff::", + "network": "10.13.42.0", + "network6": "fe80::" + }, + "lo": { + "bindings": [ + { + "address": "127.0.0.1", + "netmask": "255.0.0.0", + "network": "127.0.0.0" + } + ], + "bindings6": [ + { + "address": "::1", + "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "network": "::1" + } + ], + "ip": "127.0.0.1", + "ip6": "::1", + "mtu": 65536, + "netmask": "255.0.0.0", + "netmask6": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "network": "127.0.0.0", + "network6": "::1" + } + } + } +} diff --git a/spec/functions/ssh/ipaddresses_spec.rb b/spec/functions/ssh/ipaddresses_spec.rb new file mode 100644 index 0000000..b968986 --- /dev/null +++ b/spec/functions/ssh/ipaddresses_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe 'ssh::ipaddresses', type: :puppet_function do + it 'exists' do + is_expected.not_to be_nil + end + + context 'with dummy fact data' do + let(:facts) do + JSON.parse File.read(File.join(File.dirname(__FILE__), '../../fixtures/mock-interface-fact.json')) + end + + describe 'without parameters' do + it 'returns all IPs other than localhost' do + is_expected.to run.and_return(['172.17.0.1', '10.13.42.61', '10.0.0.110', '10.0.0.104', '10.0.0.109']) + end + end + + describe 'with excluded interface' do + it 'doesn\'t return the IPs of excluded interface' do + is_expected.to run.with_params(['docker0']).and_return(['10.13.42.61', '10.0.0.110', '10.0.0.104', '10.0.0.109']) + end + end + describe 'with excluded interfaces' do + it 'doesn\'t return the IPs of those interfaces' do + is_expected.to run.with_params(%w[docker0 eno1]).and_return([]) + end + end + end +end