"""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")