Authelia runs as a managed native service on Wild Central, providing two integration patterns for network services: - Forward-auth via HAProxy for apps without native SSO (Lua auth-request script intercepts requests, redirects unauthenticated users to login portal) - OIDC provider for apps with native support (Gitea, Grafana, etc.) Backend: new internal/authelia/ package with service manager, config generation, file-based user management (argon2id), and OIDC client management. API endpoints for status, config, users CRUD, and OIDC clients CRUD. HAProxy: config generator extended with lua-load, auth-request directives, and Authelia backend. Auth directives scoped to session cookie domain — only subdomains of the auth portal's parent are eligible for forward-auth. Frontend: Authentication page with enable/disable, user management, OIDC client management (add/edit/delete), and protected domains toggles. Advanced subsystem page for raw config view. Dashboard service card and sidebar entries.
157 lines
4.3 KiB
Go
157 lines
4.3 KiB
Go
// Package authelia manages the Authelia authentication service.
|
|
// Authelia provides centralized authentication for network services via
|
|
// HAProxy forward-auth and acts as an OIDC provider for apps with native support.
|
|
package authelia
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Status represents the current state of the Authelia service
|
|
type Status struct {
|
|
Active bool `json:"active"`
|
|
Version string `json:"version,omitempty"`
|
|
PID int `json:"pid,omitempty"`
|
|
LastRestart time.Time `json:"lastRestart,omitempty"`
|
|
}
|
|
|
|
// Manager handles Authelia configuration and service lifecycle
|
|
type Manager struct {
|
|
dataDir string // e.g. /var/lib/wild-central/authelia
|
|
}
|
|
|
|
// NewManager creates a new Authelia manager
|
|
func NewManager(dataDir string) *Manager {
|
|
return &Manager{dataDir: dataDir}
|
|
}
|
|
|
|
// EnsureDataDir creates the Authelia data directory if it doesn't exist
|
|
func (m *Manager) EnsureDataDir() error {
|
|
if err := os.MkdirAll(m.dataDir, 0700); err != nil {
|
|
return fmt.Errorf("creating authelia data dir: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// IsInstalled checks if the authelia binary exists on PATH
|
|
func (m *Manager) IsInstalled() bool {
|
|
_, err := exec.LookPath("authelia")
|
|
return err == nil
|
|
}
|
|
|
|
const luaScriptPath = "/etc/haproxy/lua/haproxy-auth-request.lua"
|
|
|
|
// HasLuaScript checks if the HAProxy auth-request Lua script is installed
|
|
func (m *Manager) HasLuaScript() bool {
|
|
info, err := os.Stat(luaScriptPath)
|
|
return err == nil && info.Size() > 0
|
|
}
|
|
|
|
// GetStatus returns the current Authelia service status
|
|
func (m *Manager) GetStatus() (*Status, error) {
|
|
status := &Status{}
|
|
|
|
cmd := exec.Command("systemctl", "is-active", "--quiet", "authelia")
|
|
status.Active = cmd.Run() == nil
|
|
|
|
if status.Active {
|
|
if out, err := exec.Command("systemctl", "show", "authelia.service", "--property=MainPID").Output(); err == nil {
|
|
parts := strings.Split(strings.TrimSpace(string(out)), "=")
|
|
if len(parts) == 2 {
|
|
if pid, err := strconv.Atoi(parts[1]); err == nil {
|
|
status.PID = pid
|
|
}
|
|
}
|
|
}
|
|
|
|
if out, err := exec.Command("systemctl", "show", "authelia.service", "--property=ActiveEnterTimestamp").Output(); err == nil {
|
|
parts := strings.Split(strings.TrimSpace(string(out)), "=")
|
|
if len(parts) == 2 {
|
|
if t, err := time.Parse("Mon 2006-01-02 15:04:05 MST", parts[1]); err == nil {
|
|
status.LastRestart = t
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if out, err := exec.Command("authelia", "--version").Output(); err == nil {
|
|
s := string(out)
|
|
if i := strings.Index(s, "version "); i >= 0 {
|
|
v := s[i+8:]
|
|
if j := strings.IndexByte(v, ' '); j > 0 {
|
|
v = v[:j]
|
|
}
|
|
status.Version = strings.TrimSpace(v)
|
|
}
|
|
}
|
|
|
|
return status, nil
|
|
}
|
|
|
|
// RestartService restarts the Authelia systemd service
|
|
func (m *Manager) RestartService() error {
|
|
cmd := exec.Command("systemctl", "restart", "authelia.service")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to restart authelia: %w (output: %s)", err, string(output))
|
|
}
|
|
slog.Info("authelia service restarted", "component", "authelia")
|
|
return nil
|
|
}
|
|
|
|
// StopService stops the Authelia systemd service
|
|
func (m *Manager) StopService() error {
|
|
cmd := exec.Command("systemctl", "stop", "authelia.service")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stop authelia: %w (output: %s)", err, string(output))
|
|
}
|
|
slog.Info("authelia service stopped", "component", "authelia")
|
|
return nil
|
|
}
|
|
|
|
// ReadConfig reads the current Authelia configuration file
|
|
func (m *Manager) ReadConfig() (string, error) {
|
|
data, err := os.ReadFile(m.configPath())
|
|
if err != nil {
|
|
return "", fmt.Errorf("reading authelia config: %w", err)
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// DataDir returns the Authelia data directory path
|
|
func (m *Manager) DataDir() string {
|
|
return m.dataDir
|
|
}
|
|
|
|
func (m *Manager) configPath() string {
|
|
return filepath.Join(m.dataDir, "configuration.yml")
|
|
}
|
|
|
|
func (m *Manager) usersDBPath() string {
|
|
return filepath.Join(m.dataDir, "users_database.yml")
|
|
}
|
|
|
|
func (m *Manager) sqlitePath() string {
|
|
return filepath.Join(m.dataDir, "db.sqlite3")
|
|
}
|
|
|
|
func (m *Manager) jwksPath() string {
|
|
return filepath.Join(m.dataDir, "jwks.pem")
|
|
}
|
|
|
|
func (m *Manager) notificationPath() string {
|
|
return filepath.Join(m.dataDir, "notification.txt")
|
|
}
|
|
|
|
func (m *Manager) oidcClientsPath() string {
|
|
return filepath.Join(m.dataDir, "oidc_clients.yml")
|
|
}
|