diff --git a/src/pyinfra/operations/server.py b/src/pyinfra/operations/server.py index da6a34d68..69f1309c0 100644 --- a/src/pyinfra/operations/server.py +++ b/src/pyinfra/operations/server.py @@ -8,7 +8,6 @@ import os from io import StringIO from itertools import filterfalse, tee -from pathlib import Path from time import sleep from typing import TYPE_CHECKING @@ -949,7 +948,9 @@ def read_any_pub_key_file(key): if state.cwd: try_path = os.path.join(state.cwd, key) - if Path(try_path).exists(): + # NOTE: os.path.exists swallows OSError (eg ENAMETOOLONG from long inline + # keys), whereas Path.exists re-raises it on Python < 3.14. + if os.path.exists(try_path): with open(try_path) as f: return [key.strip() for key in f.readlines()] diff --git a/tests/conftest.py b/tests/conftest.py index 7456b1c2d..808fb2e1c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +import errno +import os from pathlib import Path from unittest.mock import patch @@ -6,16 +8,34 @@ from pyinfra_testing.util import patch_files as _patch_files +def _check_name_too_long(filename): + # Real filesystems reject path components over NAME_MAX (255 bytes) with + # ENAMETOOLONG, eg when an inline SSH key is tried as a file path. + for component in str(filename).split(os.sep): + if len(os.fsencode(component)) > 255: + raise OSError(errno.ENAMETOOLONG, os.strerror(errno.ENAMETOOLONG), str(filename)) + + class patch_files(_patch_files): """ Extend ``pyinfra_testing.util.patch_files`` to also patch ``pathlib.Path`` methods, now that pyinfra itself uses pathlib for local filesystem access. """ + def exists(self, filename, *args): + # os.path.exists swallows OSError (eg ENAMETOOLONG) and returns False. + try: + _check_name_too_long(filename) + except OSError: + return False + return super().exists(filename, *args) + def __enter__(self): patch_self = self def _path_exists(p): + # Path.exists re-raises ENAMETOOLONG on Python < 3.14. + _check_name_too_long(p) return patch_self.exists(str(p)) def _path_is_file(p): diff --git a/tests/operations/server.user_authorized_keys/long_inline_key_not_a_file.yaml b/tests/operations/server.user_authorized_keys/long_inline_key_not_a_file.yaml new file mode 100644 index 000000000..d40c8083f --- /dev/null +++ b/tests/operations/server.user_authorized_keys/long_inline_key_not_a_file.yaml @@ -0,0 +1,27 @@ +# Long inline public keys must not be treated as files: on Python < 3.14 +# Path.exists() raises OSError (ENAMETOOLONG) for such values, see +# https://github.com/pyinfra-dev/pyinfra/issues/1907 +args: + - someuser +kwargs: + public_keys: + - ssh-rsa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA bob +facts: + server.Home: + user=someuser: /home/someuser + files.Directory: + path=/home/someuser/.ssh: + user: someuser + group: someuser + mode: 700 + files.File: + path=/home/someuser/.ssh/authorized_keys: + user: someuser + group: someuser + mode: 600 + server.AuthorizedKeys: + path=/home/someuser/.ssh/authorized_keys, user=someuser: [] + files.FindInFile: + extended_regex=False, interpolate_variables=False, path=/home/someuser/.ssh/authorized_keys, pattern=^.*ssh-rsa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA bob.*$: [] +commands: + - "( [ $(tail -c1 /home/someuser/.ssh/authorized_keys | wc -l) -eq 0 ] && echo ; echo 'ssh-rsa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA bob' ) >> /home/someuser/.ssh/authorized_keys"