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

default SSH host key policy is insecure #2071

Open
anarcat opened this issue Mar 18, 2020 · 7 comments · May be fixed by #2076
Open

default SSH host key policy is insecure #2071

anarcat opened this issue Mar 18, 2020 · 7 comments · May be fixed by #2076

Comments

@anarcat
Copy link

anarcat commented Mar 18, 2020

I was quite surprised to find that code in Fabric's Connection class:

        #: The `paramiko.client.SSHClient` instance this connection wraps.
        client = SSHClient()
        client.set_missing_host_key_policy(AutoAddPolicy())
        self.client = client

This changes the default connect policy from RejectPolicy() to AutoAddPolicy which means that a ssh connexion will not warn when an SSH key changes or on new keys. I find this behavior questionable, especially since it's actually very hard to change, because it's embedded deep inside Fabric (inside the Connection constructor).

I tried to monkeypatch this away in a a prototype.py but could not: make this work:

#!/usr/bin/python3

import fabric
import fabric.connection

class SaferConnection(Connection):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.client.set_missing_host_key_policy(RejectPolicy())

fabric.Connection = SaferConnection
fabric.connection.Connection = SaferConnection

c = fabric.Connection('root@88.99.194.57')

c.run('uptime')

for some weird reason, this crashes with:

anarcat@curie:tsa-misc(master)$ ./spike.py 
Traceback (most recent call last):
  File "./spike.py", line 10, in <module>
    c = fabric.Connection('root@88.99.194.57')
  File "/home/anarcat/src/tor/tsa-misc/fabric_tpa/__init__.py", line 56, in __init__
    super().__init__(*args, **kwargs)
  File "/usr/lib/python3/dist-packages/fabric/connection.py", line 371, in __init__
    super(Connection, self).__init__(config=config)
TypeError: __init__() missing 1 required positional argument: 'host'

It looks like the pre-py3 super() call is causing problems here. I tried to fix that in cd004d4 but it seems it is a recurring problem in the code and, besides, it's hardly an intuitive way to make a change.

Shouldn't the default here be safer? Maybe have a prompt like SSH does instead of just gobbling up any SSH key change? I understand if we would want to do TOFU and just trust unknown fingerprints, but from what I can tell, Fabric, by default, will trust any SSH server fingerprint whatsoever.

I find this terrifying.

@anarcat
Copy link
Author

anarcat commented Mar 18, 2020

i found that adding this to my fabfile.py will at least make it "safe" insofar as it will just crash if Fabric isn't properly patched to support it:

# monkeypatch the default fabric connexion to workaround
# https://github.com/fabric/fabric/issues/2071
logging.warning('Fabric Connection monkeypatched, will crash without a patch')
fabric.Connection = SaferConnection
fabric.connection.Connection = SaferConnection

@anarcat
Copy link
Author

anarcat commented Mar 18, 2020

i ended up doing this in the short term:

# hack to enforce a different key policy: https://github.com/fabric/fabric/issues/2071
def safe_open(self):
    self.client.set_missing_host_key_policy(RejectPolicy())
    Connection.open_orig(self)


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

in the long term, i would much prefer if the policy was set to Warning or at least customizable somewhat.

@anarcat anarcat changed the title default paramiko host key policy is insecure default SSH host key policy is insecure Mar 18, 2020
@RedKrieg
Copy link
Contributor

After some digging, I think this happens because when the connection is made, nothing ever calls paramiko's client.load_system_host_keys() function, but by using AutoAddPolicy() we are relying on those keys to be loaded by default for security. I would add a --no-known-hosts flag to fabric to conditionally skip calling client.load_system_host_keys() for each Connection() instead of changing to RejectPolicy() by default.

anarcat added a commit to anarcat/fabric that referenced this issue Mar 18, 2020
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
anarcat added a commit to anarcat/fabric that referenced this issue Mar 18, 2020
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.
@anarcat
Copy link
Author

anarcat commented Mar 18, 2020

indeed, we are not loading any host keys at all, which makes AutoAddPolicy() fail open (which is pretty bad for Paramiko to do, if you ask me), but that's a different question.

i started working on this problem in #2072, maybe we can followup there. my approach is to:

  1. make the policy customizable
  2. load the (proper) known_hosts file

that should be sufficient for my use case.

anarcat added a commit to anarcat/fabric that referenced this issue 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 issue 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 issue Mar 18, 2020
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
anarcat added a commit to anarcat/fabric that referenced this issue Mar 18, 2020
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.
anarcat added a commit to anarcat/fabric that referenced this issue 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 issue 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 issue 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 issue Mar 19, 2020
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
anarcat added a commit to anarcat/fabric that referenced this issue Mar 19, 2020
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.
anarcat added a commit to anarcat/fabric that referenced this issue 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 issue Mar 19, 2020
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
anarcat added a commit to anarcat/fabric that referenced this issue Mar 19, 2020
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.
anarcat added a commit to anarcat/fabric that referenced this issue 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 anarcat linked a pull request Mar 19, 2020 that will close this issue
@harshitgupta1337
Copy link

Has there been any updates on this issue? Any plans for merging it?

@anarcat
Copy link
Author

anarcat commented Sep 6, 2023

this is still a problem, latest is in #2076

anarcat added a commit to anarcat/fabric that referenced this issue Sep 6, 2023
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
anarcat added a commit to anarcat/fabric that referenced this issue Sep 6, 2023
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.
anarcat added a commit to anarcat/fabric that referenced this issue 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 issue Sep 6, 2023
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.
anarcat added a commit to anarcat/fabric that referenced this issue 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
Copy link
Author

anarcat commented Sep 6, 2023

@bitprophet i'm sorry to ping you about this one, but it seems to me this is a capital one to fix. At the very least make the default policy warning! i made a more minimal change in #2280 to change the policy to RejectPolicy by default, hopefully that will be easier to merge?

at least we should be warning here... but I think WarningPolicy is too light...

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 a pull request may close this issue.

3 participants