2020-02-12 13:53:27 -05:00
package dev
import (
2021-04-08 20:54:32 -04:00
"context"
"crypto/ed25519"
2020-02-12 13:53:27 -05:00
"fmt"
2020-09-24 03:31:26 -04:00
"net"
2021-04-08 20:54:32 -04:00
"net/url"
2020-02-12 13:53:27 -05:00
"runtime"
"strings"
2021-04-08 20:54:32 -04:00
"github.com/hashicorp/boundary/internal/auth/oidc"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/config"
2021-04-08 20:54:32 -04:00
"github.com/hashicorp/boundary/internal/db"
2020-09-23 01:23:10 -04:00
"github.com/hashicorp/boundary/internal/host/static"
2020-09-24 03:31:26 -04:00
"github.com/hashicorp/boundary/internal/iam"
2021-04-08 20:54:32 -04:00
"github.com/hashicorp/boundary/internal/kms"
2020-09-23 01:23:10 -04:00
"github.com/hashicorp/boundary/internal/servers/controller"
2020-09-22 21:37:37 -04:00
"github.com/hashicorp/boundary/internal/servers/controller/handlers"
2020-09-23 01:23:10 -04:00
"github.com/hashicorp/boundary/internal/servers/worker"
2020-09-24 03:31:26 -04:00
"github.com/hashicorp/boundary/internal/target"
2020-09-23 01:23:10 -04:00
"github.com/hashicorp/boundary/internal/types/scope"
2021-02-11 13:14:18 -05:00
"github.com/hashicorp/boundary/sdk/strutil"
2021-04-08 20:54:32 -04:00
capoidc "github.com/hashicorp/cap/oidc"
2020-02-12 13:53:27 -05:00
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
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
2020-03-10 15:48:04 -04:00
type Command struct {
* base . 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
2021-04-08 20:54:32 -04:00
oidcSetup oidcSetup
2020-09-24 17:29:12 -04:00
flagLogLevel string
flagLogFormat string
flagCombineLogs bool
flagLoginName string
flagPassword string
2021-02-11 13:14:18 -05:00
flagUnprivilegedLoginName string
flagUnprivilegedPassword string
2020-09-24 17:29:12 -04:00
flagIdSuffix string
flagHostAddress string
flagTargetDefaultPort int
flagTargetSessionMaxSeconds int
flagTargetSessionConnectionLimit int
flagControllerAPIListenAddr string
flagControllerClusterListenAddr string
2020-10-30 16:54:53 -04:00
flagControllerPublicClusterAddr string
2021-05-14 12:23:12 -05:00
flagControllerOnly bool
flagWorkerAuthKey string
2020-09-24 17:29:12 -04:00
flagWorkerProxyListenAddr string
flagWorkerPublicAddr string
flagPassthroughDirectory string
flagRecoveryKey string
2020-09-25 17:36:47 -04:00
flagDatabaseUrl string
2021-05-03 13:11:28 -05:00
flagContainerImage string
2020-09-24 17:29:12 -04:00
flagDisableDatabaseDestruction bool
2020-02-12 13:53:27 -05:00
}
2020-03-10 15:48:04 -04:00
func ( c * Command ) Synopsis () string {
2020-08-10 17:15:02 -04:00
return "Start a Boundary 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 := `
2020-08-10 17:15:02 -04:00
Usage: boundary dev [options]
2020-02-12 13:53:27 -05:00
Start a dev environment:
2020-08-10 17:15:02 -04:00
$ boundary 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 ,
2020-08-10 17:15:02 -04:00
EnvVar : "BOUNDARY_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" ,
EnvVar : "BOUNDARY_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.` ,
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" ,
EnvVar : "BOUNDARY_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" ,
EnvVar : "BOUNDARY_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" ,
EnvVar : "BOUNDARY_DEV_UNPRIVILEGED_PASSWORD" ,
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" ,
EnvVar : "BOUNDARY_DEV_UNPRIVILEGED_LOGIN_NAME" ,
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" ,
Target : & c . flagControllerAPIListenAddr ,
2020-08-10 17:15:02 -04:00
EnvVar : "BOUNDARY_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 ,
EnvVar : "BOUNDARY_DEV_HOST_ADDRESS" ,
Usage : "Address to use for the default host that is created. Must be a bare host or IP address, no port." ,
})
f . IntVar ( & base . IntVar {
Name : "target-default-port" ,
Default : 22 ,
Target : & c . flagTargetDefaultPort ,
EnvVar : "BOUNDARY_DEV_TARGET_DEFAULT_PORT" ,
Usage : "Default port to use for the default target that is created." ,
})
2020-09-24 17:29:12 -04:00
f . IntVar ( & base . IntVar {
2020-12-09 16:05:48 -05:00
Name : "target-session-connection-limit" ,
Target : & c . flagTargetSessionConnectionLimit ,
Default : - 1 ,
EnvVar : "BOUNDARY_DEV_TARGET_SESSION_CONNECTION_LIMIT" ,
Usage : "Maximum number of connections per session to set on the default target. -1 means unlimited." ,
2020-09-24 17:29:12 -04:00
})
f . IntVar ( & base . IntVar {
Name : "target-session-max-seconds" ,
Target : & c . flagTargetSessionMaxSeconds ,
EnvVar : "BOUNDARY_DEV_TARGET_SESSION_MAX_SECONDS" ,
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 ,
2020-08-10 17:15:02 -04:00
EnvVar : "BOUNDARY_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 ,
EnvVar : "BOUNDARY_DEV_CONTROLLER_PUBLIC_CLUSTER_ADDRESS" ,
Usage : "Public address at which the controller is reachable for cluster tasks (like worker connections)." ,
})
2021-05-14 12:23:12 -05:00
f . BoolVar ( & base . BoolVar {
Name : "controller-only" ,
Target : & c . flagControllerOnly ,
Usage : "If set, only a dev controller will be started instead of both a dev controller and dev worker" ,
})
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 ,
2020-09-22 08:28:10 -04:00
EnvVar : "BOUNDARY_DEV_WORKER_PROXY_LISTEN_ADDRESS" ,
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 ,
2020-09-22 08:28:10 -04:00
EnvVar : "BOUNDARY_DEV_WORKER_PUBLIC_ADDRESS" ,
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 ,
EnvVar : "BOUNDARY_DEV_WORKER_AUTH_KEY" ,
Usage : "If set, a valid base64-encoded AES key to be used for worker-auth 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 {
Name : "passthrough-directory" ,
Target : & c . flagPassthroughDirectory ,
EnvVar : "BOUNDARY_DEV_PASSTHROUGH_DIRECTORY" ,
Usage : "Enables a passthrough directory in the webserver at /" ,
})
f . StringVar ( & base . StringVar {
Name : "recovery-key" ,
Target : & c . flagRecoveryKey ,
EnvVar : "BOUNDARY_DEV_RECOVERY_KEY" ,
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` ,
})
2020-09-25 17:36:47 -04:00
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 {
2020-02-12 13:53:27 -05:00
c . CombineLogs = c . flagCombineLogs
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 :
c . Config , err = config . DevController ()
default :
c . Config , err = config . DevCombined ()
}
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-05-14 12:23:12 -05:00
if c . flagWorkerAuthKey != "" {
c . Config . DevWorkerAuthKey = c . flagWorkerAuthKey
for _ , kms := range c . Config . Seals {
if strutil . StrListContains ( kms . Purpose , "worker-auth" ) {
kms . Config [ "key" ] = c . flagWorkerAuthKey
}
}
}
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
}
2021-04-08 20:54:32 -04:00
c . DevPasswordAuthMethodId = fmt . Sprintf ( "%s_%s" , password . AuthMethodPrefix , c . flagIdSuffix )
c . DevOidcAuthMethodId = fmt . Sprintf ( "%s_%s" , oidc . AuthMethodPrefix , c . flagIdSuffix )
2020-09-24 03:31:26 -04:00
c . DevUserId = fmt . Sprintf ( "%s_%s" , iam . UserPrefix , c . flagIdSuffix )
2021-02-11 13:14:18 -05:00
c . DevUnprivilegedUserId = "u_" + strutil . Reverse ( strings . TrimPrefix ( c . DevUserId , "u_" ))
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 )
c . DevHostCatalogId = fmt . Sprintf ( "%s_%s" , static . HostCatalogPrefix , c . flagIdSuffix )
c . DevHostSetId = fmt . Sprintf ( "%s_%s" , static . HostSetPrefix , c . flagIdSuffix )
c . DevHostId = fmt . Sprintf ( "%s_%s" , static . HostPrefix , c . flagIdSuffix )
2020-09-24 03:31:26 -04:00
c . DevTargetId = fmt . Sprintf ( "%s_%s" , target . TcpTargetPrefix , c . flagIdSuffix )
2020-07-14 14:32:47 -07:00
}
2020-09-22 21:37:37 -04:00
if c . flagLoginName != "" {
c . DevLoginName = c . flagLoginName
2020-08-07 10:18:40 -07:00
}
2020-09-22 21:37:37 -04:00
if c . flagPassword != "" {
c . DevPassword = c . flagPassword
2020-08-07 10:18:40 -07:00
}
2021-02-11 13:14:18 -05:00
if c . flagUnprivilegedLoginName != "" {
c . DevUnprivilegedLoginName = c . flagUnprivilegedLoginName
}
if c . flagUnprivilegedPassword != "" {
c . DevUnprivilegedPassword = c . flagUnprivilegedPassword
}
2020-09-24 03:31:26 -04:00
c . DevTargetDefaultPort = c . flagTargetDefaultPort
host , port , err := net . SplitHostPort ( c . flagHostAddress )
if err != nil {
if ! strings . Contains ( err . Error (), "missing port" ) {
c . UI . Error ( fmt . Errorf ( "Invalid host address specified: %w" , err ). Error ())
2021-03-04 20:05:17 -05:00
return base . CommandUserError
2020-09-24 03:31:26 -04:00
}
host = c . flagHostAddress
}
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
2020-09-23 01:23:10 -04:00
c . Config . PassthroughDirectory = c . flagPassthroughDirectory
2020-02-12 13:53:27 -05:00
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" :
2020-09-22 21:37:37 -04: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 {
c . Config . Worker . Controllers = [] string { l . Address }
}
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
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
}
}
2021-02-11 20:10:48 -05:00
if err := c . SetupControllerPublicClusterAddress ( c . Config , c . flagControllerPublicClusterAddr ); 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
}
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
}
2020-03-20 12:51:39 -04:00
base . StartMemProfiler ( c . Logger )
2020-03-19 15:17:40 -04:00
2020-09-23 01:23:10 -04:00
if err := c . SetupMetrics ( c . UI , c . Config . Telemetry ); 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-09-23 01:23:10 -04:00
if c . flagRecoveryKey != "" {
c . Config . DevRecoveryKey = c . flagRecoveryKey
2020-08-20 13:34:03 -04:00
}
2020-09-23 01:23:10 -04:00
if err := c . SetupKMSes ( 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
}
2020-08-19 16:47:18 -04:00
if c . WorkerAuthKms == nil {
2020-05-11 13:27:54 -04:00
c . UI . Error ( "Worker Auth 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
}
2020-08-12 00:40:39 -04:00
c . InfoKeys = append ( c . InfoKeys , "[Controller] AEAD Key Bytes" )
2020-09-23 01:23:10 -04:00
c . Info [ "[Controller] AEAD Key Bytes" ] = c . Config . DevControllerKey
2020-08-12 00:40:39 -04:00
c . InfoKeys = append ( c . InfoKeys , "[Worker-Auth] AEAD Key Bytes" )
2020-09-23 01:23:10 -04:00
c . Info [ "[Worker-Auth] AEAD Key Bytes" ] = c . Config . DevWorkerAuthKey
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
2020-02-12 13:53:27 -05:00
// Initialize the listeners
2020-09-23 01:23:10 -04:00
if err := c . SetupListeners ( c . UI , c . Config . SharedConfig , [] string { "api" , "cluster" , "proxy" }); 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
}
2020-05-15 10:19:55 -04: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
2021-05-03 13:11:28 -05:00
var opts [] base . Option
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 {
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 {
c . ShutdownFuncs = append ( c . ShutdownFuncs , c . DestroyDevDatabase )
}
default :
2021-01-25 14:43:50 -07:00
c . DatabaseUrl , err = config . ParseAddress ( c . flagDatabaseUrl )
2020-09-25 17:36:47 -04:00
if err != nil && err != config . ErrNotAUrl {
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
2021-04-08 20:54:32 -04:00
if err := c . startDevOidcAuthMethod (); err != nil {
c . UI . Error ( fmt . Errorf ( "Error starting dev OIDC auth method: %w" , err ). Error ())
return base . CommandCliError
}
2020-09-23 01:23:10 -04:00
c . PrintInfo ( c . UI )
2020-02-12 13:53:27 -05:00
c . ReleaseLogGate ()
2020-09-23 01:23:10 -04:00
{
conf := & controller . Config {
RawConfig : c . Config ,
Server : c . Server ,
}
var err error
c . controller , err = controller . New ( conf )
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 )
if err := c . controller . Shutdown ( false ); err != nil {
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
if ! c . flagControllerOnly {
2020-09-23 01:23:10 -04:00
conf := & worker . Config {
RawConfig : c . Config ,
Server : c . Server ,
}
var err error
c . worker , err = worker . New ( conf )
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
}
if err := c . worker . Start (); err != nil {
retErr := fmt . Errorf ( "Error starting worker: %w" , err )
if err := c . worker . Shutdown ( false ); err != nil {
c . UI . Error ( retErr . Error ())
retErr = fmt . Errorf ( "Error shutting down worker: %w" , err )
}
c . UI . Error ( retErr . Error ())
if err := c . controller . Shutdown ( false ); err != nil {
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
}
2020-03-22 20:17:23 -04:00
}
2020-02-12 13:53:27 -05:00
// Wait for shutdown
shutdownTriggered := false
for ! shutdownTriggered {
select {
case <- c . ShutdownCh :
2020-08-10 17:15:02 -04:00
c . UI . Output ( "==> Boundary dev environment shutdown triggered" )
2020-02-12 13:53:27 -05:00
2021-05-14 12:23:12 -05:00
if ! c . flagControllerOnly {
if err := c . worker . Shutdown ( false ); err != nil {
c . UI . Error ( fmt . Errorf ( "Error shutting down worker: %w" , err ). Error ())
}
2020-09-23 01:23:10 -04:00
}
if err := c . controller . Shutdown ( false ); err != nil {
c . UI . Error ( fmt . Errorf ( "Error shutting down controller: %w" , err ). Error ())
}
2020-02-12 13:53:27 -05:00
shutdownTriggered = true
case <- c . SigUSR2Ch :
buf := make ([] byte , 32 * 1024 * 1024 )
n := runtime . Stack ( buf [:], true )
c . Logger . Info ( "goroutine trace" , "stack" , string ( buf [: n ]))
}
}
2021-03-04 20:05:17 -05:00
return base . CommandSuccess
2020-02-12 13:53:27 -05:00
}
2021-04-08 20:54:32 -04:00
type oidcSetup struct {
clientId string
clientSecret oidc . ClientSecret
oidcPort int
callbackPort string
hostAddr string
authMethod * oidc . AuthMethod
pubKey [] byte
privKey [] byte
testProvider * capoidc . TestProvider
createUnpriv bool
callbackUrl * url . URL
}
func ( c * Command ) startDevOidcAuthMethod () error {
var err error
if c . DevOidcAuthMethodId == "" {
c . DevOidcAuthMethodId , err = db . NewPublicId ( oidc . AuthMethodPrefix )
if err != nil {
return fmt . Errorf ( "error generating initial oidc auth method id: %w" , err )
}
}
c . InfoKeys = append ( c . InfoKeys , "generated oidc auth method id" )
c . Info [ "generated oidc auth method id" ] = c . DevOidcAuthMethodId
switch {
case c . DevUnprivilegedLoginName == "" ,
c . DevUnprivilegedPassword == "" ,
c . DevUnprivilegedUserId == "" :
default :
c . oidcSetup . createUnpriv = true
}
// Trawl through the listeners and find the api listener so we can use the
// same host name/IP
{
for _ , lnConfig := range c . Config . Listeners {
purpose := strings . ToLower ( lnConfig . Purpose [ 0 ])
if purpose != "api" {
continue
}
c . oidcSetup . hostAddr , c . oidcSetup . callbackPort , err = net . SplitHostPort ( lnConfig . Address )
if err != nil {
if strings . Contains ( err . Error (), "missing port" ) {
c . oidcSetup . hostAddr = lnConfig . Address
// Use the default API port in the callback
c . oidcSetup . callbackPort = "9200"
} else {
return fmt . Errorf ( "error splitting host/port: %w" , err )
}
}
}
if c . oidcSetup . hostAddr == "" {
return fmt . Errorf ( "could not determine address to use for built-in oidc dev listener" )
}
}
// Find an available port -- allocate one, then close the listener, and
// re-use it. This is a sort of hacky way to get around the chicken and egg
// of the auth method needing to know the discovery URL and the test
// provider needing to know the callback URL.
l , err := net . Listen ( "tcp" , fmt . Sprintf ( "%s:0" , c . oidcSetup . hostAddr ))
if err != nil {
return fmt . Errorf ( "error finding port for oidc test provider: %w" , err )
}
c . oidcSetup . oidcPort = l .( * net . TCPListener ). Addr ().( * net . TCPAddr ). Port
if err := l . Close (); err != nil {
return fmt . Errorf ( "error closing initial test port: %w" , err )
}
c . oidcSetup . callbackUrl , err = url . Parse ( fmt . Sprintf ( "http://%s:%s" , c . oidcSetup . hostAddr , c . oidcSetup . callbackPort ))
if err != nil {
return fmt . Errorf ( "error parsing oidc test provider callback url: %w" , err )
}
// Generate initial IDs/keys
{
c . oidcSetup . clientId , err = capoidc . NewID ()
if err != nil {
return fmt . Errorf ( "unable to generate client id: %w" , err )
}
clientSecret , err := capoidc . NewID ()
if err != nil {
return fmt . Errorf ( "unable to generate client secret: %w" , err )
}
c . oidcSetup . clientSecret = oidc . ClientSecret ( clientSecret )
c . oidcSetup . pubKey , c . oidcSetup . privKey , err = ed25519 . GenerateKey ( nil )
if err != nil {
return fmt . Errorf ( "unable to generate signing key: %w" , err )
}
}
// Create the subject information and testing provider
{
logger , err := capoidc . NewTestingLogger ( c . Logger . Named ( "dev-oidc" ))
if err != nil {
return fmt . Errorf ( "unable to create logger: %w" , err )
}
subInfo := map [ string ] * capoidc . TestSubject {
c . DevLoginName : {
Password : c . DevPassword ,
UserInfo : map [ string ] interface {}{
"email" : "admin@localhost" ,
"name" : "Admin User" ,
},
},
}
if c . oidcSetup . createUnpriv {
subInfo [ c . DevUnprivilegedLoginName ] = & capoidc . TestSubject {
Password : c . DevUnprivilegedPassword ,
UserInfo : map [ string ] interface {}{
"email" : "user@localhost" ,
"name" : "Unprivileged User" ,
},
}
}
clientSecret := string ( c . oidcSetup . clientSecret )
c . oidcSetup . testProvider = capoidc . StartTestProvider (
logger ,
capoidc . WithNoTLS (),
capoidc . WithTestHost ( c . oidcSetup . hostAddr ),
capoidc . WithTestPort ( c . oidcSetup . oidcPort ),
capoidc . WithTestDefaults ( & capoidc . TestProviderDefaults {
CustomClaims : map [ string ] interface {}{
"mode" : "dev" ,
},
SubjectInfo : subInfo ,
SigningKey : & capoidc . TestSigningKey {
PrivKey : ed25519 . PrivateKey ( c . oidcSetup . privKey ),
PubKey : ed25519 . PublicKey ( c . oidcSetup . pubKey ),
Alg : capoidc . EdDSA ,
},
2021-05-02 15:25:29 -04:00
AllowedRedirectURIs : [] string { fmt . Sprintf ( "%s/v1/auth-methods/oidc:authenticate:callback" , c . oidcSetup . callbackUrl . String ())},
2021-04-08 20:54:32 -04:00
ClientID : & c . oidcSetup . clientId ,
ClientSecret : & clientSecret ,
}))
c . ShutdownFuncs = append ( c . ShutdownFuncs , func () error {
c . oidcSetup . testProvider . Stop ()
return nil
})
}
// Create auth method and link accounts
{
c . oidcSetup . authMethod , err = c . createInitialOidcAuthMethod ()
if err != nil {
return fmt . Errorf ( "error creating initial oidc auth method: %w" , err )
}
}
return nil
}
func ( c * Command ) createInitialOidcAuthMethod () ( * oidc . AuthMethod , error ) {
rw := db . New ( c . Database )
kmsRepo , err := kms . NewRepository ( rw , rw )
if err != nil {
return nil , fmt . Errorf ( "error creating kms repository: %w" , err )
}
kmsCache , err := kms . NewKms ( kmsRepo , kms . WithLogger ( c . Logger . Named ( "kms" )))
if err != nil {
return nil , fmt . Errorf ( "error creating kms cache: %w" , err )
}
if err := kmsCache . AddExternalWrappers (
kms . WithRootWrapper ( c . RootKms ),
); err != nil {
return nil , fmt . Errorf ( "error adding config keys to kms: %w" , err )
}
discoveryUrl , err := url . Parse ( fmt . Sprintf ( "http://%s:%d" , c . oidcSetup . hostAddr , c . oidcSetup . oidcPort ))
if err != nil {
return nil , fmt . Errorf ( "error parsing oidc test provider address: %w" , err )
}
// Create the auth method
oidcRepo , err := oidc . NewRepository ( rw , rw , kmsCache )
if err != nil {
return nil , fmt . Errorf ( "error creating oidc repo: %w" , err )
}
authMethod , err := oidc . NewAuthMethod (
scope . Global . String (),
c . oidcSetup . clientId ,
c . oidcSetup . clientSecret ,
oidc . WithName ( "Generated global scope initial oidc auth method" ),
oidc . WithDescription ( "Provides initial administrative and unprivileged authentication into Boundary" ),
oidc . WithIssuer ( discoveryUrl ),
oidc . WithApiUrl ( c . oidcSetup . callbackUrl ),
oidc . WithSigningAlgs ( oidc . EdDSA ),
oidc . WithOperationalState ( oidc . ActivePublicState ))
if err != nil {
return nil , fmt . Errorf ( "error creating new in memory oidc auth method: %w" , err )
}
if c . DevOidcAuthMethodId == "" {
c . DevOidcAuthMethodId , err = db . NewPublicId ( oidc . AuthMethodPrefix )
if err != nil {
return nil , fmt . Errorf ( "error generating initial oidc auth method id: %w" , err )
}
}
cancelCtx , cancel := context . WithCancel ( c . Context )
defer cancel ()
go func () {
select {
case <- c . ShutdownCh :
cancel ()
case <- cancelCtx . Done ():
}
}()
c . oidcSetup . authMethod , err = oidcRepo . CreateAuthMethod (
cancelCtx ,
authMethod ,
oidc . WithPublicId ( c . DevOidcAuthMethodId ))
if err != nil {
return nil , fmt . Errorf ( "error saving oidc auth method to the db: %w" , err )
}
// Create accounts
{
createAndLinkAccount := func ( loginName , userId , typ string ) error {
acct , err := oidc . NewAccount (
c . oidcSetup . authMethod . GetPublicId (),
loginName ,
oidc . WithDescription ( fmt . Sprintf ( "Initial %s OIDC account" , typ )),
)
if err != nil {
return fmt . Errorf ( "error generating %s oidc account: %w" , typ , err )
}
acct , err = oidcRepo . CreateAccount (
cancelCtx ,
c . oidcSetup . authMethod . GetScopeId (),
acct ,
)
if err != nil {
return fmt . Errorf ( "error creating %s oidc account: %w" , typ , err )
}
// Link accounts to existing user
iamRepo , err := iam . NewRepository ( rw , rw , kmsCache )
if err != nil {
return fmt . Errorf ( "unable to create iam repo: %w" , err )
}
u , _ , err := iamRepo . LookupUser ( cancelCtx , userId )
if err != nil {
return fmt . Errorf ( "error looking up %s user: %w" , typ , err )
}
if _ , err = iamRepo . AddUserAccounts ( cancelCtx , u . GetPublicId (), u . GetVersion (), [] string { acct . GetPublicId ()}); err != nil {
return fmt . Errorf ( "error associating initial %s user with account: %w" , typ , err )
}
return nil
}
if err := createAndLinkAccount ( c . DevLoginName , c . DevUserId , "admin" ); err != nil {
return nil , err
}
if c . oidcSetup . createUnpriv {
if err := createAndLinkAccount ( c . DevUnprivilegedLoginName , c . DevUnprivilegedUserId , "unprivileged" ); err != nil {
return nil , err
}
}
}
return nil , nil
}