Files
mrambossekandClaude Fable 5 89a5ff9139
server-test / test (push) Successful in 44s
adminauth: a break-glass local admin alongside OIDC
If the IdP is misconfigured, unreachable, or the admin group is a typo, the
operator is locked out of their own server with no way back short of editing
JSON on disk. A fallback that only matters when everything else is broken is
exactly the thing you cannot add later - by then you cannot get in to add it.

Stored as PBKDF2-HMAC-SHA256 from the standard library (Go 1.24+ has it, so no
dependency), 600k iterations, per-credential salt. A password rather than a
bearer token on purpose: a break-glass credential is the one most likely to end
up in a backup or a config-management repo, and a hash survives that where a
token does not. There is no email reset flow and should not be -
--set-admin-password on the host is the reset, and whoever can run it already
has the machine.

The password is read from stdin, never a flag, so it stays out of shell history
and the process list; piping still works for automation.

Details the tests pin, each for a reason:
  - the username is compared in constant time too, or a fast rejection is a
    timing oracle for which usernames exist;
  - the *stored* iteration count is used, so raising the constant later does not
    lock out existing passwords;
  - the throttle grows with consecutive failures but stays bounded and forgives
    after a quiet minute - a break-glass credential an attacker can lock out is
    a denial of service against the one person who needs it;
  - sessions are MAC-checked before anything in them is read, and rotating the
    secret invalidates every one at once, which is how they are revoked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-01 17:12:54 +02:00

35 lines
1019 B
Go

// SPDX-FileCopyrightText: 2026 Echolot contributors
// SPDX-License-Identifier: GPL-3.0-or-later
//go:build linux
package system
import (
"os"
"syscall"
"unsafe"
)
// DisableEcho turns off terminal echo while a password is typed, returning a function that puts
// the terminal back. Both are best-effort: when stdin is a pipe (the automation case) there is
// no terminal to change and nothing to restore.
func DisableEcho(f *os.File) (func(), error) {
fd := f.Fd()
var t syscall.Termios
if _, _, errno := syscall.Syscall6(syscall.SYS_IOCTL, fd,
syscall.TCGETS, uintptr(unsafe.Pointer(&t)), 0, 0, 0); errno != 0 {
return nil, errno // not a terminal; nothing to do
}
original := t
t.Lflag &^= syscall.ECHO
if _, _, errno := syscall.Syscall6(syscall.SYS_IOCTL, fd,
syscall.TCSETS, uintptr(unsafe.Pointer(&t)), 0, 0, 0); errno != 0 {
return nil, errno
}
return func() {
_, _, _ = syscall.Syscall6(syscall.SYS_IOCTL, fd,
syscall.TCSETS, uintptr(unsafe.Pointer(&original)), 0, 0, 0)
}, nil
}