Mailing List Archive

Feature request: a good way to supply short-lived certificates to openssh
Some systems like to have a CA supply short-lived certificates to ssh clients. The basic idea is that servers enable certificate authentication, clients authenticate to the CA out of band, and the CA issues client certificates that are valid for a short enough time that users don't want to manually drop them into ~/.ssh or otherwise think about them. There are a handful of commercial examples, and it's also pretty straightforward to implement this manually.

As far as I an tell, ssh and ssh_config don't have a great way to handle this usage model. Various kludges and solutions that sort of work include:

User runs a command to get the certificate, and the command puts the certificate somewhere that matches the CertificateFile. If the user forgets to run the command, they fail to authenticate and have to do it manually.

ssh_config contains a Match ... exec [command to refresh the certificate]. This sort of works, except that it runs the command far too frequently. For example, ssh -O exit [name] refreshes the certificate, and it should not do so.

IdentityAgent could point to a custom agent. This would be more useful if ssh could _start_ the agent. In any case, this requires an agent, which is more complex than a tool that refreshes a certificate.

PKCS11Provider doesn't seem useful.

ProxyCommand could specify a command that refreshes the certificate. Cloudflare recommended this at one point, and it worked about as poorly as might be expected. (That is, very poorly indeed.)


So my feature request: a way to do this for real. Here are a couple of ideas:

PreAuthCommand: runs a command before reading any files associated with authentication. (This especially means that the command runs, and completes, before opening the CertificateFile.)

CertificateCommand: runs a command that outputs a certificate or perhaps just a CertificateFile directive.

I'm sure there are more ways to make this better, hence this feature request.

Thanks,
Andy
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: Feature request: a good way to supply short-lived certificates to openssh [ In reply to ]
On Tue, 7 Mar 2023 at 05:26, Andy Lutomirski <luto@kernel.org> wrote:
[...]
> ssh_config contains a Match ... exec [command to refresh the certificate]. This sort of works,
> except that it runs the command far too frequently. For example, ssh -O exit [name] refreshes
> the certificate, and it should not do so.

You can have the command check if the cert is expired or near expired
before refreshing it. I've done this in the past with expiring
certificates.

> This would be more useful if ssh could _start_ the agent.

This is difficult because ssh relies on $SSH_AUTH_SOCK in its
environment to find the agent's socket.

In normal use, the way this works is usually one of:
- the agent is started before the shell (eg by a desktop
environment), the shell inherits SSH_AUTH_SOCK from the window manager
or equivalent and ssh inherits it from the shell.
- the agent starts the command, which inherits SSH_AUTH_SOCK
directly from the agent
- the agent outputs SSH_AUTH_SOCK on stdout for the shell to parse
(this is why you need to eval it, otherwise the agent has no way of
setting SSH_AUTH_SOCK in its parent shell).

--
Darren Tucker (dtucker at dtucker.net)
GPG key 11EAA6FA / A86E 3E07 5B19 5880 E860 37F4 9357 ECEF 11EA A6FA
Good judgement comes with experience. Unfortunately, the experience
usually comes from bad judgement.
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: Feature request: a good way to supply short-lived certificates to openssh [ In reply to ]
On Tue, 7 Mar 2023, Darren Tucker wrote:

> On Tue, 7 Mar 2023 at 05:26, Andy Lutomirski <luto@kernel.org> wrote:
> [...]
> > ssh_config contains a Match ... exec [command to refresh the certificate]. This sort of works,
> > except that it runs the command far too frequently. For example, ssh -O exit [name] refreshes
> > the certificate, and it should not do so.
>
> You can have the command check if the cert is expired or near expired
> before refreshing it. I've done this in the past with expiring
> certificates.
>
> > This would be more useful if ssh could _start_ the agent.
>
> This is difficult because ssh relies on $SSH_AUTH_SOCK in its
> environment to find the agent's socket.

You could probably rig something up using a fixed agent socket path.
E.g.

IdentityAgent ~/.ssh/.agent-sock

Match !canonical exec "check-and-start-agent.sh"
# Will start agent at ~/.ssh/.agent-sock if not already running.

The agent socket is AFAIK only opened after configuration processing
completes, so this should work.

It might also be possible to skip the Match block using some socket
activation trick.

-d
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: Feature request: a good way to supply short-lived certificates to openssh [ In reply to ]
On Mon, Mar 6, 2023, at 2:09 PM, Darren Tucker wrote:
> On Tue, 7 Mar 2023 at 05:26, Andy Lutomirski <luto@kernel.org> wrote:
> [...]
>> ssh_config contains a Match ... exec [command to refresh the certificate]. This sort of works,
>> except that it runs the command far too frequently. For example, ssh -O exit [name] refreshes
>> the certificate, and it should not do so.
>
> You can have the command check if the cert is expired or near expired
> before refreshing it. I've done this in the past with expiring
> certificates.

