Marco Ricci commited on 2026-06-22 15:03:49
Zeige 1 geänderte Dateien mit 37 Einfügungen und 114 Löschungen.
The `key_to_phrase` vault utitilies test set checks the various error conditions that can occur when obtaining an equivalent vault master passphrase from a master SSH key. The test has 10 parametrization instances, and used to run against the `ssh_agent_client_with_test_keys_loaded` fixture, which currently has 8 parametrization instances, making for a total of 80 tests attempted. However, the tests either focus on construction failures or they inject fake agent responses into the client, so in all 80 cases, there is no actual I/O occurring with the agent. For the 5 external agents (the first 50 cases), this is wasteful, and for the 3 internal agents (the remaining 30 cases), the results are identical among them. So cut this down to testing against 1 of the internal agents (the stubbed SSH agent with address support), for a total of 10 test cases, none of which involve external I/O. As a bonus, because the remaining internal agent is guaranteed reentrant, we can do away with the parametrization instance distinction between construction failures and other failures, and with the special handling of potentially non-reentrant agents. To make this work, because some tests simulate broken support for system-specific SSH agent socket providers, our setup code needs to force the use of the respective provider. As a result, only the `UNSET_PROVIDERS`, `UNSET_AF_UNIX_AND_ENSURE_USE`, `UNSET_NATIVE_AND_ENSURE_USE` and `UNSET_WINDLL_AND_ENSURE_USE` constants from our `SystemSupportAction` enum are still in use, the `UNSET_AF_UNIX`, `UNSET_NATIVE` and `UNSET_WINDLL` constants are not. We retire those constants, and remove the "skip the test if that provider is not currently in use" machinery associated with those constants. The remaining "ensure use" machinery is short enough that it can then be inlined at the call sites.
| ... | ... |
@@ -69,6 +69,21 @@ DUMMY_KEY3_B64 = data.DUMMY_KEY3_B64 |
| 69 | 69 |
TEST_CONFIGS = data.TEST_CONFIGS |
| 70 | 70 |
|
| 71 | 71 |
|
| 72 |
+@pytest.fixture |
|
| 73 |
+def use_stub_agent_with_address() -> Generator[None, None, None]: |
|
| 74 |
+ """Enforce use of the stubbed SSH agent with socket address.""" |
|
| 75 |
+ with pytest.MonkeyPatch.context() as monkeypatch: |
|
| 76 |
+ monkeypatch.setattr( |
|
| 77 |
+ ssh_agent.SSHAgentClient, |
|
| 78 |
+ "SOCKET_PROVIDERS", |
|
| 79 |
+ (_types.BuiltinSSHAgentSocketProvider.STUB_AGENT_WITH_ADDRESS,), |
|
| 80 |
+ ) |
|
| 81 |
+ monkeypatch.setenv( |
|
| 82 |
+ "SSH_AUTH_SOCK", machinery.StubbedSSHAgentSocketWithAddress.ADDRESS |
|
| 83 |
+ ) |
|
| 84 |
+ yield |
|
| 85 |
+ |
|
| 86 |
+ |
|
| 72 | 87 |
def vault_config_exporter_shell_interpreter( # noqa: C901 |
| 73 | 88 |
script: str | Iterable[str], |
| 74 | 89 |
/, |
| ... | ... |
@@ -278,38 +293,26 @@ class SystemSupportAction(str, enum.Enum): |
| 278 | 293 |
"""Test fixture settings for [`ssh_agent.SSHAgentClient`][] system support. |
| 279 | 294 |
|
| 280 | 295 |
Attributes: |
| 281 |
- UNSET_AF_UNIX: |
|
| 282 |
- Ensure lack of support for UNIX domain sockets. |
|
| 283 | 296 |
UNSET_AF_UNIX_AND_ENSURE_USE: |
| 284 | 297 |
Ensure lack of support for UNIX domain sockets, and that the |
| 285 | 298 |
agent will use this socket provider. |
| 286 |
- UNSET_NATIVE: |
|
| 287 |
- Ensure both `UNSET_AF_UNIX` and `UNSET_WINDLL`. |
|
| 288 | 299 |
UNSET_NATIVE_AND_ENSURE_USE: |
| 289 | 300 |
Ensure both `UNSET_AF_UNIX` and `UNSET_WINDLL`, and that the |
| 290 | 301 |
agent will use the native socket provider. |
| 291 | 302 |
UNSET_PROVIDER_LIST: |
| 292 | 303 |
Ensure an empty list of SSH agent socket providers. |
| 293 |
- UNSET_WINDLL: |
|
| 294 |
- Ensure lack of support for Windows named pipes. |
|
| 295 | 304 |
UNSET_WINDLL_AND_ENSURE_USE: |
| 296 | 305 |
Ensure lack of support for Windows named pipes, and that the |
| 297 | 306 |
agent will use this socket provider. |
| 298 | 307 |
|
| 299 | 308 |
""" |
| 300 | 309 |
|
| 301 |
- UNSET_AF_UNIX = enum.auto() |
|
| 302 |
- """""" |
|
| 303 | 310 |
UNSET_AF_UNIX_AND_ENSURE_USE = enum.auto() |
| 304 | 311 |
"""""" |
| 305 |
- UNSET_NATIVE = enum.auto() |
|
| 306 |
- """""" |
|
| 307 | 312 |
UNSET_NATIVE_AND_ENSURE_USE = enum.auto() |
| 308 | 313 |
"""""" |
| 309 | 314 |
UNSET_PROVIDER_LIST = enum.auto() |
| 310 | 315 |
"""""" |
| 311 |
- UNSET_WINDLL = enum.auto() |
|
| 312 |
- """""" |
|
| 313 | 316 |
UNSET_WINDLL_AND_ENSURE_USE = enum.auto() |
| 314 | 317 |
"""""" |
| 315 | 318 |
|
| ... | ... |
@@ -329,91 +332,33 @@ class SystemSupportAction(str, enum.Enum): |
| 329 | 332 |
monkeypatch.setattr( |
| 330 | 333 |
ssh_agent.SSHAgentClient, "SOCKET_PROVIDERS", [] |
| 331 | 334 |
) |
| 332 |
- elif self in {self.UNSET_NATIVE, self.UNSET_NATIVE_AND_ENSURE_USE}:
|
|
| 333 |
- self.check_or_ensure_use( |
|
| 334 |
- _types.BuiltinSSHAgentSocketProvider.NATIVE, |
|
| 335 |
- monkeypatch=monkeypatch, |
|
| 336 |
- ensure_use=(self == self.UNSET_NATIVE_AND_ENSURE_USE), |
|
| 335 |
+ elif self == self.UNSET_NATIVE_AND_ENSURE_USE: |
|
| 336 |
+ monkeypatch.setattr( |
|
| 337 |
+ ssh_agent.SSHAgentClient, |
|
| 338 |
+ "SOCKET_PROVIDERS", |
|
| 339 |
+ [_types.BuiltinSSHAgentSocketProvider.NATIVE], |
|
| 337 | 340 |
) |
| 338 | 341 |
monkeypatch.delattr(socket, "AF_UNIX", raising=False) |
| 339 | 342 |
monkeypatch.delattr(ctypes, "WinDLL", raising=False) |
| 340 | 343 |
monkeypatch.delattr(ctypes, "windll", raising=False) |
| 341 |
- elif self in {self.UNSET_AF_UNIX, self.UNSET_AF_UNIX_AND_ENSURE_USE}:
|
|
| 342 |
- self.check_or_ensure_use( |
|
| 343 |
- _types.BuiltinSSHAgentSocketProvider.POSIX, |
|
| 344 |
- monkeypatch=monkeypatch, |
|
| 345 |
- ensure_use=(self == self.UNSET_AF_UNIX_AND_ENSURE_USE), |
|
| 344 |
+ elif self == self.UNSET_AF_UNIX_AND_ENSURE_USE: |
|
| 345 |
+ monkeypatch.setattr( |
|
| 346 |
+ ssh_agent.SSHAgentClient, |
|
| 347 |
+ "SOCKET_PROVIDERS", |
|
| 348 |
+ [_types.BuiltinSSHAgentSocketProvider.SSH_AUTH_SOCK_ON_POSIX], |
|
| 346 | 349 |
) |
| 347 | 350 |
monkeypatch.delattr(socket, "AF_UNIX", raising=False) |
| 348 |
- elif self in {self.UNSET_WINDLL, self.UNSET_WINDLL_AND_ENSURE_USE}:
|
|
| 349 |
- self.check_or_ensure_use( |
|
| 350 |
- _types.BuiltinSSHAgentSocketProvider.WINDOWS, |
|
| 351 |
- monkeypatch=monkeypatch, |
|
| 352 |
- ensure_use=(self == self.UNSET_WINDLL_AND_ENSURE_USE), |
|
| 351 |
+ elif self == self.UNSET_WINDLL_AND_ENSURE_USE: |
|
| 352 |
+ monkeypatch.setattr( |
|
| 353 |
+ ssh_agent.SSHAgentClient, |
|
| 354 |
+ "SOCKET_PROVIDERS", |
|
| 355 |
+ [_types.BuiltinSSHAgentSocketProvider.WINDOWS_NAMED_PIPE], |
|
| 353 | 356 |
) |
| 354 | 357 |
monkeypatch.delattr(ctypes, "WinDLL", raising=False) |
| 355 | 358 |
monkeypatch.delattr(ctypes, "windll", raising=False) |
| 356 | 359 |
else: |
| 357 | 360 |
raise AssertionError() |
| 358 | 361 |
|
| 359 |
- @staticmethod |
|
| 360 |
- def check_or_ensure_use( |
|
| 361 |
- provider: str, /, *, monkeypatch: pytest.MonkeyPatch, ensure_use: bool |
|
| 362 |
- ) -> None: |
|
| 363 |
- """Check that the named SSH agent socket provider will be used. |
|
| 364 |
- |
|
| 365 |
- Either ensure that the socket provider will definitely be used, |
|
| 366 |
- or, upon detecting that it won't be used, skip the test. |
|
| 367 |
- |
|
| 368 |
- Args: |
|
| 369 |
- provider: |
|
| 370 |
- The provider to check for. |
|
| 371 |
- ensure_use: |
|
| 372 |
- If true, ensure that the socket provider will definitely |
|
| 373 |
- be used. If false, then check for whether it will be |
|
| 374 |
- used, and skip this test if not. |
|
| 375 |
- monkeypatch: |
|
| 376 |
- The monkeypatch context within which the fixture |
|
| 377 |
- adjustments should be executed. |
|
| 378 |
- |
|
| 379 |
- """ |
|
| 380 |
- if ensure_use: |
|
| 381 |
- monkeypatch.setattr( |
|
| 382 |
- ssh_agent.SSHAgentClient, "SOCKET_PROVIDERS", [provider] |
|
| 383 |
- ) |
|
| 384 |
- else: # pragma: no cover [external] |
|
| 385 |
- # This branch operates completely on instrumented or on |
|
| 386 |
- # externally defined, non-deterministic state. |
|
| 387 |
- intended: ( |
|
| 388 |
- _types.SSHAgentSocketProvider |
|
| 389 |
- | socketprovider.NoSuchProviderError |
|
| 390 |
- | None |
|
| 391 |
- ) |
|
| 392 |
- try: |
|
| 393 |
- intended = socketprovider.SocketProvider.lookup(provider) |
|
| 394 |
- except socketprovider.NoSuchProviderError as exc: |
|
| 395 |
- intended = exc |
|
| 396 |
- actual: ( |
|
| 397 |
- _types.SSHAgentSocketProvider |
|
| 398 |
- | socketprovider.NoSuchProviderError |
|
| 399 |
- | None |
|
| 400 |
- ) |
|
| 401 |
- for name in ssh_agent.SSHAgentClient.SOCKET_PROVIDERS: |
|
| 402 |
- try: |
|
| 403 |
- actual = socketprovider.SocketProvider.lookup(name) |
|
| 404 |
- except socketprovider.NoSuchProviderError as exc: |
|
| 405 |
- actual = exc |
|
| 406 |
- if actual is None: |
|
| 407 |
- continue |
|
| 408 |
- break |
|
| 409 |
- else: |
|
| 410 |
- actual = None |
|
| 411 |
- if intended != actual: |
|
| 412 |
- pytest.skip( |
|
| 413 |
- f"{provider!r} SSH agent socket provider "
|
|
| 414 |
- f"is not currently in use" |
|
| 415 |
- ) |
|
| 416 |
- |
|
| 417 | 362 |
|
| 418 | 363 |
class Parametrize(types.SimpleNamespace): |
| 419 | 364 |
"""Common test parametrizations.""" |
| ... | ... |
@@ -477,7 +422,6 @@ class Parametrize(types.SimpleNamespace): |
| 477 | 422 |
"sign_action", |
| 478 | 423 |
"pattern", |
| 479 | 424 |
"warnings_patterns", |
| 480 |
- "tests_construction_failure", |
|
| 481 | 425 |
], |
| 482 | 426 |
[ |
| 483 | 427 |
pytest.param( |
| ... | ... |
@@ -487,7 +431,6 @@ class Parametrize(types.SimpleNamespace): |
| 487 | 431 |
SignAction.FAIL, |
| 488 | 432 |
"not loaded into the agent", |
| 489 | 433 |
[], |
| 490 |
- False, |
|
| 491 | 434 |
id="key-not-loaded", |
| 492 | 435 |
), |
| 493 | 436 |
pytest.param( |
| ... | ... |
@@ -497,7 +440,6 @@ class Parametrize(types.SimpleNamespace): |
| 497 | 440 |
SignAction.FAIL, |
| 498 | 441 |
"SSH agent failed to or refused to", |
| 499 | 442 |
[], |
| 500 |
- False, |
|
| 501 | 443 |
id="list-keys-refused", |
| 502 | 444 |
), |
| 503 | 445 |
pytest.param( |
| ... | ... |
@@ -507,7 +449,6 @@ class Parametrize(types.SimpleNamespace): |
| 507 | 449 |
SignAction.FAIL, |
| 508 | 450 |
"SSH agent failed to or refused to", |
| 509 | 451 |
[], |
| 510 |
- False, |
|
| 511 | 452 |
id="list-keys-protocol-error", |
| 512 | 453 |
), |
| 513 | 454 |
pytest.param( |
| ... | ... |
@@ -517,7 +458,6 @@ class Parametrize(types.SimpleNamespace): |
| 517 | 458 |
SignAction.FAIL, |
| 518 | 459 |
"Cannot connect to the SSH agent", |
| 519 | 460 |
[], |
| 520 |
- True, |
|
| 521 | 461 |
id="agent-address-mangled", |
| 522 | 462 |
), |
| 523 | 463 |
pytest.param( |
| ... | ... |
@@ -527,17 +467,15 @@ class Parametrize(types.SimpleNamespace): |
| 527 | 467 |
SignAction.FAIL, |
| 528 | 468 |
"Cannot find any running SSH agent", |
| 529 | 469 |
[], |
| 530 |
- True, |
|
| 531 | 470 |
id="agent-address-missing", |
| 532 | 471 |
), |
| 533 | 472 |
pytest.param( |
| 534 | 473 |
None, |
| 535 | 474 |
None, |
| 536 |
- SystemSupportAction.UNSET_NATIVE, |
|
| 475 |
+ SystemSupportAction.UNSET_NATIVE_AND_ENSURE_USE, |
|
| 537 | 476 |
SignAction.FAIL, |
| 538 | 477 |
"does not support communicating with it", |
| 539 | 478 |
[], |
| 540 |
- True, |
|
| 541 | 479 |
id="no-native-agent-available", |
| 542 | 480 |
), |
| 543 | 481 |
pytest.param( |
| ... | ... |
@@ -547,7 +485,6 @@ class Parametrize(types.SimpleNamespace): |
| 547 | 485 |
SignAction.FAIL, |
| 548 | 486 |
"does not support communicating with it", |
| 549 | 487 |
[], |
| 550 |
- True, |
|
| 551 | 488 |
id="no-agents-in-agent-provider-list", |
| 552 | 489 |
), |
| 553 | 490 |
pytest.param( |
| ... | ... |
@@ -557,7 +494,6 @@ class Parametrize(types.SimpleNamespace): |
| 557 | 494 |
SignAction.FAIL, |
| 558 | 495 |
"does not support communicating with it", |
| 559 | 496 |
["Cannot connect to an SSH agent via UNIX domain sockets"], |
| 560 |
- True, |
|
| 561 | 497 |
id="no-unix-domain-sockets", |
| 562 | 498 |
), |
| 563 | 499 |
pytest.param( |
| ... | ... |
@@ -567,7 +503,6 @@ class Parametrize(types.SimpleNamespace): |
| 567 | 503 |
SignAction.FAIL, |
| 568 | 504 |
"does not support communicating with it", |
| 569 | 505 |
["Cannot connect to an SSH agent via Windows named pipes"], |
| 570 |
- True, |
|
| 571 | 506 |
id="no-windows-named-pipes", |
| 572 | 507 |
), |
| 573 | 508 |
pytest.param( |
| ... | ... |
@@ -577,7 +512,6 @@ class Parametrize(types.SimpleNamespace): |
| 577 | 512 |
SignAction.FAIL_RUNTIME, |
| 578 | 513 |
"violates the communication protocol", |
| 579 | 514 |
[], |
| 580 |
- False, |
|
| 581 | 515 |
id="sign-violates-protocol", |
| 582 | 516 |
), |
| 583 | 517 |
], |
| ... | ... |
@@ -1474,20 +1408,18 @@ class TestMisc: |
| 1474 | 1408 |
) |
| 1475 | 1409 |
|
| 1476 | 1410 |
@Parametrize.KEY_TO_PHRASE_SETTINGS |
| 1477 |
- def test_key_to_phrase( # noqa: C901 |
|
| 1411 |
+ def test_key_to_phrase( |
|
| 1478 | 1412 |
self, |
| 1479 |
- request: pytest.FixtureRequest, |
|
| 1480 |
- ssh_agent_client_with_test_keys_loaded: ssh_agent.SSHAgentClient, |
|
| 1413 |
+ use_stub_agent_with_address: None, |
|
| 1481 | 1414 |
list_keys_action: ListKeysAction | None, |
| 1482 | 1415 |
system_support_action: SystemSupportAction | None, |
| 1483 | 1416 |
address_action: SocketAddressAction | None, |
| 1484 | 1417 |
sign_action: SignAction, |
| 1485 | 1418 |
pattern: str, |
| 1486 | 1419 |
warnings_patterns: list[str], |
| 1487 |
- tests_construction_failure: bool, |
|
| 1488 | 1420 |
) -> None: |
| 1489 | 1421 |
"""All errors in [`cli_helpers.key_to_phrase`][] are handled.""" |
| 1490 |
- |
|
| 1422 |
+ del use_stub_agent_with_address |
|
| 1491 | 1423 |
captured_warnings: list[str] = [] |
| 1492 | 1424 |
|
| 1493 | 1425 |
class ErrCallback(BaseException): |
| ... | ... |
@@ -1503,12 +1435,10 @@ class TestMisc: |
| 1503 | 1435 |
if args: # pragma: no branch |
| 1504 | 1436 |
captured_warnings.append(str(args[0])) |
| 1505 | 1437 |
|
| 1506 |
- with pytest.MonkeyPatch.context() as monkeypatch: |
|
| 1507 |
- loaded_keys = list( |
|
| 1508 |
- ssh_agent_client_with_test_keys_loaded.list_keys() |
|
| 1509 |
- ) |
|
| 1438 |
+ with ssh_agent.SSHAgentClient.ensure_agent_subcontext() as client: |
|
| 1439 |
+ loaded_keys = list(client.list_keys()) |
|
| 1510 | 1440 |
loaded_key = base64.standard_b64encode(loaded_keys[0][0]) |
| 1511 |
- |
|
| 1441 |
+ with pytest.MonkeyPatch.context() as monkeypatch: |
|
| 1512 | 1442 |
monkeypatch.setattr(ssh_agent.SSHAgentClient, "sign", sign_action) |
| 1513 | 1443 |
if list_keys_action: |
| 1514 | 1444 |
monkeypatch.setattr( |
| ... | ... |
@@ -1519,13 +1449,6 @@ class TestMisc: |
| 1519 | 1449 |
if system_support_action: |
| 1520 | 1450 |
system_support_action(monkeypatch) |
| 1521 | 1451 |
|
| 1522 |
- if not tests_construction_failure: |
|
| 1523 |
- pytest_machinery.ensure_singleton_client_if_non_reentrant_agent( |
|
| 1524 |
- request=request, |
|
| 1525 |
- monkeypatch=monkeypatch, |
|
| 1526 |
- client=ssh_agent_client_with_test_keys_loaded, |
|
| 1527 |
- ) |
|
| 1528 |
- |
|
| 1529 | 1452 |
with pytest.raises(ErrCallback, match=pattern) as excinfo: |
| 1530 | 1453 |
cli_helpers.key_to_phrase( |
| 1531 | 1454 |
loaded_key, error_callback=err, warning_callback=warn |
| 1532 | 1455 |