diff --git a/.changelog/4464.cfg.md b/.changelog/4464.cfg.md new file mode 100644 index 00000000000..a177f777e45 --- /dev/null +++ b/.changelog/4464.cfg.md @@ -0,0 +1,11 @@ +go/oasis-node/cmd/node: Do not allow running oasis-node as root + +Nothing in oasis-node will ever require elevated privileges. Attempting +to run a node as the root user will now terminate the node immediately +on startup. While there may be specific circumstances where it is safe +to run network services with the effective user ID set to 0, the +overwelming majority of cases where this is done is misconfiguration. + +If the previous behavior is required, the binary must be run in +unsafe/debug mode (via the intentionally undocumented flag), and +`debug.allow_root` must also be set. diff --git a/.changelog/4464.feature.md b/.changelog/4464.feature.md new file mode 100644 index 00000000000..ae2051bd701 --- /dev/null +++ b/.changelog/4464.feature.md @@ -0,0 +1,6 @@ +go/oasis-node/cmd/node: Do not allow running oasis-node as root + +Running network accessible services as the root user is extremely +bad for system security as a general rule. While it would be "ok" +if we can drop privileges, `syscall.AllThreadsSyscall` does not +work if the binary uses cgo at all. diff --git a/go/oasis-node/cmd/common/common.go b/go/oasis-node/cmd/common/common.go index ab9d973bdc8..9622afa3148 100644 --- a/go/oasis-node/cmd/common/common.go +++ b/go/oasis-node/cmd/common/common.go @@ -320,3 +320,11 @@ func SetBasicVersionTemplate(cmd *cobra.Command) { Go toolchain version: {{ toolchain }} `) } + +// IsNotRootOrAllowed returns if the current user is allowed to run a node, +// and if the effective user id is elevated or not. +func IsNotRootOrAllowed() (canRun bool, isRoot bool) { + isRoot = os.Geteuid() == 0 + canRun = !isRoot || flags.DebugAllowRoot() + return +} diff --git a/go/oasis-node/cmd/common/flags/flags.go b/go/oasis-node/cmd/common/flags/flags.go index b4f6d3192f4..012ae5887b5 100644 --- a/go/oasis-node/cmd/common/flags/flags.go +++ b/go/oasis-node/cmd/common/flags/flags.go @@ -14,6 +14,9 @@ const ( // CfgDebugTestEntity is the command line flag to enable the debug test // entity. CfgDebugTestEntity = "debug.test_entity" + // CfgDebugAllowRoot is the command line flag to enable running the node + // as root. + CfgDebugAllowRoot = "debug.allow_root" // CfgGenesisFile is the flag used to specify a genesis file. CfgGenesisFile = "genesis.file" // CfgConsensusValidator is the flag used to opt-in to being a validator. @@ -38,6 +41,8 @@ var ( ForceFlags = flag.NewFlagSet("", flag.ContinueOnError) // DebugTestEntityFlags has the test entity enable flag. DebugTestEntityFlags = flag.NewFlagSet("", flag.ContinueOnError) + // DebugAllowRootFlag has the root enable flag. + DebugAllowRootFlag = flag.NewFlagSet("", flag.ContinueOnError) // GenesisFileFlags has the genesis file flag. GenesisFileFlags = flag.NewFlagSet("", flag.ContinueOnError) @@ -75,6 +80,11 @@ func DebugTestEntity() bool { return DebugDontBlameOasis() && viper.GetBool(CfgDebugTestEntity) } +// DebugAllowRoot returns true iff the root account enable flag is set. +func DebugAllowRoot() bool { + return DebugDontBlameOasis() && viper.GetBool(CfgDebugAllowRoot) +} + // GenesisFile returns the set genesis file. func GenesisFile() string { return viper.GetString(CfgGenesisFile) @@ -107,9 +117,12 @@ func init() { GenesisFileFlags.StringP(CfgGenesisFile, "g", "genesis.json", "path to genesis file") - DebugDontBlameOasisFlag.Bool(CfgDebugDontBlameOasis, false, "Enable debug/unsafe/insecure options") + DebugDontBlameOasisFlag.Bool(CfgDebugDontBlameOasis, false, "enable debug/unsafe/insecure options") _ = DebugDontBlameOasisFlag.MarkHidden(CfgDebugDontBlameOasis) + DebugAllowRootFlag.Bool(CfgDebugAllowRoot, false, "allow running as root account") + _ = DebugAllowRootFlag.MarkHidden(CfgDebugAllowRoot) + DryRunFlag.BoolP(CfgDryRun, "n", false, "don't actually do anything, just show what will be done") AssumeYesFlag.BoolP(CfgAssumeYes, cfgAssumeYesShort, false, "automatically assume yes for all questions") @@ -118,6 +131,7 @@ func init() { VerboseFlags, ForceFlags, DebugTestEntityFlags, + DebugAllowRootFlag, GenesisFileFlags, ConsensusValidatorFlag, DebugDontBlameOasisFlag, diff --git a/go/oasis-node/cmd/debug/byzantine/byzantine.go b/go/oasis-node/cmd/debug/byzantine/byzantine.go index 8db58f8dde2..b79489677c6 100644 --- a/go/oasis-node/cmd/debug/byzantine/byzantine.go +++ b/go/oasis-node/cmd/debug/byzantine/byzantine.go @@ -310,6 +310,7 @@ func init() { byzantineCmd.PersistentFlags().AddFlagSet(flags.GenesisFileFlags) byzantineCmd.PersistentFlags().AddFlagSet(flags.DebugDontBlameOasisFlag) byzantineCmd.PersistentFlags().AddFlagSet(flags.DebugTestEntityFlags) + byzantineCmd.PersistentFlags().AddFlagSet(flags.DebugAllowRootFlag) byzantineCmd.PersistentFlags().AddFlagSet(grpc.ServerLocalFlags) byzantineCmd.PersistentFlags().AddFlagSet(grpc.ServerTCPFlags) byzantineCmd.PersistentFlags().AddFlagSet(p2p.Flags) diff --git a/go/oasis-node/cmd/ias/proxy.go b/go/oasis-node/cmd/ias/proxy.go index d9e2e5b2d4b..67258b19e32 100644 --- a/go/oasis-node/cmd/ias/proxy.go +++ b/go/oasis-node/cmd/ias/proxy.go @@ -269,5 +269,6 @@ func init() { proxyFlags.AddFlagSet(cmdGrpc.ClientFlags) proxyFlags.AddFlagSet(flags.GenesisFileFlags) proxyFlags.AddFlagSet(flags.DebugDontBlameOasisFlag) + proxyFlags.AddFlagSet(flags.DebugAllowRootFlag) proxyFlags.AddFlagSet(pprof.Flags) } diff --git a/go/oasis-node/cmd/node/node.go b/go/oasis-node/cmd/node/node.go index 5bc5d5899e7..80a9c1a4dee 100644 --- a/go/oasis-node/cmd/node/node.go +++ b/go/oasis-node/cmd/node/node.go @@ -559,6 +559,18 @@ func NewNode() (node *Node, err error) { // nolint: gocyclo return nil, errors.New("data directory not configured") } + // Check to see if the user has elevated privs or not. + canRun, isRoot := cmdCommon.IsNotRootOrAllowed() + if !canRun { + logger.Error("running with elevated privileges not allowed") + return nil, errors.New("running with elevated privileges not allowed") + } + if isRoot { + // The flags for allowing running as root must be set, warn. + // If something bad happens, Don't Blame Oasis. + logger.Warn("running with elevated privileges is NOT RECOMMENDED") + } + // Load configured values for all registered crash points. crash.LoadViperArgValues() @@ -733,6 +745,7 @@ func Register(parentCmd *cobra.Command) { func init() { Flags.AddFlagSet(flags.DebugTestEntityFlags) + Flags.AddFlagSet(flags.DebugAllowRootFlag) Flags.AddFlagSet(flags.ConsensusValidatorFlag) Flags.AddFlagSet(flags.GenesisFileFlags) diff --git a/go/oasis-node/node_test.go b/go/oasis-node/node_test.go index 2b420211331..33e73af6c6a 100644 --- a/go/oasis-node/node_test.go +++ b/go/oasis-node/node_test.go @@ -68,6 +68,7 @@ var ( {"log.format", "JSON"}, {cmdCommonFlags.CfgConsensusValidator, true}, {cmdCommonFlags.CfgDebugDontBlameOasis, true}, + {cmdCommonFlags.CfgDebugAllowRoot, true}, {storageWorker.CfgBackend, "badger"}, {runtimeRegistry.CfgRuntimeMode, string(runtimeRegistry.RuntimeModeCompute)}, {runtimeRegistry.CfgRuntimeProvisioner, runtimeRegistry.RuntimeProvisionerMock}, diff --git a/go/oasis-test-runner/cmd/root.go b/go/oasis-test-runner/cmd/root.go index 4da44354847..7e792fffcbf 100644 --- a/go/oasis-test-runner/cmd/root.go +++ b/go/oasis-test-runner/cmd/root.go @@ -625,6 +625,7 @@ func init() { } viper.Set(nodeFlags.CfgDebugDontBlameOasis, true) + viper.Set(nodeFlags.CfgDebugAllowRoot, true) viper.Set(nodeCommon.CfgDebugAllowTestKeys, true) }) } diff --git a/go/oasis-test-runner/oasis/args.go b/go/oasis-test-runner/oasis/args.go index db1f8cf7ac8..6df61c9fae8 100644 --- a/go/oasis-test-runner/oasis/args.go +++ b/go/oasis-test-runner/oasis/args.go @@ -113,6 +113,13 @@ func (args *argBuilder) debugDontBlameOasis() *argBuilder { return args } +func (args *argBuilder) debugAllowRoot() *argBuilder { + args.vec = append(args.vec, Argument{ + Name: flags.CfgDebugAllowRoot, + }) + return args +} + func (args *argBuilder) debugAllowTestKeys() *argBuilder { args.vec = append(args.vec, Argument{ Name: cmdCommon.CfgDebugAllowTestKeys, diff --git a/go/oasis-test-runner/oasis/byzantine.go b/go/oasis-test-runner/oasis/byzantine.go index 68c60ec7166..3dd4a959472 100644 --- a/go/oasis-test-runner/oasis/byzantine.go +++ b/go/oasis-test-runner/oasis/byzantine.go @@ -41,6 +41,7 @@ type ByzantineCfg struct { func (worker *Byzantine) AddArgs(args *argBuilder) error { args.debugDontBlameOasis(). + debugAllowRoot(). debugAllowTestKeys(). debugSetRlimit(). debugEnableProfiling(worker.Node.pprofPort). diff --git a/go/oasis-test-runner/oasis/client.go b/go/oasis-test-runner/oasis/client.go index 044d073fc77..372c46a4614 100644 --- a/go/oasis-test-runner/oasis/client.go +++ b/go/oasis-test-runner/oasis/client.go @@ -29,6 +29,7 @@ type ClientCfg struct { func (client *Client) AddArgs(args *argBuilder) error { args.debugDontBlameOasis(). + debugAllowRoot(). debugAllowTestKeys(). debugSetRlimit(). debugEnableProfiling(client.Node.pprofPort). diff --git a/go/oasis-test-runner/oasis/compute.go b/go/oasis-test-runner/oasis/compute.go index eceee626fb9..4728d73c40d 100644 --- a/go/oasis-test-runner/oasis/compute.go +++ b/go/oasis-test-runner/oasis/compute.go @@ -151,6 +151,7 @@ func (worker *Compute) AddArgs(args *argBuilder) error { defer worker.RUnlock() args.debugDontBlameOasis(). + debugAllowRoot(). debugAllowTestKeys(). debugSetRlimit(). debugEnableProfiling(worker.Node.pprofPort). diff --git a/go/oasis-test-runner/oasis/ias.go b/go/oasis-test-runner/oasis/ias.go index 3751257c042..648dc6bcafd 100644 --- a/go/oasis-test-runner/oasis/ias.go +++ b/go/oasis-test-runner/oasis/ias.go @@ -24,6 +24,7 @@ type iasProxy struct { func (ias *iasProxy) AddArgs(args *argBuilder) error { args.debugDontBlameOasis(). + debugAllowRoot(). debugAllowTestKeys(). debugSetRlimit(). grpcServerPort(ias.grpcPort). diff --git a/go/oasis-test-runner/oasis/keymanager.go b/go/oasis-test-runner/oasis/keymanager.go index 12dca6fa98f..779308c6671 100644 --- a/go/oasis-test-runner/oasis/keymanager.go +++ b/go/oasis-test-runner/oasis/keymanager.go @@ -254,6 +254,7 @@ func (km *Keymanager) AddArgs(args *argBuilder) error { } args.debugDontBlameOasis(). + debugAllowRoot(). debugAllowTestKeys(). debugSetRlimit(). debugEnableProfiling(km.Node.pprofPort). diff --git a/go/oasis-test-runner/oasis/seed.go b/go/oasis-test-runner/oasis/seed.go index e95e8f86401..2fe051f60a3 100644 --- a/go/oasis-test-runner/oasis/seed.go +++ b/go/oasis-test-runner/oasis/seed.go @@ -35,6 +35,7 @@ func (seed *Seed) AddArgs(args *argBuilder) error { } args.debugDontBlameOasis(). + debugAllowRoot(). debugAllowTestKeys(). debugSetRlimit(). workerCertificateRotation(true). diff --git a/go/oasis-test-runner/oasis/sentry.go b/go/oasis-test-runner/oasis/sentry.go index 37c0a08e08c..c4f1206183d 100644 --- a/go/oasis-test-runner/oasis/sentry.go +++ b/go/oasis-test-runner/oasis/sentry.go @@ -71,6 +71,7 @@ func (sentry *Sentry) AddArgs(args *argBuilder) error { } args.debugDontBlameOasis(). + debugAllowRoot(). debugAllowTestKeys(). debugSetRlimit(). debugEnableProfiling(sentry.Node.pprofPort). diff --git a/go/oasis-test-runner/oasis/validator.go b/go/oasis-test-runner/oasis/validator.go index aa9d8aefb07..e4621d6ea87 100644 --- a/go/oasis-test-runner/oasis/validator.go +++ b/go/oasis-test-runner/oasis/validator.go @@ -75,6 +75,7 @@ func (val *Validator) ExternalGRPCAddress() string { func (val *Validator) AddArgs(args *argBuilder) error { args.debugDontBlameOasis(). + debugAllowRoot(). debugAllowTestKeys(). debugSetRlimit(). debugEnableProfiling(val.Node.pprofPort).