├── README.md ├── Vagrantfile ├── host-bootstrap.sh ├── keys ├── ssh_host_ecdsa_key ├── ssh_host_ecdsa_key-cert.pub ├── ssh_host_ecdsa_key.pub ├── ssh_host_key.pub └── ssh_user_key.pub └── step ├── certs ├── intermediate_ca.crt ├── root_ca.crt ├── ssh_host_key.pub └── ssh_user_key.pub ├── config ├── ca.json └── defaults.json └── secrets ├── intermediate_ca_key ├── root_ca_key ├── ssh_host_key └── ssh_user_key /README.md: -------------------------------------------------------------------------------- 1 | # step-ssh-example 2 | 3 | This repo contains a showcase of how to use SSH certificates (for hosts & users) generated by [`step-ca`](https://github.com/smallstep/certificates) using the [`step CLI's`](https://github.com/smallstep/cli) `ssh` sub-command. 4 | 5 | If you haven't already you should [read our blog 6 | post](https://smallstep.com/blog/use-ssh-certificates/) on why SSH certificates 7 | are better than SSH public keys for authentication and how you can achieve de 8 | facto SSH Single Sign-on while doing away with pesky public key management 9 | across your server fleet. 10 | 11 | This document describes: 12 | 13 | * how to provision [`step-ca`](https://github.com/smallstep/certificates) to issue SSH host & user certificates. 14 | * how `sshd` is configured to accept user certificates for client authentication using a CA key. 15 | * how `sshd` is configured to present a host certificate for host authentication on the client-side. 16 | * how to configure a user's `ssh` to accept host certificate signed by a CA key. 17 | * how to configure a user's `ssh` to present a user certificate for authentication on the server-side. 18 | 19 | The code in this repo comes with a pre-generated PKI. You will need `step` 20 | v0.13.3+ ([installation docs](https://github.com/smallstep/cli#installation-guide)) 21 | and [Vagrant](https://www.vagrantup.com/docs/installation/) (plus a provider like 22 | [VirtualBox](https://www.virtualbox.org/)) installed locally. 23 | 24 | ## Setup VM 25 | 26 | We're going to run a CA in your local environment, and we'll use `ssh` to 27 | connect to a Vagrant VM 28 | (representing a remote host) that has `sshd` pre-configured to accept 29 | user certificates signed by our CA. 30 | 31 | With Vagrant installed, run the following commands inside the repo: 32 | 33 |

 34 | $ vagrant up
 35 | Bringing machine 'testhost' up with 'virtualbox' provider...
 36 | ==> testhost: Importing base box 'ubuntu/bionic64'...
 37 |     [...]
 38 | ==> testhost: Preparing network interfaces based on configuration...
 39 |     testhost: Adapter 1: nat
 40 |     testhost: Adapter 2: hostonly
 41 | ==> testhost: Forwarding ports...
 42 |     testhost: 22 (guest) => 2222 (host) (adapter 1)
 43 | ==> testhost: Running 'pre-boot' VM customizations...
 44 | ==> testhost: Booting VM...
 45 | ==> testhost: Waiting for machine to boot. This may take a few minutes...
 46 |     testhost: SSH address: 127.0.0.1:2222
 47 |     testhost: SSH username: vagrant
 48 |     testhost: SSH auth method: private key
 49 |     testhost: VirtualBox Version: 6.0
 50 |     [...]
 51 | ==> testhost: Setting hostname...
 52 | ==> testhost: Configuring and enabling network interfaces...
 53 | ==> testhost: Mounting shared folders...
 54 |     testhost: /keys => /Users/sourishkrout/dev/src/smallstep/code/src/github.com/smallstep/step-examples/ssh-example/keys
 55 |     testhost: /vagrant => /Users/sourishkrout/dev/src/smallstep/code/src/github.com/smallstep/step-examples/ssh-example
 56 | ==> testhost: Running provisioner: shell...
 57 |     testhost: Running: inline script
 58 |     testhost: Add following line to your local hosts ~/.ssh/known_hosts file to accept host certs
 59 |     testhost: @cert-authority * ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJJM+jkIdieQvdPb8DwnfnJudEc9PgVBqLDWHKgvqoIiMXhuIyGstQ9ULOBMdJkqxMjkRTFZp1iFvIk+iU6hwTA=
 60 |     testhost: Add a /etc/hosts file entry `testhost` to resolve to 192.168.0.101
 61 |     testhost: Check out README.md to learn how to grab user ssh certs to log into testhost
 62 | 
