Skip to content

Commit 5e8ae4c

Browse files
committed
Manually merge PR108 to handle missing bits of ssh key generation on py3. It is needed for cloud and jupyter integration at least. Added unit tests for it. Rework dependency handling to include the paramiko installlation needed in shared.ssh and therefore now also for the unit test of it. That requires a more recent pip and careful version selection on certain legacy platforms.
git-svn-id: svn+ssh://svn.code.sf.net/p/migrid/code/trunk@6114 b75ad72c-e7d7-11dd-a971-7dbc132099af
1 parent 56540d5 commit 5e8ae4c

File tree

6 files changed

+110
-15
lines changed

6 files changed

+110
-15
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ else
6464
./envhelp/py3.depends: $(REQS_PATH)
6565
endif
6666
@rm -f ./envhelp/py3.depends
67+
@echo "upgrading venv pip as required for some dependencies"
68+
@./envhelp/venv/bin/pip3 install --upgrade pip
6769
@echo "installing dependencies from $(REQS_PATH)"
6870
@./envhelp/venv/bin/pip3 install -r $(REQS_PATH)
6971
ifeq ($(MIG_ENV),'local')

envhelp/docker/Dockerfile.python2

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ FROM python:2
22

33
WORKDIR /usr/src/app
44

5-
COPY requirements.txt ./
6-
RUN pip install --no-cache-dir -r requirements.txt
5+
COPY requirements.txt local-requirements.txt ./
6+
RUN pip install --no-cache-dir -r requirements.txt -r local-requirements.txt
77

88
CMD [ "python", "--version" ]

local-requirements.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
# migrid dependencies on a format suitable for pip install as described on
22
# https://pip.pypa.io/en/stable/reference/requirement-specifiers/
3-
autopep8
3+
# This list is mainly used to specify addons needed for the unit tests.
4+
# We only need autopep8 on py 3 as it's used in 'make fmt' (with py3)
5+
autopep8;python_version >= "3"
6+
# We need paramiko for the ssh unit tests
7+
# NOTE: paramiko-3.0.0 dropped python2 and python3.6 support
8+
paramiko;python_version >= "3.7"
9+
paramiko<3;python_version < "3.7"

mig/shared/compat.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,13 @@
3434
from past.builtins import basestring
3535

3636
import codecs
37+
import io
3738
import sys
39+
# NOTE: StringIO is only available in python2
40+
try:
41+
import StringIO
42+
except ImportError:
43+
StringIO = None
3844

3945
PY2 = sys.version_info[0] < 3
4046
_TYPE_UNICODE = type(u"")
@@ -72,3 +78,15 @@ def ensure_native_string(string_or_bytes):
7278
else:
7379
textual_output = string_or_bytes
7480
return textual_output
81+
82+
83+
def NativeStringIO(initial_value=''):
84+
"""Mock StringIO pseudo-class to create a StringIO matching the native
85+
string coding form. That is a BytesIO with utf8 on python 2 and unicode
86+
StringIO otherwise. Optional string helpers are automatically converted
87+
accordingly.
88+
"""
89+
if PY2 and StringIO is not None:
90+
return StringIO.StringIO(initial_value)
91+
else:
92+
return io.StringIO(initial_value)

mig/shared/ssh.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# --- BEGIN_HEADER ---
55
#
66
# ssh - remote command wrappers using ssh/scp
7-
# Copyright (C) 2003-2023 The MiG Project lead by Brian Vinter
7+
# Copyright (C) 2003-2024 The MiG Project lead by Brian Vinter
88
#
99
# This file is part of MiG.
1010
#
@@ -35,14 +35,6 @@
3535
import sys
3636
import tempfile
3737

38-
# NOTE: we would prefer the modern io.BytesIO but it fails in python2 during
39-
# rsa_key.write_private_key(string_io_obj) with a Paramiko error:
40-
# TypeError: 'unicode' does not have the buffer interface
41-
try:
42-
from cStringIO import StringIO as LegacyStringIO
43-
except ImportError:
44-
from io import BytesIO as LegacyStringIO
45-
4638
try:
4739
import paramiko
4840
except ImportError:
@@ -51,6 +43,7 @@
5143

