I can't rely on an ssh agent to provide a private key. Sometimes I need to provide a password, sometimes I need to manually load the key file. But the docker-py code only connects with this in SSHHTTPAdapter:
self.ssh_client.connect(
parsed.hostname, parsed.port, parsed.username,
)
There is no flexibility here. How am I supposed to connect with a custom key or password? I thought I might hack my way in by reassigning APIClient._custom_adapter to my own subclass of SSHHTTPAdapter, but then I realized the APIClient.__init__ is a huge mess that does way too much. That method would always raise an exception so I would also have to totally reimplement that method in a subclass. This is too much maintenance overhead for my deployment script that I would like to keep as simple as possible.
It should be exposed in SSHHTTPAdapter. I might even recommend doing both of these:
It should also be exposed somehow in APIClient.__init__, for example:
I'm happy to take the lead on this and submit a PR, but I would like to get some feedback first.
Hi!
You can already provide a custom password through the base_url parameter, e.g. ssh://user:[email protected]:22. For more advanced uses, you should be able to write your own Adapter subclass and pass it to the APIClient through mount() (see the requests doc on that topic). For example,
ssh_adapter = MySSHAdapter(url, key_path)
client = APIClient(base_url='ssh://bogus:22')
client.mount('http+docker://ssh', ssh_adapter)
I think eventually we may want to have the option of passing a private key to the Client instantiation in some form (TBD - maybe something similar to the TLSConfig), but anything beyond that, like first-class adapter modularity, would probably be beyond the scope of what we're trying to do with the library.
Hope that helps!
Hey, thanks for your reply, but there are some issues with your recommendations.
first-class adapter modularity
What does this mean?
You can already provide a custom password through the base_url parameter, e.g. ssh://user:[email protected]:22
This is not right. I get the following exception:
File "docker/client.py", line 40, in __init__
self.api = APIClient(*args, **kwargs)
File "docker/api/client.py", line 133, in __init__
base_url, IS_WINDOWS_PLATFORM, tls=bool(tls)
File "docker/utils/utils.py", line 265, in parse_host
'Invalid bind address format: {}'.format(addr)
docker.errors.DockerException: Invalid bind address format: ssh://user:[email protected]:22
Also as you can see in the source I quoted in my original comment, there is no way that the password would be used for the ssh connection, even if the url were valid.
So regardless of whether you want to use a password or a custom key file, you would definitely need to provide a custom Adapter class. And your recommended code to do so does not work because it will try to connect to a host called "bogus" on line two. If you leave out base_url, it changes it to http+unix and tries to connect to the local docker daemon. If there is no local docker daemon, it fails here.
There's no way to instantiate an APIClient without it trying to connect to a docker daemon. This is what I was talking about with __init__ doing too much and was the whole reason why I made this ticket.
A better design would be that APIClient does not even try to connect unless you explicitly call a method that needs a connection. There needs to be a way to provide a custom adapter or at least ssh credentials. APIClient.__init__ should really be broken up into several methods and SSHHTTPAdapter needs some changes too.
I actually tried using a hostname that my ssh agent can provide a key for and I got a valid SSH connection. But then I saw this error whenever I tried to do anything. It looks like it's treating the ssh connection like it can make GET requests directly over that connection which doesn't seem right.
File "docker/api/container.py", line 210, in containers
res = self._result(self._get(u, params=params), True)
File "docker/utils/decorators.py", line 46, in inner
return f(self, *args, **kwargs)
File "docker/api/client.py", line 235, in _get
return self.get(url, **self._set_request_timeout(kwargs))
File "requests/sessions.py", line 546, in get
return self.request('GET', url, **kwargs)
File "requests/sessions.py", line 533, in request
resp = self.send(prep, **send_kwargs)
File "requests/sessions.py", line 646, in send
r = adapter.send(request, **kwargs)
File "requests/adapters.py", line 498, in send
raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', BadStatusLine('No status line received - the server has closed the connection',))
@dnut FYI I just got SSH to work to answer your question. It's not documented and i had to read through some of the source code but here are a few things i learned.
@cmcga1125 I can get SSH to work on my desktop, but I need this to run on our build servers with custom credentials.
If you're wanting to use an ssh key, it has to be in the ~/.ssh/id_rsa location - or - /root/.ssh/id_rsa (if alpine)
This is a problem for me. I need to be able to specify any key file.
@dnut - i'm running in a docker container - which allows me to map in the file, would that work?
Bump. I just went down the same rabbit hole as @dnut . Being able to specify the path to a SSH keyfile and/or the key material directly via an argument is very much needed for any automation when using this feature.
Got the same issue here. I am using this sdk but, I had to shift to the CLI option since it was not clear to me whether authenticating with SSH credentials was an option for the sdk.
The answer provided by @cmcga1125 helps, but a slightly more elaborated step-by-step would really be super nice! :)
Most helpful comment
Hey, thanks for your reply, but there are some issues with your recommendations.
What does this mean?
Password in URL
This is not right. I get the following exception:
Also as you can see in the source I quoted in my original comment, there is no way that the password would be used for the ssh connection, even if the url were valid.
Setting a Custom Adapter
So regardless of whether you want to use a password or a custom key file, you would definitely need to provide a custom Adapter class. And your recommended code to do so does not work because it will try to connect to a host called "bogus" on line two. If you leave out base_url, it changes it to http+unix and tries to connect to the local docker daemon. If there is no local docker daemon, it fails here.
There's no way to instantiate an APIClient without it trying to connect to a docker daemon. This is what I was talking about with __init__ doing too much and was the whole reason why I made this ticket.
A better design would be that APIClient does not even try to connect unless you explicitly call a method that needs a connection. There needs to be a way to provide a custom adapter or at least ssh credentials. APIClient.__init__ should really be broken up into several methods and SSHHTTPAdapter needs some changes too.
Does SSH work at all?
I actually tried using a hostname that my ssh agent can provide a key for and I got a valid SSH connection. But then I saw this error whenever I tried to do anything. It looks like it's treating the ssh connection like it can make GET requests directly over that connection which doesn't seem right.