diff --git a/app/apps/keys/certificates.py b/app/apps/keys/certificates.py index e8f902b..cb7e957 100644 --- a/app/apps/keys/certificates.py +++ b/app/apps/keys/certificates.py @@ -83,6 +83,7 @@ def _sign_public_key( serial: int, validity_days: int, comment: str, + validity_override: str | None = None, ) -> str: if not ca_private_key or not ca_public_key: raise RuntimeError("CA material missing") @@ -102,7 +103,7 @@ def _sign_public_key( "-n", principal, "-V", - f"+{validity_days}d", + validity_override or f"+{validity_days}d", "-z", str(serial), pubkey_path, diff --git a/app/apps/servers/consumers.py b/app/apps/servers/consumers.py index 5925d0e..bf3b2d1 100644 --- a/app/apps/servers/consumers.py +++ b/app/apps/servers/consumers.py @@ -8,6 +8,7 @@ import tempfile from channels.db import database_sync_to_async from channels.generic.websocket import AsyncWebsocketConsumer +from django.conf import settings from django.utils import timezone from apps.audit.matching import find_matching_event_type @@ -108,7 +109,8 @@ class ShellConsumer(AsyncWebsocketConsumer): async def _start_ssh(self, user): # Generate a short-lived keypair + SSH certificate and then # bridge the WebSocket to an SSH subprocess. - self.tempdir = tempfile.TemporaryDirectory(prefix="keywarden-shell-") + temp_base = "/dev/shm" if os.path.isdir("/dev/shm") and os.access("/dev/shm", os.W_OK) else None + self.tempdir = tempfile.TemporaryDirectory(prefix="keywarden-shell-", dir=temp_base) key_path, cert_path = await asyncio.to_thread( _generate_session_keypair, self.tempdir.name, @@ -152,6 +154,13 @@ class ShellConsumer(AsyncWebsocketConsumer): stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT, ) + for path in (key_path, cert_path, f"{key_path}.pub"): + try: + os.remove(path) + except FileNotFoundError: + continue + except Exception: + pass self.reader_task = asyncio.create_task(self._stream_output()) async def _stream_output(self): @@ -246,6 +255,7 @@ def _generate_session_keypair(tempdir: str, user, principal: str) -> tuple[str, raise RuntimeError("ssh-keygen not available") from exc except subprocess.CalledProcessError as exc: raise RuntimeError(f"ssh-keygen failed: {exc.stderr.decode('utf-8', 'ignore')}") from exc + os.chmod(key_path, 0o600) pubkey_path = key_path + ".pub" with open(pubkey_path, "r", encoding="utf-8") as handle: public_key = handle.read().strip() @@ -257,6 +267,7 @@ def _generate_session_keypair(tempdir: str, user, principal: str) -> tuple[str, principal=principal, serial=serial, validity_days=1, + validity_override=f"+{settings.KEYWARDEN_SHELL_CERT_VALIDITY_MINUTES}m", comment=identity, ) cert_path = key_path + "-cert.pub" diff --git a/app/keywarden/settings/base.py b/app/keywarden/settings/base.py index 93db7d0..0554199 100644 --- a/app/keywarden/settings/base.py +++ b/app/keywarden/settings/base.py @@ -103,6 +103,7 @@ SESSION_CACHE_ALIAS = "default" KEYWARDEN_AGENT_CERT_VALIDITY_DAYS = int(os.getenv("KEYWARDEN_AGENT_CERT_VALIDITY_DAYS", "90")) KEYWARDEN_USER_CERT_VALIDITY_DAYS = int(os.getenv("KEYWARDEN_USER_CERT_VALIDITY_DAYS", "30")) +KEYWARDEN_SHELL_CERT_VALIDITY_MINUTES = int(os.getenv("KEYWARDEN_SHELL_CERT_VALIDITY_MINUTES", "15")) KEYWARDEN_ACCOUNT_USERNAME_TEMPLATE = os.getenv( "KEYWARDEN_ACCOUNT_USERNAME_TEMPLATE", "{{username}}_{{user_id}}" )