Ephemeral keys for xterm.js. Initial rework of audit logging. All endpoints now return a 401 regardless of presence if not logged in.

This commit is contained in:
2026-02-03 08:26:37 +00:00
parent 3e17d6412c
commit 667b02f0c3
28 changed files with 1546 additions and 181 deletions

View File

@@ -1,17 +1,140 @@
import json
from django import forms
from django.contrib import admin
from unfold.admin import ModelAdmin
from unfold.decorators import action # type: ignore
from .matching import list_api_endpoint_suggestions, list_websocket_endpoint_suggestions
from .models import AuditEventType, AuditLog
class AuditEventTypeAdminForm(forms.ModelForm):
endpoints_text = forms.CharField(
required=False,
widget=forms.Textarea(
attrs={
"rows": 8,
"placeholder": "/api/v1/servers/\nGET /api/v1/servers/<int:server_id>/\n/ws/servers/*/shell/",
}
),
help_text=(
"One endpoint pattern per line. Supports '*' wildcards and optional METHOD prefixes "
"like 'GET /api/v1/servers/*'."
),
label="Endpoint patterns",
)
ip_whitelist_text = forms.CharField(
required=False,
widget=forms.Textarea(attrs={"rows": 4, "placeholder": "10.0.0.1\n192.168.1.0/24"}),
help_text="One IP address or CIDR range per line.",
label="IP whitelist entries",
)
ip_blacklist_text = forms.CharField(
required=False,
widget=forms.Textarea(attrs={"rows": 4, "placeholder": "203.0.113.10\n198.51.100.0/24"}),
help_text="One IP address or CIDR range per line.",
label="IP blacklist entries",
)
class Meta:
model = AuditEventType
fields = (
"key",
"title",
"description",
"kind",
"default_severity",
"endpoints_text",
"ip_whitelist_enabled",
"ip_whitelist_text",
"ip_blacklist_enabled",
"ip_blacklist_text",
)
class Media:
js = ("audit/eventtype_form.js",)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
instance = kwargs.get("instance") or getattr(self, "instance", None)
if instance and instance.pk:
self.fields["endpoints_text"].initial = "\n".join(instance.endpoints or [])
self.fields["ip_whitelist_text"].initial = "\n".join(instance.ip_whitelist or [])
self.fields["ip_blacklist_text"].initial = "\n".join(instance.ip_blacklist or [])
self.fields["endpoints_text"].widget.attrs["data-api-suggestions"] = json.dumps(
list_api_endpoint_suggestions()
)
self.fields["endpoints_text"].widget.attrs["data-ws-suggestions"] = json.dumps(
list_websocket_endpoint_suggestions()
)
def _lines_to_list(self, value: str) -> list[str]:
results: list[str] = []
for line in (value or "").splitlines():
candidate = line.strip()
if candidate:
results.append(candidate)
return results
def clean_endpoints_text(self) -> str:
value = self.cleaned_data.get("endpoints_text", "")
# Normalize whitespace but keep the raw text for display.
lines = self._lines_to_list(value)
return "\n".join(lines)
def save(self, commit: bool = True):
instance: AuditEventType = super().save(commit=False)
endpoints_text = self.cleaned_data.get("endpoints_text", "")
whitelist_text = self.cleaned_data.get("ip_whitelist_text", "")
blacklist_text = self.cleaned_data.get("ip_blacklist_text", "")
instance.endpoints = self._lines_to_list(endpoints_text)
instance.ip_whitelist = self._lines_to_list(whitelist_text)
instance.ip_blacklist = self._lines_to_list(blacklist_text)
if commit:
instance.save()
return instance
@admin.register(AuditEventType)
class AuditEventTypeAdmin(ModelAdmin):
list_display = ("key", "title", "default_severity", "created_at")
search_fields = ("key", "title", "description")
list_filter = ("default_severity",)
form = AuditEventTypeAdminForm
list_display = ("key", "title", "kind", "default_severity", "created_at")
search_fields = ("key", "title", "description", "endpoints")
list_filter = ("kind", "default_severity", "ip_whitelist_enabled", "ip_blacklist_enabled")
ordering = ("key",)
compressed_fields = True
fieldsets = (
(
"Event Type",
{
"fields": (
"key",
"title",
"description",
"kind",
"default_severity",
)
},
),
(
"Endpoints",
{
"fields": ("endpoints_text",),
"description": "Only matching endpoints will create audit events.",
},
),
(
"IP Controls",
{
"fields": (
"ip_whitelist_enabled",
"ip_whitelist_text",
"ip_blacklist_enabled",
"ip_blacklist_text",
),
},
),
)
@admin.register(AuditLog)
@@ -87,5 +210,3 @@ class AuditLogAdmin(ModelAdmin):
{"fields": ("metadata",)},
),
)