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,
|
serial: int,
|
||||||
validity_days: int,
|
validity_days: int,
|
||||||
comment: str,
|
comment: str,
|
||||||
|
validity_override: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
if not ca_private_key or not ca_public_key:
|
if not ca_private_key or not ca_public_key:
|
||||||
raise RuntimeError("CA material missing")
|
raise RuntimeError("CA material missing")
|
||||||
@@ -102,7 +103,7 @@ def _sign_public_key(
|
|||||||
"-n",
|
"-n",
|
||||||
principal,
|
principal,
|
||||||
"-V",
|
"-V",
|
||||||
f"+{validity_days}d",
|
validity_override or f"+{validity_days}d",
|
||||||
"-z",
|
"-z",
|
||||||
str(serial),
|
str(serial),
|
||||||
pubkey_path,
|
pubkey_path,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import tempfile
|
|||||||
|
|
||||||
from channels.db import database_sync_to_async
|
from channels.db import database_sync_to_async
|
||||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||||
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from apps.audit.matching import find_matching_event_type
|
from apps.audit.matching import find_matching_event_type
|
||||||
@@ -108,7 +109,8 @@ class ShellConsumer(AsyncWebsocketConsumer):
|
|||||||
async def _start_ssh(self, user):
|
async def _start_ssh(self, user):
|
||||||
# Generate a short-lived keypair + SSH certificate and then
|
# Generate a short-lived keypair + SSH certificate and then
|
||||||
# bridge the WebSocket to an SSH subprocess.
|
# 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(
|
key_path, cert_path = await asyncio.to_thread(
|
||||||
_generate_session_keypair,
|
_generate_session_keypair,
|
||||||
self.tempdir.name,
|
self.tempdir.name,
|
||||||
@@ -152,6 +154,13 @@ class ShellConsumer(AsyncWebsocketConsumer):
|
|||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.STDOUT,
|
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())
|
self.reader_task = asyncio.create_task(self._stream_output())
|
||||||
|
|
||||||
async def _stream_output(self):
|
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
|
raise RuntimeError("ssh-keygen not available") from exc
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
raise RuntimeError(f"ssh-keygen failed: {exc.stderr.decode('utf-8', 'ignore')}") from 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"
|
pubkey_path = key_path + ".pub"
|
||||||
with open(pubkey_path, "r", encoding="utf-8") as handle:
|
with open(pubkey_path, "r", encoding="utf-8") as handle:
|
||||||
public_key = handle.read().strip()
|
public_key = handle.read().strip()
|
||||||
@@ -257,6 +267,7 @@ def _generate_session_keypair(tempdir: str, user, principal: str) -> tuple[str,
|
|||||||
principal=principal,
|
principal=principal,
|
||||||
serial=serial,
|
serial=serial,
|
||||||
validity_days=1,
|
validity_days=1,
|
||||||
|
validity_override=f"+{settings.KEYWARDEN_SHELL_CERT_VALIDITY_MINUTES}m",
|
||||||
comment=identity,
|
comment=identity,
|
||||||
)
|
)
|
||||||
cert_path = key_path + "-cert.pub"
|
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_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_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 = os.getenv(
|
||||||
"KEYWARDEN_ACCOUNT_USERNAME_TEMPLATE", "{{username}}_{{user_id}}"
|
"KEYWARDEN_ACCOUNT_USERNAME_TEMPLATE", "{{username}}_{{user_id}}"
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user