Templating SSH Client Configuration
Note: When I first got the idea for this I used gomplate , then I realized my dotfile manager of choice, chezmoi is better suited for my usecase. I have dumped the files I created in this gist just in case someone finds them useful.
In my my previous post
, I explained how I use Match in my ssh_config to dynamically select my jump host. At first I had a few files in a git repository that I symlink to ~/.ssh/conf.d and have them includeed in ~/.ssh/config. But the more hosts I added the more unmanageable the soultion becoame. Recently I started using devpod
for development and it started modifying ~/.ssh/config which started breaking in all sorts of ways, so I decided to redo my configuration and thought templating it would be a good idea. I ended up liking the end result and wanted to share.
~/.local/share/chezmoi/.chezmoitemplates/ssh_host:
1{{- $host := . -}}
2{{- range (default (list (list "always" "none")) (index . "proxy")) }}
3Match host {{ $host.name }},{{ $host.ips|join "," }}{{ if ne (index . 0) "none" }} exec "~/.ssh/network_detect/{{ index . 0 }}"{{ end }} {{- if not (contains "*" (index $host.ips 0)) }}
4HostName {{ index $host.ips 0 }}{{ end }}
5 User {{ index $host "user" | default "root" }}
6{{- if index $host "key" }}
7 IdentitiesOnly yes
8{{- if eq (printf "%T" $host.key) "string" }}
9 IdentityFile ~/.ssh/{{ $host.key }}
10{{- else -}}
11{{ range $host.key }}
12 IdentityFile ~/.ssh/{{ . }}
13{{- end -}}
14{{- end -}}
15{{- end -}}
16{{- range $env, $env_val := default nil (index $host "env") }}
17 SetEnv {{ $env }}={{ $env_val }}
18{{- end }}
19{{- range $option, $option_val := default nil (index $host "options") }}
20 {{ $option }} {{ $option_val }}
21{{- end }}
22 ProxyJump {{ index . 1 }}
23{{ end -}}
~/.local/share/chezmoi/private_dot_ssh/config.inc.tmpl:
1AddKeysToAgent yes
2{{- range values .ssh_servers -}}
3{{ range . }}
4{{ template "ssh_host" . }}
5{{- end -}}
6{{- end -}}
7{{- "" }}
8{{- $hosts := list }}
9{{- range (values .ssh_servers) }}
10{{- range . }}
11{{- $hosts = append $hosts .name }}
12{{- end }}
13{{- end }}
14# Allow auto completions for shells that support it
15{{- range ($hosts | uniq) }}
16Host {{ . -}}
17{{ end }}
18
19Host *
20 ForwardAgent no
21 ServerAliveInterval 30
22 ServerAliveCountMax 2
23 SetEnv TERM=xterm-256color
This allows storing server information as follows
~/.local/share/chezmoi/.chezmoidata/office.yml:
1ssh_servers:
2 office:
3 -
4 name: office-web1
5 ips:
6 - 10.1.0.1
7 key: ed25519
8 env:
9 TERM: xterm-ghostty
10 proxy:
11 -
12 - office
13 - none
14 -
15 - home
16 - home-bastion
17 -
18 - wireguard
19 - wg-bastion
~/.local/share/chezmoi/.chezmoidata/client1.yml:
1ssh_servers:
2 client1:
3 -
4 name: client1-infra
5 ips:
6 - 172.16.0.*
7 key:
8 - client1-web.pub
9 - client1-db.pub
10 proxy:
11 -
12 - office
13 - office-bastion
14 -
15 - wireguard
16 - wg-bastion
And you can even store per-machine configuration as follows
.config/chezmoi/chezmoi.json:
1{
2 "git": {
3 "autoCommit": true,
4 "autoPush": true
5 },
6 "data": {
7 "ssh_servers": {
8 "this_host_only":
9 [
10 {
11 "name": "host1",
12 "ips": [ "172.17.1.1" ]
13 }
14 ]
15 }
16 }
17}
The sub-keys under ssh_servers allows chezmoi to combine the data from mutiple files, making the configuration that little bit more maintainable.
About Me
Dev gone Ops gone DevOps. Any views expressed on this blog are mine alone and do not necessarily reflect the views of my employer.
Recent Posts