Changed ephemeral key to 30m lifespan; keys stored in /dev/shm; explicit 0600 perms; delete keys when session opens.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user