Hi there, I'm working on a project to write a ProxyCommand that reaches out to an SSH CA to receive an SSH certificate prior to the connection. The ProxyCommand also creates a tunnel to the upstream SSH server. When using ProxyCommand alone, the issue is that the identity files are loaded as soon as SSH has fork/exec'd the process. It does not wait for a valid server negotiation. I found the ProxyUseFdPass flag which seemed promising -- here, the identity files weren't loaded until after the file descriptors are passed back to the SSH client. Perhaps I could fetch the identity file, return the fds, and then tunnel the traffic. Unfortunately, it blocks on waitpid(), so this doesn't work either -- I need the process to stay open to tunnel data. I considered trying to fork, disown the child, and run the tunnel inside the child, but unfortunately I am working with Golang, which doesn't allow forking (except to execute another application.) I'm looking for any tips on how I might be able to work around this problem. I'd also be interested in understanding why the identity files are loaded prior to negotiating a valid server connection. Thanks, John
On Thu, 9 Nov 2017, John Maguire wrote:> Hi there, > > I'm working on a project to write a ProxyCommand that reaches out to an SSH > CA to receive an SSH certificate prior to the connection. The ProxyCommand > also creates a tunnel to the upstream SSH server. > > When using ProxyCommand alone, the issue is that the identity files are > loaded as soon as SSH has fork/exec'd the process. It does not wait for a > valid server negotiation. > > I found the ProxyUseFdPass flag which seemed promising -- here, the > identity files weren't loaded until after the file descriptors are passed > back to the SSH client. Perhaps I could fetch the identity file, return the > fds, and then tunnel the traffic. Unfortunately, it blocks on waitpid(), so > this doesn't work either -- I need the process to stay open to tunnel data. > > I considered trying to fork, disown the child, and run the tunnel inside > the child, but unfortunately I am working with Golang, which doesn't allow > forking (except to execute another application.) > > I'm looking for any tips on how I might be able to work around this > problem. I'd also be interested in understanding why the identity files are > loaded prior to negotiating a valid server connection.I don't think you'll be able to achieve what you want with a ProxyCommand - as far as ssh is concerned, it's just a dumb pipe. Couldn't you do it as a wrapper to ssh that does the CA operations then launches ssh with an explicit ProxyCommand argument? Otherwise, you might want to check out https://github.com/sevlyar/go-daemon -- it seems to allow a daemon()-like operation that could let you use fd passing. -d
Thanks for the feedback. A couple of the goals for this project are that (a) it works for any use cases that utilize SSH (so Git, scp, rsync, etc.) -- meaning that configuration options are highly preferable to a wrapper, and (b) that this application supports Windows, MacOS, and Linux for tunneling (even if the SSH CA feature is unsupported on Windows). Thanks for the link to go-daemon, I'll look a little closer, but I don't think this architecture will work. Is there a reason that load_key_public must occur prior to ssh_exchange_identification? It strikes me that this would be a non-issue if the key were not loaded prior to the banners being exchanged. Though I do understand that this is a very atypical use-case for the ProxyCommand flag. On Thu, Nov 9, 2017 at 10:03 PM, Damien Miller <djm at mindrot.org> wrote:> On Thu, 9 Nov 2017, John Maguire wrote: > > > Hi there, > > > > I'm working on a project to write a ProxyCommand that reaches out to an > SSH > > CA to receive an SSH certificate prior to the connection. The > ProxyCommand > > also creates a tunnel to the upstream SSH server. > > > > When using ProxyCommand alone, the issue is that the identity files are > > loaded as soon as SSH has fork/exec'd the process. It does not wait for a > > valid server negotiation. > > > > I found the ProxyUseFdPass flag which seemed promising -- here, the > > identity files weren't loaded until after the file descriptors are passed > > back to the SSH client. Perhaps I could fetch the identity file, return > the > > fds, and then tunnel the traffic. Unfortunately, it blocks on waitpid(), > so > > this doesn't work either -- I need the process to stay open to tunnel > data. > > > > I considered trying to fork, disown the child, and run the tunnel inside > > the child, but unfortunately I am working with Golang, which doesn't > allow > > forking (except to execute another application.) > > > > I'm looking for any tips on how I might be able to work around this > > problem. I'd also be interested in understanding why the identity files > are > > loaded prior to negotiating a valid server connection. > > I don't think you'll be able to achieve what you want with a ProxyCommand - > as far as ssh is concerned, it's just a dumb pipe. > > Couldn't you do it as a wrapper to ssh that does the CA operations then > launches ssh with an explicit ProxyCommand argument? > > Otherwise, you might want to check out https://github.com/sevlyar/go- > daemon > -- it seems to allow a daemon()-like operation that could let you use > fd passing. > > -d >
I've done this exact thing
the short answer is, what damian said, have the command that reaches
out to the ca fork/exec ssh. eg.
Match Host <your hosts>
ProxyCommand ssh_cert_script -W %h:%p
and then you end your ssh_cert_script with something like 'exec ssh
${*}'
or, in go:
// end of func main() {
if len(args) > 1 {
execSSH(args[1:])
}
}
func execSSH(sshArgs []string) {
path, err := exec.LookPath("ssh")
if err != nil {
log.Fatalf("%v\n", err)
}
sshArgs = append([]string{path}, sshArgs...)
if err = syscall.Exec(sshArgs[0], sshArgs, os.Environ()); err != nil {
log.Fatalf("%v\n", err)
}
}
On Thu, Nov 9, 2017 at 7:03 PM, Damien Miller <djm at mindrot.org>
wrote:> On Thu, 9 Nov 2017, John Maguire wrote:
>
>> Hi there,
>>
>> I'm working on a project to write a ProxyCommand that reaches out
to an SSH
>> CA to receive an SSH certificate prior to the connection. The
ProxyCommand
>> also creates a tunnel to the upstream SSH server.
>>
>> When using ProxyCommand alone, the issue is that the identity files are
>> loaded as soon as SSH has fork/exec'd the process. It does not wait
for a
>> valid server negotiation.
>>
>> I found the ProxyUseFdPass flag which seemed promising -- here, the
>> identity files weren't loaded until after the file descriptors are
passed
>> back to the SSH client. Perhaps I could fetch the identity file, return
the
>> fds, and then tunnel the traffic. Unfortunately, it blocks on
waitpid(), so
>> this doesn't work either -- I need the process to stay open to
tunnel data.
>>
>> I considered trying to fork, disown the child, and run the tunnel
inside
>> the child, but unfortunately I am working with Golang, which
doesn't allow
>> forking (except to execute another application.)
>>
>> I'm looking for any tips on how I might be able to work around this
>> problem. I'd also be interested in understanding why the identity
files are
>> loaded prior to negotiating a valid server connection.
>
> I don't think you'll be able to achieve what you want with a
ProxyCommand -
> as far as ssh is concerned, it's just a dumb pipe.
>
> Couldn't you do it as a wrapper to ssh that does the CA operations then
> launches ssh with an explicit ProxyCommand argument?
>
> Otherwise, you might want to check out https://github.com/sevlyar/go-daemon
> -- it seems to allow a daemon()-like operation that could let you use
> fd passing.
>
> -d
> _______________________________________________
> openssh-unix-dev mailing list
> openssh-unix-dev at mindrot.org
> https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev