167 lines
7.1 KiB
Go
167 lines
7.1 KiB
Go
package main
|
|
|
|
// The machine-authority gate, in ONE place.
|
|
//
|
|
// Five call sites used to ask `os.Geteuid() != 0` directly: set-mode networked
|
|
// (mode.go), claim-anchor and claim-anchor --repin (anchor.go), claim-owner
|
|
// (owner.go), and unpair-on-a-networked-box (config.go). On Windows that
|
|
// expression is always true — Go hardcodes Geteuid to -1 there — so all five
|
|
// were permanently refused, and a Windows machine could never become a networked
|
|
// bastion or be anchored. Nobody chose that; it fell out of a Unix idiom used
|
|
// where the platform has no such concept.
|
|
//
|
|
// So the question is asked through processIsRoot(), which each platform answers
|
|
// in its own terms (euid 0 on Unix; elevated + Administrators on Windows,
|
|
// privilege_windows.go), and the REFUSAL text is written once, here, so it tells
|
|
// the user what to actually do on the machine they are actually on.
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
)
|
|
|
|
// adminHowTo is the platform's instruction for "do this with machine authority".
|
|
// Kept beside the check so a new gate cannot invent its own wording, and so the
|
|
// Unix text never reaches a Windows user (telling someone to run `sudo` on
|
|
// Windows is how a real instruction becomes noise).
|
|
func adminHowTo() string { return adminHowToFor(runtime.GOOS) }
|
|
|
|
func adminHowToFor(goos string) string {
|
|
if goos == "windows" {
|
|
return "run this from an Administrator terminal (right-click → Run as administrator)"
|
|
}
|
|
return "run it with `sudo -H`"
|
|
}
|
|
|
|
// machineAuthorityHeld is processIsRoot, held in a variable so a test can
|
|
// assert both sides of every machine-authority gate whatever euid the suite
|
|
// runs under. Nothing outside tests assigns it.
|
|
var machineAuthorityHeld = processIsRoot
|
|
|
|
// requireMachineAuthority returns nil when this process administers the machine,
|
|
// and otherwise an error naming the ACT being refused and how to retry it.
|
|
//
|
|
// `act` reads into "X must run with machine authority" — pass the command, e.g.
|
|
// "claim-anchor".
|
|
func requireMachineAuthority(act string) error {
|
|
if machineAuthorityHeld() {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%s needs machine authority — whoever administers this box is what stands behind it; %s",
|
|
act, adminHowTo())
|
|
}
|
|
|
|
// anchorCommandHint is the claim-anchor line to print after `set-mode
|
|
// networked` succeeded — the same command on both platforms, but reached
|
|
// differently, so the prefix has to match where the user actually is.
|
|
//
|
|
// NO `<token>`. It said `claim-anchor <token>` and kept saying it after the
|
|
// token became optional, so the one instruction the user is handed at the exact
|
|
// moment they need it still sent them looking for a credential that no longer
|
|
// exists. A usage string is not documentation about the command, it is the
|
|
// command — when they disagree the string wins, because it is what gets typed.
|
|
func anchorCommandHint() string { return anchorCommandHintFor(runtime.GOOS) }
|
|
|
|
func anchorCommandHintFor(goos string) string {
|
|
if goos == "windows" {
|
|
return "privatae-bastion claim-anchor (from this Administrator terminal)"
|
|
}
|
|
return "sudo -H privatae-bastion claim-anchor"
|
|
}
|
|
|
|
// setModeCommandHint is the same shape for `set-mode`, and it exists because
|
|
// the setup page hardcoded the unix spelling with no platform branch — so a
|
|
// Windows user reading the anchor panel was told to run `sudo -H`. `sudo` is
|
|
// not a command on Windows; the line was not merely unidiomatic, it was
|
|
// untypeable.
|
|
//
|
|
// -H on unix is not decoration either: set-mode writes through configDir()
|
|
// (mode.go), which is $HOME-derived, so a bare `sudo` can land the mode file in
|
|
// the invoking user's directory while the root daemon reads its own and sees no
|
|
// change. That is the same hazard fixed one command over for claim-owner,
|
|
// and main.go printed the un--H form for BOTH.
|
|
func setModeCommandHint() string { return setModeCommandHintFor(runtime.GOOS) }
|
|
|
|
func setModeCommandHintFor(goos string) string {
|
|
if goos == "windows" {
|
|
return "privatae-bastion set-mode networked (from this Administrator terminal)"
|
|
}
|
|
return "sudo -H privatae-bastion set-mode networked"
|
|
}
|
|
|
|
// runCommandHint is how to START the bastion on THIS machine, said the way the
|
|
// operator would type it. Separate from restartHint, which is about a change
|
|
// taking effect on a daemon that is already up.
|
|
func runCommandHint() string {
|
|
if systemService.installed && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
|
|
return serviceRunHintFor(runtime.GOOS)
|
|
}
|
|
return runCommandHintFor(runtime.GOOS)
|
|
}
|
|
|
|
func runCommandHintFor(goos string) string {
|
|
if goos == "windows" {
|
|
return "privatae-bastion run"
|
|
}
|
|
return "systemctl start privatae-bastion"
|
|
}
|
|
|
|
// restartHint is how to bring the daemon back on THIS machine after a change
|
|
// that only takes at startup.
|
|
//
|
|
// It said `systemctl restart privatae-bastion` unconditionally — the third
|
|
// Linux instruction printed at a Windows user inside one command's output,
|
|
// after `sudo` in the machine-authority error and PowerShell at every Mac in
|
|
// the dashboard. Windows has no systemctl, and an ordinary install has no
|
|
// service: deploy/install.ps1 installs the exe and registers a logon task. So
|
|
// the honest instruction there is to start it again. A networked install on
|
|
// macOS or Windows runs as a system service (sysservice.go), and for that
|
|
// machine restartHint names the service manager's own restart instead.
|
|
//
|
|
// TAKES THE PLATFORM AS AN ARGUMENT, and that is the point. The first version
|
|
// branched on runtime.GOOS directly, so the only machine that could test the
|
|
// Windows string was a Windows machine — a mutation reverting this exact fix
|
|
// passed clean on the Linux builder. That is the same hole that let 'darwin'
|
|
// match 'win' in the dashboard: a platform branch nothing off-platform can
|
|
// reach is a platform branch nothing checks.
|
|
func restartHint() string {
|
|
if systemService.installed && (runtime.GOOS == "darwin" || runtime.GOOS == "windows") {
|
|
return serviceRestartHintFor(runtime.GOOS)
|
|
}
|
|
return restartHintFor(runtime.GOOS)
|
|
}
|
|
|
|
func restartHintFor(goos string) string {
|
|
if goos == "windows" {
|
|
return "Start the bastion again so the change takes: privatae-bastion run\n" +
|
|
" (Windows has no service for it — it runs in a terminal, so close the old one first.)"
|
|
}
|
|
return "Restart the service so the change takes: systemctl restart privatae-bastion"
|
|
}
|
|
|
|
// stepupEnrollHint is the stepup-enroll line `pair` prints for a pairing with
|
|
// no second factor. It is run as whoever ran `pair`, since that is whose state
|
|
// the pairing is in: with machine authority (an anchored or networked machine,
|
|
// a root install) through sudo -H or this Administrator terminal, and as the
|
|
// person's own account on a local install. --host is named when more than one
|
|
// host is paired, as stepup-enroll then requires.
|
|
func stepupEnrollHint(host string, paired int) string {
|
|
return stepupEnrollHintFor(runtime.GOOS, machineAuthorityHeld(), host, paired)
|
|
}
|
|
|
|
func stepupEnrollHintFor(goos string, authority bool, host string, paired int) string {
|
|
cmd := "privatae-bastion stepup-enroll"
|
|
if paired > 1 {
|
|
cmd += " --host " + host
|
|
}
|
|
switch {
|
|
case goos == "windows" && authority:
|
|
return cmd + " (from this Administrator terminal)"
|
|
case goos == "windows":
|
|
return cmd
|
|
case authority:
|
|
return "sudo -H " + cmd
|
|
}
|
|
return cmd
|
|
}
|