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
@@ -73,6 +73,9 @@ type Config struct {
|
||||
OIDCClientID string // ECHOLOT_OIDC_CLIENT_ID / --oidc-client-id
|
||||
OIDCAdminGroup string // ECHOLOT_OIDC_ADMIN_GROUP / --oidc-admin-group
|
||||
|
||||
// Break-glass admin username; the password lives hashed in the state store.
|
||||
AdminUser string // ECHOLOT_ADMIN_USER / --admin-user
|
||||
|
||||
// Mode
|
||||
Docker bool // --docker (or autodetected; env ECHOLOT_DOCKER=1 forces)
|
||||
}
|
||||
@@ -131,6 +134,9 @@ func Load(args []string) (*Config, *Actions, error) {
|
||||
|
||||
fs.BoolVar(&a.InstallSystemd, "install-systemd", false, "install a systemd unit for this binary and exit")
|
||||
fs.BoolVar(&a.UninstallSystemd, "uninstall-systemd", false, "remove the systemd unit and exit")
|
||||
fs.BoolVar(&a.SetAdminPassword, "set-admin-password", false,
|
||||
"set the break-glass admin password (username as --admin-user, password read from stdin) and exit")
|
||||
fs.StringVar(&c.AdminUser, "admin-user", envOr("ADMIN_USER", "admin"), "username for the break-glass admin")
|
||||
fs.BoolVar(&a.SelfUpdate, "self-update", false, "check for a newer release, replace this binary, and exit")
|
||||
fs.BoolVar(&a.Version, "version", false, "print version and exit")
|
||||
|
||||
@@ -165,6 +171,7 @@ type Actions struct {
|
||||
InstallSystemd bool
|
||||
UninstallSystemd bool
|
||||
SelfUpdate bool
|
||||
SetAdminPassword bool
|
||||
Version bool
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user