├── 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 <