2026-04-06 15:48:37 +05:30
// Copyright IBM Corp. 2020, 2026
2022-12-20 20:51:56 +00:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-02-12 13:53:27 -05:00
package dev
import (
2021-07-28 06:31:16 -04:00
"context"
2021-06-15 12:35:29 -05:00
"errors"
2020-02-12 13:53:27 -05:00
"fmt"
2022-09-02 14:32:41 -04:00
"math/rand"
2021-07-28 14:35:58 -07:00
"os"
2020-02-12 13:53:27 -05:00
"runtime"
"strings"
2022-09-19 09:59:18 -04:00
"sync"
atm "sync/atomic"
2021-11-03 12:39:41 -04:00
"time"
2020-02-12 13:53:27 -05:00
2026-10-12 10:33:41 -07:00
"repo.anhonesthost.net/CyberCoveLLC/threshold/globals"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/cmd/base"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/cmd/config"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/cmd/ops"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/daemon/controller"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/daemon/controller/handlers"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/daemon/worker"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/event"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/server"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/server/store"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/types/scope"
"repo.anhonesthost.net/CyberCoveLLC/threshold/internal/util"
2021-07-14 17:50:15 -07:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
2021-07-14 17:37:40 -07:00
"github.com/hashicorp/go-secure-stdlib/strutil"
2023-03-30 16:20:28 -04:00
"github.com/hashicorp/nodeenrollment"
2022-06-07 14:56:04 -04:00
"github.com/hashicorp/nodeenrollment/types"
2020-02-12 13:53:27 -05:00
"github.com/mitchellh/cli"
2022-06-07 14:56:04 -04:00
"github.com/mr-tron/base58"
2020-02-12 13:53:27 -05:00
"github.com/posener/complete"
2022-04-27 16:25:05 -04:00
"go.uber.org/atomic"
2022-06-07 14:56:04 -04:00
"google.golang.org/protobuf/proto"
2020-02-12 13:53:27 -05:00
)
2023-03-30 16:20:28 -04:00
const (
2023-06-02 12:47:59 -04:00
ControllerGeneratedAuthTokenWorkerAuthMechanism = "controller-generated-auth-token"
WorkerGeneratedAuthTokenWorkerAuthMechanism = "worker-generated-auth-token"
KmsWorkerAuthMechanism = "kms"
RandomWorkerAuthMechanism = "random"
DeprecatedKmsWorkerAuthMechanism = "deprecated-kms"
2023-03-30 16:20:28 -04:00
)
2021-01-07 10:02:32 -05:00
var (
_ cli . Command = ( * Command )( nil )
_ cli . CommandAutocomplete = ( * Command )( nil )
)
2020-02-12 13:53:27 -05:00
2023-03-09 20:31:20 -05:00
var extraSelfTerminationConditionFuncs [] func ( * Command , chan struct {})
2023-03-07 16:29:34 -05:00
2020-03-10 15:48:04 -04:00
type Command struct {
* base . Server
2022-03-28 17:16:39 +01:00
opsServer * ops . Server
2020-02-12 13:53:27 -05:00
SighupCh chan struct {}
childSighupCh [] chan struct {}
ReloadedCh chan struct {}
SigUSR2Ch chan struct {}
2020-09-23 01:23:10 -04:00
Config * config . Config
controller * controller . Controller
worker * worker . Worker
2023-12-15 15:26:35 -08:00
flagLogLevel string
flagLogFormat string
flagCombineLogs bool
flagLoginName string
flagPassword string
flagUnprivilegedLoginName string
flagUnprivilegedPassword string
flagIdSuffix string
flagSecondaryIdSuffix string
flagHostAddress string
2024-08-20 14:43:51 +01:00
flagTargetDefaultPort uint16
2024-08-16 15:28:20 +01:00
flagTargetSessionMaxSeconds int64
flagTargetSessionConnectionLimit int64
2023-12-15 15:26:35 -08:00
flagControllerApiListenAddr string
flagControllerClusterListenAddr string
flagControllerPublicClusterAddr string
flagControllerOnly bool
flagWorkerAuthKey string
flagWorkerProxyListenAddr string
flagWorkerPublicAddr string
flagOpsListenAddr string
2026-04-27 09:35:40 -07:00
flagDebug bool
2023-12-15 15:26:35 -08:00
flagUiPassthroughDir string
flagRecoveryKey string
flagDatabaseUrl string
flagContainerImage string
flagDisableDatabaseDestruction bool
flagEventFormat string
flagAudit string
flagObservations string
flagTelemetry string
flagSysEvents string
flagEveryEventAllowFilters [] string
flagEveryEventDenyFilters [] string
flagCreateLoopbackPlugin bool
flagPluginExecutionDir string
flagSkipPlugins bool
flagSkipOidcAuthMethodCreation bool
flagSkipLdapAuthMethodCreation bool
flagSkipAliasTargetCreation bool
flagWorkerDnsServer string
flagWorkerAuthMethod string
flagWorkerAuthStorageDir string
flagWorkerAuthStorageSkipCleanup bool
flagWorkerAuthWorkerRotationInterval time . Duration
flagWorkerAuthCaCertificateLifetime time . Duration
flagWorkerAuthDebuggingEnabled bool
flagWorkerRecordingStorageDir string
2025-11-19 16:24:20 -08:00
flagSshKnownHostsPath string
2023-12-15 15:26:35 -08:00
flagWorkerRecordingStorageMinimumAvailableCapacity string
flagBsrKey string
2020-02-12 13:53:27 -05:00
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Synopsis () string {
2026-10-12 10:20:06 -07:00
return "Start a Threshold dev environment"
2020-02-12 13:53:27 -05:00
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Help () string {
2020-02-12 13:53:27 -05:00
helpText := `
2026-10-12 10:20:06 -07:00
Usage: threshold dev [options]
2020-02-12 13:53:27 -05:00
Start a dev environment:
2026-10-12 10:20:06 -07:00
$ threshold dev
2020-02-12 13:53:27 -05:00
For a full list of examples, please see the documentation.
` + c . Flags (). Help ()
return strings . TrimSpace ( helpText )
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Flags () * base . FlagSets {
2020-02-12 13:53:27 -05:00
set := c . FlagSet ( base . FlagSetHTTP )
f := set . NewFlagSet ( "Command Options" )
f . StringVar ( & base . StringVar {
Name : "log-level" ,
Target : & c . flagLogLevel ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_LOG_LEVEL" ,
2020-02-12 13:53:27 -05:00
Completion : complete . PredictSet ( "trace" , "debug" , "info" , "warn" , "err" ),
Usage : "Log verbosity level. Supported values (in order of more detail to less) are " +
"\"trace\", \"debug\", \"info\", \"warn\", and \"err\"." ,
})
f . StringVar ( & base . StringVar {
Name : "log-format" ,
Target : & c . flagLogFormat ,
Completion : complete . PredictSet ( "standard" , "json" ),
Usage : `Log format. Supported values are "standard" and "json".` ,
})
2020-07-14 14:32:47 -07:00
f . StringVar ( & base . StringVar {
2020-09-24 03:31:26 -04:00
Name : "id-suffix" ,
Target : & c . flagIdSuffix ,
Default : "1234567890" ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_ID_SUFFIX" ,
Usage : `If set, auto-created resources will use this value for their identifier (along with their resource-specific prefix). Must be 10 alphanumeric characters. As an example, if this is set to "1234567890", the generated password auth method ID will be "ampw_1234567890", the generated TCP target ID will be "ttcp_1234567890", and so on. Must be different from -secondary-id-suffix (THRESHOLD_DEV_SECONDARY_ID_SUFFIX).` ,
2022-12-20 20:51:56 +00:00
})
f . StringVar ( & base . StringVar {
Name : "secondary-id-suffix" ,
Target : & c . flagSecondaryIdSuffix ,
Default : "0987654321" ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_SECONDARY_ID_SUFFIX" ,
Usage : `If set, secondary auto-created resources will use this value for their identifier (along with their resource-specific prefix). Must be 10 alphanumeric characters. Currently only used for the target resource. Must be different from -id-suffix (THRESHOLD_DEV_ID_SUFFIX).` ,
2020-07-14 14:32:47 -07:00
})
2020-02-12 13:53:27 -05:00
f . StringVar ( & base . StringVar {
2020-09-25 17:36:47 -04:00
Name : "password" ,
Target : & c . flagPassword ,
Default : "password" ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_PASSWORD" ,
2021-02-11 13:14:18 -05:00
Usage : "Initial admin login password. If set to the empty string, one will be autogenerated." ,
2020-02-12 13:53:27 -05:00
})
2020-08-07 10:18:40 -07:00
f . StringVar ( & base . StringVar {
2020-09-25 17:36:47 -04:00
Name : "login-name" ,
Target : & c . flagLoginName ,
Default : "admin" ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_LOGIN_NAME" ,
2020-09-25 17:40:10 -04:00
Usage : "Initial admin login name. If set to the empty string, one will be autogenerated." ,
2020-08-07 10:18:40 -07:00
})
2021-02-11 13:14:18 -05:00
f . StringVar ( & base . StringVar {
Name : "unprivileged-password" ,
Target : & c . flagUnprivilegedPassword ,
Default : "password" ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_UNPRIVILEGED_PASSWORD" ,
2021-02-11 13:14:18 -05:00
Usage : "Initial unprivileged user login password. If set to the empty string, one will be autogenerated." ,
})
f . StringVar ( & base . StringVar {
Name : "unprivileged-login-name" ,
Target : & c . flagUnprivilegedLoginName ,
Default : "user" ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_UNPRIVILEGED_LOGIN_NAME" ,
2021-02-11 13:14:18 -05:00
Usage : "Initial unprivileged user name. If set to the empty string, one will be autogenerated." ,
})
2020-02-12 13:53:27 -05:00
f . StringVar ( & base . StringVar {
2020-09-22 21:37:37 -04:00
Name : "api-listen-address" ,
2024-01-23 14:33:09 -05:00
Target : & c . flagControllerApiListenAddr ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_CONTROLLER_API_LISTEN_ADDRESS" ,
2020-10-16 13:45:47 -04:00
Usage : "Address to bind to for controller \"api\" purpose. If this begins with a forward slash, it will be assumed to be a Unix domain socket path." ,
2020-03-22 08:27:24 -04:00
})
2020-09-24 03:31:26 -04:00
f . StringVar ( & base . StringVar {
Name : "host-address" ,
Default : "localhost" ,
Target : & c . flagHostAddress ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_HOST_ADDRESS" ,
2020-09-24 03:31:26 -04:00
Usage : "Address to use for the default host that is created. Must be a bare host or IP address, no port." ,
})
2024-08-20 14:43:51 +01:00
f . Uint16Var ( & base . Uint16Var {
2020-09-24 03:31:26 -04:00
Name : "target-default-port" ,
Default : 22 ,
Target : & c . flagTargetDefaultPort ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_TARGET_DEFAULT_PORT" ,
2020-09-24 03:31:26 -04:00
Usage : "Default port to use for the default target that is created." ,
})
2024-08-16 15:28:20 +01:00
f . Int64Var ( & base . Int64Var {
2020-12-09 16:05:48 -05:00
Name : "target-session-connection-limit" ,
Target : & c . flagTargetSessionConnectionLimit ,
Default : - 1 ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_TARGET_SESSION_CONNECTION_LIMIT" ,
2020-12-09 16:05:48 -05:00
Usage : "Maximum number of connections per session to set on the default target. -1 means unlimited." ,
2020-09-24 17:29:12 -04:00
})
2024-08-16 15:28:20 +01:00
f . Int64Var ( & base . Int64Var {
2020-09-24 17:29:12 -04:00
Name : "target-session-max-seconds" ,
Target : & c . flagTargetSessionMaxSeconds ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_TARGET_SESSION_MAX_SECONDS" ,
2020-09-24 17:29:12 -04:00
Usage : "Max seconds to use for sessions on the default target." ,
})
2020-03-22 08:27:24 -04:00
f . StringVar ( & base . StringVar {
2020-09-22 21:37:37 -04:00
Name : "cluster-listen-address" ,
Target : & c . flagControllerClusterListenAddr ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_CONTROLLER_CLUSTER_LISTEN_ADDRESS" ,
2020-10-16 13:45:47 -04:00
Usage : "Address to bind to for controller \"cluster\" purpose. If this begins with a forward slash, it will be assumed to be a Unix domain socket path." ,
2020-02-12 13:53:27 -05:00
})
2020-10-30 16:54:53 -04:00
f . StringVar ( & base . StringVar {
Name : "controller-public-cluster-address" ,
Target : & c . flagControllerPublicClusterAddr ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_CONTROLLER_PUBLIC_CLUSTER_ADDRESS" ,
2020-10-30 16:54:53 -04:00
Usage : "Public address at which the controller is reachable for cluster tasks (like worker connections)." ,
})
2022-03-28 17:16:39 +01:00
f . StringVar ( & base . StringVar {
Name : "ops-listen-address" ,
Target : & c . flagOpsListenAddr ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_OPS_LISTEN_ADDRESS" ,
2022-03-28 17:16:39 +01:00
Usage : "Address to bind to for \"ops\" purpose. If this begins with a forward slash, it will be assumed to be a Unix domain socket path." ,
})
2020-10-30 16:54:53 -04:00
2026-04-27 09:35:40 -07:00
f . BoolVar ( & base . BoolVar {
Name : "debug" ,
Target : & c . flagDebug ,
Usage : "Enable debug mode. Currently this exposes pprof endpoints on the ops listener." ,
Hidden : true ,
})
2021-05-14 12:23:12 -05:00
f . BoolVar ( & base . BoolVar {
Name : "controller-only" ,
Target : & c . flagControllerOnly ,
2024-04-03 20:56:51 -04:00
Usage : "If set, only a dev controller will be started instead of both a dev controller and dev worker." ,
2021-05-14 12:23:12 -05:00
})
2020-09-22 08:28:10 -04:00
f . StringVar ( & base . StringVar {
2020-09-22 21:37:37 -04:00
Name : "proxy-listen-address" ,
Target : & c . flagWorkerProxyListenAddr ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_WORKER_PROXY_LISTEN_ADDRESS" ,
2020-09-22 08:28:10 -04:00
Usage : "Address to bind to for worker \"proxy\" purpose." ,
})
f . StringVar ( & base . StringVar {
2020-09-22 21:37:37 -04:00
Name : "worker-public-address" ,
Target : & c . flagWorkerPublicAddr ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_WORKER_PUBLIC_ADDRESS" ,
2020-09-22 08:28:10 -04:00
Usage : "Public address at which the worker is reachable for session proxying." ,
})
2021-05-14 12:23:12 -05:00
f . StringVar ( & base . StringVar {
Name : "worker-auth-key" ,
Target : & c . flagWorkerAuthKey ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_WORKER_AUTH_KEY" ,
2021-05-14 12:23:12 -05:00
Usage : "If set, a valid base64-encoded AES key to be used for worker-auth purposes" ,
})
2023-04-02 17:45:43 -04:00
f . StringVar ( & base . StringVar {
Name : "bsr-key" ,
Target : & c . flagBsrKey ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_BSR_KEY" ,
2023-04-02 17:45:43 -04:00
Usage : "If set, a valid base64-encoded AES key to be used for bsr purposes" ,
})
2020-08-26 23:27:46 -04:00
f . BoolVar ( & base . BoolVar {
2020-09-22 21:37:37 -04:00
Name : "disable-database-destruction" ,
Target : & c . flagDisableDatabaseDestruction ,
2020-09-16 10:10:06 -04:00
Usage : "If set, if a database is created automatically in Docker, it will not be removed when the dev server is shut down." ,
})
2020-02-12 13:53:27 -05:00
f . BoolVar ( & base . BoolVar {
2020-05-21 16:12:31 -04:00
Name : "combine-logs" ,
Target : & c . flagCombineLogs ,
Usage : "If set, both startup information and logs will be sent to stdout. If not set (the default), startup information will go to stdout and logs will be sent to stderr." ,
2020-02-12 13:53:27 -05:00
})
2020-09-22 21:37:37 -04:00
f . StringVar ( & base . StringVar {
2021-10-14 12:13:07 -04:00
Name : "ui-passthrough-dir" ,
Target : & c . flagUiPassthroughDir ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_UI_PASSTHROUGH_DIR" ,
2020-09-22 21:37:37 -04:00
Usage : "Enables a passthrough directory in the webserver at /" ,
})
f . StringVar ( & base . StringVar {
Name : "recovery-key" ,
Target : & c . flagRecoveryKey ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_RECOVERY_KEY" ,
2020-09-22 21:37:37 -04:00
Usage : "Specifies the base64'd 256-bit AES key to use for recovery operations" ,
})
2020-05-21 16:12:31 -04:00
2020-09-25 17:36:47 -04:00
f . StringVar ( & base . StringVar {
Name : "database-url" ,
Target : & c . flagDatabaseUrl ,
Usage : `If set, specifies the URL used to connect to the database for initialization (otherwise a Docker container will be started). This can refer to a file on disk (file://) from which a URL will be read; an env var (env://) from which the URL will be read; or a direct database URL.` ,
})
2021-05-03 13:11:28 -05:00
f . StringVar ( & base . StringVar {
Name : "container-image" ,
Target : & c . flagContainerImage ,
Usage : `Specifies a container image to be utilized. Must be in <repo>:<tag> format` ,
})
2021-07-25 17:40:54 -04:00
f . StringVar ( & base . StringVar {
Name : "event-format" ,
Target : & c . flagEventFormat ,
2021-08-03 15:06:19 -04:00
Completion : complete . PredictSet ( "cloudevents-json" , "cloudevents-text" , "hclog-text" , "hclog-json" ),
2022-07-11 16:12:02 -04:00
Usage : `Event format. Supported values are "cloudevents-json", "cloudevents-text", "hclog-json", and "hclog-text".` ,
2021-07-25 17:40:54 -04:00
})
f . StringVar ( & base . StringVar {
Name : "observation-events" ,
Target : & c . flagObservations ,
Completion : complete . PredictSet ( "true" , "false" ),
Usage : `Emit observation events. Supported values are "true" and "false".` ,
})
2023-09-13 16:13:18 -07:00
f . StringVar ( & base . StringVar {
Name : "telemetry-events" ,
Target : & c . flagTelemetry ,
Completion : complete . PredictSet ( "true" , "false" ),
Usage : `Emit telemetry events. Supported values are "true" and "false".` ,
})
2021-07-25 17:40:54 -04:00
f . StringVar ( & base . StringVar {
Name : "audit-events" ,
Target : & c . flagAudit ,
Completion : complete . PredictSet ( "true" , "false" ),
Usage : `Emit audit events. Supported values are "true" and "false".` ,
})
f . StringVar ( & base . StringVar {
Name : "system-events" ,
Target : & c . flagSysEvents ,
Completion : complete . PredictSet ( "true" , "false" ),
Usage : `Emit system events. Supported values are "true" and "false".` ,
})
f . StringSliceVar ( & base . StringSliceVar {
Name : "event-allow-filter" ,
Target : & c . flagEveryEventAllowFilters ,
Usage : `The optional every event allow filter. May be specified multiple times.` ,
})
f . StringSliceVar ( & base . StringSliceVar {
Name : "event-deny-filter" ,
Target : & c . flagEveryEventDenyFilters ,
Usage : `The optional every event deny filter. May be specified multiple times.` ,
})
2020-09-25 17:36:47 -04:00
2021-10-14 11:56:08 -04:00
f . StringVar ( & base . StringVar {
Name : "plugin-execution-dir" ,
Target : & c . flagPluginExecutionDir ,
2026-10-12 10:20:06 -07:00
EnvVar : "THRESHOLD_DEV_PLUGIN_EXECUTION_DIR" ,
Usage : "Specifies where Threshold should write plugins that it is executing; if not set defaults to system temp directory." ,
2021-10-14 11:56:08 -04:00
})
2023-10-20 11:26:58 -07:00
f . BoolVar ( & base . BoolVar {
Name : "skip-plugins" ,
Target : & c . flagSkipPlugins ,
Usage : "Skip loading compiled-in plugins. This does not prevent loopback plugins from being loaded if enabled." ,
Hidden : true ,
})
2024-01-23 14:33:09 -05:00
f . BoolVar ( & base . BoolVar {
Name : "skip-oidc-auth-method-creation" ,
Target : & c . flagSkipOidcAuthMethodCreation ,
Usage : "Skip creating a test OIDC auth method. This is useful if e.g. running a Unix API listener." ,
})
f . BoolVar ( & base . BoolVar {
Name : "skip-ldap-auth-method-creation" ,
Target : & c . flagSkipLdapAuthMethodCreation ,
Usage : "Skip creating a test LDAP auth method. This is useful if e.g. running a Unix API listener." ,
})
2024-02-23 12:19:46 -05:00
f . BoolVar ( & base . BoolVar {
Name : "skip-alias-target-creation" ,
Target : & c . flagSkipAliasTargetCreation ,
Usage : "Skip creating test targets using an alias." ,
Hidden : true ,
})
2023-10-20 11:26:58 -07:00
f . StringVar ( & base . StringVar {
Name : "worker-dns-server" ,
Target : & c . flagWorkerDnsServer ,
Usage : "Use a custom DNS server when workers resolve endpoints." ,
Hidden : true ,
})
2023-03-30 16:20:28 -04:00
2022-09-02 14:32:41 -04:00
f . StringVar ( & base . StringVar {
2023-06-02 12:47:59 -04:00
Name : "worker-auth-method" ,
Target : & c . flagWorkerAuthMethod ,
Default : RandomWorkerAuthMechanism ,
Completion : complete . PredictSet ( ControllerGeneratedAuthTokenWorkerAuthMechanism , WorkerGeneratedAuthTokenWorkerAuthMechanism , KmsWorkerAuthMechanism , RandomWorkerAuthMechanism ),
Usage : `Allows specifying how the generated worker will authenticate to the controller.` ,
2022-04-27 16:25:05 -04:00
})
2022-06-03 13:25:58 -07:00
f . StringVar ( & base . StringVar {
Name : "worker-auth-storage-dir" ,
Target : & c . flagWorkerAuthStorageDir ,
2023-03-30 16:20:28 -04:00
Usage : "Specifies the directory to store worker authentication credentials in dev mode. Setting this will make use of file storage at the specified location; otherwise in-memory storage or a temporary directory will be used." ,
2022-06-03 13:25:58 -07:00
})
2023-03-28 09:39:28 -07:00
f . StringVar ( & base . StringVar {
Name : "worker-recording-storage-dir" ,
Target : & c . flagWorkerRecordingStorageDir ,
2023-04-08 16:15:14 -07:00
Usage : "Specifies the directory to store worker session recordings in dev mode. If not provided a temp directory will be created. Session recording is an Enterprise-only feature." ,
2023-03-28 09:39:28 -07:00
})
2025-11-19 16:24:20 -08:00
f . StringVar ( & base . StringVar {
Name : "worker-ssh-known-hosts-path" ,
Target : & c . flagSshKnownHostsPath ,
Usage : "Specifies the path of the known_hosts file to be used by the worker for SSH host key verification of an SSH target in dev mode. SSH targets and SSH credential injection are Enterprise-only features." ,
})
2023-12-15 15:26:35 -08:00
f . StringVar ( & base . StringVar {
Name : "worker-recording-storage-minimum-available-capacity" ,
Target : & c . flagWorkerRecordingStorageMinimumAvailableCapacity ,
Usage : "Specifies the minimum amount of available disk space a worker needs in the recording storage directory to process sessions with session recording enabled. Input should be a capacity string: 4kib or 3GB. Defaults to 500mib." ,
})
2022-06-03 13:25:58 -07:00
f . BoolVar ( & base . BoolVar {
Name : "worker-auth-storage-skip-cleanup" ,
Target : & c . flagWorkerAuthStorageSkipCleanup ,
2023-03-30 16:20:28 -04:00
Usage : "Prevents deletion of worker credential storage directory if set. Has no effect unless worker-auth-storage-dir is specified." ,
})
f . BoolVar ( & base . BoolVar {
Name : "worker-auth-enable-debugging" ,
Target : & c . flagWorkerAuthDebuggingEnabled ,
Usage : "Turn on debug logging of the worker authentication process." ,
2022-06-03 13:25:58 -07:00
})
2021-09-28 09:40:12 -04:00
f . BoolVar ( & base . BoolVar {
2023-04-08 16:15:14 -07:00
Name : "create-loopback-plugin" ,
Target : & c . flagCreateLoopbackPlugin ,
2021-09-28 09:40:12 -04:00
Hidden : true ,
})
2022-06-07 14:26:33 -04:00
f . DurationVar ( & base . DurationVar {
2023-05-17 18:12:19 -04:00
Name : "worker-auth-worker-rotation-interval" ,
Target : & c . flagWorkerAuthWorkerRotationInterval ,
Hidden : true ,
})
f . DurationVar ( & base . DurationVar {
Name : "worker-auth-ca-certificate-lifetime" ,
Target : & c . flagWorkerAuthCaCertificateLifetime ,
2022-06-07 14:26:33 -04:00
Hidden : true ,
})
2020-02-12 13:53:27 -05:00
return set
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) AutocompleteArgs () complete . Predictor {
2020-02-12 13:53:27 -05:00
return complete . PredictNothing
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) AutocompleteFlags () complete . Flags {
2020-02-12 13:53:27 -05:00
return c . Flags (). Completions ()
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Run ( args [] string ) int {
2021-07-28 06:31:16 -04:00
const op = "dev.(Command).Run"
2020-02-12 13:53:27 -05:00
c . CombineLogs = c . flagCombineLogs
2022-08-08 09:13:47 -07:00
defer func () {
if err := c . RunShutdownFuncs (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error running shutdown tasks: %w" , err ). Error ())
}
}()
2020-02-12 13:53:27 -05:00
var err error
f := c . Flags ()
if err = f . Parse ( args ); err != nil {
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2021-05-14 12:23:12 -05:00
switch c . flagControllerOnly {
case true :
2024-04-03 20:56:51 -04:00
c . Config , err = config . DevController (
config . WithObservationsEnabled ( true ),
config . WithSysEventsEnabled ( true ),
2025-12-22 11:30:10 +05:30
config . WithRandomReader ( c . SecureRandomReader ),
2024-04-03 20:56:51 -04:00
)
2021-05-14 12:23:12 -05:00
default :
2025-12-22 11:30:10 +05:30
c . Config , err = config . DevCombined ( config . WithRandomReader ( c . SecureRandomReader ))
2021-05-14 12:23:12 -05:00
}
2020-02-12 13:53:27 -05:00
if err != nil {
2020-05-11 17:03:23 -04:00
c . UI . Error ( fmt . Errorf ( "Error creating controller dev config: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2021-10-14 11:56:08 -04:00
2021-05-14 12:23:12 -05:00
if c . flagWorkerAuthKey != "" {
c . Config . DevWorkerAuthKey = c . flagWorkerAuthKey
for _ , kms := range c . Config . Seals {
2022-03-16 17:04:07 -04:00
if strutil . StrListContains ( kms . Purpose , globals . KmsPurposeWorkerAuth ) {
2021-05-14 12:23:12 -05:00
kms . Config [ "key" ] = c . flagWorkerAuthKey
}
}
}
2021-10-14 11:56:08 -04:00
2023-04-02 17:45:43 -04:00
if c . flagBsrKey != "" {
c . Config . DevBsrKey = c . flagBsrKey
for _ , kms := range c . Config . Seals {
if strutil . StrListContains ( kms . Purpose , globals . KmsPurposeBsr ) {
kms . Config [ "key" ] = c . flagBsrKey
}
}
}
2023-03-30 16:20:28 -04:00
c . WorkerAuthDebuggingEnabled . Store ( c . flagWorkerAuthDebuggingEnabled )
2021-10-14 11:56:08 -04:00
c . DevLoginName = c . flagLoginName
c . DevPassword = c . flagPassword
c . DevUnprivilegedLoginName = c . flagUnprivilegedLoginName
c . DevUnprivilegedPassword = c . flagUnprivilegedPassword
c . DevTargetDefaultPort = c . flagTargetDefaultPort
c . Config . Plugins . ExecutionDir = c . flagPluginExecutionDir
2023-05-23 14:27:04 -07:00
if ! c . flagControllerOnly {
2025-11-19 16:24:20 -08:00
c . Config . Worker . SshKnownHostsPath = c . flagSshKnownHostsPath
2023-05-23 14:27:04 -07:00
c . Config . Worker . AuthStoragePath = c . flagWorkerAuthStorageDir
c . Config . Worker . RecordingStoragePath = c . flagWorkerRecordingStorageDir
2023-12-15 15:26:35 -08:00
c . Config . Worker . RecordingStorageMinimumAvailableCapacity = c . flagWorkerRecordingStorageMinimumAvailableCapacity
2023-05-23 14:27:04 -07:00
if c . Config . Worker . RecordingStoragePath == "" {
// Create a temp dir for recording storage
const pattern = "recordingstorage"
c . Config . Worker . RecordingStoragePath , err = os . MkdirTemp ( "" , pattern )
if err != nil {
c . UI . Error ( fmt . Errorf ( "Error creating storage temp dir: %w" , err ). Error ())
return base . CommandCliError
}
c . ShutdownFuncs = append ( c . ShutdownFuncs , func () error { return os . RemoveAll ( c . Config . Worker . RecordingStoragePath ) })
2023-04-08 16:15:14 -07:00
}
}
2020-09-22 21:37:37 -04:00
if c . flagIdSuffix != "" {
if len ( c . flagIdSuffix ) != 10 {
c . UI . Error ( "Invalid ID suffix, must be exactly 10 characters" )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-07-14 14:32:47 -07:00
}
2021-04-08 20:54:32 -04:00
if ! handlers . ValidId ( handlers . Id ( "abc_" + c . flagIdSuffix ), "abc" ) {
2020-09-22 21:37:37 -04:00
c . UI . Error ( "Invalid ID suffix, must be in the set A-Za-z0-9" )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-07-14 14:32:47 -07:00
}
2023-03-10 15:06:39 -05:00
c . DevPasswordAuthMethodId = fmt . Sprintf ( "%s_%s" , globals . PasswordAuthMethodPrefix , c . flagIdSuffix )
c . DevOidcAuthMethodId = fmt . Sprintf ( "%s_%s" , globals . OidcAuthMethodPrefix , c . flagIdSuffix )
2023-04-28 11:35:54 -04:00
c . DevLdapAuthMethodId = fmt . Sprintf ( "%s_%s" , globals . LdapAuthMethodPrefix , c . flagIdSuffix )
2023-03-10 15:06:39 -05:00
c . DevUserId = fmt . Sprintf ( "%s_%s" , globals . UserPrefix , c . flagIdSuffix )
c . DevPasswordAccountId = fmt . Sprintf ( "%s_%s" , globals . PasswordAccountPrefix , c . flagIdSuffix )
c . DevOidcAccountId = fmt . Sprintf ( "%s_%s" , globals . OidcAccountPrefix , c . flagIdSuffix )
2021-02-11 13:14:18 -05:00
c . DevUnprivilegedUserId = "u_" + strutil . Reverse ( strings . TrimPrefix ( c . DevUserId , "u_" ))
2023-03-10 15:06:39 -05:00
c . DevUnprivilegedPasswordAccountId = fmt . Sprintf ( "%s_" , globals . PasswordAccountPrefix ) + strutil . Reverse ( strings . TrimPrefix ( c . DevPasswordAccountId , fmt . Sprintf ( "%s_" , globals . PasswordAccountPrefix )))
c . DevUnprivilegedOidcAccountId = fmt . Sprintf ( "%s_" , globals . OidcAccountPrefix ) + strutil . Reverse ( strings . TrimPrefix ( c . DevOidcAccountId , fmt . Sprintf ( "%s_" , globals . OidcAccountPrefix )))
2020-09-23 01:23:10 -04:00
c . DevOrgId = fmt . Sprintf ( "%s_%s" , scope . Org . Prefix (), c . flagIdSuffix )
c . DevProjectId = fmt . Sprintf ( "%s_%s" , scope . Project . Prefix (), c . flagIdSuffix )
2023-03-10 15:06:39 -05:00
c . DevHostCatalogId = fmt . Sprintf ( "%s_%s" , globals . StaticHostCatalogPrefix , c . flagIdSuffix )
c . DevHostSetId = fmt . Sprintf ( "%s_%s" , globals . StaticHostSetPrefix , c . flagIdSuffix )
c . DevHostId = fmt . Sprintf ( "%s_%s" , globals . StaticHostPrefix , c . flagIdSuffix )
c . DevTargetId = fmt . Sprintf ( "%s_%s" , globals . TcpTargetPrefix , c . flagIdSuffix )
2020-07-14 14:32:47 -07:00
}
2022-12-20 20:51:56 +00:00
if c . flagSecondaryIdSuffix != "" {
if len ( c . flagSecondaryIdSuffix ) != 10 {
c . UI . Error ( "Invalid secondary ID suffix, must be exactly 10 characters" )
return base . CommandUserError
}
if ! handlers . ValidId ( handlers . Id ( "abc_" + c . flagSecondaryIdSuffix ), "abc" ) {
c . UI . Error ( "Invalid secondary ID suffix, must be in the set A-Za-z0-9" )
return base . CommandUserError
}
2023-03-10 15:06:39 -05:00
c . DevSecondaryTargetId = fmt . Sprintf ( "%s_%s" , globals . TcpTargetPrefix , c . flagSecondaryIdSuffix )
2022-12-20 20:51:56 +00:00
}
if c . flagIdSuffix != "" && c . flagSecondaryIdSuffix != "" &&
strings . EqualFold ( c . flagIdSuffix , c . flagSecondaryIdSuffix ) {
c . UI . Error ( "Primary and secondary ID suffixes are equal, must be distinct" )
return base . CommandUserError
}
2021-10-14 11:56:08 -04:00
2024-11-01 13:27:40 -04:00
host , port , err := util . SplitHostPort ( c . flagHostAddress )
2025-05-13 16:55:28 +01:00
if err != nil && ! errors . Is ( err , util . ErrMissingPort ) {
2024-11-01 13:27:40 -04:00
c . UI . Error ( fmt . Errorf ( "Invalid host address specified: %w" , err ). Error ())
return base . CommandUserError
2020-09-24 03:31:26 -04:00
}
if port != "" {
c . UI . Error ( `Port must not be specified as part of the dev host address` )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-24 03:31:26 -04:00
}
2020-09-24 17:29:12 -04:00
if c . flagTargetSessionMaxSeconds < 0 {
c . UI . Error ( `Specified target session max sessions cannot be negative` )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-24 17:29:12 -04:00
}
c . DevTargetSessionMaxSeconds = c . flagTargetSessionMaxSeconds
c . DevTargetSessionConnectionLimit = c . flagTargetSessionConnectionLimit
2020-09-24 03:31:26 -04:00
c . DevHostAddress = host
2020-07-14 14:32:47 -07:00
2021-10-14 12:13:07 -04:00
c . Config . DevUiPassthroughDir = c . flagUiPassthroughDir
2020-02-12 13:53:27 -05:00
2023-10-20 11:26:58 -07:00
c . SkipPlugins = c . flagSkipPlugins
2024-02-23 12:19:46 -05:00
c . SkipAliasTargetCreation = c . flagSkipAliasTargetCreation
2023-10-20 11:26:58 -07:00
c . WorkerDnsServer = c . flagWorkerDnsServer
2020-09-23 01:23:10 -04:00
for _ , l := range c . Config . Listeners {
2020-03-22 08:27:24 -04:00
if len ( l . Purpose ) != 1 {
2020-09-23 01:23:10 -04:00
c . UI . Error ( "Only one purpose supported for each listener" )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-03-22 08:27:24 -04:00
}
switch l . Purpose [ 0 ] {
case "api" :
2024-01-23 14:33:09 -05:00
if c . flagControllerApiListenAddr != "" {
l . Address = c . flagControllerApiListenAddr
2020-03-22 08:27:24 -04:00
}
2020-10-16 13:45:47 -04:00
if strings . HasPrefix ( l . Address , "/" ) {
l . Type = "unix"
}
2020-03-22 08:27:24 -04:00
case "cluster" :
2020-09-22 21:37:37 -04:00
if c . flagControllerClusterListenAddr != "" {
l . Address = c . flagControllerClusterListenAddr
2021-05-14 12:23:12 -05:00
if ! c . flagControllerOnly {
2022-06-13 15:06:03 -07:00
c . Config . Worker . InitialUpstreams = [] string { l . Address }
2021-05-14 12:23:12 -05:00
}
2021-02-11 20:10:48 -05:00
} else {
l . Address = "127.0.0.1:9201"
2020-10-16 13:45:47 -04:00
}
if strings . HasPrefix ( l . Address , "/" ) {
l . Type = "unix"
2020-03-22 08:27:24 -04:00
}
2020-09-22 08:28:10 -04:00
2022-03-28 17:16:39 +01:00
case "ops" :
if c . flagOpsListenAddr != "" {
l . Address = c . flagOpsListenAddr
}
if strings . HasPrefix ( l . Address , "/" ) {
l . Type = "unix"
}
2020-09-22 08:28:10 -04:00
case "proxy" :
2020-09-22 21:37:37 -04:00
if c . flagWorkerProxyListenAddr != "" {
l . Address = c . flagWorkerProxyListenAddr
2021-02-11 20:10:48 -05:00
} else {
l . Address = "127.0.0.1:9202"
2020-09-22 08:28:10 -04:00
}
2020-03-22 08:27:24 -04:00
}
}
2022-09-12 09:37:13 -04:00
if err := c . Config . SetupControllerPublicClusterAddress ( c . flagControllerPublicClusterAddr ); err != nil {
2021-02-11 20:10:48 -05:00
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2021-02-11 20:10:48 -05:00
}
c . InfoKeys = append ( c . InfoKeys , "controller public cluster addr" )
c . Info [ "controller public cluster addr" ] = c . Config . Controller . PublicClusterAddr
if err := c . SetupWorkerPublicAddress ( c . Config , c . flagWorkerPublicAddr ); err != nil {
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2021-02-11 20:10:48 -05:00
}
2021-05-14 12:23:12 -05:00
if ! c . flagControllerOnly {
c . InfoKeys = append ( c . InfoKeys , "worker public proxy addr" )
c . Info [ "worker public proxy addr" ] = c . Config . Worker . PublicAddr
}
2021-02-11 20:10:48 -05:00
2020-02-12 13:53:27 -05:00
if err := c . SetupLogging ( c . flagLogLevel , c . flagLogFormat , "" , "" ); err != nil {
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2022-06-15 20:01:03 -04:00
serverName , err := os . Hostname ()
if err != nil {
c . UI . Error ( fmt . Errorf ( "Unable to determine hostname: %w" , err ). Error ())
return base . CommandCliError
}
var serverTypes [] string
if c . Config . Controller != nil {
serverTypes = append ( serverTypes , "controller" )
}
if c . Config . Worker != nil {
serverTypes = append ( serverTypes , "worker" )
2021-07-25 17:40:54 -04:00
}
2022-06-15 20:01:03 -04:00
serverName = fmt . Sprintf ( "%s/%s" , serverName , strings . Join ( serverTypes , "+" ))
2021-07-25 17:40:54 -04:00
eventFlags , err := base . NewEventFlags ( event . TextSinkFormat , base . ComposedOfEventArgs {
Format : c . flagEventFormat ,
Audit : c . flagAudit ,
Observations : c . flagObservations ,
2023-09-13 16:13:18 -07:00
Telemetry : c . flagTelemetry ,
2021-07-25 17:40:54 -04:00
SysEvents : c . flagSysEvents ,
Allow : c . flagEveryEventAllowFilters ,
Deny : c . flagEveryEventDenyFilters ,
})
if err != nil {
c . UI . Error ( err . Error ())
return base . CommandUserError
}
2021-07-08 15:50:25 -04:00
2022-03-16 17:04:07 -04:00
base . StartMemProfiler ( c . Context )
if err := c . SetupEventing (
2023-06-06 17:09:08 -04:00
c . Context ,
2022-03-16 17:04:07 -04:00
c . Logger ,
c . StderrLock ,
serverName ,
base . WithEventerConfig ( c . Config . Eventing ),
2022-03-16 17:25:34 -04:00
base . WithEventFlags ( eventFlags ),
base . WithEventGating ( true )); err != nil {
2022-03-16 17:04:07 -04:00
c . UI . Error ( err . Error ())
return base . CommandCliError
}
2020-03-19 15:17:40 -04:00
2020-09-23 01:23:10 -04:00
if c . flagRecoveryKey != "" {
c . Config . DevRecoveryKey = c . flagRecoveryKey
2020-08-20 13:34:03 -04:00
}
2022-06-15 22:39:52 -04:00
if err := c . SetupKMSes ( c . Context , c . UI , c . Config ); err != nil {
2020-02-12 13:53:27 -05:00
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2020-08-19 16:47:18 -04:00
if c . RootKms == nil {
2020-05-11 13:27:54 -04:00
c . UI . Error ( "Controller KMS not found after parsing KMS blocks" )
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-05-11 13:27:54 -04:00
}
2022-06-15 22:39:52 -04:00
2023-06-02 12:47:59 -04:00
if c . flagWorkerAuthMethod != DeprecatedKmsWorkerAuthMechanism &&
c . flagWorkerAuthMethod != KmsWorkerAuthMechanism {
2023-03-30 16:20:28 -04:00
// Flip a coin to decide between file storage and inmem. It's
// transparent to users, but keeps both exercised.
randStorage := rand . New ( rand . NewSource ( time . Now (). UnixMicro ())). Intn ( 2 )
if randStorage == 0 {
const pattern = "nodeenrollment"
c . Config . Worker . AuthStoragePath , err = os . MkdirTemp ( "" , pattern )
if err != nil {
c . UI . Error ( fmt . Errorf ( "Error creating temp dir: %w" , err ). Error ())
return base . CommandCliError
}
if ! c . flagWorkerAuthStorageSkipCleanup && c . flagWorkerAuthStorageDir != "" {
c . ShutdownFuncs = append ( c . ShutdownFuncs , func () error { return os . RemoveAll ( c . Config . Worker . AuthStoragePath ) })
}
}
2020-05-11 13:27:54 -04:00
}
2023-03-30 16:20:28 -04:00
2023-10-20 11:26:58 -07:00
c . InfoKeys = append ( c . InfoKeys , "[Root] AEAD Key Bytes" )
c . Info [ "[Root] AEAD Key Bytes" ] = c . Config . DevControllerKey
2020-08-20 13:34:03 -04:00
c . InfoKeys = append ( c . InfoKeys , "[Recovery] AEAD Key Bytes" )
2020-09-23 01:23:10 -04:00
c . Info [ "[Recovery] AEAD Key Bytes" ] = c . Config . DevRecoveryKey
2023-03-30 16:20:28 -04:00
c . InfoKeys = append ( c . InfoKeys , "[Worker-Auth] AEAD Key Bytes" )
c . Info [ "[Worker-Auth] AEAD Key Bytes" ] = c . Config . DevWorkerAuthKey
2023-04-02 17:45:43 -04:00
c . InfoKeys = append ( c . InfoKeys , "[Bsr] AEAD Key Bytes" )
c . Info [ "[Bsr] AEAD Key Bytes" ] = c . Config . DevBsrKey
2023-10-20 11:26:58 -07:00
if c . Config . DevWorkerAuthStorageKey != "" {
c . InfoKeys = append ( c . InfoKeys , "[Worker-Auth-Storage] AEAD Key Bytes" )
c . Info [ "[Worker-Auth-Storage] AEAD Key Bytes" ] = c . Config . DevWorkerAuthStorageKey
}
2020-02-12 13:53:27 -05:00
// Initialize the listeners
2022-03-28 17:16:39 +01:00
if err := c . SetupListeners ( c . UI , c . Config . SharedConfig , [] string { "api" , "cluster" , "proxy" , "ops" }); err != nil {
2020-02-12 13:53:27 -05:00
c . UI . Error ( err . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
// Write out the PID to the file now that server has successfully started
2020-09-23 01:23:10 -04:00
if err := c . StorePidFile ( c . Config . PidFile ); err != nil {
2020-03-20 12:51:39 -04:00
c . UI . Error ( fmt . Errorf ( "Error storing PID: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-02-12 13:53:27 -05:00
}
2021-05-03 13:11:28 -05:00
var opts [] base . Option
2024-01-23 14:33:09 -05:00
if c . flagSkipOidcAuthMethodCreation {
opts = append ( opts , base . WithSkipOidcAuthMethodCreation ())
}
if c . flagSkipLdapAuthMethodCreation {
opts = append ( opts , base . WithSkipLdapAuthMethodCreation ())
}
2023-04-08 16:15:14 -07:00
if c . flagCreateLoopbackPlugin {
2023-04-19 11:27:00 -07:00
c . DevLoopbackPluginId = "pl_1234567890"
2023-04-08 16:15:14 -07:00
c . EnabledPlugins = append ( c . EnabledPlugins , base . EnabledPluginLoopback )
2022-09-13 10:01:49 -07:00
c . Config . Controller . Scheduler . JobRunIntervalDuration = 100 * time . Millisecond
2023-05-17 13:16:50 -04:00
c . Info [ "Generated Dev Loopback plugin id" ] = c . DevLoopbackPluginId
2021-09-28 09:40:12 -04:00
}
2020-09-25 17:36:47 -04:00
switch c . flagDatabaseUrl {
case "" :
if c . flagDisableDatabaseDestruction {
opts = append ( opts , base . WithSkipDatabaseDestruction ())
}
2021-05-03 13:11:28 -05:00
if c . flagContainerImage != "" {
opts = append ( opts , base . WithContainerImage ( c . flagContainerImage ))
}
if err := c . CreateDevDatabase ( c . Context , opts ... ); err != nil {
2022-06-01 13:35:46 -04:00
c . UI . Error ( fmt . Errorf ( "Error creating dev database container: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-25 17:36:47 -04:00
}
2021-05-03 13:11:28 -05:00
2020-09-25 17:36:47 -04:00
if ! c . flagDisableDatabaseDestruction {
2022-03-16 17:04:07 -04:00
// Use background context here so that we don't immediately fail to
// cleanup if the command context has already been canceled
c . ShutdownFuncs = append ( c . ShutdownFuncs , func () error { return c . DestroyDevDatabase ( context . Background ()) })
2020-09-25 17:36:47 -04:00
}
default :
2021-07-14 17:50:15 -07:00
c . DatabaseUrl , err = parseutil . ParsePath ( c . flagDatabaseUrl )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
2020-09-25 17:36:47 -04:00
c . UI . Error ( fmt . Errorf ( "Error parsing database url: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-25 17:36:47 -04:00
}
2021-05-03 13:11:28 -05:00
if err := c . CreateDevDatabase ( c . Context , opts ... ); err != nil {
2020-09-25 17:36:47 -04:00
c . UI . Error ( fmt . Errorf ( "Error connecting to database: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-25 17:36:47 -04:00
}
2020-09-16 10:10:06 -04:00
}
2020-02-12 13:53:27 -05:00
2020-09-23 01:23:10 -04:00
{
2024-11-08 11:09:29 -05:00
c . EnabledPlugins = append ( c . EnabledPlugins , base . EnabledPluginAws , base . EnabledPluginHostAzure , base . EnabledPluginGCP )
2024-04-12 11:28:40 -04:00
if base . MinioEnabled {
c . EnabledPlugins = append ( c . EnabledPlugins , base . EnabledPluginMinio )
}
2020-09-23 01:23:10 -04:00
conf := & controller . Config {
RawConfig : c . Config ,
Server : c . Server ,
2023-05-17 18:12:19 -04:00
TestOverrideWorkerAuthCaCertificateLifetime : c . flagWorkerAuthCaCertificateLifetime ,
2020-09-23 01:23:10 -04:00
}
var err error
2022-03-16 17:04:07 -04:00
c . controller , err = controller . New ( c . Context , conf )
2020-09-23 01:23:10 -04:00
if err != nil {
c . UI . Error ( fmt . Errorf ( "Error initializing controller: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-23 01:23:10 -04:00
}
2020-03-22 20:17:23 -04:00
2020-09-23 01:23:10 -04:00
if err := c . controller . Start (); err != nil {
retErr := fmt . Errorf ( "Error starting controller: %w" , err )
2022-03-10 12:54:10 +00:00
if err := c . controller . Shutdown (); err != nil {
2020-09-23 01:23:10 -04:00
c . UI . Error ( retErr . Error ())
retErr = fmt . Errorf ( "Error shutting down controller: %w" , err )
}
c . UI . Error ( retErr . Error ())
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-23 01:23:10 -04:00
}
2020-03-22 20:17:23 -04:00
}
2021-05-14 12:23:12 -05:00
2022-04-27 16:25:05 -04:00
errorEncountered := atomic . NewBool ( false )
2021-05-14 12:23:12 -05:00
if ! c . flagControllerOnly {
2020-09-23 01:23:10 -04:00
conf := & worker . Config {
RawConfig : c . Config ,
Server : c . Server ,
}
var err error
2023-04-08 16:15:14 -07:00
c . worker , err = worker . New ( c . Context , conf )
2020-09-23 01:23:10 -04:00
if err != nil {
2022-03-28 17:16:39 +01:00
c . UI . Error ( fmt . Errorf ( "Error initializing worker: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-23 01:23:10 -04:00
}
2023-05-17 18:12:19 -04:00
c . worker . TestOverrideAuthRotationPeriod = c . flagWorkerAuthWorkerRotationInterval
2022-06-07 14:26:33 -04:00
2023-06-02 12:47:59 -04:00
if c . flagWorkerAuthMethod == RandomWorkerAuthMechanism {
2022-09-02 14:32:41 -04:00
// Flip a coin. Use one method or the other; it's transparent to
// users, but keeps both exercised.
2023-03-30 16:20:28 -04:00
randPki := rand . New ( rand . NewSource ( time . Now (). UnixMicro ())). Intn ( 3 )
switch randPki {
case 0 :
2023-06-02 12:47:59 -04:00
c . flagWorkerAuthMethod = ControllerGeneratedAuthTokenWorkerAuthMechanism
2023-03-30 16:20:28 -04:00
case 1 :
2023-06-02 12:47:59 -04:00
c . flagWorkerAuthMethod = WorkerGeneratedAuthTokenWorkerAuthMechanism
2023-03-30 16:20:28 -04:00
default :
2023-06-02 12:47:59 -04:00
c . flagWorkerAuthMethod = KmsWorkerAuthMechanism
2022-09-02 14:32:41 -04:00
}
2023-03-30 16:20:28 -04:00
}
switch c . flagWorkerAuthMethod {
2023-06-02 12:47:59 -04:00
case ControllerGeneratedAuthTokenWorkerAuthMechanism :
2023-03-30 16:20:28 -04:00
// Controller-led
serversRepo , err := c . controller . ServersRepoFn ()
if err != nil {
c . UI . Error ( fmt . Errorf ( "Error instantiating server repo: %w" , err ). Error ())
if err := c . controller . Shutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error with controller shutdown: %w" , err ). Error ())
2022-09-02 14:32:41 -04:00
}
2023-03-30 16:20:28 -04:00
return base . CommandCliError
}
2022-09-02 14:32:41 -04:00
2023-03-30 16:20:28 -04:00
// Create the worker in the database and fetch an activation token
worker , err := serversRepo . CreateWorker ( c . Context , & server . Worker {
Worker : & store . Worker {
ScopeId : scope . Global . String (),
},
2025-12-22 11:30:10 +05:30
}, server . WithCreateControllerLedActivationToken ( true ), server . WithRandomReader ( c . SecureRandomReader ))
2023-03-30 16:20:28 -04:00
if err != nil {
c . UI . Error ( fmt . Errorf ( "Error creating worker in database: %w" , err ). Error ())
if err := c . controller . Shutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error with controller shutdown: %w" , err ). Error ())
2022-09-02 14:32:41 -04:00
}
2023-03-30 16:20:28 -04:00
return base . CommandCliError
}
2022-09-02 14:32:41 -04:00
2023-03-30 16:20:28 -04:00
// Set the activation token in the config and nil out the worker
// auth KMS so we don't use it via PKI-KMS
c . WorkerAuthKms = nil
conf . RawConfig . Worker . ControllerGeneratedActivationToken = worker . ControllerGeneratedActivationToken
2022-09-02 14:32:41 -04:00
2023-06-02 12:47:59 -04:00
case WorkerGeneratedAuthTokenWorkerAuthMechanism :
2023-03-30 16:20:28 -04:00
// Clear this out as presence of it causes PKI-KMS behavior
c . WorkerAuthKms = nil
2023-06-02 12:47:59 -04:00
case KmsWorkerAuthMechanism , DeprecatedKmsWorkerAuthMechanism :
2023-03-30 16:20:28 -04:00
if c . WorkerAuthKms == nil {
c . UI . Error ( "Worker Auth KMS not found after parsing KMS blocks" )
return base . CommandUserError
2022-09-02 14:32:41 -04:00
}
}
2020-09-23 01:23:10 -04:00
if err := c . worker . Start (); err != nil {
retErr := fmt . Errorf ( "Error starting worker: %w" , err )
2022-03-09 20:21:36 +00:00
if err := c . worker . Shutdown (); err != nil {
2020-09-23 01:23:10 -04:00
c . UI . Error ( retErr . Error ())
retErr = fmt . Errorf ( "Error shutting down worker: %w" , err )
}
c . UI . Error ( retErr . Error ())
2022-03-10 12:54:10 +00:00
if err := c . controller . Shutdown (); err != nil {
2020-09-23 01:23:10 -04:00
c . UI . Error ( fmt . Errorf ( "Error with controller shutdown: %w" , err ). Error ())
}
2021-03-04 20:05:17 -05:00
return base . CommandCliError
2020-09-23 01:23:10 -04:00
}
2022-04-27 16:25:05 -04:00
2023-06-02 12:47:59 -04:00
if c . flagWorkerAuthMethod != DeprecatedKmsWorkerAuthMechanism {
2022-06-03 17:47:44 -04:00
c . InfoKeys = append ( c . InfoKeys , "worker auth current key id" )
2022-06-07 16:43:07 -04:00
c . Info [ "worker auth current key id" ] = c . worker . WorkerAuthCurrentKeyId . Load ()
2022-06-05 22:50:44 -04:00
c . InfoKeys = append ( c . InfoKeys , "worker auth storage path" )
2023-03-30 16:20:28 -04:00
if c . Config . Worker . AuthStoragePath != "" {
c . Info [ "worker auth storage path" ] = c . Config . Worker . AuthStoragePath
} else {
c . Info [ "worker auth storage path" ] = "(in-memory)"
}
2022-09-02 14:32:41 -04:00
2023-06-02 12:47:59 -04:00
if c . flagWorkerAuthMethod == WorkerGeneratedAuthTokenWorkerAuthMechanism {
2022-09-02 14:32:41 -04:00
req := c . worker . WorkerAuthRegistrationRequest
if req == "" {
c . UI . Error ( "No worker auth registration request found at worker start time" )
return base . CommandCliError
2022-06-08 12:18:00 -04:00
}
2022-09-02 14:32:41 -04:00
2023-03-30 16:20:28 -04:00
if c . Config . Worker . AuthStoragePath != "" {
if err := c . StoreWorkerAuthReq ( c . worker . WorkerAuthRegistrationRequest , c . Config . Worker . AuthStoragePath ); err != nil {
// Shutdown on failure
retErr := fmt . Errorf ( "Error storing worker auth request: %w" , err )
if err := c . worker . Shutdown (); err != nil {
c . UI . Error ( retErr . Error ())
retErr = fmt . Errorf ( "Error shutting down worker: %w" , err )
}
2022-09-02 14:32:41 -04:00
c . UI . Error ( retErr . Error ())
2023-03-30 16:20:28 -04:00
if err := c . controller . Shutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error with controller shutdown: %w" , err ). Error ())
}
return base . CommandCliError
2022-09-02 14:32:41 -04:00
}
2022-06-08 12:18:00 -04:00
}
2023-03-30 16:20:28 -04:00
2022-09-02 14:32:41 -04:00
go func () {
for {
select {
case <- c . Context . Done ():
return
case <- time . After ( time . Second ):
if err := authorizeWorker ( c . Context , c . controller , req ); err != nil {
c . UI . Error ( fmt . Errorf ( "Error authorizing node: %w" , err ). Error ())
errorEncountered . Store ( true )
return
}
2022-04-27 16:25:05 -04:00
return
}
}
2022-09-02 14:32:41 -04:00
}()
}
2022-04-27 16:25:05 -04:00
}
}
c . PrintInfo ( c . UI )
if err := c . ReleaseLogGate (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error releasing event gate: %w" , err ). Error ())
return base . CommandCliError
2020-03-22 20:17:23 -04:00
}
2020-02-12 13:53:27 -05:00
2026-04-27 09:35:40 -07:00
opsServer , err := ops . NewServer ( c . Context , c . Logger , c . controller , c . worker , c . flagDebug , c . Listeners ... )
2022-03-28 17:16:39 +01:00
if err != nil {
c . UI . Error ( fmt . Errorf ( "Failed to start ops listeners: %w" , err ). Error ())
return base . CommandCliError
}
c . opsServer = opsServer
c . opsServer . Start ()
2022-09-19 09:59:18 -04:00
var shutdownCompleted atm . Bool
shutdownTriggerCount := 0
2020-02-12 13:53:27 -05:00
2022-09-19 09:59:18 -04:00
var workerShutdownOnce sync . Once
workerShutdownFunc := func () {
if err := c . worker . Shutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error shutting down worker: %w" , err ). Error ())
}
if ! c . flagWorkerAuthStorageSkipCleanup && c . worker . WorkerAuthStorage != nil {
2023-03-30 16:20:28 -04:00
if cleanable , ok := c . worker . WorkerAuthStorage .( nodeenrollment . CleanableStorage ); ok {
if err := cleanable . Cleanup ( c . Context ); err != nil {
c . UI . Error ( fmt . Errorf ( "Error cleaning up authentication storage: %w" , err ). Error ())
}
}
2022-09-19 09:59:18 -04:00
}
}
workerGracefulShutdownFunc := func () {
if err := c . worker . GracefulShutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error shutting down worker gracefully: %w" , err ). Error ())
}
workerShutdownOnce . Do ( workerShutdownFunc )
}
var controllerOnce sync . Once
controllerShutdownFunc := func () {
if err := c . controller . Shutdown (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error shutting down controller: %w" , err ). Error ())
}
err := c . opsServer . Shutdown ()
if err != nil {
c . UI . Error ( fmt . Errorf ( "Failed to shutdown ops listeners: %w" , err ). Error ())
}
}
2021-07-28 14:35:58 -07:00
2022-08-03 19:29:17 -04:00
runShutdownLogic := func () {
2022-09-19 09:59:18 -04:00
switch {
case shutdownTriggerCount == 1 :
c . ContextCancel ()
go func () {
if c . Config . Controller != nil {
c . opsServer . WaitIfHealthExists ( c . Config . Controller . GracefulShutdownWaitDuration , c . UI )
}
if ! c . flagControllerOnly {
2026-10-12 10:20:06 -07:00
c . UI . Output ( "==> Threshold dev environment graceful shutdown triggered, interrupt again to enter shutdown" )
2022-09-19 09:59:18 -04:00
workerGracefulShutdownFunc ()
} else {
2026-10-12 10:20:06 -07:00
c . UI . Output ( "==> Threshold dev shutdown triggered, interrupt again to force" )
2021-07-28 14:35:58 -07:00
}
2020-02-12 13:53:27 -05:00
2022-09-19 09:59:18 -04:00
controllerOnce . Do ( controllerShutdownFunc )
shutdownCompleted . Store ( true )
}()
case shutdownTriggerCount == 2 && ! c . flagControllerOnly :
go func () {
if ! c . flagControllerOnly {
workerShutdownOnce . Do ( workerShutdownFunc )
}
if c . Config . Controller != nil {
controllerOnce . Do ( controllerShutdownFunc )
}
shutdownCompleted . Store ( true )
}()
case shutdownTriggerCount >= 2 :
go func () {
c . UI . Error ( "Forcing shutdown" )
os . Exit ( base . CommandCliError )
}()
}
2022-08-03 19:29:17 -04:00
}
2023-03-07 16:29:34 -05:00
for _ , f := range extraSelfTerminationConditionFuncs {
2023-03-09 20:31:20 -05:00
f ( c , c . ServerSideShutdownCh )
2023-03-07 16:29:34 -05:00
}
2022-09-19 09:59:18 -04:00
for ! errorEncountered . Load () && ! shutdownCompleted . Load () {
2022-08-03 19:29:17 -04:00
select {
case <- c . ServerSideShutdownCh :
2026-10-12 10:20:06 -07:00
c . UI . Output ( "==> Threshold dev environment self-terminating" )
2022-09-19 09:59:18 -04:00
shutdownTriggerCount ++
2022-08-03 19:29:17 -04:00
runShutdownLogic ()
case <- c . ShutdownCh :
2022-09-19 09:59:18 -04:00
shutdownTriggerCount ++
2022-08-03 19:29:17 -04:00
runShutdownLogic ()
2022-04-27 16:25:05 -04:00
2022-12-02 13:59:37 -05:00
case <- c . SighupCh :
2026-10-12 10:20:06 -07:00
c . UI . Output ( "==> Threshold dev environment does not support configuration reloading, taking no action" )
2022-12-02 13:59:37 -05:00
2022-04-27 16:25:05 -04:00
case <- c . SigUSR2Ch :
buf := make ([] byte , 32 * 1024 * 1024 )
n := runtime . Stack ( buf [:], true )
event . WriteSysEvent ( context . TODO (), op , "goroutine trace" , "stack" , string ( buf [: n ]))
2022-03-28 17:16:39 +01:00
2022-09-19 09:59:18 -04:00
case <- time . After ( 10 * time . Millisecond ):
2020-02-12 13:53:27 -05:00
}
}
2021-03-04 20:05:17 -05:00
return base . CommandSuccess
2020-02-12 13:53:27 -05:00
}
2022-06-07 14:56:04 -04:00
func authorizeWorker ( ctx context . Context , c * controller . Controller , request string ) error {
reqBytes , err := base58 . FastBase58Decoding ( request )
if err != nil {
return fmt . Errorf ( "error base58-decoding fetch node creds next proto value: %w" , err )
}
// Decode the proto into the request
req := new ( types . FetchNodeCredentialsRequest )
if err := proto . Unmarshal ( reqBytes , req ); err != nil {
return fmt . Errorf ( "error unmarshaling common name value: %w" , err )
}
serversRepo , err := c . ServersRepoFn ()
if err != nil {
2022-06-20 18:20:17 -07:00
return fmt . Errorf ( "error fetching server repo: %w" , err )
2022-06-07 14:56:04 -04:00
}
2022-06-20 18:20:17 -07:00
_ , err = serversRepo . CreateWorker ( ctx , & server . Worker {
2022-06-07 14:56:04 -04:00
Worker : & store . Worker {
ScopeId : scope . Global . String (),
},
2022-06-20 18:20:17 -07:00
}, server . WithFetchNodeCredentialsRequest ( req ))
2022-06-07 14:56:04 -04:00
if err != nil {
return fmt . Errorf ( "error creating worker: %w" , err )
}
return err
}