initial commit after one day coding agent session

This commit is contained in:
Hans Aschauer 2026-02-07 07:45:57 +01:00
commit 372af75b90
88 changed files with 22694 additions and 0 deletions

View file

@ -0,0 +1,157 @@
"""
Podman client wrapper with security validation.
Wraps Podman API with security validation and error handling.
All container operations are validated against security policy.
"""
import os
from pathlib import Path
from typing import Optional
from podman import PodmanClient as BasePodmanClient
from mcp_forge.security.allowlist import OperationValidator
from mcp_forge.security.audit import AuditLogger
class PodmanConnectionError(Exception):
"""Raised when connection to Podman fails."""
pass
class PodmanClient:
"""
Wrapper around Podman API with security validation.
All container operations are validated against security policy
before being sent to Podman. Provides lazy connection and
proper error handling.
"""
def __init__(
self,
socket_path: Path,
validator: OperationValidator,
audit_logger: AuditLogger
):
"""
Initialize Podman client wrapper.
Args:
socket_path: Path to Podman socket
validator: Operation validator for security checks
audit_logger: Audit logger for operation logging
"""
self.socket_path = Path(socket_path)
self.validator = validator
self.audit_logger = audit_logger
self._client: Optional[BasePodmanClient] = None
def connect(self) -> None:
"""
Connect to Podman via socket.
Raises:
PodmanConnectionError: If connection fails
"""
try:
# Verify socket exists and is accessible
self.verify_socket_access()
# Create Podman client with Unix socket
base_url = f"unix://{self.socket_path}"
self._client = BasePodmanClient(base_url=base_url)
# Test connection with ping (only if client supports it)
if hasattr(self._client, 'ping'):
self._client.ping()
except PodmanConnectionError:
# Re-raise our own exceptions
raise
except Exception as e:
raise PodmanConnectionError(
f"Failed to connect to Podman at {self.socket_path}: {e}"
) from e
def ping(self) -> bool:
"""
Test connection to Podman.
Returns:
True if connection is healthy
Raises:
PodmanConnectionError: If not connected or ping fails
"""
if self._client is None:
raise PodmanConnectionError("Not connected to Podman")
try:
result = self._client.ping()
return result == "OK" or result is True
except Exception as e:
raise PodmanConnectionError(f"Ping failed: {e}") from e
def disconnect(self) -> None:
"""Disconnect from Podman and cleanup."""
if self._client is not None:
try:
self._client.close()
except Exception:
pass # Ignore errors during cleanup
finally:
self._client = None
def verify_socket_access(self) -> None:
"""
Verify that socket exists and is accessible.
Raises:
PodmanConnectionError: If socket is not accessible
"""
if not self.socket_path.exists():
raise PodmanConnectionError(
f"Podman socket not found: {self.socket_path}"
)
if not os.access(self.socket_path, os.R_OK):
raise PodmanConnectionError(
f"Podman socket is not readable: {self.socket_path}"
)
def check_api_version(self) -> dict:
"""
Get Podman API version information.
Returns:
Dictionary with version information
Raises:
PodmanConnectionError: If not connected
"""
if self._client is None:
raise PodmanConnectionError("Not connected to Podman")
try:
return self._client.version()
except Exception as e:
raise PodmanConnectionError(
f"Failed to get API version: {e}"
) from e
@property
def client(self) -> BasePodmanClient:
"""
Get underlying Podman client (lazy connection).
Returns:
Connected Podman client
Raises:
PodmanConnectionError: If connection fails
"""
if self._client is None:
self.connect()
assert self._client is not None # Type narrowing for mypy
return self._client