feat: add Bastillion-style SSH key enforcement worker
This commit is contained in:
@@ -39,6 +39,7 @@ import (
|
||||
"git.techniverse.net/scriptos/keywarden/internal/models"
|
||||
"git.techniverse.net/scriptos/keywarden/internal/security"
|
||||
"git.techniverse.net/scriptos/keywarden/internal/servers"
|
||||
"git.techniverse.net/scriptos/keywarden/internal/worker"
|
||||
)
|
||||
|
||||
// sessionData holds session metadata for timeout tracking
|
||||
@@ -56,6 +57,7 @@ type Handler struct {
|
||||
deploy *deploy.Service
|
||||
audit *audit.Service
|
||||
cron *cron.Service
|
||||
worker *worker.Service
|
||||
mail *mail.Service
|
||||
db *database.DB // direct database access for backup/restore
|
||||
templates map[string]*template.Template
|
||||
@@ -169,6 +171,9 @@ type PageData struct {
|
||||
|
||||
// System Information
|
||||
SystemInfo *SystemInfo
|
||||
|
||||
// Key Enforcement
|
||||
EnforcementStatus map[string]string
|
||||
}
|
||||
|
||||
// SystemInfo holds runtime system information for the settings page
|
||||
@@ -242,7 +247,7 @@ func formatUptime(start time.Time) string {
|
||||
}
|
||||
|
||||
// New creates a new Handler
|
||||
func New(authSvc *auth.Service, keysSvc *keys.Service, serversSvc *servers.Service, deploySvc *deploy.Service, auditSvc *audit.Service, cronSvc *cron.Service, mailSvc *mail.Service, db *database.DB, templateFS embed.FS, staticFS embed.FS, dataDir string, secureCookies bool, baseURL string) *Handler {
|
||||
func New(authSvc *auth.Service, keysSvc *keys.Service, serversSvc *servers.Service, deploySvc *deploy.Service, auditSvc *audit.Service, cronSvc *cron.Service, workerSvc *worker.Service, mailSvc *mail.Service, db *database.DB, templateFS embed.FS, staticFS embed.FS, dataDir string, secureCookies bool, baseURL string) *Handler {
|
||||
// Create sub-FS so /static/css/... maps to static/css/... in embed
|
||||
staticSub, err := fs.Sub(staticFS, "static")
|
||||
if err != nil {
|
||||
@@ -262,6 +267,7 @@ func New(authSvc *auth.Service, keysSvc *keys.Service, serversSvc *servers.Servi
|
||||
deploy: deploySvc,
|
||||
audit: auditSvc,
|
||||
cron: cronSvc,
|
||||
worker: workerSvc,
|
||||
mail: mailSvc,
|
||||
db: db,
|
||||
sessions: make(map[string]*sessionData),
|
||||
@@ -432,6 +438,7 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/admin/masterkey/regenerate", h.requireOwner(h.handleMasterKeyRegenerate))
|
||||
mux.HandleFunc("/admin/backup/export", h.requireOwner(h.handleBackupExport))
|
||||
mux.HandleFunc("/admin/backup/import", h.requireOwner(h.handleBackupImport))
|
||||
mux.HandleFunc("/admin/enforcement/run", h.requireOwner(h.handleEnforcementRunNow))
|
||||
}
|
||||
|
||||
// handleAPIHealth returns a JSON health status (no auth required).
|
||||
@@ -2993,6 +3000,7 @@ func (h *Handler) handleAdminSettings(w http.ResponseWriter, r *http.Request) {
|
||||
EmailEnabled: h.mail.IsEnabled(),
|
||||
MasterKeyPublic: masterPub,
|
||||
MasterKeyFingerprint: masterFP,
|
||||
EnforcementStatus: h.worker.GetStatus(),
|
||||
}
|
||||
|
||||
// Check for flash message from query parameters (e.g. after backup restore)
|
||||
@@ -3047,6 +3055,31 @@ func (h *Handler) handleAdminSettings(w http.ResponseWriter, r *http.Request) {
|
||||
if len(changed) > 0 {
|
||||
h.audit.Log(userID, audit.ActionPasswordPolicyChanged, fmt.Sprintf("Security settings updated: %s", strings.Join(changed, ", ")), clientIP(r))
|
||||
}
|
||||
case "enforcement_settings":
|
||||
// Key enforcement settings
|
||||
batch := make(map[string]string)
|
||||
enforceMode := r.FormValue("enforce_mode")
|
||||
if enforceMode == "" {
|
||||
enforceMode = "disabled"
|
||||
}
|
||||
batch["enforce_mode"] = enforceMode
|
||||
changed = append(changed, "enforce_mode="+enforceMode)
|
||||
|
||||
enforceInterval := r.FormValue("enforce_interval")
|
||||
if enforceInterval == "" {
|
||||
enforceInterval = "15"
|
||||
}
|
||||
batch["enforce_interval"] = enforceInterval
|
||||
changed = append(changed, "enforce_interval="+enforceInterval)
|
||||
|
||||
if err := h.auth.SetSettingsBatch(batch); err != nil {
|
||||
logging.Error("Failed to save enforcement settings: %v", err)
|
||||
http.Redirect(w, r, "/admin/settings?flash_type=danger&flash_msg="+url.QueryEscape("Failed to save enforcement settings: "+err.Error()), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
if len(changed) > 0 {
|
||||
h.audit.Log(userID, audit.ActionEnforcementSettings, fmt.Sprintf("Enforcement settings updated: %s", strings.Join(changed, ", ")), clientIP(r))
|
||||
}
|
||||
default:
|
||||
// Application settings (existing behavior)
|
||||
batch := make(map[string]string)
|
||||
@@ -3124,6 +3157,22 @@ func (h *Handler) handleMasterKeyRegenerate(w http.ResponseWriter, r *http.Reque
|
||||
http.Redirect(w, r, "/admin/settings?flash_type=success&flash_msg="+url.QueryEscape("System master key successfully regenerated."), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// handleEnforcementRunNow triggers an immediate key enforcement run (owner only)
|
||||
func (h *Handler) handleEnforcementRunNow(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Redirect(w, r, "/admin/settings", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
userID := h.getUserID(r)
|
||||
logging.Info("Key enforcement manual run triggered by user_id=%d", userID)
|
||||
h.audit.Log(userID, audit.ActionEnforcementRun, "Manual key enforcement run triggered", clientIP(r))
|
||||
|
||||
h.worker.RunNow()
|
||||
|
||||
http.Redirect(w, r, "/admin/settings?flash_type=success&flash_msg="+url.QueryEscape("Key enforcement run started. Check the audit log for results."), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
// --- Cron Job Handlers ---
|
||||
|
||||
// handleAPICronAssignments returns assignments for a given user as JSON (for AJAX).
|
||||
|
||||
Reference in New Issue
Block a user