5244
from mig.shared.base import client_id_dir, force_utf8
5345
from mig.shared.conf import get_resource_exe, get_configuration_object
46+
from mig.shared.compat import NativeStringIO
5447
from mig.shared.defaults import ssh_conf_dir
5548
from mig.shared.safeeval import subprocess_popen, subprocess_pipe
5649

@@ -95,8 +88,8 @@ def parse_pub_key(public_key):
9588
msg = 'Invalid ssh public key: (%s)' % public_key
9689
raise ValueError(msg)
9790

98-
head, tail = public_key.split(' ')[ssh_type_idx:2+ssh_type_idx]
99-
bits = base64.decodestring(tail)
91+
head, tail = public_key.split(' ')[ssh_type_idx:2 + ssh_type_idx]
92+
bits = base64.b64decode(tail)
10093
msg = paramiko.Message(bits)
10194
parse_key = type_map[head]
10295
return parse_key(msg)
@@ -511,7 +504,12 @@ def generate_ssh_rsa_key_pair(size=2048, public_key_prefix='',
511504
raise Exception("You need paramiko to provide the ssh/sftp service")
512505
rsa_key = paramiko.RSAKey.generate(size)
513506

514-
string_io_obj = LegacyStringIO()
507+
# NOTE: we would prefer the modern io.BytesIO on python2 but it fails
508+
# during rsa_key.write_private_key(string_io_obj) with a Paramiko
509+
# TypeError: 'unicode' does not have the buffer interface.
510+
# Use compat NativeStringIO to pick a suitable StringIO for the
511+
# active platform.
512+
string_io_obj = NativeStringIO()
515513
rsa_key.write_private_key(string_io_obj)
516514

517515
private_key = string_io_obj.getvalue()

tests/test_mig_shared_ssh.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# --- BEGIN_HEADER ---
4+
#
5+
# test_mig_shared_ssh - unit test of the corresponding mig shared module
6+
# Copyright (C) 2003-2024 The MiG Project by the Science HPC Center at UCPH
7+
#
8+
# This file is part of MiG.
9+
#
10+
# MiG is free software: you can redistribute it and/or modify
11+
# it under the terms of the GNU General Public License as published by
12+
# the Free Software Foundation; either version 2 of the License, or
13+
# (at your option) any later version.
14+
#
15+
# MiG is distributed in the hope that it will be useful,
16+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
# GNU General Public License for more details.
19+
#
20+
# You should have received a copy of the GNU General Public License
21+
# along with this program; if not, write to the Free Software
22+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
23+
# USA.
24+
#
25+
# --- END_HEADER ---
26+
#
27+
28+
"""Unit tests for the migrid module pointed to in the filename"""
29+
30+
import os
31+
import sys
32+
33+
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__))))
34+
35+
from tests.support import TEST_OUTPUT_DIR, MigTestCase, FakeConfiguration, \
36+
cleanpath, testmain
37+
from mig.shared.ssh import supported_pub_key_parsers, parse_pub_key, \
38+
generate_ssh_rsa_key_pair
39+
40+
41+
class MigSharedSsh(MigTestCase):
42+
"""Wrap unit tests for the corresponding module"""
43+
44+
def test_ssh_key_generate_and_parse(self):
45+
parsers = supported_pub_key_parsers()
46+
# NOTE: should return a non-empty dict of algos and parsers
47+
self.assertTrue(parsers)
48+
self.assertTrue('ssh-rsa' in parsers)
49+
50+
# Generate common sized keys and parse the result
51+
for keysize in (2048, 3072, 4096):
52+
(priv_key, pub_key) = generate_ssh_rsa_key_pair(size=keysize)
53+
self.assertTrue(priv_key)
54+
self.assertTrue(pub_key)
55+
56+
# NOTE: parse_pub_key expects a native string so we use this case
57+
try:
58+
parsed = parse_pub_key(pub_key)
59+
except ValueError as vae:
60+
#print("Error in parsing pub key: %r" % vae)
61+
parsed = None
62+
self.assertTrue(parsed is not None)
63+
64+
(priv_key, pub_key) = generate_ssh_rsa_key_pair(size=keysize,
65+
encode_utf8=True)
66+
self.assertTrue(priv_key)
67+
self.assertTrue(pub_key)
68+
69+
70+
if __name__ == '__main__':
71+
testmain()

0 commit comments

Comments
 (0)