bastion 0.60.0
ci / personal-data (push) Successful in 4s
ci / test (push) Successful in 3m36s
ci / fuzz (push) Successful in 3m35s

This commit is contained in:
Privatae
2026-11-02 00:10:58 +00:00
parent 4a5c310482
commit 4f8285ef18
100 changed files with 11537 additions and 840 deletions
+99
View File
@@ -0,0 +1,99 @@
package main
// owner_account.go — on Linux and macOS, the one account the machine's
// bastion opens its console to without sudo (consolelane.go).
//
// Installed for the whole machine, the bastion runs as root and its console
// signs in with root's ui.secret. The person who installed it works in their
// own account, and before this record they reached the console only through
// sudo. The record names that account by uid, in the service's own state,
// where a process running as the person can neither read nor write it.
//
// WHO WRITES IT. The installer's root step, from the account sudo ran it for
// (SUDO_UID), or an administrator by hand: `approvals-owner --account <name>`.
// The rules are approvals_owner.go's for the Windows SID: the same account
// again changes nothing; another account is refused unless --replace is
// typed at a terminal; a record that cannot be read is never quietly replaced.
import (
"encoding/json"
"errors"
"fmt"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"time"
)
// lookupOwnerAccount finds an account by name, or by uid when given a number.
var lookupOwnerAccount = func(who string) (*user.User, error) {
if _, err := strconv.ParseUint(who, 10, 32); err == nil {
return user.LookupId(who)
}
return user.Lookup(who)
}
// privatae-bastion approvals-owner --account <name|uid> [--replace]
func cmdOwnerAccount(who string, replace bool) error {
if err := requireMachineAuthority("approvals-owner"); err != nil {
return err
}
if why := ownerServiceState(); why != "" {
return fmt.Errorf("approvals-owner records the owner in the system service's state: %s — nothing was changed", why)
}
who = strings.TrimSpace(who)
u, err := lookupOwnerAccount(who)
if err != nil {
return fmt.Errorf("no account %q on this machine — nothing was changed", who)
}
n, err := strconv.ParseUint(u.Uid, 10, 32)
if err != nil {
return fmt.Errorf("account %s has a uid this system does not use (%q) — nothing was changed", u.Username, u.Uid)
}
uid := uint32(n)
if uid == 0 {
return errors.New("root administers this machine already and opens the console with its own key; " +
"name the person's own account — nothing was changed")
}
if replace && !ownerConsole() {
return errors.New("approvals-owner --replace hands this machine's console to another account, and is taken only " +
"at a terminal; this one's input or output goes to a file or a pipe — nothing was changed")
}
cur, curName, present, err := ownerAccountLoad()
switch {
case err == nil && present && cur == uid:
fmt.Printf("this machine's owner account is already %s\n", u.Username)
return nil
case present && !replace:
was := curName
if was == "" {
was = "uid " + strconv.FormatUint(uint64(cur), 10)
}
if err != nil {
was = "an account its record no longer names readably (" + err.Error() + ")"
}
return fmt.Errorf("this machine's owner account is %s, and stays so — nothing was changed.\n"+
" To hand it to %s, run at a terminal: sudo -H privatae-bastion approvals-owner --account %s --replace",
was, u.Username, u.Username)
}
p, err := approvalsOwnerPath()
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(p), 0o700); err != nil {
return err
}
raw, err := json.MarshalIndent(approvalsOwnerRecord{UID: &uid, Account: u.Username,
SetAt: time.Now().UTC().Format(time.RFC3339)}, "", " ")
if err != nil {
return err
}
if err := atomicWrite(p, raw); err != nil {
return err
}
fmt.Printf("this machine's owner account is %s: `privatae-bastion ui` opens the console from it.\n"+
" Anything that stops an agent or widens access still takes the owner's code.\n", u.Username)
return nil
}