Hector Martin 'marcan'
2018-Mar-20 09:17 UTC
Informing the SSH agent of the target user@server
Hi, I'm prototyping an SSH agent that can broker access to a large set of remotely held SSH keys (potentially hundreds or thousands)*. Since SSH servers have a limit on maximum authentication attempts, the client needs to be able to request whichever particular key it needs for a given target user at server from the agent. Currently, the SSH agent protocol[1] only supports unconditionally listing all held keys, it doesn't include information about what the target server is. If all the possible keys are known in advance this could be potentially configured as a massive autogenerated ssh_config and a bundle of public keys to match, assigning the required key to each target host, but this is less than ideal. What I really want is for ssh to just ask the agent "give me a list of keys for this user at host" and the agent would offer only those, probably just one. Thoughts? The SSH agent protocol has an extension mechanism, but I'm not aware of it being used for anything at the moment. Ideas: - Extend the SSH_AGENTC_REQUEST_IDENTITIES request by adding a payload with connection information (ideally a tag-value system so different metadata can be provided in the future). I've experimentally determined that the current standard ssh-agent does not care about a non-empty payload, so this would be backwards compatible with the current implementation (it would still just return all keys). This could be enabled by a config option, to ensure the current-spec-compliant behavior is available too. - Implement an extension that can be queried for, which then replaces SSH_AGENTC_REQUEST_IDENTITIES with an extension request that does the same thing, with additional connection info. Might still require a config option to let people avoid the 1RTT penalty if unneeded, but it should be spec-compliant. If this sounds like a good idea I'd be happy to give implementing it in OpenSSH a go and submit a patch. I want to get a feeling for whether any of this sounds like a workable plan before I do that. Hacky approaches that would not require changes to the ssh client include using setting IdentityAgent to <path>/%u@%h:%p where <path> is a virtual filesystem that materializes sockets as required; using LD_PRELOAD to hook the agent socket and inject the appropriate info; setting ProxyCommand to something that informs the agent ahead of time; or just wrapping ssh in something that tries to figure out what you're trying to do ahead of time. Obviously all of these have various drawbacks and are less than ideal; good enough for a prototype, but I'd much rather have a sane way of achieving this in the future. In particular, having this in mainline SSH means agent forwarding would work as intended without having to have anything installed on intermediate servers, just the custom agent on the client side. [1] https://tools.ietf.org/html/draft-miller-ssh-agent-02 * This will be an open-source project. -- Hector Martin Public key: https://mrcn.st/pub
you should check out https://github.com/StanfordSNR/guardian-agent ATM it's hard because ssh only contacts the agent when it needs it and drops the connection immediately when done. To "pin" an agent request to a remote identity we'd either need to make the connections persistent and add an extension to inform the agent of the remote identity OR do this on every request. The latter is probably easier. I.e. have ssh inject a SSH_AGENTC_EXTENSION "remote-id at openssh.com" string("user at host") before each agent request (ssh would need to eat the reply too) and the agent uses that to filter the keys it is prepared to send. Specifying which hosts a given key is allowed for could be done with a key constraint. This is backwards-compatible with old agents; if an agent doesn't support the extension, then it would continue working as usual, offering all keys. It also limits the modifications needed to ssh. At present, ssh doesn't have any knowledge of what happens over the agent socket and, critically, doesn't inspect or modify messages there. This design would have it do a one-shot transaction whenever it opens the socket and then it can go back to not caring what happens there afterwards. The downside of this design is that it blurs the trust boundary for ssh-agent; no longer would it be making decisions solely on its own - it would be trusting ssh not to lie to it about the remote destination. I had more grandiose plans to allow each sshd to sign agent requests with the hostkey as they passed through, to allow some sort of chain of trust. Unfortunately that would require fairly far reaching changes to the SSH protocol to enable binding those signatures to the transport instance over which they occur. On Tue, 20 Mar 2018, Hector Martin 'marcan' wrote:> Hi, > > I'm prototyping an SSH agent that can broker access to a large set of remotely > held SSH keys (potentially hundreds or thousands)*. Since SSH servers have a > limit on maximum authentication attempts, the client needs to be able to > request whichever particular key it needs for a given target user at server from > the agent. Currently, the SSH agent protocol[1] only supports unconditionally > listing all held keys, it doesn't include information about what the target > server is. > > If all the possible keys are known in advance this could be potentially > configured as a massive autogenerated ssh_config and a bundle of public keys > to match, assigning the required key to each target host, but this is less > than ideal. What I really want is for ssh to just ask the agent "give me a > list of keys for this user at host" and the agent would offer only those, > probably just one. > > Thoughts? The SSH agent protocol has an extension mechanism, but I'm not aware > of it being used for anything at the moment. > > Ideas: > - Extend the SSH_AGENTC_REQUEST_IDENTITIES request by adding a payload with > connection information (ideally a tag-value system so different metadata can > be provided in the future). I've experimentally determined that the current > standard ssh-agent does not care about a non-empty payload, so this would be > backwards compatible with the current implementation (it would still just > return all keys). This could be enabled by a config option, to ensure the > current-spec-compliant behavior is available too. > > - Implement an extension that can be queried for, which then replaces > SSH_AGENTC_REQUEST_IDENTITIES with an extension request that does the same > thing, with additional connection info. Might still require a config option to > let people avoid the 1RTT penalty if unneeded, but it should be > spec-compliant. > > If this sounds like a good idea I'd be happy to give implementing it in > OpenSSH a go and submit a patch. I want to get a feeling for whether any of > this sounds like a workable plan before I do that. > > Hacky approaches that would not require changes to the ssh client include > using setting IdentityAgent to <path>/%u@%h:%p where <path> is a virtual > filesystem that materializes sockets as required; using LD_PRELOAD to hook the > agent socket and inject the appropriate info; setting ProxyCommand to > something that informs the agent ahead of time; or just wrapping ssh in > something that tries to figure out what you're trying to do ahead of time. > Obviously all of these have various drawbacks and are less than ideal; good > enough for a prototype, but I'd much rather have a sane way of achieving this > in the future. In particular, having this in mainline SSH means agent > forwarding would work as intended without having to have anything installed on > intermediate servers, just the custom agent on the client side. > > [1] https://tools.ietf.org/html/draft-miller-ssh-agent-02 > > * This will be an open-source project. > > -- > Hector Martin > Public key: https://mrcn.st/pub > _______________________________________________ > openssh-unix-dev mailing list > openssh-unix-dev at mindrot.org > https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev >
On 21/03/18 10:30, Damien Miller wrote:> The downside of this design is that it blurs the trust boundary for > ssh-agent; no longer would it be making decisions solely on its own - it > would be trusting ssh not to lie to it about the remote destination.That doesn't sound particularly bad to me.? Sure, the agent might try a private key for the wrong destination, but that already happens. The method you outlined sounds rather good.
Hector Martin 'marcan'
2018-Mar-21 10:24 UTC
Informing the SSH agent of the target user@server
On 2018-03-21 09:00, Damien Miller wrote:> you should check out https://github.com/StanfordSNR/guardian-agentVery nice trick. Requires a modified/bespoke client, of course, but it's definitely a very powerful approach. I'm going to have customized clients in some use cases anyway; I'll have to think about whether I can use this in some cases.> ATM it's hard because ssh only contacts the agent when it needs it and > drops the connection immediately when done. To "pin" an agent request > to a remote identity we'd either need to make the connections persistent > and add an extension to inform the agent of the remote identity OR > do this on every request. The latter is probably easier. > > I.e. have ssh inject a > > SSH_AGENTC_EXTENSION "remote-id at openssh.com" string("user at host") > > before each agent request (ssh would need to eat the reply too) and the > agent uses that to filter the keys it is prepared to send. Specifying > which hosts a given key is allowed for could be done with a key > constraint.That sounds reasonable. If the extension and identity list request are pipelined, it wouldn't introduce a latency penalty.> It also limits the modifications needed to ssh. At present, ssh doesn't > have any knowledge of what happens over the agent socket and, critically, > doesn't inspect or modify messages there. This design would have it do a > one-shot transaction whenever it opens the socket and then it can go back > to not caring what happens there afterwards.There are two independent ideas here: the ultimate ssh client asking the agent for which specific key it needs, and intermediate (forwarding) clients tagging the requests with what host they are being made on behalf of. I was mostly asking about the former; the latter would be useful too, but they're tangential. So right now we just have - SSH_AGENTC_REQUEST_IDENTITIES But the final ssh could do (1): - SSH_AGENTC_EXTENSION "remote-id at openssh.com" string("user at host") - SSH_AGENTC_REQUEST_IDENTITIES Or an intermediate forwarding ssh could insert a tag (2): - SSH_AGENTC_EXTENSION "forwarded-for at openssh.com" string("user2 at host2") - SSH_AGENTC_REQUEST_IDENTITIES (which would of course nest with multiple chained forwards, similar to SMTP Received headers) Or we could have both at once (1+2) - SSH_AGENTC_EXTENSION "forwarded-for at openssh.com" string("user2 at host2") - SSH_AGENTC_EXTENSION "remote-id at openssh.com" string("user at host") - SSH_AGENTC_REQUEST_IDENTITIES I was asking about case 1 (merely filtering a potentially huge list of identities to the appropriate one to log in to a given host), which does not require any changes to the forwarding at all, but case 2 has value too. Sidenote: I'd want more than a literal user at host string as the remote-id. Port at least, but also hostkey and an extra freeform option-specified tag (use case: things like GitHub, where the the key is all that tells different users apart).> The downside of this design is that it blurs the trust boundary for > ssh-agent; no longer would it be making decisions solely on its own - it > would be trusting ssh not to lie to it about the remote destination.In my design, the idea is to simply use a separate key for each user at host. Therefore, if something along the way lies, you just get the wrong key which won't work on the actual host you're trying to connect to. The agent would have its own policy and UI to ensure the user is informed of (and acknowledges) what identity is being used (and therefore what target is being connected to). This is why I expect to have potentially hundreds/thousands of keys - one for each user at host.> I had more grandiose plans to allow each sshd to sign agent requests > with the hostkey as they passed through, to allow some sort of chain of > trust. Unfortunately that would require fairly far reaching changes to > the SSH protocol to enable binding those signatures to the transport > instance over which they occur.This sounds interesting. If you bound a *usage* of a key with a given target user at host/hostkey (case 1 above), say by including constraints in the to-be-signed message that sshd enforces and the agent can inspect before signing it, it would allow use cases like having a single identity that many hosts trust, while the agent can still verify and apply policy on every individual use. This sounds "relatively" simple to implement, though it would still require changes to the core protocol to implement that idea of constraints (e.g. this signature is only valid for this host key/username combo). Binding the forwarding path cryptographically (2) sounds a lot more involved. I guess you can try to enforce that there is a "secure path" between the end-user/agent and the target host, but the actual transport isn't end-to-end anyway (in the usual ssh a ssh b scenario). If you're trying to have end-to-end security I think you'd just ProxyCommand your way through and then you don't have to care about agent forwarding or trusting intermediate servers, assuming you have your host key ahead of time. -- Hector Martin "marcan" (marcan at marcan.st) Public Key: https://mrcn.st/pub
On Wed, 21 Mar 2018, Damien Miller wrote:> I had more grandiose plans to allow each sshd to sign agent requests > with the hostkey as they passed through, to allow some sort of chain > of trust. Unfortunately that would require fairly far reaching > changes to the SSH protocol to enable binding those signatures to the > transport instance over which they occur.I should add that one of the things that put me off pursing this further was implementing ProxyJump/-J. Complex schemes for verifying agent request provenance seem inferior in most ways than using ProxyJump to set up end-to-end ssh sessions with the ultimate destination. For that case, the main thing you want to do is locally subsetting which keys ssh-agent is willing to present to remote destinations and that's a way simpler problem. -d