True, but that doesn't help with the -O exit use case. And it's really quite silly for any configuration using ControlMaster -- I don't want my certificates renewed when I'm joining an existing ControlMaster question.

So I still think that openssh doesn't have a great mechanism more this, and I think my feature request still makes sense.
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: Feature request: a good way to supply short-lived certificates to openssh [ In reply to ]
On 07/03/23, Darren Tucker (dtucker@dtucker.net) wrote:
> On Tue, 7 Mar 2023 at 05:26, Andy Lutomirski <luto@kernel.org> wrote:
> [...]
> > ssh_config contains a Match ... exec [command to refresh the certificate].
> > This sort of works, except that it runs the command far too frequently.
> > For example, ssh -O exit [name] refreshes the certificate, and it should
> > not do so.
>
> You can have the command check if the cert is expired or near expired
> before refreshing it. I've done this in the past with expiring
> certificates.

I was intrigued by Darren's note about a command to check certificate expiry. I've put together a quick POC in go to list expiring certificates: https://gist.github.com/rorycl/d194243c61b349021935c97f751a931e

Output is something like:

0 key ssh-ed25519 : is not a certificate
1 key ssh-ed25519-cert-v01@openssh.com
comment: acmeinc_briony_from:2023-03-07T08:18_to:2023-03-07T11:18UTC
validity: 2023-03-07 08:37:23 GMT to 2023-03-07 11:37:23 GMT
expiring in 60m? true

I'd be grateful to Andy if he explained what sort of command he runs to refresh certificates. I understood most refresh arrangements to involve OAuth2.

Rory

_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: Feature request: a good way to supply short-lived certificates to openssh [ In reply to ]
This is the approach I take:

- generate a private key and certificate and stuff them into ssh-agent

- set the label in ssh-agent to something that identifies the key+cert,
and the ssh-agent expiry time to be the same as the certificate expiry
time, or slightly less

- each time you make an ssh connection, query the agent to see if
there's the expected key+cert, and generate a new one if not

I'm using Hashicorp Vault to generate the certs, and I wrote this code
for the client side:

https://github.com/candlerb/vault-ssh-agent-login

It skips the cert generation if there appears to be a valid cert already
in the agent.

I invoke this via a wrapper script (below). I haven't looked into
hooking it directly into ssh_config (which is what this thread was
originally about).

#!/bin/bash -eu
export VAULT_ADDR="https://vault.example.net:8200"

case "${1:-}" in
"-force") OPT="-force"; shift ;;
"")       OPT="" ;;
*)        OPT="-quiet" ;;
esac

vault-ssh-agent-login -role=my_ssh_role \
  -valid-principals="brian,ubuntu" \
  -auth-method=oidc -auth-path=google $OPT

[ $# -gt 0 ] && exec ssh "$@"


_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: Feature request: a good way to supply short-lived certificates to openssh [ In reply to ]
On Tue, Mar 7, 2023, at 3:25 AM, Rory Campbell-Lange wrote:
> On 07/03/23, Darren Tucker (dtucker@dtucker.net) wrote:
>> On Tue, 7 Mar 2023 at 05:26, Andy Lutomirski <luto@kernel.org> wrote:
>> [...]
>> > ssh_config contains a Match ... exec [command to refresh the certificate].
>> > This sort of works, except that it runs the command far too frequently.
>> > For example, ssh -O exit [name] refreshes the certificate, and it should
>> > not do so.
>>
>> You can have the command check if the cert is expired or near expired
>> before refreshing it. I've done this in the past with expiring
>> certificates.
>
> I was intrigued by Darren's note about a command to check certificate
> expiry. I've put together a quick POC in go to list expiring
> certificates:
> https://gist.github.com/rorycl/d194243c61b349021935c97f751a931e
>
> Output is something like:
>
> 0 key ssh-ed25519 : is not a certificate
> 1 key ssh-ed25519-cert-v01@openssh.com
> comment: acmeinc_briony_from:2023-03-07T08:18_to:2023-03-07T11:18UTC
> validity: 2023-03-07 08:37:23 GMT to 2023-03-07 11:37:23 GMT
> expiring in 60m? true

Nifty,

>
> I'd be grateful to Andy if he explained what sort of command he runs to
> refresh certificates. I understood most refresh arrangements to involve
> OAuth2.

The actual setup I'm using is:

Host myhost
Match host myhost exec "cloudflared access ssh-gen --hostname myhost.domain"
ProxyCommand cloudflared access ssh --hostname myhost.domain
IdentityFile ~/.cloudflared/blahblah
CertificateFile ~/.cloudflared/blahblah.pub

cloudflared is this thing (open source!):

https://github.com/cloudflare/cloudflared

There are two pieces of magic here. One is the "couldflared access ssh-gen" command. It's annoyingly slow (which could be fixed, presumably), and it refreshes the certificates in ~/.cloudflared, using (I presume -- haven't checked) OAuth2 behind the scenes. The other is the ProxyCommand, which, as I've configured it, is just a proxy.

