From f5eaac9f751ee5d6026ee2bea2677134ae10ee4d Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 13 Jan 2026 21:56:28 +1100 Subject: [PATCH 1/7] Support `--remote-ssh-config [path-to-ssh-config]` as an argument in case extra params are required beyond `--remote-port` or `--remote-user`. Note: `--remote-host` must still be set, but it can be an 'alias' represented by the 'Host' value in the ssh config. --- CHANGELOG.md | 4 ++++ debian/changelog | 6 +++++ enroll/cli.py | 48 ++++++++++++++++++++++++++++++++------- enroll/remote.py | 59 ++++++++++++++++++++++++++++++++++++++++++++---- pyproject.toml | 2 +- rpm/enroll.spec | 5 +++- 6 files changed, 110 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29da559..0772cc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.4.2 + + * Support `--remote-ssh-config [path-to-ssh-config]` as an argument in case extra params are required beyond `--remote-port` or `--remote-user`. Note: `--remote-host` must still be set, but it can be an 'alias' represented by the 'Host' value in the ssh config. + # 0.4.1 * Add interactive output when 'enroll diff --enforce' is invoking Ansible. diff --git a/debian/changelog b/debian/changelog index 086b52e..58e80e3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +enroll (0.4.2) unstable; urgency=medium + + * Support `--remote-ssh-config [path-to-ssh-config]` as an argument in case extra params are required beyond `--remote-port` or `--remote-user`. Note: `--remote-host` must still be set, but it can be an 'alias' represented by the 'Host' value in the ssh config. + + -- Miguel Jacq Tue, 13 Jan 2026 21:55:00 +1100 + enroll (0.4.1) unstable; urgency=medium * Add interactive output when 'enroll diff --enforce' is invoking Ansible. diff --git a/enroll/cli.py b/enroll/cli.py index c1f0870..69e85ed 100644 --- a/enroll/cli.py +++ b/enroll/cli.py @@ -350,16 +350,33 @@ def _add_remote_args(p: argparse.ArgumentParser) -> None: "--remote-host", help="SSH host to run harvesting on (if set, harvest runs remotely and is pulled locally).", ) + p.add_argument( + "--remote-ssh-config", + nargs="?", + const=str(Path.home() / ".ssh" / "config"), + default=None, + help=( + "Use OpenSSH-style ssh_config settings for --remote-host. " + "If provided without a value, defaults to ~/.ssh/config. " + "(Applies HostName/User/Port/IdentityFile/ProxyCommand/HostKeyAlias when supported.)" + ), + ) p.add_argument( "--remote-port", type=int, - default=22, - help="SSH port for --remote-host (default: 22).", + default=None, + help=( + "SSH port for --remote-host. If omitted, defaults to 22, or a value from ssh_config when " + "--remote-ssh-config is set." + ), ) p.add_argument( "--remote-user", - default=os.environ.get("USER") or None, - help="SSH username for --remote-host (default: local $USER).", + default=None, + help=( + "SSH username for --remote-host. If omitted, defaults to local $USER, or a value from ssh_config when " + "--remote-ssh-config is set." + ), ) # Align terminology with Ansible: "become" == sudo. @@ -728,6 +745,17 @@ def main() -> None: ) args = ap.parse_args(argv) + # Preserve historical defaults for remote harvesting unless ssh_config lookup is enabled. + # This lets ssh_config values take effect when the user did not explicitly set + # --remote-user / --remote-port. + if hasattr(args, "remote_host"): + rsc = getattr(args, "remote_ssh_config", None) + if not rsc: + if getattr(args, "remote_port", None) is None: + setattr(args, "remote_port", 22) + if getattr(args, "remote_user", None) is None: + setattr(args, "remote_user", os.environ.get("USER") or None) + try: if args.cmd == "harvest": sops_fps = getattr(args, "sops", None) @@ -745,8 +773,9 @@ def main() -> None: ask_become_pass=args.ask_become_pass, local_out_dir=tmp_bundle, remote_host=args.remote_host, - remote_port=int(args.remote_port), + remote_port=args.remote_port, remote_user=args.remote_user, + remote_ssh_config=args.remote_ssh_config, dangerous=bool(args.dangerous), no_sudo=bool(args.no_sudo), include_paths=list(getattr(args, "include_path", []) or []), @@ -766,8 +795,9 @@ def main() -> None: ask_become_pass=args.ask_become_pass, local_out_dir=out_dir, remote_host=args.remote_host, - remote_port=int(args.remote_port), + remote_port=args.remote_port, remote_user=args.remote_user, + remote_ssh_config=args.remote_ssh_config, dangerous=bool(args.dangerous), no_sudo=bool(args.no_sudo), include_paths=list(getattr(args, "include_path", []) or []), @@ -968,8 +998,9 @@ def main() -> None: ask_become_pass=args.ask_become_pass, local_out_dir=tmp_bundle, remote_host=args.remote_host, - remote_port=int(args.remote_port), + remote_port=args.remote_port, remote_user=args.remote_user, + remote_ssh_config=args.remote_ssh_config, dangerous=bool(args.dangerous), no_sudo=bool(args.no_sudo), include_paths=list(getattr(args, "include_path", []) or []), @@ -998,8 +1029,9 @@ def main() -> None: ask_become_pass=args.ask_become_pass, local_out_dir=harvest_dir, remote_host=args.remote_host, - remote_port=int(args.remote_port), + remote_port=args.remote_port, remote_user=args.remote_user, + remote_ssh_config=args.remote_ssh_config, dangerous=bool(args.dangerous), no_sudo=bool(args.no_sudo), include_paths=list(getattr(args, "include_path", []) or []), diff --git a/enroll/remote.py b/enroll/remote.py index 93cee74..c7b54a6 100644 --- a/enroll/remote.py +++ b/enroll/remote.py @@ -330,8 +330,9 @@ def _remote_harvest( *, local_out_dir: Path, remote_host: str, - remote_port: int = 22, + remote_port: Optional[int] = None, remote_user: Optional[str] = None, + remote_ssh_config: Optional[str] = None, remote_python: str = "python3", dangerous: bool = False, no_sudo: bool = False, @@ -370,10 +371,60 @@ def _remote_harvest( # Users should add the key to known_hosts. ssh.set_missing_host_key_policy(paramiko.RejectPolicy()) + # Resolve SSH connection parameters. + connect_host = remote_host + connect_port = int(remote_port) if remote_port is not None else 22 + connect_user = remote_user + key_filename = None + sock = None + hostkey_name = connect_host + + if remote_ssh_config: + from paramiko.config import SSHConfig # type: ignore + from paramiko.proxy import ProxyCommand # type: ignore + import socket as _socket + + cfg_path = Path(str(remote_ssh_config)).expanduser() + if not cfg_path.exists(): + raise RuntimeError(f"SSH config file not found: {cfg_path}") + + cfg = SSHConfig() + with cfg_path.open("r", encoding="utf-8") as _fp: + cfg.parse(_fp) + hcfg = cfg.lookup(remote_host) + + connect_host = str(hcfg.get("hostname") or remote_host) + hostkey_name = str(hcfg.get("hostkeyalias") or connect_host) + + if remote_port is None and hcfg.get("port"): + try: + connect_port = int(str(hcfg.get("port"))) + except ValueError: + pass + if connect_user is None and hcfg.get("user"): + connect_user = str(hcfg.get("user")) + + ident = hcfg.get("identityfile") + if ident: + if isinstance(ident, (list, tuple)): + key_filename = [str(Path(p).expanduser()) for p in ident] + else: + key_filename = str(Path(str(ident)).expanduser()) + + proxycmd = hcfg.get("proxycommand") + if proxycmd: + sock = ProxyCommand(str(proxycmd)) + elif hostkey_name != connect_host: + # If HostKeyAlias is used, connect to HostName via a socket but + # use HostKeyAlias for known_hosts lookups. + sock = _socket.create_connection((connect_host, connect_port)) + ssh.connect( - hostname=remote_host, - port=int(remote_port), - username=remote_user, + hostname=hostkey_name if sock is not None else connect_host, + port=connect_port, + username=connect_user, + key_filename=key_filename, + sock=sock, allow_agent=True, look_for_keys=True, ) diff --git a/pyproject.toml b/pyproject.toml index 84b7887..92756d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "enroll" -version = "0.4.1" +version = "0.4.2" description = "Enroll a server's running state retrospectively into Ansible" authors = ["Miguel Jacq "] license = "GPL-3.0-or-later" diff --git a/rpm/enroll.spec b/rpm/enroll.spec index 30bac4e..98f3f8f 100644 --- a/rpm/enroll.spec +++ b/rpm/enroll.spec @@ -1,4 +1,4 @@ -%global upstream_version 0.4.1 +%global upstream_version 0.4.2 Name: enroll Version: %{upstream_version} @@ -43,6 +43,9 @@ Enroll a server's running state retrospectively into Ansible. %{_bindir}/enroll %changelog +* Tue Jan 13 2026 Miguel Jacq - %{version}-%{release} +- Support `--remote-ssh-config [path-to-ssh-config]` as an argument in case extra params are required beyond `--remote-port` or `--remote-user`. Note: `--remote-host` must still be s +et, but it can be an 'alias' represented by the 'Host' value in the ssh config. * Sun Jan 11 2026 Miguel Jacq - %{version}-%{release} - Add interactive output when 'enroll diff --enforce' is invoking Ansible. * Sat Jan 10 2026 Miguel Jacq - %{version}-%{release} From 478b0e1b9d8adf81bed7642e74857c62d6b9c315 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 13 Jan 2026 22:03:58 +1100 Subject: [PATCH 2/7] Add README example for --remote-ssh-config --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ba536b..c9b448a 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Harvest state about a host and write a harvest bundle. **Common flags** - Remote harvesting: - - `--remote-host`, `--remote-user`, `--remote-port` + - `--remote-host`, `--remote-user`, `--remote-port`, `--remote-ssh-config` - `--no-sudo` (if you don't want/need sudo) - Sensitive-data behaviour: - default: tries to avoid likely secrets @@ -355,6 +355,14 @@ enroll harvest --out /tmp/enroll-harvest enroll harvest --remote-host myhost.example.com --remote-user myuser --out /tmp/enroll-harvest ``` +### Remote harvest over SSH, where the SSH configuration is in ~/.ssh/config (e.g a different SSH key) + +Note: you must still pass `--remote-host`, but in this case, its value can be the 'Host' alias of an entry in your `~/.ssh/config`. + +```bash +enroll harvest --remote-host myhostalias --remote-ssh-config ~/.ssh/config --out /tmp/enroll-harvest +``` + ### Include paths (`--include-path`) ```bash # Add a few dotfiles from /home (still secret-safe unless --dangerous) From 1856e3a79d1e6cad2db40b67dce32b413c154660 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 16 Jan 2026 10:58:39 +1100 Subject: [PATCH 3/7] Add support for AddressFamily and ConnectTimeout in the .ssh/config when using `--remote-ssh-config`. --- CHANGELOG.md | 4 ++++ debian/changelog | 6 ++++++ enroll/remote.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 +- rpm/enroll.spec | 4 +++- 5 files changed, 66 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0772cc4..99fd7e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.4.3 + + * Add support for AddressFamily and ConnectTimeout in the .ssh/config when using `--remote-ssh-config`. + # 0.4.2 * Support `--remote-ssh-config [path-to-ssh-config]` as an argument in case extra params are required beyond `--remote-port` or `--remote-user`. Note: `--remote-host` must still be set, but it can be an 'alias' represented by the 'Host' value in the ssh config. diff --git a/debian/changelog b/debian/changelog index 58e80e3..bd5049d 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +enroll (0.4.3) unstable; urgency=medium + + * Add support for AddressFamily and ConnectTimeout in the .ssh/config when using `--remote-ssh-config`. + + -- Miguel Jacq Fri, 16 Jan 2026 11:00 +1100 + enroll (0.4.2) unstable; urgency=medium * Support `--remote-ssh-config [path-to-ssh-config]` as an argument in case extra params are required beyond `--remote-port` or `--remote-user`. Note: `--remote-host` must still be set, but it can be an 'alias' represented by the 'Host' value in the ssh config. diff --git a/enroll/remote.py b/enroll/remote.py index c7b54a6..53e47b5 100644 --- a/enroll/remote.py +++ b/enroll/remote.py @@ -379,6 +379,10 @@ def _remote_harvest( sock = None hostkey_name = connect_host + # Timeouts derived from ssh_config if set (ConnectTimeout). + # Used both for socket connect (when we create one) and Paramiko handshake/auth. + connect_timeout: Optional[float] = None + if remote_ssh_config: from paramiko.config import SSHConfig # type: ignore from paramiko.proxy import ProxyCommand # type: ignore @@ -411,14 +415,58 @@ def _remote_harvest( else: key_filename = str(Path(str(ident)).expanduser()) + # Honour OpenSSH ConnectTimeout (seconds) if present. + if hcfg.get("connecttimeout"): + try: + connect_timeout = float(str(hcfg.get("connecttimeout"))) + except (TypeError, ValueError): + connect_timeout = None + proxycmd = hcfg.get("proxycommand") + + # AddressFamily support: inet (IPv4 only), inet6 (IPv6 only), any (default). + addrfam = str(hcfg.get("addressfamily") or "any").strip().lower() + family: Optional[int] = None + if addrfam == "inet": + family = _socket.AF_INET + elif addrfam == "inet6": + family = _socket.AF_INET6 + if proxycmd: + # ProxyCommand provides the transport; AddressFamily doesn't apply here. sock = ProxyCommand(str(proxycmd)) + elif family is not None: + # Enforce the requested address family by pre-connecting the socket and + # passing it into Paramiko via sock=. + last_err: Optional[OSError] = None + infos = _socket.getaddrinfo( + connect_host, connect_port, family, _socket.SOCK_STREAM + ) + for af, socktype, proto, _, sa in infos: + s = _socket.socket(af, socktype, proto) + if connect_timeout is not None: + s.settimeout(connect_timeout) + try: + s.connect(sa) + sock = s + break + except OSError as e: + last_err = e + try: + s.close() + except Exception: + pass # nosec + if sock is None and last_err is not None: + raise last_err elif hostkey_name != connect_host: # If HostKeyAlias is used, connect to HostName via a socket but # use HostKeyAlias for known_hosts lookups. - sock = _socket.create_connection((connect_host, connect_port)) + sock = _socket.create_connection( + (connect_host, connect_port), timeout=connect_timeout + ) + # If we created a socket (sock!=None), pass hostkey_name as hostname so + # known_hosts lookup uses HostKeyAlias (or whatever hostkey_name resolved to). ssh.connect( hostname=hostkey_name if sock is not None else connect_host, port=connect_port, @@ -427,6 +475,9 @@ def _remote_harvest( sock=sock, allow_agent=True, look_for_keys=True, + timeout=connect_timeout, + banner_timeout=connect_timeout, + auth_timeout=connect_timeout, ) # If no username was explicitly provided, SSH may have selected a default. diff --git a/pyproject.toml b/pyproject.toml index 92756d1..b4e33dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "enroll" -version = "0.4.2" +version = "0.4.3" description = "Enroll a server's running state retrospectively into Ansible" authors = ["Miguel Jacq "] license = "GPL-3.0-or-later" diff --git a/rpm/enroll.spec b/rpm/enroll.spec index 98f3f8f..1217762 100644 --- a/rpm/enroll.spec +++ b/rpm/enroll.spec @@ -1,4 +1,4 @@ -%global upstream_version 0.4.2 +%global upstream_version 0.4.3 Name: enroll Version: %{upstream_version} @@ -43,6 +43,8 @@ Enroll a server's running state retrospectively into Ansible. %{_bindir}/enroll %changelog +* Fri Jan 16 2026 Miguel Jacq - %{version}-%{release} +- Add support for AddressFamily and ConnectTimeout in the .ssh/config when using `--remote-ssh-config`. * Tue Jan 13 2026 Miguel Jacq - %{version}-%{release} - Support `--remote-ssh-config [path-to-ssh-config]` as an argument in case extra params are required beyond `--remote-port` or `--remote-user`. Note: `--remote-host` must still be s et, but it can be an 'alias' represented by the 'Host' value in the ssh config. From 5f6b0f49d995e124799440a892d689022c51e2df Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Fri, 16 Jan 2026 10:59:22 +1100 Subject: [PATCH 4/7] Update dependencies --- CHANGELOG.md | 1 + poetry.lock | 91 +++++++++++++++++++++++++++------------------------- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99fd7e3..0bbd0db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.4.3 * Add support for AddressFamily and ConnectTimeout in the .ssh/config when using `--remote-ssh-config`. + * Update dependencies # 0.4.2 diff --git a/poetry.lock b/poetry.lock index 3dcf380..aaffe41 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1027,53 +1027,58 @@ files = [ [[package]] name = "tomli" -version = "2.3.0" +version = "2.4.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, - {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, - {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, - {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, - {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, - {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, - {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, - {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, - {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, - {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, - {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, - {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, - {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, - {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, + {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95"}, + {file = "tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d"}, + {file = "tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576"}, + {file = "tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a"}, + {file = "tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa"}, + {file = "tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1"}, + {file = "tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a"}, + {file = "tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b"}, + {file = "tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51"}, + {file = "tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729"}, + {file = "tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da"}, + {file = "tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0"}, + {file = "tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4"}, + {file = "tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c"}, + {file = "tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f"}, + {file = "tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86"}, + {file = "tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87"}, + {file = "tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6"}, + {file = "tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66"}, + {file = "tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702"}, + {file = "tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8"}, + {file = "tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776"}, + {file = "tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475"}, + {file = "tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9"}, + {file = "tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df"}, + {file = "tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f"}, + {file = "tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b"}, + {file = "tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087"}, + {file = "tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd"}, + {file = "tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4"}, + {file = "tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a"}, + {file = "tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c"}, ] [[package]] From 87ddf52e81343ab5d739c6c8c12360c90cadd282 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 17 Feb 2026 10:00:39 +1100 Subject: [PATCH 5/7] Update cryptography dependency --- CHANGELOG.md | 4 ++ poetry.lock | 107 ++++++++++++++++++++++++--------------------------- 2 files changed, 55 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bbd0db..cc8add9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.4.4 (unreleased) + + * Update cryptography dependency + # 0.4.3 * Add support for AddressFamily and ConnectTimeout in the .ssh/config when using `--remote-ssh-config`. diff --git a/poetry.lock b/poetry.lock index aaffe41..bdb0099 100644 --- a/poetry.lock +++ b/poetry.lock @@ -436,65 +436,60 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "46.0.3" +version = "46.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" files = [ - {file = "cryptography-46.0.3-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:109d4ddfadf17e8e7779c39f9b18111a09efb969a301a31e987416a0191ed93a"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:09859af8466b69bc3c27bdf4f5d84a665e0f7ab5088412e9e2ec49758eca5cbc"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:01ca9ff2885f3acc98c29f1860552e37f6d7c7d013d7334ff2a9de43a449315d"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6eae65d4c3d33da080cff9c4ab1f711b15c1d9760809dad6ea763f3812d254cb"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5bf0ed4490068a2e72ac03d786693adeb909981cc596425d09032d372bcc849"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5ecfccd2329e37e9b7112a888e76d9feca2347f12f37918facbb893d7bb88ee8"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a2c0cd47381a3229c403062f764160d57d4d175e022c1df84e168c6251a22eec"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:549e234ff32571b1f4076ac269fcce7a808d3bf98b76c8dd560e42dbc66d7d91"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:c0a7bb1a68a5d3471880e264621346c48665b3bf1c3759d682fc0864c540bd9e"}, - {file = "cryptography-46.0.3-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:10b01676fc208c3e6feeb25a8b83d81767e8059e1fe86e1dc62d10a3018fa926"}, - {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0abf1ffd6e57c67e92af68330d05760b7b7efb243aab8377e583284dbab72c71"}, - {file = "cryptography-46.0.3-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a04bee9ab6a4da801eb9b51f1b708a1b5b5c9eb48c03f74198464c66f0d344ac"}, - {file = "cryptography-46.0.3-cp311-abi3-win32.whl", hash = "sha256:f260d0d41e9b4da1ed1e0f1ce571f97fe370b152ab18778e9e8f67d6af432018"}, - {file = "cryptography-46.0.3-cp311-abi3-win_amd64.whl", hash = "sha256:a9a3008438615669153eb86b26b61e09993921ebdd75385ddd748702c5adfddb"}, - {file = "cryptography-46.0.3-cp311-abi3-win_arm64.whl", hash = "sha256:5d7f93296ee28f68447397bf5198428c9aeeab45705a55d53a6343455dcb2c3c"}, - {file = "cryptography-46.0.3-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:00a5e7e87938e5ff9ff5447ab086a5706a957137e6e433841e9d24f38a065217"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c8daeb2d2174beb4575b77482320303f3d39b8e81153da4f0fb08eb5fe86a6c5"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:39b6755623145ad5eff1dab323f4eae2a32a77a7abef2c5089a04a3d04366715"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:db391fa7c66df6762ee3f00c95a89e6d428f4d60e7abc8328f4fe155b5ac6e54"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:78a97cf6a8839a48c49271cdcbd5cf37ca2c1d6b7fdd86cc864f302b5e9bf459"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:dfb781ff7eaa91a6f7fd41776ec37c5853c795d3b358d4896fdbb5df168af422"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:6f61efb26e76c45c4a227835ddeae96d83624fb0d29eb5df5b96e14ed1a0afb7"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:23b1a8f26e43f47ceb6d6a43115f33a5a37d57df4ea0ca295b780ae8546e8044"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b419ae593c86b87014b9be7396b385491ad7f320bde96826d0dd174459e54665"}, - {file = "cryptography-46.0.3-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:50fc3343ac490c6b08c0cf0d704e881d0d660be923fd3076db3e932007e726e3"}, - {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22d7e97932f511d6b0b04f2bfd818d73dcd5928db509460aaf48384778eb6d20"}, - {file = "cryptography-46.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d55f3dffadd674514ad19451161118fd010988540cee43d8bc20675e775925de"}, - {file = "cryptography-46.0.3-cp314-cp314t-win32.whl", hash = "sha256:8a6e050cb6164d3f830453754094c086ff2d0b2f3a897a1d9820f6139a1f0914"}, - {file = "cryptography-46.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:760f83faa07f8b64e9c33fc963d790a2edb24efb479e3520c14a45741cd9b2db"}, - {file = "cryptography-46.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:516ea134e703e9fe26bcd1277a4b59ad30586ea90c365a87781d7887a646fe21"}, - {file = "cryptography-46.0.3-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:cb3d760a6117f621261d662bccc8ef5bc32ca673e037c83fbe565324f5c46936"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4b7387121ac7d15e550f5cb4a43aef2559ed759c35df7336c402bb8275ac9683"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:15ab9b093e8f09daab0f2159bb7e47532596075139dd74365da52ecc9cb46c5d"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:46acf53b40ea38f9c6c229599a4a13f0d46a6c3fa9ef19fc1a124d62e338dfa0"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10ca84c4668d066a9878890047f03546f3ae0a6b8b39b697457b7757aaf18dbc"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:36e627112085bb3b81b19fed209c05ce2a52ee8b15d161b7c643a7d5a88491f3"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1000713389b75c449a6e979ffc7dcc8ac90b437048766cef052d4d30b8220971"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:b02cf04496f6576afffef5ddd04a0cb7d49cf6be16a9059d793a30b035f6b6ac"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:71e842ec9bc7abf543b47cf86b9a743baa95f4677d22baa4c7d5c69e49e9bc04"}, - {file = "cryptography-46.0.3-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:402b58fc32614f00980b66d6e56a5b4118e6cb362ae8f3fda141ba4689bd4506"}, - {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ef639cb3372f69ec44915fafcd6698b6cc78fbe0c2ea41be867f6ed612811963"}, - {file = "cryptography-46.0.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b51b8ca4f1c6453d8829e1eb7299499ca7f313900dd4d89a24b8b87c0a780d4"}, - {file = "cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df"}, - {file = "cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f"}, - {file = "cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372"}, - {file = "cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32"}, - {file = "cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c"}, - {file = "cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea"}, - {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b"}, - {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb"}, - {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9394673a9f4de09e28b5356e7fff97d778f8abad85c9d5ac4a4b7e25a0de7717"}, - {file = "cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:94cd0549accc38d1494e1f8de71eca837d0509d0d44bf11d158524b0e12cebf9"}, - {file = "cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c"}, - {file = "cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1"}, + {file = "cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731"}, + {file = "cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82"}, + {file = "cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1"}, + {file = "cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48"}, + {file = "cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4"}, + {file = "cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663"}, + {file = "cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826"}, + {file = "cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d"}, + {file = "cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a"}, + {file = "cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4"}, + {file = "cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c"}, + {file = "cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4"}, + {file = "cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9"}, + {file = "cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72"}, + {file = "cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7"}, + {file = "cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d"}, ] [package.dependencies] @@ -508,7 +503,7 @@ nox = ["nox[uv] (>=2024.4.15)"] pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==46.0.3)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.5)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] From 778237740a4474e095be6f11ebf77618371e2990 Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 17 Feb 2026 10:35:51 +1100 Subject: [PATCH 6/7] Add ability to gracefully handle an encrypted private key for SSH (can be forced or automated with an env var too) --- CHANGELOG.md | 1 + README.md | 23 ++++++++- enroll/cli.py | 46 +++++++++++++++++- enroll/remote.py | 121 +++++++++++++++++++++++++++++++++++++---------- 4 files changed, 164 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc8add9..5d3c3f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # 0.4.4 (unreleased) * Update cryptography dependency + * Add capability to handle passphrases on encrypted SSH private keys. Prompting can be forced with `--ask-key-passphrase` or automated (e.g for CI) with `--ssh-key-passphrase env SOMEVAR` # 0.4.3 diff --git a/README.md b/README.md index c9b448a..9fdd756 100644 --- a/README.md +++ b/README.md @@ -89,8 +89,27 @@ Harvest state about a host and write a harvest bundle. - glob (default): supports `*` and `**` (prefix with `glob:` to force) - regex: prefix with `re:` or `regex:` - Precedence: excludes win over includes. -* Using remote mode and sudo requires password? - - `--ask-become-pass` (or `-K`) will prompt for the password. If you forget, and remote requires password for sudo, it'll still fall back to prompting for a password, but will be a bit slower to do so. + * Using remote mode and auth requires secrets? + * sudo password: + * `--ask-become-pass` (or `-K`) prompts for the sudo password. + * If you forget, and remote sudo requires a password, Enroll will still fall back to prompting in interactive mode (slightly slower due to retry). + * SSH private-key passphrase: + * `--ask-key-passphrase` prompts for the SSH key passphrase. + * `--ssh-key-passphrase-env ENV_VAR` reads the SSH key passphrase from an environment variable (useful for CI/non-interactive runs). + * If neither is provided, and Enroll detects an encrypted key in an interactive session, it will still fall back to prompting on-demand. + * In non-interactive sessions, pass `--ask-key-passphrase` or `--ssh-key-passphrase-env ENV_VAR` when using encrypted private keys. + * Note: `--ask-key-passphrase` and `--ssh-key-passphrase-env` are mutually exclusive. + +Examples (encrypted SSH key) + +```bash +# Interactive +enroll harvest --remote-host myhost.example.com --remote-user myuser --ask-key-passphrase --out /tmp/enroll-harvest + +# Non-interactive / CI +export ENROLL_SSH_KEY_PASSPHRASE='correct horse battery staple' +enroll single-shot --remote-host myhost.example.com --remote-user myuser --ssh-key-passphrase-env ENROLL_SSH_KEY_PASSPHRASE --harvest /tmp/enroll-harvest --out /tmp/enroll-ansible --fqdn myhost.example.com +``` --- diff --git a/enroll/cli.py b/enroll/cli.py index 69e85ed..44de047 100644 --- a/enroll/cli.py +++ b/enroll/cli.py @@ -22,7 +22,11 @@ from .diff import ( from .explain import explain_state from .harvest import harvest from .manifest import manifest -from .remote import remote_harvest, RemoteSudoPasswordRequired +from .remote import ( + remote_harvest, + RemoteSudoPasswordRequired, + RemoteSSHKeyPassphraseRequired, +) from .sopsutil import SopsError, encrypt_file_binary from .validate import validate_harvest from .version import get_enroll_version @@ -390,6 +394,24 @@ def _add_remote_args(p: argparse.ArgumentParser) -> None: ), ) + keyp = p.add_mutually_exclusive_group() + keyp.add_argument( + "--ask-key-passphrase", + action="store_true", + help=( + "Prompt for the SSH private key passphrase when using --remote-host. " + "If not set, enroll will still prompt on-demand if it detects an encrypted key in an interactive session." + ), + ) + keyp.add_argument( + "--ssh-key-passphrase-env", + metavar="ENV_VAR", + help=( + "Read the SSH private key passphrase from environment variable ENV_VAR " + "(useful for non-interactive runs/CI)." + ), + ) + def main() -> None: ap = argparse.ArgumentParser(prog="enroll") @@ -771,6 +793,10 @@ def main() -> None: pass remote_harvest( ask_become_pass=args.ask_become_pass, + ask_key_passphrase=bool(args.ask_key_passphrase), + ssh_key_passphrase_env=getattr( + args, "ssh_key_passphrase_env", None + ), local_out_dir=tmp_bundle, remote_host=args.remote_host, remote_port=args.remote_port, @@ -793,6 +819,10 @@ def main() -> None: ) state = remote_harvest( ask_become_pass=args.ask_become_pass, + ask_key_passphrase=bool(args.ask_key_passphrase), + ssh_key_passphrase_env=getattr( + args, "ssh_key_passphrase_env", None + ), local_out_dir=out_dir, remote_host=args.remote_host, remote_port=args.remote_port, @@ -996,6 +1026,10 @@ def main() -> None: pass remote_harvest( ask_become_pass=args.ask_become_pass, + ask_key_passphrase=bool(args.ask_key_passphrase), + ssh_key_passphrase_env=getattr( + args, "ssh_key_passphrase_env", None + ), local_out_dir=tmp_bundle, remote_host=args.remote_host, remote_port=args.remote_port, @@ -1027,6 +1061,10 @@ def main() -> None: ) remote_harvest( ask_become_pass=args.ask_become_pass, + ask_key_passphrase=bool(args.ask_key_passphrase), + ssh_key_passphrase_env=getattr( + args, "ssh_key_passphrase_env", None + ), local_out_dir=harvest_dir, remote_host=args.remote_host, remote_port=args.remote_port, @@ -1096,6 +1134,12 @@ def main() -> None: raise SystemExit( "error: remote sudo requires a password. Re-run with --ask-become-pass." ) from None + except RemoteSSHKeyPassphraseRequired as e: + msg = str(e).strip() or ( + "SSH private key passphrase is required. " + "Re-run with --ask-key-passphrase or --ssh-key-passphrase-env VAR." + ) + raise SystemExit(f"error: {msg}") from None except RuntimeError as e: raise SystemExit(f"error: {e}") from None except SopsError as e: diff --git a/enroll/remote.py b/enroll/remote.py index 53e47b5..45e2798 100644 --- a/enroll/remote.py +++ b/enroll/remote.py @@ -18,6 +18,10 @@ class RemoteSudoPasswordRequired(RuntimeError): """Raised when sudo requires a password but none was provided.""" +class RemoteSSHKeyPassphraseRequired(RuntimeError): + """Raised when SSH private key decryption needs a passphrase.""" + + def _sudo_password_required(out: str, err: str) -> bool: """Return True if sudo output indicates it needs a password/TTY.""" blob = (out + "\n" + err).lower() @@ -68,11 +72,42 @@ def _resolve_become_password( return None +def _resolve_ssh_key_passphrase( + ask_key_passphrase: bool, + *, + env_var: Optional[str] = None, + prompt: str = "SSH key passphrase: ", + getpass_fn: Callable[[str], str] = getpass.getpass, +) -> Optional[str]: + """Resolve SSH private-key passphrase from env and/or prompt. + + Precedence: + 1) --ssh-key-passphrase-env style input (env_var) + 2) --ask-key-passphrase style interactive prompt + 3) None + """ + if env_var: + val = os.environ.get(str(env_var)) + if val is None: + raise RuntimeError( + "SSH key passphrase environment variable is not set: " f"{env_var}" + ) + return val + + if ask_key_passphrase: + return getpass_fn(prompt) + + return None + + def remote_harvest( *, ask_become_pass: bool = False, + ask_key_passphrase: bool = False, + ssh_key_passphrase_env: Optional[str] = None, no_sudo: bool = False, prompt: str = "sudo password: ", + key_prompt: str = "SSH key passphrase: ", getpass_fn: Optional[Callable[[str], str]] = None, stdin: Optional[TextIO] = None, **kwargs, @@ -97,21 +132,52 @@ def remote_harvest( prompt=prompt, getpass_fn=getpass_fn, ) + ssh_key_passphrase = _resolve_ssh_key_passphrase( + ask_key_passphrase, + env_var=ssh_key_passphrase_env, + prompt=key_prompt, + getpass_fn=getpass_fn, + ) - try: - return _remote_harvest(sudo_password=sudo_password, no_sudo=no_sudo, **kwargs) - except RemoteSudoPasswordRequired: - if sudo_password is not None: - raise + while True: + try: + return _remote_harvest( + sudo_password=sudo_password, + no_sudo=no_sudo, + ssh_key_passphrase=ssh_key_passphrase, + **kwargs, + ) + except RemoteSSHKeyPassphraseRequired: + # Already tried a passphrase and still failed. + if ssh_key_passphrase is not None: + raise RemoteSSHKeyPassphraseRequired( + "SSH private key could not be decrypted with the supplied " + "passphrase." + ) from None - # Fallback prompt if interactive - if stdin is not None and getattr(stdin, "isatty", lambda: False)(): - pw = getpass_fn(prompt) - return _remote_harvest(sudo_password=pw, no_sudo=no_sudo, **kwargs) + # Fallback prompt if interactive. + if stdin is not None and getattr(stdin, "isatty", lambda: False)(): + ssh_key_passphrase = getpass_fn(key_prompt) + continue - raise RemoteSudoPasswordRequired( - "Remote sudo requires a password. Re-run with --ask-become-pass." - ) + raise RemoteSSHKeyPassphraseRequired( + "SSH private key is encrypted and needs a passphrase. " + "Re-run with --ask-key-passphrase or " + "--ssh-key-passphrase-env VAR." + ) + + except RemoteSudoPasswordRequired: + if sudo_password is not None: + raise + + # Fallback prompt if interactive. + if stdin is not None and getattr(stdin, "isatty", lambda: False)(): + sudo_password = getpass_fn(prompt) + continue + + raise RemoteSudoPasswordRequired( + "Remote sudo requires a password. Re-run with --ask-become-pass." + ) def _safe_extract_tar(tar: tarfile.TarFile, dest: Path) -> None: @@ -337,6 +403,7 @@ def _remote_harvest( dangerous: bool = False, no_sudo: bool = False, sudo_password: Optional[str] = None, + ssh_key_passphrase: Optional[str] = None, include_paths: Optional[list[str]] = None, exclude_paths: Optional[list[str]] = None, ) -> Path: @@ -467,18 +534,24 @@ def _remote_harvest( # If we created a socket (sock!=None), pass hostkey_name as hostname so # known_hosts lookup uses HostKeyAlias (or whatever hostkey_name resolved to). - ssh.connect( - hostname=hostkey_name if sock is not None else connect_host, - port=connect_port, - username=connect_user, - key_filename=key_filename, - sock=sock, - allow_agent=True, - look_for_keys=True, - timeout=connect_timeout, - banner_timeout=connect_timeout, - auth_timeout=connect_timeout, - ) + try: + ssh.connect( + hostname=hostkey_name if sock is not None else connect_host, + port=connect_port, + username=connect_user, + key_filename=key_filename, + sock=sock, + allow_agent=True, + look_for_keys=True, + timeout=connect_timeout, + banner_timeout=connect_timeout, + auth_timeout=connect_timeout, + passphrase=ssh_key_passphrase, + ) + except paramiko.PasswordRequiredException as e: # type: ignore[attr-defined] + raise RemoteSSHKeyPassphraseRequired( + "SSH private key is encrypted and no passphrase was provided." + ) from e # If no username was explicitly provided, SSH may have selected a default. # We need a concrete username for the (sudo) chown step below. From d403dcb918a812d00ecee24169599ae28439356e Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Tue, 17 Feb 2026 10:58:38 +1100 Subject: [PATCH 7/7] 0.4.4 --- CHANGELOG.md | 2 +- debian/changelog | 6 ++++++ pyproject.toml | 2 +- rpm/enroll.spec | 4 +++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d3c3f8..4b1428c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 0.4.4 (unreleased) +# 0.4.4 * Update cryptography dependency * Add capability to handle passphrases on encrypted SSH private keys. Prompting can be forced with `--ask-key-passphrase` or automated (e.g for CI) with `--ssh-key-passphrase env SOMEVAR` diff --git a/debian/changelog b/debian/changelog index bd5049d..6fa0d96 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +enroll (0.4.4) unstable; urgency=medium + + * Add capability to handle passphrases on encrypted SSH private keys. Prompting can be forced with `--ask-key-passphrase` or automated (e.g for CI) with `--ssh-key-passphrase env SOMEVAR` + + -- Miguel Jacq Tue, 17 Feb 2026 11:00 +1100 + enroll (0.4.3) unstable; urgency=medium * Add support for AddressFamily and ConnectTimeout in the .ssh/config when using `--remote-ssh-config`. diff --git a/pyproject.toml b/pyproject.toml index b4e33dc..e4eb610 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "enroll" -version = "0.4.3" +version = "0.4.4" description = "Enroll a server's running state retrospectively into Ansible" authors = ["Miguel Jacq "] license = "GPL-3.0-or-later" diff --git a/rpm/enroll.spec b/rpm/enroll.spec index 1217762..451c168 100644 --- a/rpm/enroll.spec +++ b/rpm/enroll.spec @@ -1,4 +1,4 @@ -%global upstream_version 0.4.3 +%global upstream_version 0.4.4 Name: enroll Version: %{upstream_version} @@ -43,6 +43,8 @@ Enroll a server's running state retrospectively into Ansible. %{_bindir}/enroll %changelog +* Tue Feb 16 2026 Miguel Jacq - %{version}-%{release} +- Add capability to handle passphrases on encrypted SSH private keys. Prompting can be forced with `--ask-key-passphrase` or automated (e.g for CI) with `--ssh-key-passphrase env SOMEVAR` * Fri Jan 16 2026 Miguel Jacq - %{version}-%{release} - Add support for AddressFamily and ConnectTimeout in the .ssh/config when using `--remote-ssh-config`. * Tue Jan 13 2026 Miguel Jacq - %{version}-%{release}