Skip to content

Commit 09ffe9e

Browse files
authored
Merge pull request #503 from NASA-PDS/fix/upgrade-fabric-python313-502
Upgrade fabric to v3.0 for Python 3.13 compatibility
2 parents 0042e1a + d482a52 commit 09ffe9e

3 files changed

Lines changed: 69 additions & 2 deletions

File tree

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ install_requires =
3232
connexion[swagger-ui]~=2.7.0
3333
dataclasses==0.7; python_version <= '3.6'
3434
distlib~=0.3.7
35-
fabric~=2.7.1
35+
fabric~=3.0
3636
filelock~=3.12.3
3737
Flask~=2.2.2
3838
flask-cors==3.0.9

src/pds_doi_service/core/actions/roundup/sftp.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import tempfile
55

66
import fabric.transfer # type: ignore
7+
import paramiko # type: ignore
78
from fabric import Connection # type: ignore
89
from paramiko.sftp import SFTPError # type: ignore
910
from pds_doi_service.core.actions.roundup.enumerate import get_previous_week_metadata
@@ -12,6 +13,28 @@
1213
from pds_doi_service.core.util.config_parser import DOIConfigUtil
1314

1415

16+
class FIPSCompliantAutoAddPolicy(paramiko.MissingHostKeyPolicy):
17+
"""
18+
FIPS-compliant host key policy that accepts unknown hosts without computing MD5 fingerprints.
19+
20+
This policy is similar to AutoAddPolicy but avoids calling get_fingerprint() which uses MD5
21+
and fails in FIPS mode. It's appropriate for internal SFTP servers where host key verification
22+
is not critical.
23+
"""
24+
25+
def missing_host_key(self, client, hostname, key):
26+
"""
27+
Accept the host key without computing fingerprints.
28+
29+
Args:
30+
client: SSHClient instance
31+
hostname: The hostname of the server
32+
key: The server's host key
33+
"""
34+
# Add the key without logging the fingerprint (which would use MD5)
35+
client._host_keys.add(hostname, key.get_name(), key)
36+
37+
1538
def ensure_target_dir(dir_path: str, conn: Connection):
1639
transfer = fabric.transfer.Transfer(connection=conn)
1740
sftp = transfer.sftp
@@ -53,7 +76,20 @@ def run(
5376
dest_filename = f'roundup-week-ending-{metadata.last_date.strftime("%Y%m%d")}.json'
5477
dest_path = os.path.join(dest_dir_path, dest_filename)
5578

56-
conn = Connection(host=sftp_host, port=sftp_port, user=sftp_user, connect_kwargs={"password": sftp_password})
79+
# Configure connection with FIPS-compliant host key policy
80+
# This automatically accepts unknown host keys without computing MD5 fingerprints
81+
conn = Connection(
82+
host=sftp_host,
83+
port=sftp_port,
84+
user=sftp_user,
85+
connect_kwargs={
86+
"password": sftp_password,
87+
"look_for_keys": False, # Disable SSH key auth to avoid MD5 fingerprint in FIPS mode
88+
"allow_agent": False, # Disable SSH agent to avoid MD5 fingerprint in FIPS mode
89+
},
90+
)
91+
# Set FIPS-compliant host key policy that doesn't use MD5
92+
conn.client.set_missing_host_key_policy(FIPSCompliantAutoAddPolicy())
5793
transfer = fabric.transfer.Transfer(connection=conn)
5894

5995
ensure_target_dir(dest_dir_path, conn)

src/pds_doi_service/core/actions/roundup/test/sftp_test.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,37 @@
88
from pds_doi_service.core.actions.roundup.test.base import WeeklyRoundupNotificationBaseTestCase
99

1010

11+
class SftpImportTestCase(unittest.TestCase):
12+
"""Test that SFTP dependencies (fabric, invoke) can be imported successfully.
13+
14+
This test ensures that all required dependencies for SFTP functionality are
15+
properly installed and available, catching issues like missing transitive
16+
dependencies (e.g., decorator, lexicon) that may occur with certain Python versions.
17+
"""
18+
19+
def test_fabric_imports(self):
20+
"""Test that fabric and its dependencies can be imported."""
21+
try:
22+
import fabric.transfer # noqa: F401
23+
from fabric import Connection # noqa: F401
24+
except ImportError as e:
25+
self.fail(f"Failed to import fabric dependencies: {e}")
26+
27+
def test_paramiko_imports(self):
28+
"""Test that paramiko SFTP components can be imported."""
29+
try:
30+
from paramiko.sftp import SFTPError # noqa: F401
31+
except ImportError as e:
32+
self.fail(f"Failed to import paramiko SFTP components: {e}")
33+
34+
def test_sftp_module_imports(self):
35+
"""Test that the SFTP module itself can be imported without errors."""
36+
try:
37+
from pds_doi_service.core.actions.roundup import sftp # noqa: F401
38+
except ImportError as e:
39+
self.fail(f"Failed to import SFTP module: {e}")
40+
41+
1142
class WeeklyRoundupAdsSftpNotificationTestCase(WeeklyRoundupNotificationBaseTestCase):
1243
_temp_file_path: str
1344

0 commit comments

Comments
 (0)