[X2Go-Commits] [x2gobroker] 02/02: Make remote agent's SSH HostKey policy configurable globally, backend-wise and per session profile. Fallback to RejectPolicy by default. (See Debian bug #922314).

git-admin at x2go.org git-admin at x2go.org
Sun Apr 21 15:44:04 CEST 2019


This is an automated email from the git hooks/post-receive script.

x2go pushed a commit to branch master
in repository x2gobroker.

commit 0b51f522ebd1d8747a4f401f6cf274bfcb9ddccd
Author: Mike Gabriel <mike.gabriel at das-netzwerkteam.de>
Date:   Sun Apr 21 15:21:23 2019 +0200

    Make remote agent's SSH HostKey policy configurable globally, backend-wise and per session profile. Fallback to RejectPolicy by default. (See Debian bug #922314).
---
 etc/x2gobroker.conf                   | 31 ++++++++++++++++++++++++
 x2gobroker/agent.py                   |  9 ++++++-
 x2gobroker/brokers/base_broker.py     | 42 ++++++++++++++++++++++++++++++++-
 x2gobroker/defaults.py                |  1 +
 x2gobroker/tests/test_broker_agent.py | 44 +++++++++++++++++++++++++++++++----
 5 files changed, 121 insertions(+), 6 deletions(-)

diff --git a/etc/x2gobroker.conf b/etc/x2gobroker.conf
index 44e86a4..d88faa6 100644
--- a/etc/x2gobroker.conf
+++ b/etc/x2gobroker.conf
@@ -242,6 +242,37 @@
 # below value is the default.
 #default-agent-query-mode=NONE
 
+# X2Go Broker's Host Key Policy (if agent query mode is 'SSH')
+#
+# If X2Go Broker's agent query mode is SSH, the system needs to handle
+# X2Go Server side's SSH host keys in a secure and verifyable manner.
+#
+# The agent-hostkey-policy is the default policy to be used and can be
+# either AutoAddPolicy, WarningPolicy, or RejectPolicy. The policy names
+# match the corresponding class names in Paramiko SSH.
+#
+# IMPORTANT: As RejectPolicy is the only safe default, please be aware that
+# on fresh X2Go Broker setups, SSH agent queries will always fail, until a
+# properly maintained ~x2gobroker/.ssh/known_hosts file is in place.
+#
+# There are two simple ways to create this known_hosts file:
+#
+# (a) su - x2gobroker -c "ssh <x2goserver>"
+#
+#     On the command line, you get prompted to confirm the remote
+#     X2Go server's  Follow OpenSSH interactive dialog for accepting
+#     the remote host's host key.
+#
+# (b) x2gobroker-testagent --add-to-known-hosts --host <x2goserver>
+#
+#     This command will populate the known_hosts file with the remote
+#     X2Go server's hostkey while trying to hail its X2Go Broker Agent
+#     The host key's fingerprint will be shown on stdout, but there will
+#     be no interactive confirmation. If unsure about this, use approach
+#     (a) given above.
+#
+#default-agent-hostkey-policy=RejectPolicy
+
 # Probe SSH port of X2Go Servers (availability check)
 #
 # Just before offering an X2Go Server address to a broker client, the
