SaltStack, DNS and ssh

In my last post, I showed, how we can combine SaltStack and Knot to have some basic records filled in your zone. As I was introducing the concept, I picked the most obvious and basic entries. But since we have a hammer now, everything starts to look like a nail. And there is much more that can be stored in DNS apart from IP addresses. Let’s take a look at some other examples and how to get them automatically filled in by SaltStack.

To access your servers, apart from Salt_Stack, you most likely use ssh as well. You know the situation when you are connecting for the first time to a new server and you are asked to verify the fingerprint of the ssh key? And then it gets tricky. How do you verify it? DNS can help you with that. There is a record type SSHFP that can contain fingerprint of the ssh key. But as it would be bothersome to collect all those manually, we can do it via SaltStack. But before doing that, let’s make sure we use them afterward. Make sure that your ssh_config has the option VerifyHostKeyDNS set to yes. Then your ssh will use DNS to verify the fingerprints instead of you. Now back to the original problem – how to fill all those DNS records. We will again use Mine to collect those and we will just specify the mining function. We can collect ssh keys by using built-in function like this:

mine_functions:
  ssh_keys:
    - mine_function: ssh.host_keys
    - private: False

That collects the public keys as they are. For DNS, we would like them in the format suitable for publishing. But those keys could be also useful. For example if you are managing your users, you can prepopulate known_hosts file for your users, so they wouldn’t even need to use DNS to verify those keys. If you are using users formula, you can do it simply like this:

{%- set ssh_keys = salt.saltutil.runner('mine.get', tgt='*', fun='ssh_keys', tgt_type='glob') %}

users:
  michal:
{%- if ssh_keys %}
    ssh_known_hosts:
{%- for server in ssh_keys %}
{%- if ssh_keys[server].get('ecdsa.pub', false) %}
      {{ server }}:
        key: {{ ssh_keys[server]['ecdsa.pub'].split(' ')[1] }}
        enc: {{ ssh_keys[server]['ecdsa.pub'].split(' ')[0] }}
{% endif %}
{%- endfor %}

But not everybody is directly managed in SaltStack. So let’s get back to DNS and how we will put keys in there and how do we get them in correct format. We can either convert it on the master, or we can collect them directly in correct format. For that, there is no handy function directly available, but we can actually run any command we want to get the data. So we will use cmd.run function to get the fingerprints in the correct format.

mine_functions:
  sshfp:
    - mine_function: cmd.run
    - 'sh -c "ssh-keygen -r localhost | sed \"s|.*IN SSHFP ||\""'

Once we collect them, we put them into zone similarly as the IP addresses last time.

{%- for srv, keys in salt.saltutil.runner('mine.get', tgt='*', fun='sshfp', tgt_type='compound').items() %}
{%-     for sshfp in keys.split('\n') %}
                    - name: {{ srv.split('.', 1)[0] }}{{ suff }}
                      content: {{ sshfp|yaml_dquote }}
                      type: SSHFP
{%-     endfor %}
{%- endfor %}

Great advantage is that you don’t have to fill it up manually for all your servers, but even greater advantage is that you get all keys and for each of them two hashes. In total 8 records that get filled in automatically per server. That would be bothersome to do manually. And then all your clients can choose their cipher based on their preference and still verify the key against a DNS record.

Using cmd.run to collect data in the mine brings quite some flexibility. But sometimes even that is not enough. I had to use split function to get individual records, but what if I need some more complex structure? And what if it is much more complex to get the data? Should I write a shell script and devise some serialization format? Don’t worry, there are tools to do that in much more elegant way and we will take a look at it next time.

Autor:

Zanechte komentář

Všechny údaje jsou povinné. E-mail nebude zobrazen.