Commented terminal files

This commit is contained in:
2026-02-03 09:33:49 +00:00
parent f54cc3f09b
commit 962ba27679
4 changed files with 32 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ from .utils import render_system_username
def get_active_ca(created_by=None) -> SSHCertificateAuthority:
# Reuse the most recent active CA, or lazily create one if missing.
ca = (
SSHCertificateAuthority.objects.filter(is_active=True, revoked_at__isnull=True)
.order_by("-created_at")
@@ -31,9 +32,11 @@ def issue_certificate_for_key(key: SSHKey, created_by=None) -> SSHCertificate:
if not key or not key.user_id:
raise ValueError("key must have a user")
ca = get_active_ca(created_by=created_by)
# Principal must match the system account used for SSH logins.
principal = render_system_username(key.user.username, key.user_id)
now = timezone.now()
valid_before = now + timedelta(days=settings.KEYWARDEN_USER_CERT_VALIDITY_DAYS)
# Serial should be unique and non-guessable for audit purposes.
serial = secrets.randbits(63)
safe_name = _sanitize_label(key.name or "key")
identity = f"keywarden-cert-{key.user_id}-{safe_name}-{key.id}"
@@ -70,6 +73,7 @@ def revoke_certificate_for_key(key: SSHKey) -> None:
cert = key.certificate
except SSHCertificate.DoesNotExist:
return
# Mark the cert as revoked but keep the record for audit/history.
cert.revoke()
cert.save(update_fields=["is_active", "revoked_at"])
@@ -87,6 +91,7 @@ def _sign_public_key(
) -> str:
if not ca_private_key or not ca_public_key:
raise RuntimeError("CA material missing")
# Write key material into a temp dir to avoid persisting secrets.
with tempfile.TemporaryDirectory() as tmpdir:
ca_path = os.path.join(tmpdir, "user_ca")
pubkey_path = os.path.join(tmpdir, "user.pub")
@@ -94,6 +99,7 @@ def _sign_public_key(
_write_file(ca_path + ".pub", ca_public_key.strip() + "\n", 0o644)
pubkey_with_comment = _ensure_comment(public_key, comment)
_write_file(pubkey_path, pubkey_with_comment + "\n", 0o644)
# Use ssh-keygen to sign the public key with the CA.
cmd = [
"ssh-keygen",
"-s",
@@ -114,6 +120,7 @@ def _sign_public_key(
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
# ssh-keygen writes the cert alongside the input pubkey.
cert_path = pubkey_path
if cert_path.endswith(".pub"):
cert_path = cert_path[: -len(".pub")]
@@ -126,6 +133,7 @@ def _sign_public_key(
def _ensure_comment(public_key: str, comment: str) -> str:
# Preserve the key type and base64 payload; replace/append only the comment.
parts = (public_key or "").strip().split()
if len(parts) < 2:
return public_key.strip()
@@ -136,6 +144,7 @@ def _ensure_comment(public_key: str, comment: str) -> str:
def _sanitize_label(value: str) -> str:
# Reduce label to a safe, lowercase token for certificate identity.
cleaned = re.sub(r"[^a-zA-Z0-9_-]+", "-", (value or "").strip())
cleaned = cleaned.strip("-_")
if cleaned:
@@ -146,4 +155,5 @@ def _sanitize_label(value: str) -> str:
def _write_file(path: str, data: str, mode: int) -> None:
with open(path, "w", encoding="utf-8") as handle:
handle.write(data)
# Apply explicit permissions for key material.
os.chmod(path, mode)

View File

@@ -9,6 +9,7 @@ _SANITIZE_RE = re.compile(r"[^a-z0-9_-]")
def render_system_username(username: str, user_id: int) -> str:
# Render from template and then sanitize to an OS-safe username.
template = settings.KEYWARDEN_ACCOUNT_USERNAME_TEMPLATE
raw = template.replace("{{username}}", username or "")
raw = raw.replace("{{user_id}}", str(user_id))
@@ -17,13 +18,16 @@ def render_system_username(username: str, user_id: int) -> str:
cleaned = cleaned[:MAX_USERNAME_LEN]
if cleaned:
return cleaned
# Fall back to a deterministic, non-empty username.
return f"kw_{user_id}"
def sanitize_username(raw: str) -> str:
# Normalize to lowercase and replace disallowed characters.
raw = (raw or "").lower()
raw = _SANITIZE_RE.sub("_", raw)
raw = raw.strip("-_")
if raw.startswith("-"):
# Avoid leading dash, which can be interpreted as a CLI flag.
return "kw" + raw
return raw