Files
gatehouse-api/tests/integration/client/ssh.py
T
nexgen_mirrors 015c622016 test: add comprehensive integration test suite for IAM platform
Add 162 integration tests covering authentication flows, TOTP MFA,
SSH key/certificate management, organization workflows, multi-org
access, self-service features, admin operations, authorization,
security edge cases, department/principal management, CA management,
policy compliance, WebAuthn passkeys, and ZeroTier network access.

Includes:
- Reusable API client library with session management
- Test fixtures for users, organizations, memberships, and CAs
- Helper functions for SSH key generation and verification
- Documentation for running and writing tests

Also update test configuration to disable conflicting maas plugins
and configure WebAuthn/session settings for localhost testing.
2026-04-23 15:41:37 +09:30

133 lines
4.8 KiB
Python

"""SSH client for integration tests."""
import logging
logger = logging.getLogger(__name__)
class SshClient:
"""Wraps SSH key and certificate API calls."""
def __init__(self, client):
self._client = client
# ------------------------------------------------------------------
# SSH Key Management
# ------------------------------------------------------------------
def list_keys(self) -> dict:
"""Return all SSH keys belonging to the current user."""
return self._client.get("/ssh/keys")
def add_key(self, public_key: str, description: str | None = None) -> dict:
"""Upload a new SSH public key.
Args:
public_key: The OpenSSH-format public key string.
description: Optional human-readable label.
"""
payload = {"public_key": public_key}
if description:
payload["description"] = description
logger.info("[SshClient] Adding SSH key")
return self._client.post("/ssh/keys", data=payload)
def get_key(self, key_id: str) -> dict:
"""Return a single SSH key by ID."""
return self._client.get(f"/ssh/keys/{key_id}")
def delete_key(self, key_id: str) -> dict:
"""Delete an SSH key."""
return self._client.delete(f"/ssh/keys/{key_id}")
def update_description(self, key_id: str, description: str) -> dict:
"""Update the description of an SSH key."""
return self._client.patch(
f"/ssh/keys/{key_id}/update-description",
data={"description": description},
)
# ------------------------------------------------------------------
# SSH Key Verification
# ------------------------------------------------------------------
def get_challenge(self, key_id: str) -> dict:
"""Generate a verification challenge for an SSH key.
Returns:
Response dict containing ``challenge_text``.
"""
return self._client.get(f"/ssh/keys/{key_id}/verify")
def verify_key(self, key_id: str, signature: str) -> dict:
"""Verify ownership of an SSH key by submitting a signature.
Args:
key_id: The SSH key ID.
signature: Base64-encoded signature of the challenge text.
"""
return self._client.post(
f"/ssh/keys/{key_id}/verify",
data={"action": "verify_signature", "signature": signature},
)
# ------------------------------------------------------------------
# SSH Certificate Signing
# ------------------------------------------------------------------
def sign_certificate(
self,
*,
key_id: str | None = None,
principals: list[str] | None = None,
cert_type: str = "user",
expiry_hours: int | None = None,
) -> dict:
"""Request an SSH user certificate.
Args:
key_id: SSH key to attach the certificate to.
principals: Optional list of requested principals.
cert_type: "user" or "host".
expiry_hours: Optional custom expiry within policy.
"""
payload: dict = {"cert_type": cert_type}
if key_id:
payload["key_id"] = key_id
if principals:
payload["principals"] = principals
if expiry_hours:
payload["expiry_hours"] = expiry_hours
logger.info(f"[SshClient] Signing certificate — type={cert_type}")
return self._client.post("/ssh/sign", data=payload)
def sign_host_certificate(self, *, host_public_key: str, ca_id: str | None = None) -> dict:
"""Request an SSH host certificate (admin-only).
Args:
host_public_key: The host's public key material.
ca_id: Optional CA ID (defaults to org's host CA).
"""
payload: dict = {"host_public_key": host_public_key, "cert_type": "host"}
if ca_id:
payload["ca_id"] = ca_id
return self._client.post("/ssh/sign/host", data=payload)
# ------------------------------------------------------------------
# Certificate Management
# ------------------------------------------------------------------
def list_certificates(self) -> dict:
"""Return all certificates for the current user."""
return self._client.get("/ssh/certificates")
def get_certificate(self, cert_id: str) -> dict:
"""Return a single certificate by ID."""
return self._client.get(f"/ssh/certificates/{cert_id}")
def revoke_certificate(self, cert_id: str, reason: str = "User revoked") -> dict:
"""Revoke a certificate."""
return self._client.post(
f"/ssh/certificates/{cert_id}/revoke",
data={"reason": reason},
)
def get_ca_public_key(self) -> dict:
"""Return the organization's CA public key."""
return self._client.get("/ssh/ca/public-key")