diff --git a/x2gobroker/agent.py b/x2gobroker/agent.py
index b267277..3530323 100644
--- a/x2gobroker/agent.py
+++ b/x2gobroker/agent.py
@@ -195,8 +195,15 @@ def _call_remote_broker_agent(username, task, cmdline_args=[], remote_agent=None
 
     if remote_agent is None:
         logger_error.error('With the SSH agent-query-mode a remote agent host (hostname, hostaddr, port) has to be specified!')
-    elif 'host_key_policy' not in remote_agent:
+    elif 'host_key_policy' not in remote_agent or remote_agent['host_key_policy'] == 'WarningPolicy':
         remote_agent['host_key_policy'] = paramiko.WarningPolicy()
+    elif remote_agent['host_key_policy'] == 'RejectPolicy':
+        remote_agent['host_key_policy'] = paramiko.RejectPolicy()
+    elif remote_agent['host_key_policy'] == 'AutoAddPolicy':
+        remote_agent['host_key_policy'] = paramiko.AutoAddPolicy()
+    else:
+        logger_error.error('Invalid SSH HostKey Policy: "{policy}", falling back to "RejectPolicy"!'.format(policy=remote_agent['host_key_policy']))
+        remote_agent['host_key_policy'] = paramiko.RejectPolicy()
 
     remote_hostaddr = None
     remote_hostname = None
diff --git a/x2gobroker/brokers/base_broker.py b/x2gobroker/brokers/base_broker.py
index 5b4303e..3de1426 100644
--- a/x2gobroker/brokers/base_broker.py
+++ b/x2gobroker/brokers/base_broker.py
@@ -562,6 +562,44 @@ class X2GoBroker(object):
         else:
             return _mode
 
+    def get_agent_hostkey_policy(self, profile_id):
+        """\
+        Get the agent hostkey policy (either of 'RejectPolicy',
+        'AutoAddPolicy' or 'WarningPolicy') that is configured for this
+        X2Go Session Broker instance.
+
+        The returned policy names match the MissingHostkeyPolicy class
+        names as found in Python Paramiko.
+
+        :returns: agent hostkey policy
+        :rtype: ``str``
+
+        """
+        _default_agent_hostkey_policy = "RejectPolicy"
+        _backend_agent_hostkey_policy = ""
+        _agent_hostkey_policy = ""
+
+        _profile = self.get_profile_broker(profile_id)
+        if _profile and 'broker-agent-hostkey-policy' in _profile and _profile['broker-agent-hostkey-policy']:
+            _agent_hostkey_policy = _profile['broker-agent-hostkey-policy']
+            logger_broker.debug('base_broker.X2GoBroker.get_agent_hostkey_policy(): found broker-agent-hostkey-policy in session profile with ID {id}: {value}. This one has precendence over the default and the backend value.'.format(id=profile_id, value=_agent_hostkey_policy))
+
+        elif self.config.has_value('broker_{backend}'.format(backend=self.backend_name), 'agent-hostkey-policy') and self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'agent-hostkey-policy'):
+            _backend_agent_hostkey_policy = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'agent-hostkey-policy')
+            logger_broker.debug('base_broker.X2GoBroker.get_agent_hostkey_policy(): found agent-hostkey-policy in backend config section »{backend}«: {value}. This one has precendence over the default value.'.format(backend=self.backend_name, value=_agent_hostkey_policy))
+
+        elif self.config.has_value('global', 'default-agent-hostkey-policy') and self.config.get_value('global', 'default-agent-hostkey-policy'):
+            _default_agent_hostkey_policy = self.config.get_value('global', 'default-agent-hostkey-policy')
+            logger_broker.debug('base_broker.X2GoBroker.get_agent_hostkey_policy(): found default-agent-hostkey-policy in global config section: {value}'.format(value=_default_agent_hostkey_policy))
+
+        _policy = _agent_hostkey_policy or _backend_agent_hostkey_policy or _default_agent_hostkey_policy
+
+        if _policy not in ('AutoAddPolicy', 'RejectPolicy', 'WarningPolicy'):
+            logger_broker.warn('base_broker.X2GoBroker.get_agent_hostkey_policy(): given hostkey policy ({policy}) is invalid/unknown, falling back to default hostkey policy ({default_policy}).'.format(policy=_policy, default_policy=_default_agent_hostkey_policy))
+            _policy = _default_agent_hostkey_policy
+
+        return _policy
+
     def get_session_autologin(self, profile_id):
         """\
         Detect if the given profile is configured to try automatic session
@@ -1096,7 +1134,9 @@ class X2GoBroker(object):
                 remote_agent = {
                     'hostname': remote_agent_hostname,
                     'hostaddr': remote_agent_hostaddr,
-                    'port': remote_agent_port, }
+                    'port': remote_agent_port,
+                    'host_key_policy': self.get_agent_hostkey_policy(profile_id),
+                }
 
                 try:
                     if x2gobroker.agent.ping(remote_agent=remote_agent):
diff --git a/x2gobroker/defaults.py b/x2gobroker/defaults.py
index e10ccb0..8989688 100644
--- a/x2gobroker/defaults.py
+++ b/x2gobroker/defaults.py
@@ -243,6 +243,7 @@ X2GOBROKER_CONFIG_DEFAULTS = {
         'default-authorized-keys': '%h/.x2go/authorized_keys',
         'default-sshproxy-authorized-keys': '%h/.x2go/authorized_keys',
         'default-agent-query-mode': 'NONE',
+        'default-agent-hostkey-policy': 'RejectPolicy',
         'default-portscan-x2goservers': True,
         'default-use-load-checker': False,
         'load-checker-intervals': 300,
diff --git a/x2gobroker/tests/test_broker_agent.py b/x2gobroker/tests/test_broker_agent.py
index 8d9e489..a7e4ee8 100644
--- a/x2gobroker/tests/test_broker_agent.py
+++ b/x2gobroker/tests/test_broker_agent.py
@@ -110,23 +110,39 @@ host = host1.mydomain, host2.yourdomain
 name = testprofile5
 host = host1.mydomain (10.0.2.4), host2.mydomain (10.0.2.5)
 broker-agent-query-mode = SSH
+broker-agent-hostkey-policy = WarningPolicy
 
 [testprofile6]
 name = testprofile6
 host = host1.mydomain (10.0.2.4), host2.mydomain (10.0.2.5)
 sshport = 23467
 broker-agent-query-mode = SSH
+broker-agent-hostkey-policy = WarningPolicy
 
 [testprofile7]
 name = testprofile7
 host = docker-vm-1 (docker-server:22001), docker-vm-2 (docker-server:22002)
 broker-agent-query-mode = SSH
+broker-agent-hostkey-policy = WarningPolicy
 
 [testprofile8]
 name = testprofile8
 host = docker-vm-0 (docker-server), docker-vm-1 (docker-server:22001), docker-vm-2 (docker-server:22002)
 sshport = 22000
 broker-agent-query-mode = SSH
+broker-agent-hostkey-policy = WarningPolicy
+
+[testprofile9]
+name = testprofile9
+host = host1.mydomain (10.0.2.4)
+broker-agent-query-mode = SSH
+broker-agent-hostkey-policy = AutoAddPolicy
+
+[testprofile10]
+name = testprofile10
+host = host1.mydomain (10.0.2.4)
+broker-agent-query-mode = SSH
+broker-agent-hostkey-policy = SomeUnkownPolicy
 
 """
         tf = tempfile.NamedTemporaryFile(mode='w')
