Update the "SSH key" feature flag in `--version` output
Marco Ricci

Marco Ricci commited on 2025-08-02 14:26:22
Zeige 1 geänderte Dateien mit 30 Einfügungen und 3 Löschungen.


Change the feature detection system to actually test whether it can
successfully construct an SSH agent client, and then report that the
feature is available (or not) based on the success of this test.

Previously, `derivepassphrase` would determine whether it supported
connecting to an SSH agent or not by checking for Python support for
UNIX domain sockets.  This was the same criteria as used by the actual
SSH agent client constructor to attempt, well, constructing an SSH agent
client.  However, since the SSH agent client now delegates the
construction to the socket provider mechanism, which includes fallbacks
and "available, but not sensibly callable" providers, determining
whether SSH agent support is available is not quite as straight-forward
anymore as testing for `socket.AF_UNIX` was.

I could attempt to replicate the registry entry resolving steps to see
if we have a working socket provider, but even that entails actually
running the provider and checking for the absence of certain kinds of
exceptions (but not other kinds, which indicate a manner of success!).
Plus, a separate, indepedent replication of the search logic runs the
risk of becoming desynchronized with the original implementation:
`derivepassphrase vault --version` thinks that support is or is not
available, and the SSH agent client constructor thinks the opposite.

A *far* more robust solution is to actually test the functionality we
are hoping to use directly, i.e., to test constructing an SSH agent
client: it obviously doesn't work if we don't have support, and if it
does work, we have support.  (This technique would of course generalize
to other feature flags, if we had any such.)  So that's what we'll do.
... ...
@@ -18,17 +18,21 @@ import collections
18 18
 import importlib.metadata
19 19
 import inspect
20 20
 import logging
21
-import socket
21
+import sys
22 22
 import warnings
23 23
 from typing import TYPE_CHECKING, Callable, Literal, TextIO, TypeVar
24 24
 
25 25
 import click
26 26
 import click.shell_completion
27
+import exceptiongroup
27 28
 from typing_extensions import Any, ParamSpec, override
28 29
 
29
-from derivepassphrase import _internals, _types
30
+from derivepassphrase import _internals, _types, ssh_agent
30 31
 from derivepassphrase._internals import cli_messages as _msg
31 32
 
33
+if sys.version_info < (3, 11):
34
+    from exceptiongroup import BaseExceptionGroup
35
+
32 36
 if TYPE_CHECKING:
33 37
     import types
34 38
     from collections.abc import (
... ...
@@ -1140,6 +1144,29 @@ def export_vault_version_option_callback(
1140 1144
         ctx.exit()
1141 1145
 
1142 1146
 
1147
+def _test_for_ssh_key_feature() -> bool:
1148
+    """Return true if we support SSH keys.
1149
+
1150
+    This is the feature test for [`_types.Feature.SSH_KEY`][].  We test
1151
+    this by attempting to construct an SSH agent client, reporting
1152
+    whether this can principally work, or not.
1153
+
1154
+    """
1155
+    ret = True
1156
+
1157
+    def handle_notimplementederror(_exc: BaseExceptionGroup) -> None:
1158
+        nonlocal ret
1159
+        ret = False
1160
+
1161
+    with exceptiongroup.catch({  # noqa: SIM117
1162
+        NotImplementedError: handle_notimplementederror,
1163
+        Exception: lambda _exc: None,
1164
+    }):
1165
+        with ssh_agent.SSHAgentClient.ensure_agent_subcontext():
1166
+            pass
1167
+    return ret
1168
+
1169
+
1143 1170
 def vault_version_option_callback(
1144 1171
     ctx: click.Context,
1145 1172
     param: click.Parameter,
... ...
@@ -1148,7 +1175,7 @@ def vault_version_option_callback(
1148 1175
     if value and not ctx.resilient_parsing:
1149 1176
         common_version_output(ctx, param, value)
1150 1177
         features = {
1151
-            _types.Feature.SSH_KEY: hasattr(socket, 'AF_UNIX'),
1178
+            _types.Feature.SSH_KEY: _test_for_ssh_key_feature(),
1152 1179
         }
1153 1180
         click.echo()
1154 1181
         version_info_types: dict[_msg.Label, list[str]] = {
1155 1182