bencromwell/sshush
SSH config management from YAML files, with groups - bencromwell/sshush

I wanted an infrastructure as code utility that would allow for the storage of SSH configs in plain text, so it could be put into Git repos.

Here's a general example, showing some of the options.

You can define the options in multiple files and supply them either through a glob or individually to sshush. I keep a bash alias for this:

alias compilessh='sshush -s /home/ben/.ssh/config.d/*.yml'
bash alias for compilation of source yaml to final ssh config file

Global options

global is a special directive that sets Host * and will place the options at the end of the resulting SSH config file.

global:
  UseRoaming: "no"

This results in:

Host *
    UseRoaming no

Default options

Defaults are inherited from and merged with configs with a higher specificity (think along the lines of Ansible variables' order of preference).

default:
  User: ben
  IdentityFile: ~/.ssh/id_rsa

All the other config options will pick up these defaults and then allow you to override them as and when.

Groups

Group together related items as you see fit. For example, Cisco devices may need a bunch of similar Ciphers to be set, you might have one specific key for all your AWS projects, or by AWS tenancy, etc.

In this example, all the web servers use port 2201, and they'll all use the Digital Ocean key apart from the projects-aws node.

The shortened host names is handy with Ansible, as you would refer to the hosts by SSH config host name in your Ansible inventories.

web_servers:
  Config:
    Port: 2201
    IdentityFile: ~/.ssh/digital_ocean
  Hosts:
    projects-do-1: projects-do-1.example.com
    projects-do-2: projects-do-2.example.com
    projects-aws:
      HostName: projects-aws.example.com
      IdentityFile: ~/.ssh/aws

With our global config, defaults and the web servers, this is what sshush will generate for us:

# Generated by sshush v1.4.0 (/usr/local/bin/sshush)
# From ['test.yml']

# web_servers
Host projects-do-1
    User ben
    IdentityFile ~/.ssh/digital_ocean
    Port 2201
    HostName projects-do-1.example.com

Host projects-do-2
    User ben
    IdentityFile ~/.ssh/digital_ocean
    Port 2201
    HostName projects-do-2.example.com

Host projects-aws
    User ben
    IdentityFile ~/.ssh/aws
    Port 2201
    HostName projects-aws.example.com

Host *
    UseRoaming no

As programmers we don't want to repeat ourselves, and managing one big SSH config file where we must specify things repeatedly is error prone. It's also not easy to share it with colleagues. You can choose to gitignore your globals or defaults, so that individuals can supply their own default keys and usernames.

Extends

The extends directive allows for groups to inherit config from each other.

Here's an example showing accessing older Cisco devices that have various older SSH options, such as out of date ciphers:

---
ciscos:
  Config:
    Ciphers: aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms: +diffie-hellman-group1-sha1
    HostKeyAlgorithms: ssh-rsa,ssh-dss
    PubkeyAuthentication: "no"
  Hosts:
    - oldas*.adm
    - oldcs*.adm
    - cs1.office.adm
    - cs2.office.adm
    - ms1.office.adm
    - as1.office.adm
    - as2.office.adm
    - as3.office.adm
    - as4.office.adm
    - ps1.office.adm
    - ps2.office.adm

older_ciscos:
  Extends: ciscos
  Config:
    Ciphers: aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc
  Hosts:
    - es*.office.adm
    - cr1.office2.adm

The Extends directive tells the older_ciscos group to inherit its base config from the plain ciscos group. The ciphers option is then overridden, a wildcard set of hosts matching es*.office.adm is supplied, in addition to the core router, cr1.office.2.adm.

That was a big list of hosts, so what do we have now?

$ sshush --source {global,cisco}.yml --path output.txt
sshush running with path "output.txt" and source YAML "['global.yml', 'cisco.yml']"
output.txt written successfully
# Generated by sshush v1.4.0 (/usr/local/bin/sshush)
# From ['global.yml', 'cisco.yml']


# ciscos
Host oldas*.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no

Host oldcs*.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no

Host cs1.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName cs1.office.adm

Host cs2.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName cs2.office.adm

Host ms1.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName ms1.office.adm

Host as1.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName as1.office.adm

Host as2.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName as2.office.adm

Host as3.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName as3.office.adm

Host as4.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName as4.office.adm

Host ps1.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName ps1.office.adm

Host ps2.office.adm
    Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-cbc,3des-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName ps2.office.adm

# older_ciscos
Host es*.office.adm
    Ciphers aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no

Host cr1.office2.adm
    Ciphers aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc
    KexAlgorithms +diffie-hellman-group1-sha1
    HostKeyAlgorithms ssh-rsa,ssh-dss
    PubkeyAuthentication no
    HostName cr1.office2.adm

Host *
    UseRoaming no

The yaml is a lot easier to maintain then the resultant SSH config file!