@@ -207,7 +223,7 @@ broker-agent-query-mode = SSH
         i = 0
         while i < 10:
             _remoteagent5 = inifile_backend.get_remote_agent('testprofile5')
-            self.assertTrue( _remoteagent5 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 22, 'load_factors': {}, } or _remoteagent5 == {'hostname': 'host2.mydomain', 'hostaddr': '10.0.2.5', 'port': 22, 'load_factors': {}, } )
+            self.assertTrue( _remoteagent5 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 22, 'load_factors': {}, 'host_key_policy': 'WarningPolicy'} or _remoteagent5 == {'hostname': 'host2.mydomain', 'hostaddr': '10.0.2.5', 'port': 22, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } )
             _session5 = inifile_backend.select_session('testprofile5', 'foo5N')
             self.assertTrue( _session5 == {'port': 22, 'server': '10.0.2.4', } or _session5 == {'port': 22, 'server': '10.0.2.5', } )
             i += 1
@@ -221,7 +237,7 @@ broker-agent-query-mode = SSH
         self.assertTrue( _profile6['host'][0] in ('host1.mydomain', 'host2.mydomain') )
         self.assertTrue( 'status' not in _profile6 )
         _remoteagent6 = inifile_backend.get_remote_agent('testprofile6')
-        self.assertTrue( _remoteagent6 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 23467, 'load_factors': {}, } or _remoteagent6 == {'hostname': 'host2.mydomain', 'hostaddr': '10.0.2.5', 'port': 23467, 'load_factors': {}, } )
+        self.assertTrue( _remoteagent6 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 23467, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } or _remoteagent6 == {'hostname': 'host2.mydomain', 'hostaddr': '10.0.2.5', 'port': 23467, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } )
         _session6 = inifile_backend.select_session('testprofile6', 'foo6N')
         self.assertTrue( _session6 == {'port': 23467, 'server': '10.0.2.4', } or _session6 == {'port': 23467, 'server': '10.0.2.5', } )
 
