In my last post, I showed how we can combine SaltStack and Knot to have some basic records filled 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 get’s 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
Salt_stack. But before doing that, let’s make sure we use them afterward.
Make sure that your ssh_config
contains has the option VerifyHostKeyDNS
set
to yes
. Then your ssh ask DNS to verify the fingerprints instead of you. Now
back to the original problem - how to fill all the 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.
knot:
server:
...
zone
example.com:
...
records:
...
{%- 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.