Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support SSH connection #1014

Merged
merged 1 commit into from Aug 14, 2018
Merged

support SSH connection #1014

merged 1 commit into from Aug 14, 2018

Conversation

@AkihiroSuda
Copy link
Collaborator

@AkihiroSuda AkihiroSuda commented Apr 20, 2018

Signed-off-by: Akihiro Suda suda.akihiro@lab.ntt.co.jp

- What I did

Added support for SSH connection. e.g. docker -H ssh://me@server

- How I did it

Followed @tonistiigi 's suggestion: #889 (comment)

The cli should accept ssh://me@server for DOCKER_HOST and -H. Using that would execute ssh with the passed config.
The ssh command would call a hidden command on the docker CLI binary on the remote side. For example, docker dial-stdio.
This command will make a connection to the local DOCKER_HOST variable (almost always the default local socket) and forward that connection on the commands stdio.
Even though this command is supposed to run locally to the dockerd binary, we think that it is an invalid configuration for this feature to remove the local docker binary so we can rely on it always being present.

- How to verify it

docker -H ssh://me@server run -it --rm busybox

- Description for the changelog

support SSH connection

- A picture of a cute animal (not mandatory but encouraged)

penguin

https://commons.wikimedia.org/wiki/File:Manchot_Ad%C3%A9lie_juv%C3%A9nile.jpg

Vendor: moby/moby#36630 (merged)


Tests (currently tested manually):

  • echo hello | ./docker -H ssh://10.2.54.48 run -i --rm busybox cat; echo world
  • ./docker -H ssh://10.2.54.48 run --rm busybox echo hello; echo world
  • ./docker -H ssh://10.2.54.48 run -i --rm busybox echo hello; echo world
@cpuguy83
Copy link
Collaborator

@cpuguy83 cpuguy83 commented Apr 23, 2018

Design LGTM! cc @tonistiigi for design as well.
Tested this out and it works pretty well.

I did notice that this:

echo hello | ./docker-dev -H ssh://docker@$ip run -i --rm busybox cat

Doesn't echo anything to my term when using the ssh connector.
But a normal interactive container works fine. Image pull also works well.

@AkihiroSuda
Copy link
Collaborator Author

@AkihiroSuda AkihiroSuda commented Apr 23, 2018

@cpuguy83
Thank you for testing.

The issue can be mitigated by increasing this timeout, which is the equivalent of socat -t and nc -q: https://github.com/docker/cli/pull/1014/files#diff-065a55b1c9d14a6817c97ca733652920R17

However, increasing this value introduces extra sleep.

I'll try to find a more robust way

@AkihiroSuda AkihiroSuda reopened this Apr 23, 2018
@vdemeester
Copy link
Collaborator

@vdemeester vdemeester commented Apr 24, 2018

Design LGTM too, this is cool 😻

Copy link
Member

@tonistiigi tonistiigi left a comment

Overall design LGTM. I don't understand the timeout logic on handling EOF though.

if !ok {
return errors.New("the raw stream connection does not implement halfCloser")
}
stdio := &cliHalfCloser{dockerCli}

This comment has been minimized.

@tonistiigi

tonistiigi Apr 25, 2018
Member