@@ -233,7 +249,7 @@ broker-agent-query-mode = SSH
         i = 0
         while i < 10:
             _remoteagent7 = inifile_backend.get_remote_agent('testprofile7')
-            self.assertTrue( _remoteagent7 == {'hostname': 'docker-vm-1', 'hostaddr': 'docker-server', 'port': 22001, 'load_factors': {}, } or _remoteagent7 == {'hostname': 'docker-vm-2', 'hostaddr': 'docker-server', 'port': 22002, 'load_factors': {}, } )
+            self.assertTrue( _remoteagent7 == {'hostname': 'docker-vm-1', 'hostaddr': 'docker-server', 'port': 22001, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } or _remoteagent7 == {'hostname': 'docker-vm-2', 'hostaddr': 'docker-server', 'port': 22002, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } )
             _session7 = inifile_backend.select_session('testprofile7', 'foo7N')
             self.assertTrue( _session7 == {'port': 22001, 'server': 'docker-server', } or _session7 == {'port': 22001, 'server': 'docker-server', } )
             i += 1
@@ -246,11 +262,31 @@ broker-agent-query-mode = SSH
         i = 0
         while i < 10:
             _remoteagent8 = inifile_backend.get_remote_agent('testprofile8')
-            self.assertTrue( _remoteagent8 == {'hostname': 'docker-vm-0', 'hostaddr': 'docker-server', 'port': 22000, 'load_factors': {}, } or _remoteagent8 == {'hostname': 'docker-vm-1', 'hostaddr': 'docker-server', 'port': 22001, 'load_factors': {}, } or _remoteagent8 == {'hostname': 'docker-vm-2', 'hostaddr': 'docker-server', 'port': 22002, 'load_factors': {}, } )
+            self.assertTrue( _remoteagent8 == {'hostname': 'docker-vm-0', 'hostaddr': 'docker-server', 'port': 22000, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } or _remoteagent8 == {'hostname': 'docker-vm-1', 'hostaddr': 'docker-server', 'port': 22001, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } or _remoteagent8 == {'hostname': 'docker-vm-2', 'hostaddr': 'docker-server', 'port': 22002, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } )
             _session8 = inifile_backend.select_session('testprofile8', 'foo8N')
             self.assertTrue( _session8 == {'port': 22000, 'server': 'docker-server', } or _session8 == {'port': 22001, 'server': 'docker-server', } or _session8 == {'port': 22001, 'server': 'docker-server', } )
             i += 1
 
+        # test "testprofile9", test if hostkey policy is propagated from session profile config to remote agent settings
+
+        _list9 = inifile_backend.list_profiles(username='foo9N')
+        _profile9 = _list9['testprofile9']
+        _profile9['host'].sort()
+        self.assertTrue( _profile9['host'][0] in ('host1.mydomain') )
+        self.assertTrue( 'status' not in _profile9 )
+        _remoteagent9 = inifile_backend.get_remote_agent('testprofile9')
+        self.assertTrue( _remoteagent9 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 22, 'load_factors': {}, 'host_key_policy': 'AutoAddPolicy'})
+
+        # test "testprofile10", test if an invalid hostkey policy is propagated from session profile config to remote agent settings and ignored with RejectPolicy as fallback
+
+        _list10 = inifile_backend.list_profiles(username='foo10N')
+        _profile10 = _list10['testprofile10']
+        _profile10['host'].sort()
+        self.assertTrue( _profile10['host'][0] in ('host1.mydomain') )
+        self.assertTrue( 'status' not in _profile10 )
+        _remoteagent10 = inifile_backend.get_remote_agent('testprofile10')
+        self.assertTrue( _remoteagent10 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 22, 'load_factors': {}, 'host_key_policy': 'RejectPolicy'})
+
         x2gobroker.agent._call_local_broker_agent = _save_local_broker_agent_call
         x2gobroker.agent._call_remote_broker_agent = _save_remote_broker_agent_call
         x2gobroker.utils.portscan = _save_portscan

--
Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/x2gobroker.git


More information about the x2go-commits mailing list