This is a kludge. On the one hand, it mostly works. On the other hand, it behaves poorly when doing anything other than just connecting. The case that bothers me the most is ssh -O command myhost.

I think the most straightforward change to openssh would be to allow me to rewrite it as:

Host myhost
PreAuthCommand cloudflared access ssh-gen --hostname myhost.domain
ProxyCommand cloudflared access ssh --hostname myhost.domain
IdentityFile ~/.cloudflared/blahblah
CertificateFile ~/.cloudflared/blahblah.pub

ssh -O would not invoke the PreAuthCommand, and other ssh commands that don't need to authenticate would also not invoke it (e.g. ssh reusing an existing connection). But an ssh command that did need to authenticate would read it before opening and of the the credential files.


(I have no affiliation with Cloudflare.)
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev
Re: Feature request: a good way to supply short-lived certificates to openssh [ In reply to ]
On 07/03/23, Andy Lutomirski (luto@kernel.org) wrote:
> On Tue, Mar 7, 2023, at 3:25 AM, Rory Campbell-Lange wrote:
> > On 07/03/23, Darren Tucker (dtucker@dtucker.net) wrote:
> >> On Tue, 7 Mar 2023 at 05:26, Andy Lutomirski <luto@kernel.org> wrote:
> >> [...]
> >> > ssh_config contains a Match ... exec [command to refresh the certificate].
> >> > This sort of works, except that it runs the command far too frequently.
> >> > For example, ssh -O exit [name] refreshes the certificate, and it should
> >> > not do so.
> >>
> >> You can have the command check if the cert is expired or near expired
> >> before refreshing it. I've done this in the past with expiring
> >> certificates.

I've put up a tool called `lsagentcerts` at
https://github.com/rorycl/lsagentcerts -- let me know if is helpful. I'm not sure the operation is quite right and the output is clunky. I should probably put the key signatures in the output...

> > I'd be grateful to Andy if he explained what sort of command he runs to
> > refresh certificates. I understood most refresh arrangements to involve
> > OAuth2.
>
> The actual setup I'm using is:
>
> Host myhost
> Match host myhost exec "cloudflared access ssh-gen --hostname myhost.domain"
> ProxyCommand cloudflared access ssh --hostname myhost.domain
> IdentityFile ~/.cloudflared/blahblah
> CertificateFile ~/.cloudflared/blahblah.pub
>
> cloudflared is this thing (open source!):
>
> https://github.com/cloudflare/cloudflared

I hadn't heard of this. Cool.

> There are two pieces of magic here. One is the "couldflared access ssh-gen" command. It's annoyingly slow (which could be fixed, presumably), and it refreshes the certificates in ~/.cloudflared, using (I presume -- haven't checked) OAuth2 behind the scenes. The other is the ProxyCommand, which, as I've configured it, is just a proxy.

A quick glance suggests (at "handleCertificateGeneration", which takes a JWT and uses it build a signPayload https://github.com/cloudflare/cloudflared/blob/bf3136debbe0d847dd0e27c8e91eb21a7e4af73d/sshgen/sshgen.go#L74) that the slowness may be caused by Cloudflare's certificate signing process (in "SignCert").

...

> I think the most straightforward change to openssh would be to allow me to rewrite it as:
>
> Host myhost
> PreAuthCommand cloudflared access ssh-gen --hostname myhost.domain
> ProxyCommand cloudflared access ssh --hostname myhost.domain
> IdentityFile ~/.cloudflared/blahblah

Maybe something like this would work:

Match host myhost exec "lsagentcerts -e 5s -t || \
cloudflared access ssh-gen --hostname myhost.domain"
ProxyCommand cloudflared access ssh --hostname myhost.domain
Host myhost
...

"lsagentcerts -e 5s -t" stands for "expiring in 5 seconds, terse mode".

Cheers
Rory
_______________________________________________
openssh-unix-dev mailing list
openssh-unix-dev@mindrot.org
https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev