SSH Signed Certificates with host principal validation

Hello!

I’m trying to create a vault setup with the SSH secrets engine using client certificates. The basic workflow I got working, meaning I have a CA, deployed the public on a target server that I want to login to, and SSH authentication is working without issues.

What I want to add is a way to very if a user is authorized to log in to specific machines. Consider the following, simple, scenario:

  • User Jim
  • User Naomi
  • Server Ceres
  • Server Tycho
  • Jim is allowed on Ceres
  • Naomi is allowed on Tycho

I want to use the same secret mount point (i.e. ssh/) and the same CA.

My idea was to create two roles (Jim, Naomi) and then match a list of space stations servers that the users are allowed to log into, or maybe wildcard with a dns sub-domain, like *.belt.example.com.

I have found no indication so far that this supported by the SSH certification format, although it supports the Principals field in the signed certificate. I was hoping there is something similar like AllowedHosts where Vault would put all the hosts from the role that the user is allowed to log in to.

I would prefer not to involve any external tools other then SSH and Vault, to keep authorization- and authentication-management completely inside Vault.

Addendum: I just read about Boundary. It sounds like it can manage what I’m trying to achieve (although I just learned about it and have not looked into the docs further than the intro). It introduces two points that I would like to avoid though: It looks like the server needs to contact the Boundary server to verify the login attempt is valid and it is another service (which is not a deal-breaker, but we would like to avoid it).

This is where the Principals field is important.

You’d setup SSH on the two servers to only allow connections that have a certificate with say the server name in the Principals field. Then on the Vault side you can have a role which allows access to Ceres and one for Tycho, with the correct users allowed to access the correct role.

When you request a SSH certificate it is produced with the correct Principals field value, which when presented to the server grants access.

On the server side you can have a list of principals which are allowed access, so for example you could have an additional value of “All” which allows access to both servers.

We have a script that users use to automate all this. You say which server you want to access, it talks to Vault to get the correct certificate and then it passes that to SSH. From the user’s point of view it looks the same as just running “ssh ” but takes a fraction longer due to the extra work needed. The certificates generated are very short lived, which is in line with good practice.

My impression was that the principals field in user certificates was specifically for user names, so how do you represent host name limitations there?

Thank you Stuart.

We have a script that users use to automate all this. You say which server you want to access, it talks to Vault to get the correct certificate and then it passes that to SSH. From the user’s point of view it looks the same as just running “ssh ” but takes a fraction longer due to the extra work needed. The certificates generated are very short lived, which is in line with good practice.

This workflow is exactly what I had in mind; creating a wrapper that the user uses instead of plain SSH which will ask for a certificate in the background.

You’d setup SSH on the two servers to only allow connections that have a certificate with say the server name in the Principals field. Then on the Vault side you can have a role which allows access to Ceres and one for Tycho, with the correct users allowed to access the correct role.

Would you be able to provide an excerpt of your SSHD configuration that is responsible for that? So far I had the impression that the Principals field in the certificate would work like this:

  • In Client Authentication: Principals equals the username that the user is trying to login as, e.g. the root-part in root@some-host.
  • In Host Authentication: The hostname of the client that is connecting? I’m not sure about this one as I have not used this before.

I’ll look into it some more. Again, thank you!

The Principals field is just a string that you decide, with it being checked against a list of the server side against another fixed list. How exactly you use that string is up to you, but it isn’t directly related to either the username or hostname you are connecting to (it can be, but no requirement).

So on the server side (which might be several instances of the same type of server) we might have the list of allowed principals as something like:

product-dev-ingest
product-dev-all
product-all

Meaning we could then access the server if we have any of those three principals in my certificate. Other servers might have product-dev-process instead of the first entry, or product-prod-ingest, etc.

As a user you can request a certificate which old allows access to a single type of server in a particular environment (which matches the top entry), all servers in an environment (middle) or all servers everywhere (bottom).

The impression I get from OpenSSH docs, the de facto standard Linux SSH server, is that the interpretation of the principals field is specifically usernames?

It is just a string that has to match between the certificate and the list of allowed values on the server.

SSH itself (both server & client) doesn’t do any sort of checking to see if the string is the same as the username, hostname or anything, so you can make it whatever you want that works for your use case.

We use it as a set of groups of servers you are allowed to access.

Yes! I got a first proof-of-concept working. SSHD configuration essentials look like this:

root@vaultc:/etc/ssh# tail -n2 sshd_config
TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem
AuthorizedPrincipalsFile /etc/ssh/authorized_principles
root@vaultc:/etc/ssh#

And the file /etc/ssh/authorized_principles contains the group that the server belongs to. When a user asks for a certificate hey states which content he’d like to have in the principals field like this:

vault write -field=signed_key ssh/sign/mygroup public_key=@$HOME/.ssh/id_ed25519.pub valid_principals="mygroup" > ~/temp-key-mygroup

I’m sure there is potential for optimisation but this seems to work so far.

Thanks Stuart, have a great day!

Thanks for the pointer to AuthorizedPrincipalsFile, which is what I was missing.

Now I have learnt about it though, I see a bit of a problem… the above configuration means someone who can sign a certificate through Vault, can authenticate as any user including root on the target host.

I would imagine that is not intended?

I propose an alternative configuration - leave OpenSSH in its default configuration, where principals are usernames, and use the PAM access file /etc/security/access.conf to define who is allowed and forbidden to authenticate on each particular host.

It depends how you have it configured. You can have a different list of allowed principals for each user (using %u), so you could have nothing for root (for example) or different listings for several users. Also it is good practice to have the SSH server configured to not allow direct access to the root user anyway.