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

customizable host key policy #2072

Closed
wants to merge 377 commits into from
Closed

Conversation

anarcat
Copy link

@anarcat anarcat commented Mar 18, 2020

The current Fabric code does not check or save SSH host keys at all. This PR fixes the problem by ensuring we properly call the Paramiko SSH keys loader when we create our connection object.

We also load the right UserKnownHosts file based on the ssh_config, which paramiko does not do out of the box.

We might want to push the latter into paramiko, but first it seems to me the default Fabric policy of "everything goes" should b fixed.

Note that this also allows callers to override the AutoAddPolicy that is currently in use.

This should fix #2071

anarcat added a commit to anarcat/fabric that referenced this pull request Mar 18, 2020
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
anarcat added a commit to anarcat/fabric that referenced this pull request Mar 18, 2020
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
@anarcat
Copy link
Author

anarcat commented Mar 18, 2020

i got as far as this for the unit tests, which is "not very":

modified   tests/connection.py
@@ -12,6 +12,7 @@
 from mock import patch, Mock, call, ANY
 from paramiko.client import SSHClient, AutoAddPolicy
 from paramiko import SSHConfig
+from paramiko.ssh_exception import BadHostKeyException
 import pytest  # for mark
 from pytest import skip, param
 from pytest_relaxed import raises
@@ -1154,3 +1155,10 @@ def tunnel_errors_bubble_up(self):
 
         def listener_errors_bubble_up(self):
             skip()
+
+    class known_hosts:
+        @raises(BadHostKeyException)
+        def changed_host_key_fails(self):
+            c = Connection('localhost')
+            c.open()
+            

@anarcat
Copy link
Author

anarcat commented Mar 18, 2020

the test suite fails, but that's because sphinx cannot reach pyinvoke.org, i doubt it's related to this patch:

loading intersphinx inventory from http://pyinvoke.org/objects.inv...
[...]
sphinx.errors.SphinxWarning: failed to reach any of the inventories with the following issues:

https://travis-ci.org/github/fabric/fabric/jobs/664111778

... not sure what's up with that, but it's true i'm having trouble reaching pyinvoke.org myself

@anarcat
Copy link
Author

anarcat commented Mar 18, 2020

#2073 should fix that particular build issue.

@anarcat anarcat changed the title WIP: customizable host key policy customizable host key policy Mar 18, 2020
anarcat added a commit to anarcat/fabric that referenced this pull request Mar 18, 2020
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
@anarcat
Copy link
Author

anarcat commented Mar 18, 2020

rebased on top of #2073 because that actually fixes the test suite

anarcat added a commit to anarcat/fabric that referenced this pull request Mar 18, 2020
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
anarcat added a commit to anarcat/fabric that referenced this pull request Mar 18, 2020
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
It looks like http://pyinvoke.org/ does not respond. Assume the
correct URL is www.pyinvoke.org instead, which does.

And while we're here, switch all of those to HTTPS because we're in
the future now and HTTPS just works.
This is essential in case we want Fabric library consumers and users
to override the Paramiko host key policy. One, for example, might want
to override the default to prompt users when a new key needs to be
added (which is silently done by default right now).
This removes the Fabric-specific host key policy which is to
automatically accept new keys. I don't see why Fabric would do
anything different than the default here at this stage, especially
since we don't seem to be *loading* any keys, let alone saving them.

This is a stopgap measure to fix fabric#2071
This has the following consequences:

 1. the AutoAddPolicy starts to work correctly, as the
    `load_host_keys` call signals that it should save new fingerprints
    to the known_hosts file

 2. it will also correctly *fail* if the fingerprint on a host
    changes, which was *not* the case before, as the known_hosts file
    was never loaded

 3. it will correctly load the right `UserKnownHostsFile` according to
    the rules defined in the ssh configuration

Regarding the last point, that is essential in my setup because I have
a config block like this:

Host *.example.com
     UserKnownHostsFile ~/.ssh/known_hosts.example.com

... which allows me to "group" known_hosts file and synchronize them
from remote servers.
This covers the following use case:

    Host *.example.org
         UserKnownHostsFile ~/.ssh/known_hosts ~/.ssh/known_hosts.example.org

... which is also valid.
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
@anarcat
Copy link
Author

anarcat commented Mar 19, 2020

this needs to be republished against the master branch, sorry for the noise. :(

@anarcat anarcat closed this Mar 19, 2020
@anarcat anarcat deleted the host-key-policy branch March 19, 2020 15:15
anarcat added a commit to anarcat/fabric that referenced this pull request Mar 19, 2020
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
anarcat added a commit to anarcat/fabric that referenced this pull request Sep 6, 2023
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
anarcat added a commit to anarcat/fabric that referenced this pull request Sep 6, 2023
This makes it easier to monkeypatch to earlier versions. With this
patch, I can copy-paste the setup_ssh_client() function into my code
and monkeypatch older fabric versions with:

    # hack to fix Fabric key policy:
    # fabric#2071
    def safe_open(self):
        SaferConnection.setup_ssh_client(self)
        Connection.open_orig(self)

    class SaferConnection(Connection):
        # this function is a copy-paste from fabric#2072
        def setup_ssh_client(self):
            # [...]

    Connection.open_orig = Connection.open
    Connection.open = safe_open

This is otherwise a noop.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

default SSH host key policy is insecure
9 participants