015c622016
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.
133 lines
4.8 KiB
Python
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")
|