From 1b74efd2d08aff7f24d272120cc7fbb035faae43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Hertzog?= Date: Thu, 26 Mar 2015 17:50:32 +0100 Subject: [PATCH] Add a new openssh.known_hosts state This state manages /etc/ssh/ssh_known_hosts and fills it with public SSH host keys of other minions. --- README.rst | 39 +++++++++++++++++++++++++++++++++++ openssh/files/ssh_known_hosts | 34 ++++++++++++++++++++++++++++++ openssh/known_hosts.sls | 16 ++++++++++++++ openssh/map.jinja | 12 +++++++++++ pillar.example | 24 +++++++++++++++++++++ 5 files changed, 125 insertions(+) create mode 100644 openssh/files/ssh_known_hosts create mode 100644 openssh/known_hosts.sls diff --git a/README.rst b/README.rst index ee2de6b..d33b9ab 100644 --- a/README.rst +++ b/README.rst @@ -41,3 +41,42 @@ Installs the ssh daemon configuration file included in this formula by values from pillar. ``pillar.example`` results in the generation of the default ``sshd_config`` file on Debian Wheezy. +``openssh.known_hosts`` +----------------------- + +Manages the site-wide ssh_known_hosts file and fills it with the +public SSH host keys of all minions. You can restrict the set of minions +whose keys are listed by using the pillar data ``openssh:known_hosts:target`` +and ``openssh:known_hosts:expr_form`` (those fields map directly to the +corresponding attributes of the ``mine.get`` function). + +The Salt mine is used to share the public SSH host keys, you must thus +configure it accordingly on all hosts that must export their keys. Two +mine functions are required, one that exports the keys (one key per line, +as they are stored in ``/etc/ssh/ssh_host_*_key.pub``) and one that defines +the public hostname that the keys are associated to. Here's the way to +setup those functions through pillar:: + + # Required for openssh.known_hosts + mine_functions: + public_ssh_host_keys: + mine_function: cmd.run + cmd: cat /etc/ssh/ssh_host_*_key.pub + public_ssh_hostname: + mine_function: grains.get + key: id + +The above example assumes that the minion identifier is a valid DNS name +that can be used to connect to the host. If that's not the case, you might +want to use the ``fqdn`` grain instead of the ``id`` one. The above example +also uses the default mine function names used by this formula. If you have to +use other names, then you should indicate the names to use in pillar keys +``openssh:known_hosts:mine_keys_function`` and +``openssh:known_hosts:mine_hostname_function``. + +You can also integrate alternate DNS names of the various hosts in the +ssh_known_hosts files. You just have to list all the alternate DNS names as a +list in the ``openssh:known_hosts:aliases`` pillar key. Whenever the IPv4 or +IPv6 behind one of those DNS entries matches an IPv4 or IPv6 behind the +official hostname of a minion, the alternate DNS name will be associated to the +minion's public SSH host key. diff --git a/openssh/files/ssh_known_hosts b/openssh/files/ssh_known_hosts new file mode 100644 index 0000000..62849d4 --- /dev/null +++ b/openssh/files/ssh_known_hosts @@ -0,0 +1,34 @@ +{%- set target = salt['pillar.get']('openssh:known_hosts:target', '*') -%} +{%- set expr_form = salt['pillar.get']('openssh:known_hosts:expr_form', 'glob') -%} +{%- set keys_function = salt['pillar.get']('openssh:known_hosts:mine_keys_function', 'public_ssh_host_keys') -%} +{%- set hostname_function = salt['pillar.get']('openssh:known_hosts:mine_hostname_function', 'public_ssh_hostname') -%} +{#- Lookup IP of all aliases so that when we have a matching IP, we inject the alias name + in the SSH known_hosts entry -#} +{%- set aliases = salt['pillar.get']('openssh:known_hosts:aliases', []) -%} +{%- set aliases_ips = {} -%} +{%- for alias in aliases -%} + {%- for ip in salt['dig.A'](alias) + salt['dig.AAAA'](alias) -%} + {%- do aliases_ips.setdefault(ip, []).append(alias) -%} + {%- endfor -%} +{%- endfor -%} +{#- Loop over targetted minions -#} +{%- set host_keys = salt['mine.get'](target, keys_function, expr_form=expr_form) -%} +{%- set host_names = salt['mine.get'](target, hostname_function, expr_form=expr_form) -%} +{%- for host, keys in host_keys|dictsort -%} + {%- set ip4 = salt['dig.A'](host) -%} + {%- set ip6 = salt['dig.AAAA'](host) -%} + {%- set names = [host_names.get(host, host)] -%} + {%- for ip in ip4 + ip6 -%} + {%- do names.append(ip) -%} + {%- for alias in aliases_ips.get(ip, []) -%} + {%- if alias not in names -%} + {%- do names.append(alias) -%} + {%- endif -%} + {%- endfor -%} + {%- endfor -%} + {%- for line in keys.split('\n') -%} + {%- if line -%} +{{ ','.join(names) }} {{ line }} +{% endif -%} + {%- endfor -%} +{%- endfor -%} diff --git a/openssh/known_hosts.sls b/openssh/known_hosts.sls new file mode 100644 index 0000000..8f8d2a8 --- /dev/null +++ b/openssh/known_hosts.sls @@ -0,0 +1,16 @@ +{% from "openssh/map.jinja" import openssh with context %} + +ensure dig is available: + pkg.installed: + - name: {{ openssh.dig_pkg }} + +manage ssh_known_hosts file: + file.managed: + - name: {{ openssh.ssh_known_hosts }} + - source: salt://openssh/files/ssh_known_hosts + - template: jinja + - user: root + - group: root + - mode: 644 + - require: + - pkg: ensure dig is available diff --git a/openssh/map.jinja b/openssh/map.jinja index 883c96d..17e9f8d 100644 --- a/openssh/map.jinja +++ b/openssh/map.jinja @@ -7,6 +7,8 @@ 'sshd_config_src': 'salt://openssh/files/sshd_config', 'banner': '/etc/ssh/banner', 'banner_src': 'salt://openssh/files/banner', + 'dig_pkg': 'dnsutils', + 'ssh_known_hosts': '/etc/ssh/ssh_known_hosts', }, 'Debian': { 'server': 'openssh-server', @@ -16,6 +18,8 @@ 'sshd_config_src': 'salt://openssh/files/sshd_config', 'banner': '/etc/ssh/banner', 'banner_src': 'salt://openssh/files/banner', + 'dig_pkg': 'dnsutils', + 'ssh_known_hosts': '/etc/ssh/ssh_known_hosts', }, 'FreeBSD': { 'service': 'sshd', @@ -23,6 +27,8 @@ 'sshd_config_src': 'salt://openssh/files/sshd_config', 'banner': '/etc/ssh/banner', 'banner_src': 'salt://openssh/files/banner', + 'dig_pkg': 'bind-tools', + 'ssh_known_hosts': '/etc/ssh/ssh_known_hosts', }, 'Gentoo': { 'server': 'net-misc/openssh', @@ -32,6 +38,8 @@ 'sshd_config_src': 'salt://openssh/files/sshd_config', 'banner': '/etc/ssh/banner', 'banner_src': 'salt://openssh/files/banner', + 'dig_pkg': 'net-dns/bind-tools', + 'ssh_known_hosts': '/etc/ssh/ssh_known_hosts', }, 'RedHat': { 'server': 'openssh-server', @@ -41,6 +49,8 @@ 'sshd_config_src': 'salt://openssh/files/sshd_config', 'banner': '/etc/ssh/banner', 'banner_src': 'salt://openssh/files/banner', + 'dig_pkg': 'bind-utils', + 'ssh_known_hosts': '/etc/ssh/ssh_known_hosts', }, 'Suse': { 'server': 'openssh', @@ -50,5 +60,7 @@ 'sshd_config_src': 'salt://openssh/files/sshd_config', 'banner': '/etc/ssh/banner', 'banner_src': 'salt://openssh/files/banner', + 'dig_pkg': 'bind-utils', + 'ssh_known_hosts': '/etc/ssh/ssh_known_hosts', }, }, merge=salt['pillar.get']('openssh:lookup')) %} diff --git a/pillar.example b/pillar.example index ab7f662..720b8c4 100644 --- a/pillar.example +++ b/pillar.example @@ -104,3 +104,27 @@ openssh: -----END OPENSSH PRIVATE KEY----- public_key: | ssh-ed25519 NOT_DEFINED + + known_hosts: + # The next 2 settings restrict the set of minions that will be added in + # the generated ssh_known_hosts files (the default is to match all minions) + target: '*' + expr_form: 'glob' + # Name of mining functions used to gather public keys and hostnames + # (the default values are shown here) + mine_keys_function: public_ssh_host_keys + mine_hostname_function: public_ssh_hostname + # List of DNS entries also pointing to our managed machines and that we want + # to inject in our generated ssh_known_hosts file + aliases: + - cname-to-minion.example.org + - alias.example.org + +# Required for openssh.known_hosts +mine_functions: + public_ssh_host_keys: + mine_function: cmd.run + cmd: cat /etc/ssh/ssh_host_*_key.pub + public_ssh_hostname: + mine_function: grains.get + key: id