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>
This commit is contained in:
co-authored by
Claude Fable 5
parent
ce6d0c2f64
commit
89a5ff9139
@@ -0,0 +1,34 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user