It may be confusing to use dockerCli here. I'd just use os pkg.

}()
select {
case err := <-stdin2conn:
logrus.Debugf("stdin2conn: %v", err)

This comment has been minimized.

@tonistiigi

tonistiigi Apr 25, 2018
Member

is this wip?

// ConnectionHelper allows to connect to a remote host with custom stream provider binary.
type ConnectionHelper struct {
Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
DummyHost string // dummy URL used for HTTP requests. e.g. "http://docker"

This comment has been minimized.

@tonistiigi

tonistiigi Apr 25, 2018
Member

Just Host. We can also leave a sane default here if WithHost is not called. So connection helper case doesn't need to call it at all (or even better if connectionhelper just returns the client options directly instead of a struct).

clientOpts = append(clientOpts, client.WithHost(host))
} else {
clientOpts = append(clientOpts, func(c *client.Client) error {
httpClient := &http.Client{

This comment has been minimized.

@tonistiigi

tonistiigi Apr 25, 2018
Member

I think we should just add WithDialer instead of overriding client and remove WithHijackDialer. Using it would create a new client with a transport pointing to the current dialer and set it to hijack as well.

func runDialStdio(dockerCli command.Cli, opts dialStdioOptions) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
conn, err := dockerCli.Client().DialRaw(ctx)

This comment has been minimized.

@tonistiigi

tonistiigi Apr 25, 2018
Member

I think this would be slightly better as a Dialer() func(ctx) (net.Conn, error) . Otherwise, it seems like a method that calls to the remote side. We could also solve this by breaking up NewClientWithOpts so that we always get access to dialer in cli before creating the client and store the dialer so we can reuse it.

}

flags := cmd.Flags()
flags.DurationVarP(&opts.timeout, "timeout", "t", 500*time.Millisecond, "After EOF from one of inputs, wait for this duration before exit")

This comment has been minimized.

@tonistiigi

tonistiigi Apr 25, 2018
Member

I'm not sure I understand this timeout. Why isn't the close of connection handled cleanly? This could be just a connection timeout but then it should be much bigger (20 sec) and increasing it shouldn't have any downsides when timeout isn't reached.

stdin2conn := make(chan error)
conn2stdout := make(chan error)

go func() {

This comment has been minimized.

@tonistiigi

tonistiigi Apr 25, 2018
Member

These might be better handled with a wrapper around conn. But I guess I don't understand the need for the current timeout atm.

@cpuguy83
Copy link
Collaborator

@cpuguy83 cpuguy83 commented Jul 2, 2018

Any update here?

@AkihiroSuda
Copy link
Collaborator Author

@AkihiroSuda AkihiroSuda commented Jul 3, 2018

Sorry still stuck at the stream connection issue #1014 (comment)

Will try to look into this again ASAP

@cpuguy83
Copy link
Collaborator

@cpuguy83 cpuguy83 commented Jul 3, 2018

No worries I just thought this was already merged and realized it wasn't!

@AkihiroSuda AkihiroSuda force-pushed the AkihiroSuda:connhelper-sshonly branch 2 times, most recently from 5cd375a to e6fc199 Jul 27, 2018
@codecov-io
Copy link

@codecov-io codecov-io commented Jul 27, 2018

Codecov Report

Merging #1014 into master will decrease coverage by 0.28%.
The diff coverage is 34.46%.

@@            Coverage Diff            @@
##           master   #1014      +/-   ##
=========================================
- Coverage   54.29%     54%   -0.29%     
=========================================
  Files         268     272       +4     
  Lines       17843   18068     +225     
=========================================
+ Hits         9687    9758      +71     
- Misses       7546    7694     +148     
- Partials      610     616       +6
@AkihiroSuda AkihiroSuda changed the title support SSH connection [WIP] support SSH connection Jul 27, 2018
@cpuguy83
Copy link
Collaborator

@cpuguy83 cpuguy83 commented Aug 15, 2018

Sorry, meant to link the comment ;)

#1014 (comment)

@AkihiroSuda
Copy link
Collaborator Author

@AkihiroSuda AkihiroSuda commented Aug 15, 2018

I think the problem is substantially covered by this change, although it does not negotiate versions actually for avoiding potential performance overhead: #1014 (comment)

updated to print full args, so that user can notice whether the error is from SSH or Docker

$ ./docker -H ssh://dummy@github.com info
error during connect: Get http://docker/v1.38/info: command [ssh -l dummy github.com -- docker
system dial-stdio] has exited with exit status 255, please make sure the URL is valid, and Docker 
18.09 or later is installed on the remote host: stderr="dummy@github.com: Permission denied 
(publickey).\r\n"
@sandys
Copy link

@sandys sandys commented Aug 20, 2018

How does one add options to SSH here ?
We enforce hardware tokens for all our SSH (using opensc). We use epass2003 tokens. This is how the command looks.

#Ubuntu -
ssh  -o PKCS11Provider=/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so  user@gcp.red.com

#OSX -
ssh -o PKCS11Provider=/Library/OpenSC/lib/opensc-pkcs11.so user@gcp.red.com

@AkihiroSuda
Copy link
Collaborator Author

@AkihiroSuda AkihiroSuda commented Aug 20, 2018

@sandys ~/.ssh/config should work

Host foo
        Hostname          gcp.red.com
        PKCS11Provider    /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
@sandys
Copy link

@sandys sandys commented Aug 20, 2018

@AkihiroSuda
Copy link
Collaborator Author

@AkihiroSuda AkihiroSuda commented Aug 20, 2018

Currently no, could you open a github issue?

Secondly we have more -o options that we pass.

Any option that cannot be configured via ~/.ssh/config?

@BretFisher
Copy link
Contributor

@BretFisher BretFisher commented Oct 17, 2018

From ops and sysadmins everywhere, we thank you for this fantastic and unexpected feature. I'm hoping this will seriously cut down the number of times I see people opening dockerd TCP w/o TLS and just opt for SSH endpoints for remote mgmt. 👍 😂

@overmike
Copy link

@overmike overmike commented Nov 9, 2018

is this possible to use in docker golang client rather than cli? just like docker-py understand the ssh:// DOCKER_HOST

@cpuguy83
Copy link
Collaborator

@cpuguy83 cpuguy83 commented Nov 9, 2018

@overmike No, none of the client helpers will set this up for you. Nothing is stopping someone from doing this themselves with the client, though. client.NewClient accepts an http client, which you can setup on your own.

The CLI is using a helper subcommand (hidden from --help) to assist in wiring up std i/o with the remote.

@overmike
Copy link

@overmike overmike commented Nov 12, 2018

@cpuguy83 , thanks for your feedback. It clarifies I need to add connhelper code in our golang client.

I think you meant "docker system dial-stdio" for the hidden subcommand. And seems like docker-py 3.6.0 is going to support ssh DOCKER_HOST.

@bullno1
Copy link

@bullno1 bullno1 commented Nov 14, 2018

How does it handle authentication? Does it support signed SSH certificate? What about revocation?

@cpuguy83
Copy link
Collaborator

@cpuguy83 cpuguy83 commented Nov 14, 2018

It just shells out to the ssh bin. You can add whatever options for the host to your ssh config. I think there is another pr to support passing ssh opts through an environment var as well.

// GetConnectionHelper returns nil without error when no helper is registered for the scheme.
// URL is like "ssh://me@server01".
func GetConnectionHelper(daemonURL string) (*ConnectionHelper, error) {
u, err := url.Parse(daemonURL)

This comment has been minimized.

@zmilonas

zmilonas Nov 20, 2018

You can actually get a unparsedHost here which is not parseable - has no schema.
Like 127.0.0.1:2364

This comment has been minimized.

@AkihiroSuda

AkihiroSuda Nov 20, 2018
Author Collaborator

GetConnectionHelper returns nil without error when no helper is registered for the scheme.

This comment has been minimized.

@zmilonas

zmilonas Nov 21, 2018

I think, this is actually unrelated to what I'm talking about. Just a line below you check for err from url.Parse() and since there is a possibility the passed daemonURL is in 127.0.0.1:2345 format url.Parse() will rightfully return an error per its specification - no protocol scheme. I am not sure where should the scheme be added - here or higher in the call stack but this line in particular introduced a regression in the newest stable release of docker engine - 18.09.0

This comment has been minimized.

@thaJeztah

thaJeztah Nov 21, 2018
Member

@zmilonas looks like you mean the issue that was addressed by #1443 ?

This comment has been minimized.

@zmilonas

zmilonas Nov 21, 2018

Well it might have been addressed but is not fixed 😅 I will file a new issue shortly then.

This comment has been minimized.

@thaJeztah

thaJeztah Nov 21, 2018
Member

It's fixed on master, which is what this repository is tracking.

A backport PR is open to include it in the 18.09 codebase

This comment has been minimized.

@zmilonas

zmilonas Nov 21, 2018

Can we expect it to end up in some kind of 18.09.01 release then?

This comment has been minimized.

@thaJeztah

thaJeztah Nov 21, 2018
Member

Yes, that's what the backport is for. In the meantime you could either modify your configuration (use tcp://<IP|host>:<port>), or download the static binaries for Docker 18.06 (or "nightly", which has the latest changes from "master") https://download.docker.com/linux/static/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

You can’t perform that action at this time.