gliderlabs / ssh

Easy SSH servers in Golang
https://godoc.org/github.com/gliderlabs/ssh
BSD 3-Clause "New" or "Revised" License
3.71k stars 450 forks source link

Build a SSH bastion #47

Closed arkan closed 2 years ago

arkan commented 7 years ago

Hi,

Is there any example of SSH bastion created with this package?

Basically I'd like to create a SSH server accepting connections, verifying the identity with Public SSH keys, and then forwarding the connections to another server.

Since I believe this is a pretty standard need, it could be interesting to add such an example in the repo.

Thanks

progrium commented 7 years ago

How would you expect authentication to work on the second leg? We don't have access to the private key used for the initial leg, so we can't use the same key for the second. We can use the same username, but maybe a single shared keypair for all bastion connections...

arkan commented 7 years ago

@progrium Thanks for the reply!

In this kind of scenario, the Bastion would know the private keys to connect to the servers. The bastion prevents to share private keys with users, and only the bastion knows them.

Maybe https://github.com/kennylevinsen/sshmuxd could help?

arkan commented 7 years ago

@progrium I just published a poc which implements a basic poc of a SSH bastion using this SSH package. I had to tweak a bit the package to export a new field. I'm not satisfied the way it's implemented but before spending some time on it I'd like to know if this is something you'd like to support in this package? If so, would you have any idea on how to achieve this?

Source: https://github.com/arkan/bastion and the relevant commit about the change is https://github.com/arkan/bastion/commit/bab08c812d8a1e021d753184c9338251c76c6ea6

Thanks!

moul commented 7 years ago

@arkan very nice, I tried to find a solution without modifying gliderlabs/ssh, your update is generic and working like a charm

progrium commented 7 years ago

@arkan cool, but can you explain your patch? You're just exposing arbitrary requests?

arkan commented 7 years ago

When the channel creation is accepted two values are returned: the channel, but also a Go chan containing SSH requests.

This Go chan is consumed by the handleRequests method and there is currently no way to export those gossh.Request messages outside of this package.

This is yet required in my usecase to implement a SSH bastion. I need to have access to the 2 SSH channels as well as the two Go chans to get SSH requests.

Then the Bastion does:

I also implemented a TTyRec adapter but this is not useful in our usecase :)

Regarding a possible implementation, I could maybe add a Requests(chan *gossh) method to the Session interface. And then the implementation is pretty straightforward, and will only forward request if the Go chan is specified. It will become the caller responsibility to close this channel. What do you think? Does it make sense for you?

progrium commented 7 years ago

That makes sense. It's a reasonable approach.

Though also, and this is more work, but what do you think of a whole different interface for writing a bastion? I've started a sort of lower level generic abstraction for different kinds of handlers different from a session handler. Perhaps something more appropriate for making a gateway/bastion could be made separate from the session handler?

I'm thinking out loud. The Session interface is already quite big and I'm guessing you aren't using a lot of it? I generally like the idea of more specialized interfaces because that also implies the types of things you can build with the library.

arkan commented 7 years ago

I like the idea. This is a good approach especially because exposing raw information is only useful for specific purposes. Bastion is one of them.

So if I correctly understood, your idea would to have something like this? (naming left aside)

type BastionSession interface {
  Session
  Requests() chan *gossh.Request
}

...

ssh.HandleBastion(func(sess ssh.BastionSession) {
   // Bastion implementation goes here
})

Is that what you were thinking about?

progrium commented 7 years ago

Something like this. One question is how much of the Session interface do you use to make a bastion? If you don't use all of it maybe we can disaggregate it into smaller interfaces that this and Session share.

belak commented 7 years ago

I'm not sure exposing it like this would be the best method. If a server doesn't currently use MaskedReqs, when the buffered channel is full, any new requests will hang and not be handled. It's a little ugly, but possibly adding a callback would be better (HandleChannelReques or something like that)... this means if the user doesn't define it, we can just discard the request.

This does additionally start a discussion on what we should do with requests we already handle internally but the user may want to see (such as window-change)... since these shouldn't be replied to twice.

Also, I'm not sure we're currently handling all messages properly (we should be replying with false to any message we don't recognize/isn't handled).

arkan commented 7 years ago

One question is how much of the Session interface do you use to make a bastion

I clearly don't use all of them.

If you don't use all of it maybe we can disaggregate it into smaller interfaces that this and Session share

Maybe this is something that can be done as a second step? (albeit I agree with you on the concept)

Also, I'm not sure we're currently handling all messages properly (we should be replying with false to any message we don't recognize/isn't handled).

I think (and correctly if I'm wrong @belak) that the replies calls should only be made if req.WantReply is set to true?

progrium commented 7 years ago

I thought I fixed the message reply issue, but it looks like there are some branches I haven't merged. I'll need to clean that up before we continue.

Second step for the deeper stuff could work. I realize I haven't merged some of the underlying refactoring for channel type handlers. But to avoid making APIs we remove, you should duplicate the methods of Session into your new interface that you use.

belak commented 7 years ago

I think (and correctly if I'm wrong @belak) that the replies calls should only be made if req.WantReply is set to true?

Yep. Though, I think crypto/ssh handles that automatically if you always Reply.

moul commented 6 years ago

Hey, just released this https://github.com/moul/sshportal

(I first began to work on this when I read this thread :p)

arkan commented 6 years ago

This looks awesome @moul !

I'll check this in detail - Thanks :)

moul commented 6 years ago

FYI, I fixed the version of gliderlabs (https://github.com/gliderlabs/ssh/tree/0c9c3575f476a0c2779aafb89d1838ca4ab7ac16 2 commits in the past), and cherry-picked https://github.com/arkan/bastion/commit/bab08c812d8a1e021d753184c9338251c76c6ea6

Will try to switch to newest gliderlabs/ssh as soon as we choose a solution

progrium commented 6 years ago

Hey everybody, is this issue actionable?

@moul maybe you can make another issue to track anything that needs to be resolved upstream here?

moul commented 6 years ago

@moul maybe you can make another issue to track anything that needs to be resolved upstream here?

Yep, no problem, will do it now

josegonzalez commented 5 years ago

I think this issue can be closed, as there is an example - sshportal - for bastion servers. If we want something simpler as an example, that could maybe be an addition, but I believe the above works fine?

@progrium Do you think a section for "projects using gliderlabs/ssh" would suffice for for closing? I can PR that if so.

progrium commented 5 years ago

@josegonzalez sounds good