63 | 64 | ### Configure ssh client to accept host certs 65 | 66 | Go ahead and follow the instructions printed by Vagrant. This will enable your 67 | local SSH client to accept SSH host certificates (signed by the root SSH host 68 | private key). The following command will append the SSH host CA key 69 | (root SSH host public key corresponding to the root SSH host private key) to 70 | your local `known_hosts` file: 71 | 72 |

 73 | me@local:~$ echo "@cert-authority * ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJJM+jkIdieQvdPb8DwnfnJudEc9PgVBqLDWHKgvqoIiMXhuIyGstQ9ULOBMdJkqxMjkRTFZp1iFvIk+iU6hwTA=" >> ~/.ssh/known_hosts
 74 | 
75 | 76 | You can also find the root SSH host CA key stored at 77 | `step/certs/ssh_host_key.pub` in this repo. 78 | 79 | Certificates bind names to public keys. This SSH host certificate has the 80 | identity `testhost` which is why the following entry must be added to the 81 | local `/etc/hosts` file on the VM: 82 | 83 |

 84 | vagrant@testhost:~$ tail -n 1 /etc/hosts
 85 | 192.168.0.101   testhost
 86 | 
87 | 88 | ### Configure sshd to accept user certs 89 | 90 | Vagrant has already configured `sshd` on `testhost`, the VM 91 | generated by Vagrant. Please note that for demo purposes the PKI is shared with 92 | the VM using a shared directory mount. Below you can see the relevant lines 93 | from the `testhost` VM's `sshd_config`: 94 | 95 |

 96 | vagrant@testhost:~$ tail -n 5 /etc/ssh/sshd_config
 97 | #       PermitTTY no
 98 | #       ForceCommand cvs server
 99 | TrustedUserCAKeys /keys/ssh_user_key.pub
100 | HostKey /keys/ssh_host_ecdsa_key
101 | HostCertificate /keys/ssh_host_ecdsa_key-cert.pub
102 | 
103 | 104 | * TrustUserCAKeys: The root SSH user public key used to verify SSH 105 | user certificates. 106 | * HostKey: The SSH private key specific to this host. 107 | * HostCertificate: The SSH public certificate that uniquely 108 | identifies this host (signed by the root SSH host private key). 109 | 110 | ### Login to VM via SSH user cert 111 | 112 | A valid user certificate is required to log into the `testhost` VM. Using the 113 | `step` CLI we will authenticate with our SSH-enabled CA and fetch a new SSH 114 | certificate. 115 | 116 | In one terminal window run the following command to startup your CA (password 117 | is `password`): 118 | 119 |

120 | me@local:~$ export STEPPATH=`pwd`/step
121 | me@local:~$ step-ca step/config/ca.json
122 | Please enter the password to decrypt step/secrets/intermediate_ca_key: password
123 | Please enter the password to decrypt step/secrets/ssh_host_key: password
124 | Please enter the password to decrypt step/secrets/ssh_user_key: password
125 | 2019/09/11 22:59:01 Serving HTTPS on :443 ...
126 | 
127 | 128 | In another terminal window run: 129 | 130 |

131 | me@local:~$ export STEPPATH=`pwd`/step
132 | me@local:~$ step ssh certificate testuser testuser_ecdsa --ca-url https://localhost --root step/certs/root_ca.crt
133 | ✔ Provisioner: admin (JWK) [kid: ux6AhkfzgclpI65xJeGHzNqHCmdCl0-nWO8YqF1mcn0]
134 | ✔ Please enter the password to decrypt the provisioner key: password
135 | ✔ CA: https://localhost
136 | Please enter the password to encrypt the private key: your-own-password
137 | ✔ Private Key: testuser_ecdsa
138 | ✔ Public Key: testuser_ecdsa.pub
139 | ✔ Certificate: testuser_ecdsa-cert.pub
140 | ✔ SSH Agent: yes
141 | 
142 | 143 | > NOTE: `step-ca` enforces authentication for all certificate requests and uses 144 | > the concept of 145 | > [provisioners](https://github.com/smallstep/certificates/blob/master/docs/provisioners.md) 146 | > to carry out this enforcement. Provisioners are configured in 147 | > `step/config/ca.json`. Authenticating as one of the sanctioned provisioners 148 | > indicates to `step-ca` that you have the right to provisione new 149 | > certificates. In the above invocation of `step ssh certificate` we have 150 | > authenticated our request using a JWK provisioner, which simply requires a 151 | > password to decrypt a private key. However, there are a handful of supported 152 | > provisioners, each with it's own authentication methods. The OIDC provisioner 153 | > is particularly interesting for SSH user certificates because it enables 154 | > Single Sign-On SSH. 155 | 156 | Conveniently, `step ssh certificate` adds the new SSH user certificate to your 157 | local `ssh agent`. The default lifetime of an SSH certificate from `step-ca` is 158 | 4hrs. The lifetime can be configured using command line options (run `step ssh 159 | certificate -h` for documentation and examples). 160 | 161 |

162 | me@local:~$ ssh-add -l
163 | 256 SHA256:xt5VeMEG8uf+SBlddauylJHv9+Bl0E6H+46AV94+Its testuser (ECDSA-CERT)
164 | 
165 | me@local:~$ ssh testuser@testhost
166 | Welcome to Ubuntu 18.04.3 LTS (GNU/Linux 4.15.0-55-generic x86_64)
167 | 
168 | [...]
169 | 
170 | testuser@testhost:~$
171 | 
172 | 173 | Boom! As you can see the `testhost` VM will welcome you with a matching 174 | `testuser@testhost` prompt. 175 | 176 | Learn how to use OAuth OIDC proviers like Gsuite or Instance Identity Documents 177 | to bootstrap SSH host and user certificates in our blog post [If you’re not using SSH 178 | certificates you’re doing SSH 179 | wrong](https://smallstep.com/blog/use-ssh-certificates/) or check out the 180 | `step` CLI reference at 181 | [https://smallstep.com/docs/cli/ssh/](https://smallstep.com/docs/cli/ssh/). 182 | 183 | ### Generate ssh host certificates 184 | 185 | This example repo includes a pre-generated SSH host certificate and key. To replace it 186 | or generate SSH certificates for other hosts running following command: 187 | 188 |

189 | vagrant@testhost:~$ step ssh certificate --host --principal testhost \
190 |   --principal testhost.internal \
191 |   testhost ssh_host_ecdsa_key
192 | 
193 | 194 | Where `--principal` identifies the hostname(s) (ideally FQDNs) for the machine. 195 | For a single principal you can short cut the command to: 196 | 197 |

198 | vagrant@testhost:~$ step ssh certificate --host testhost ssh_host_ecdsa_key
199 | 
200 | 201 | ## Generate your own PKI for `step-ca` 202 | 203 | We recommend using your own PKI for usage outside of this example. You can 204 | initialize your [`step-ca`](https://github.com/smallstep/certificates) with 205 | both X509 and SSH certificates using the following command: 206 | 207 |

208 | $ export STEPPATH=/tmp/mystep
209 | $ step ca init --ssh
210 | ✔ What would you like to name your new PKI? (e.g. Smallstep): Smallstep
211 | ✔ What DNS names or IP addresses would you like to add to your new CA? (e.g. ca.smallstep.com[,1.1.1.1,etc.]): localhost
212 | ✔ What address will your new CA listen at? (e.g. :443): :443
213 | ✔ What would you like to name the first provisioner for your new CA? (e.g. you@smallstep.com): admin
214 | ✔ What do you want your password to be? [leave empty and we'll generate one]:
215 | 
216 | Generating root certificate...
217 | all done!
218 | 
219 | Generating intermediate certificate...
220 | 
221 | Generating user and host SSH certificate signing keys...
222 | all done!
223 | 
224 | ✔ Root certificate: /tmp/mystep/certs/root_ca.crt
225 | ✔ Root private key: /tmp/mystep/secrets/root_ca_key
226 | ✔ Root fingerprint: d601c93a6256080e42cf02087fdc737f1429226ada6c040bac6494332e01527e
227 | ✔ Intermediate certificate: /tmp/mystep/certs/intermediate_ca.crt
228 | ✔ Intermediate private key: /tmp/mystep/secrets/intermediate_ca_key
229 | ✔ SSH user root certificate: /tmp/mystep/certs/ssh_user_key.pub
230 | ✔ SSH user root private key: /tmp/mystep/secrets/ssh_user_key
231 | ✔ SSH host root certificate: /tmp/mystep/certs/ssh_host_key.pub
232 | ✔ SSH host root private key: /tmp/mystep/secrets/ssh_host_key
233 | ✔ Default configuration: /tmp/mystep/config/defaults.json
234 | ✔ Certificate Authority configuration: /tmp/mystep/config/ca.json
235 | 
236 | Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.
237 | 
238 | 239 | Now you can launch your instance of 240 | [`step-ca`](https://github.com/smallstep/certificates) with your own PKI like 241 | so: 242 | 243 |

244 | $ step-ca $(step path)/config/ca.json
245 | Please enter the password to decrypt /tmp/mystep/secrets/intermediate_ca_key:
246 | Please enter the password to decrypt /tmp/mystep/secrets/ssh_host_key:
247 | Please enter the password to decrypt /tmp/mystep/secrets/ssh_user_key:
248 | 2019/09/11 23:34:13 Serving HTTPS on :443 ...
249 | 
250 | 251 | Please note that after you regenerate `ssh_host_key.pub` and `ssh_user_key.pub` 252 | you will have to reconfigure `ssh` and `sshd` for clients and hosts to accept 253 | the new CA keys. Check out [this host bootstrapping script](./host-bootstrap.sh) for 254 | configuration examples. 255 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | 3 | config.vm.define "testhost" do |host| 4 | host.vm.box = "ubuntu/bionic64" 5 | host.vm.hostname = "testhost" 6 | config.vm.provision "shell", inline: <<-SHELL 7 | sudo adduser --quiet --disabled-password --gecos '' testuser 2>/dev/null 8 | echo 'TrustedUserCAKeys /keys/ssh_user_key.pub' >> /etc/ssh/sshd_config 9 | echo 'HostKey /keys/ssh_host_ecdsa_key' >> /etc/ssh/sshd_config 10 | echo 'HostCertificate /keys/ssh_host_ecdsa_key-cert.pub' >> /etc/ssh/sshd_config 11 | service ssh restart 12 | echo 'Add following line to your local hosts ~/.ssh/known_hosts file to accept host certs' 13 | echo "@cert-authority * $(cat /keys/ssh_host_key.pub | tr -d '\n')" 14 | echo 'Add a /etc/hosts file entry `testhost` to resolve to 192.168.0.101' 15 | echo 'Check out README.md to learn how to grab user ssh certs to log into testhost' 16 | SHELL 17 | host.vm.network "private_network", ip: "192.168.0.101" 18 | host.vm.synced_folder "keys", "/keys" 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /host-bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Install `step` 4 | curl -LO https://github.com/smallstep/cli/releases/download/v0.12.0/step-cli_0.12.0_amd64.deb 5 | sudo dpkg -i step-cli_0.12.0_amd64.deb 6 | 7 | # Configure `step` to connect to & trust our `step-ca` 8 | step ca bootstrap --ca-url ec2-54-167-89-236.compute-1.amazonaws.com \ 9 | --fingerprint 34d7a0c1d8ffc3e52cd7bde990f027622afb957c70b8e0e10fd482db47adc7c5 10 | 11 | # Install the CA cert for validating user certificates (from ~/.ssh/certs/ssh_user_key.pub` on the CA). 12 | echo "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBK+28xkD7pKCo5ltgUaebEngnNJZRzr+iN/sxnwSEFL0AFExpzE0FMG2W1PIh8WaHJciSvJaMp3/u00/ZvDYx9U=" > $(step path)/certs/ssh_user_key.pub 13 | 14 | # Get an SSH host certificate 15 | export HOSTNAME="$(curl -s http://169.254.169.254/latest/meta-data/public-hostname)" 16 | export TOKEN=$(step ca token $HOSTNAME --ssh --host --provisioner "mike@example.com" --password-file <(echo "pass")) 17 | sudo step ssh certificate $HOSTNAME /etc/ssh/ssh_host_ecdsa_key.pub --host --sign --provisioner "mike@example.com" --token $TOKEN 18 | 19 | # Configure `sshd` 20 | sudo tee -a /etc/ssh/sshd_config > /dev/null <