2022-09-29 09:01:33 -04:00
// Copyright (c) HashiCorp, Inc.
2022-06-09 16:39:20 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-02-12 13:53:27 -05:00
package config
import (
"bytes"
2022-09-29 09:01:33 -04:00
"context"
2020-02-12 13:53:27 -05:00
"crypto/rand"
"encoding/base64"
2021-12-03 11:47:00 +00:00
"encoding/json"
2020-09-16 09:28:39 -04:00
"errors"
2020-02-12 13:53:27 -05:00
"fmt"
"io"
2022-09-12 09:37:13 -04:00
"net"
2022-09-29 09:01:33 -04:00
"os"
2021-12-07 13:01:04 +00:00
"reflect"
2021-12-08 15:44:13 -05:00
"strconv"
2020-09-16 09:28:39 -04:00
"strings"
2020-11-16 10:39:18 -08:00
"time"
2020-02-12 13:53:27 -05:00
2022-09-29 09:01:33 -04:00
"github.com/hashicorp/boundary/globals"
2022-05-16 15:14:12 -07:00
"github.com/hashicorp/boundary/internal/db"
2023-09-12 08:15:34 -04:00
"github.com/hashicorp/boundary/internal/event"
2023-10-02 21:02:17 +00:00
"github.com/hashicorp/boundary/internal/ratelimit"
2023-12-15 15:26:35 -08:00
"github.com/hashicorp/boundary/internal/storage"
2022-11-10 13:14:57 -05:00
"github.com/hashicorp/boundary/internal/util"
2022-09-29 09:01:33 -04:00
kms_plugin_assets "github.com/hashicorp/boundary/plugins/kms"
"github.com/hashicorp/boundary/sdk/wrapper"
"github.com/hashicorp/go-hclog"
2022-03-16 17:04:07 -04:00
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
2022-09-12 09:37:13 -04:00
"github.com/hashicorp/go-secure-stdlib/listenerutil"
2021-07-14 17:40:39 -07:00
"github.com/hashicorp/go-secure-stdlib/parseutil"
2022-09-29 09:01:33 -04:00
"github.com/hashicorp/go-secure-stdlib/pluginutil/v2"
2021-07-14 17:37:40 -07:00
"github.com/hashicorp/go-secure-stdlib/strutil"
2020-02-12 13:53:27 -05:00
"github.com/hashicorp/hcl"
2021-08-04 14:01:58 -04:00
"github.com/hashicorp/hcl/hcl/ast"
2021-01-21 16:27:20 -05:00
"github.com/mitchellh/mapstructure"
2020-02-12 13:53:27 -05:00
)
2023-03-07 16:29:34 -05:00
var extraParsingFuncs [] func ( * Config ) error
2020-03-20 12:51:39 -04:00
const (
2021-04-14 15:26:21 -05:00
desktopCorsOrigin = "serve://boundary"
2020-03-20 12:51:39 -04:00
devConfig = `
2020-02-12 13:53:27 -05:00
disable_mlock = true
telemetry {
prometheus_retention_time = "24h"
disable_hostname = true
}
2020-03-20 12:51:39 -04:00
`
devControllerExtraConfig = `
2020-08-12 00:40:39 -04:00
controller {
name = "dev-controller"
description = "A default controller created in dev mode"
}
2020-02-12 13:53:27 -05:00
kms "aead" {
2020-08-14 10:12:31 -04:00
purpose = "root"
2020-02-12 13:53:27 -05:00
aead_type = "aes-gcm"
key = "%s"
2020-08-19 16:47:18 -04:00
key_id = "global_root"
2020-02-12 13:53:27 -05:00
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "%s"
2020-08-19 16:47:18 -04:00
key_id = "global_worker-auth"
2020-02-12 13:53:27 -05:00
}
2020-03-22 08:27:24 -04:00
2023-04-02 17:45:43 -04:00
kms "aead" {
purpose = "bsr"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_bsr"
}
2020-08-20 13:34:03 -04:00
kms "aead" {
purpose = "recovery"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_recovery"
}
2020-03-22 08:27:24 -04:00
listener "tcp" {
purpose = "api"
tls_disable = true
2020-05-21 12:12:08 -04:00
cors_enabled = true
cors_allowed_origins = ["*"]
2020-03-22 08:27:24 -04:00
}
listener "tcp" {
purpose = "cluster"
}
2022-03-28 17:16:39 +01:00
listener "tcp" {
purpose = "ops"
tls_disable = true
}
2024-11-01 13:27:40 -04:00
`
devIpv6ControllerExtraConfig = `
controller {
name = "dev-controller"
description = "A default controller created in dev mode"
}
kms "aead" {
purpose = "root"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_root"
}
kms "aead" {
purpose = "worker-auth"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_worker-auth"
}
kms "aead" {
purpose = "bsr"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_bsr"
}
kms "aead" {
purpose = "recovery"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_recovery"
}
listener "tcp" {
address = "[::1]"
purpose = "api"
tls_disable = true
cors_enabled = true
cors_allowed_origins = ["*"]
}
listener "tcp" {
address = "[::1]"
purpose = "cluster"
}
listener "tcp" {
address = "[::1]"
purpose = "ops"
tls_disable = true
}
`
devIpv6WorkerExtraConfig = `
listener "tcp" {
address = "[::1]"
purpose = "proxy"
}
worker {
name = "w_1234567890"
description = "A default worker created in dev mode"
public_addr = "[::1]"
initial_upstreams = ["[::1]"]
tags {
type = ["dev", "local"]
}
}
kms "aead" {
purpose = "worker-auth-storage"
aead_type = "aes-gcm"
key = "%s"
key_id = "worker-auth-storage"
}
2020-03-22 08:27:24 -04:00
`
devWorkerExtraConfig = `
listener "tcp" {
2020-09-03 13:24:34 -04:00
purpose = "proxy"
2020-03-22 08:27:24 -04:00
}
2020-07-20 13:52:16 -04:00
worker {
2022-05-16 15:14:12 -07:00
name = "w_1234567890"
2020-07-20 13:52:16 -04:00
description = "A default worker created in dev mode"
2022-06-13 15:06:03 -07:00
initial_upstreams = ["127.0.0.1"]
2021-01-21 16:27:20 -05:00
tags {
type = ["dev", "local"]
}
2020-07-20 13:52:16 -04:00
}
2022-08-05 12:12:07 -04:00
kms "aead" {
purpose = "worker-auth-storage"
aead_type = "aes-gcm"
key = "%s"
key_id = "worker-auth-storage"
}
2020-02-12 13:53:27 -05:00
`
2023-05-29 13:30:16 -07:00
// We use a custom Content-Security-Policy because we need to add wasm-unsafe-eval
// as a script-src to support asciinema playback on the Admin UI. Users can still
// override this value via the configuration.
defaultCsp = "default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; frame-src 'self'; font-src 'self'; connect-src 'self'; img-src 'self' data:; style-src 'self'; media-src 'self'; manifest-src 'self'; style-src-attr 'self'; frame-ancestors 'self'"
2020-03-20 12:51:39 -04:00
)
2020-08-10 17:15:02 -04:00
// Config is the configuration for the boundary controller
2020-03-20 12:51:39 -04:00
type Config struct {
* configutil . SharedConfig `hcl:"-"`
2020-09-23 01:23:10 -04:00
Worker * Worker `hcl:"worker"`
Controller * Controller `hcl:"controller"`
// Dev-related options
2022-06-03 13:25:58 -07:00
DevController bool `hcl:"-"`
DevUiPassthroughDir string `hcl:"-"`
DevControllerKey string `hcl:"-"`
DevWorkerAuthKey string `hcl:"-"`
DevWorkerAuthStorageKey string `hcl:"-"`
2023-04-02 17:45:43 -04:00
DevBsrKey string `hcl:"-"`
2022-06-03 13:25:58 -07:00
DevRecoveryKey string `hcl:"-"`
2021-07-08 15:50:25 -04:00
// Eventing configuration for the controller
Eventing * event . EventerConfig `hcl:"events"`
2021-10-14 11:56:08 -04:00
// Plugin-related options
Plugins Plugins `hcl:"plugins"`
2022-06-09 16:39:20 -04:00
2023-03-30 16:20:28 -04:00
// Internal field for use with HCP deployments. Used if controllers/
// initial_upstreams is not set.
2022-06-16 09:21:11 -05:00
HcpbClusterId string `hcl:"hcp_boundary_cluster_id"`
2023-03-30 16:20:28 -04:00
// Whether to log information from the worker authentication process. This
// shouldn't divulge any secrets but may contain information considered
// sensitive. This should only be enabled for debugging purposes, and can be
// toggled with SIGHUP.
EnableWorkerAuthDebugging bool `hcl:"enable_worker_auth_debugging"`
2023-05-30 18:20:49 -04:00
// For opting out of license utilization reporting
Reporting Reporting `hcl:"reporting"`
2020-08-12 00:40:39 -04:00
}
type Controller struct {
2023-04-08 16:15:14 -07:00
Name string `hcl:"name"`
Description string `hcl:"description"`
Database * Database `hcl:"database"`
PublicClusterAddr string `hcl:"public_cluster_addr"`
Scheduler Scheduler `hcl:"scheduler"`
2020-11-16 10:39:18 -08:00
// AuthTokenTimeToLive is the total valid lifetime of a token denoted by time.Duration
2022-11-10 13:14:57 -05:00
AuthTokenTimeToLive any `hcl:"auth_token_time_to_live"`
AuthTokenTimeToLiveDuration time . Duration `hcl:"-"`
2020-11-16 10:39:18 -08:00
// AuthTokenTimeToStale is the total time a token can go unused before becoming invalid
// denoted by time.Duration
2022-11-10 13:14:57 -05:00
AuthTokenTimeToStale any `hcl:"auth_token_time_to_stale"`
AuthTokenTimeToStaleDuration time . Duration `hcl:"-"`
2021-07-14 09:14:24 -07:00
2022-03-28 17:16:39 +01:00
// GracefulShutdownWait is the amount of time that we'll wait before actually
// starting the Controller shutdown. This allows the health endpoint to
// return a status code to indicate that the instance is shutting down.
2022-11-10 13:14:57 -05:00
GracefulShutdownWait any `hcl:"graceful_shutdown_wait_duration"`
GracefulShutdownWaitDuration time . Duration `hcl:"-"`
2022-03-28 17:16:39 +01:00
2025-01-09 10:26:37 -08:00
// WorkerRPCGracePeriod represents the period of time (as a duration)
2022-11-10 13:14:57 -05:00
// that the controller will wait before deciding a worker is disconnected
2025-01-09 10:26:37 -08:00
// and marking connections from it as canceling. It is also used to evaluate
// whether a worker is available for routing based on the time since its last routing info report.
// For backwards compatibility this is still called worker_status_grace_period,
// though it is now used with both the SessionInfo and RoutingInfo RPCs.
2022-11-10 13:14:57 -05:00
//
// TODO: This isn't documented (on purpose) because the right place for this
// is central configuration so you can't drift across controllers, but we
// don't have that yet.
2025-01-09 10:26:37 -08:00
WorkerRPCGracePeriod any `hcl:"worker_status_grace_period"`
WorkerRPCGracePeriodDuration time . Duration `hcl:"-"`
2022-11-10 13:14:57 -05:00
// LivenessTimeToStale represents the period of time (as a duration) after
// which it will consider other controllers to be no longer accessible,
// based on time since their last status update in the database
//
// TODO: This isn't documented (on purpose) because the right place for this
// is central configuration so you can't drift across controllers, but we
// don't have that yet.
2023-12-15 15:26:35 -08:00
LivenessTimeToStale any `hcl:"liveness_time_to_stale"`
2022-11-10 13:14:57 -05:00
LivenessTimeToStaleDuration time . Duration `hcl:"-"`
2024-08-13 14:27:39 -07:00
// TODO: This isn't documented (on purpose) because the right place for this
// is central configuration so you can't drift across controllers and workers
// but we don't have that yet.
GetDownstreamWorkersTimeout any `hcl:"get_downstream_workers_timeout"`
GetDownstreamWorkersTimeoutDuration time . Duration `hcl:"-"`
2022-11-10 13:14:57 -05:00
// SchedulerRunJobInterval is the time interval between waking up the
// scheduler to run pending jobs.
2021-07-14 09:14:24 -07:00
//
// TODO: This field is currently internal.
2022-11-10 13:14:57 -05:00
SchedulerRunJobInterval time . Duration `hcl:"-"`
2023-03-07 16:29:34 -05:00
2023-12-04 17:25:16 +00:00
ApiRateLimits ratelimit . Configs `hcl:"-"`
ApiRateLimiterMaxQuotas int `hcl:"api_rate_limit_max_quotas"`
ApiRateLimitDisable bool `hcl:"api_rate_limit_disable"`
2023-10-02 21:02:17 +00:00
2023-03-07 16:29:34 -05:00
// License is the license used by HCP builds
License string `hcl:"license"`
2023-10-23 16:35:35 -07:00
// MaxPageSize overrides the default and max page size.
// The default page size is what is used when the page size
// is not explicitly provided by the user. The max page size
// is the greatest number the page size can be set to before
// it is rejected by the controller.
MaxPageSizeRaw any `hcl:"max_page_size"`
MaxPageSize uint `hcl:"-"`
2025-01-22 13:31:42 -08:00
// ConcurrentPasswordHashWorkers controls the number of concurrent password
// hashing workers is allowed. The default value is 1. Increasing this number
// will increase the authentication throughput of all userpass auth methods,
// but at the cost of bursty memory and CPU use. Can also be controlled via
// the environment variable BOUNDARY_CONTROLLER_CONCURRENT_PASSWORD_HASH_WORKERS.
ConcurrentPasswordHashWorkersRaw any `hcl:"concurrent_password_hash_workers"`
ConcurrentPasswordHashWorkers uint `hcl:"-"`
2020-07-20 13:52:16 -04:00
}
2023-06-06 17:09:08 -04:00
func ( c * Controller ) InitNameIfEmpty ( ctx context . Context ) error {
2021-07-25 17:40:54 -04:00
if c == nil {
2022-06-15 20:01:03 -04:00
return fmt . Errorf ( "controller config is empty" )
2021-07-25 17:40:54 -04:00
}
2022-06-15 20:01:03 -04:00
if c . Name != "" {
return nil
2021-07-25 17:40:54 -04:00
}
2022-06-15 20:01:03 -04:00
var err error
2023-06-06 17:09:08 -04:00
c . Name , err = db . NewPublicId ( ctx , "c" )
2022-06-15 20:01:03 -04:00
if err != nil {
return fmt . Errorf ( "error auto-generating controller name: %w" , err )
}
return nil
2021-07-25 17:40:54 -04:00
}
2020-07-20 13:52:16 -04:00
type Worker struct {
2021-12-07 13:01:04 +00:00
Name string `hcl:"name"`
Description string `hcl:"description"`
PublicAddr string `hcl:"public_addr"`
// We use a raw interface here so that we can take in a string
// value pointing to an env var or file. We then resolve that
2022-06-13 15:06:03 -07:00
// and get the actual upstream controller or worker addresses.
InitialUpstreams [] string `hcl:"-"`
InitialUpstreamsRaw any `hcl:"initial_upstreams"`
2021-01-21 16:27:20 -05:00
// We use a raw interface for parsing so that people can use JSON-like
// syntax that maps directly to the filter input or possibly more familiar
2021-12-03 11:47:00 +00:00
// key=value syntax, as well as accepting a string denoting an env or file
// pointer. This is trued up in the Parse function below.
2021-01-21 16:27:20 -05:00
Tags map [ string ][] string `hcl:"-"`
2022-10-25 18:29:38 -05:00
TagsRaw any `hcl:"tags"`
2021-07-14 09:14:24 -07:00
2025-01-09 10:26:37 -08:00
// ControllerRPCCallTimeout represents the period of time (as a duration) that
// the worker will allow the SessionInfo, RoutingInfo and Statistics RPC calls to
// attempt to finish before canceling them to try again.
// For backwards compatibility this is still called status_call_timeout,
// though it is now used to control the SessionInfo, Statistics and RoutingInfo RPCs.
2021-07-14 09:14:24 -07:00
//
2022-11-10 13:14:57 -05:00
// TODO: This is currently not documented and considered internal.
2025-01-09 10:26:37 -08:00
ControllerRPCCallTimeout any `hcl:"status_call_timeout"`
ControllerRPCCallTimeoutDuration time . Duration `hcl:"-"`
2022-11-10 13:14:57 -05:00
2024-08-13 14:27:39 -07:00
// GetDownstreamWorkersTimeout represents the period of time (as a duration) timeout
// for GetDownstreamWorkers call in DownstreamWorkerTicker
//
// TODO: This is currently not documented and considered internal.
GetDownstreamWorkersTimeout any `hcl:"get_downstream_workers_timeout"`
GetDownstreamWorkersTimeoutDuration time . Duration `hcl:"-"`
2025-01-09 10:26:37 -08:00
// SuccessfulControllerRPCGracePeriod represents the period of time (as a duration)
// that the worker will wait before closing connections if it cannot
// successfully complete a session info report to a controller. It is also used
// to evaluate whether the upstreams need to be recalculated in case of repeated
// routing info report failures. This cannot be less than ControllerRPCCallTimeout.
// For backwards compatibility this is still called successful_status_grace_period,
// though it is now used to control the SessionInfo and RoutingInfo RPCs.
2022-11-10 13:14:57 -05:00
//
// TODO: This is currently not documented and considered internal.
2025-01-09 10:26:37 -08:00
SuccessfulControllerRPCGracePeriod any `hcl:"successful_status_grace_period"`
SuccessfulControllerRPCGracePeriodDuration time . Duration `hcl:"-"`
2022-05-11 17:15:38 -07:00
2022-06-03 17:32:33 -04:00
// AuthStoragePath represents the location a worker stores its node credentials, if set
AuthStoragePath string `hcl:"auth_storage_path"`
2022-09-02 14:32:41 -04:00
2023-03-28 09:39:28 -07:00
// RecordingStoragePath represents the location a worker caches session recordings before
// they are sync'ed to the corresponding storage bucket. The path must already exist.
RecordingStoragePath string `hcl:"recording_storage_path"`
2023-12-15 15:26:35 -08:00
// RecordingStorageMinimumAvailableCapacity represents the minimum amount of available
// disk space a worker needs in the path defined by RecordingStoragePath for processing
// sessions with recording enabled. The expected input value for this field is a
// “capacity string“. Supported suffixes are kb, kib, mb, mib, gb, gib, tb, tib, which
// are not case sensitive. We use a raw interface for parsing so that users can input a
// "capacity string" which is converted into a uint64 value that is measured in bytes.
RecordingStorageMinimumAvailableCapacity any `hcl:"recording_storage_minimum_available_capacity"`
RecordingStorageMinimumAvailableDiskSpace uint64 `hcl:"-"`
2022-09-02 14:32:41 -04:00
// ControllerGeneratedActivationToken is a controller-generated activation
// token used to register this worker to the cluster. It can be a path, env
// var, or direct value.
ControllerGeneratedActivationToken string `hcl:"controller_generated_activation_token"`
2023-03-30 16:20:28 -04:00
2024-01-12 11:58:12 -05:00
// UseDeprecatedKmsAuthMethod indicates that the worker should use the
// pre-0.13 method of using KMSes to authenticate. This is currently only
// supported to throw an error if used telling people they need to upgrade.
2023-03-30 16:20:28 -04:00
UseDeprecatedKmsAuthMethod bool `hcl:"use_deprecated_kms_auth_method"`
2025-01-09 10:26:37 -08:00
// TestWorkerRPCInterval represents the base period of time that
// the worker will wait between invoking the controller RPCs.
// This is not exposed to users and only used in tests.
TestWorkerRPCInterval time . Duration `hcl:"-"`
2020-03-20 12:51:39 -04:00
}
2020-09-16 09:28:39 -04:00
type Database struct {
2022-07-05 15:25:55 -04:00
Url string `hcl:"url"`
MigrationUrl string `hcl:"migration_url"`
MaxOpenConnections int `hcl:"-"`
2022-10-25 18:29:38 -05:00
MaxOpenConnectionsRaw any `hcl:"max_open_connections"`
2022-07-05 15:25:55 -04:00
MaxIdleConnections * int `hcl:"-"`
2022-10-25 18:29:38 -05:00
MaxIdleConnectionsRaw any `hcl:"max_idle_connections"`
ConnMaxIdleTime any `hcl:"max_idle_time"`
2022-07-05 15:25:55 -04:00
ConnMaxIdleTimeDuration * time . Duration `hcl:"-"`
2022-09-07 16:28:07 -04:00
// SkipSharedLockAcquisition allows skipping grabbing the database shared
2022-09-07 16:44:23 -04:00
// lock. This is dangerous unless you know what you're doing, and you should
// not set it unless you are the reason it's here in the first place, as not
// only it dangerous but it will be removed at some point in the future.
2022-09-07 16:28:07 -04:00
SkipSharedLockAcquisition bool `hcl:"skip_shared_lock_acquisition"`
2020-09-16 09:28:39 -04:00
}
2022-09-13 10:01:49 -07:00
// Scheduler is the configuration block that specifies the job scheduler behavior on the controller
type Scheduler struct {
// JobRunInterval is the time interval between waking up the
// scheduler to run pending jobs.
//
2022-10-25 18:29:38 -05:00
JobRunInterval any `hcl:"job_run_interval"`
2022-09-13 10:01:49 -07:00
JobRunIntervalDuration time . Duration
// MonitorInterval is the time interval between waking up the
// scheduler to monitor for jobs that are defunct.
//
2022-10-25 18:29:38 -05:00
MonitorInterval any `hcl:"monitor_interval"`
2022-09-13 10:01:49 -07:00
MonitorIntervalDuration time . Duration
}
2021-10-14 11:56:08 -04:00
type Plugins struct {
ExecutionDir string `hcl:"execution_dir"`
}
2023-05-30 18:20:49 -04:00
type Reporting struct {
License License `hcl:"license"`
}
type License struct {
Enabled bool `hcl:"enabled"`
}
2020-08-10 17:15:02 -04:00
// DevWorker is a Config that is used for dev mode of Boundary
2023-04-14 20:01:14 -04:00
// workers. Supported options: WithObservationsEnabled, WithSysEventsEnabled,
// WithAuditEventsEnabled, TestWithErrorEventsEnabled
func DevWorker ( opt ... Option ) ( * Config , error ) {
2022-08-05 12:12:07 -04:00
workerAuthStorageKey := DevKeyGeneration ()
2024-11-01 13:27:40 -04:00
opts , err := getOpts ( opt ... )
if err != nil {
return nil , fmt . Errorf ( "error parsing options: %w" , err )
}
2022-08-05 12:12:07 -04:00
hclStr := fmt . Sprintf ( devConfig + devWorkerExtraConfig , workerAuthStorageKey )
2024-11-01 13:27:40 -04:00
if opts . withIPv6Enabled {
hclStr = fmt . Sprintf ( devConfig + devIpv6WorkerExtraConfig , workerAuthStorageKey )
}
2022-08-05 12:12:07 -04:00
parsed , err := Parse ( hclStr )
2020-03-20 12:51:39 -04:00
if err != nil {
return nil , fmt . Errorf ( "error parsing dev config: %w" , err )
}
2023-04-14 20:01:14 -04:00
parsed . Eventing . AuditEnabled = opts . withAuditEventsEnabled
parsed . Eventing . ObservationsEnabled = opts . withObservationsEnabled
parsed . Eventing . SysEventsEnabled = opts . withSysEventsEnabled
parsed . Eventing . ErrorEventsDisabled = ! opts . testWithErrorEventsEnabled
2020-03-20 12:51:39 -04:00
return parsed , nil
}
2022-06-03 13:25:58 -07:00
func DevKeyGeneration () string {
var numBytes int64 = 32
2020-03-20 12:51:39 -04:00
randBuf := new ( bytes . Buffer )
n , err := randBuf . ReadFrom ( & io . LimitedReader {
R : rand . Reader ,
2020-08-20 13:34:03 -04:00
N : numBytes ,
2020-03-20 12:51:39 -04:00
})
if err != nil {
2020-07-20 13:52:16 -04:00
panic ( err )
2020-03-20 12:51:39 -04:00
}
2020-08-20 13:34:03 -04:00
if n != numBytes {
2022-06-03 13:25:58 -07:00
panic ( fmt . Errorf ( "expected to read 32 bytes, read %d" , n ))
2020-03-20 12:51:39 -04:00
}
2023-10-31 10:32:46 -04:00
devKey := base64 . StdEncoding . EncodeToString ( randBuf . Bytes ())
2022-06-03 13:25:58 -07:00
return devKey
2020-07-20 13:52:16 -04:00
}
2020-08-10 17:15:02 -04:00
// DevController is a Config that is used for dev mode of Boundary
2020-07-20 13:52:16 -04:00
// controllers
2023-04-14 20:01:14 -04:00
func DevController ( opt ... Option ) ( * Config , error ) {
2024-11-01 13:27:40 -04:00
opts , err := getOpts ( opt ... )
if err != nil {
return nil , fmt . Errorf ( "error parsing options: %w" , err )
}
2022-06-03 13:25:58 -07:00
controllerKey := DevKeyGeneration ()
workerAuthKey := DevKeyGeneration ()
2023-04-02 17:45:43 -04:00
bsrKey := DevKeyGeneration ()
2022-06-03 13:25:58 -07:00
recoveryKey := DevKeyGeneration ()
2020-02-12 13:53:27 -05:00
2023-04-02 17:45:43 -04:00
hclStr := fmt . Sprintf ( devConfig + devControllerExtraConfig , controllerKey , workerAuthKey , bsrKey , recoveryKey )
2024-11-01 13:27:40 -04:00
if opts . withIPv6Enabled {
hclStr = fmt . Sprintf ( devConfig + devIpv6ControllerExtraConfig , controllerKey , workerAuthKey , bsrKey , recoveryKey )
}
2020-03-10 15:48:04 -04:00
parsed , err := Parse ( hclStr )
2020-02-12 13:53:27 -05:00
if err != nil {
return nil , fmt . Errorf ( "error parsing dev config: %w" , err )
}
2020-03-20 12:51:39 -04:00
parsed . DevController = true
2020-09-23 01:23:10 -04:00
parsed . DevControllerKey = controllerKey
parsed . DevWorkerAuthKey = workerAuthKey
2023-04-02 17:45:43 -04:00
parsed . DevBsrKey = bsrKey
2020-09-23 01:23:10 -04:00
parsed . DevRecoveryKey = recoveryKey
2023-04-14 20:01:14 -04:00
parsed . Eventing . AuditEnabled = opts . withAuditEventsEnabled
parsed . Eventing . ObservationsEnabled = opts . withObservationsEnabled
parsed . Eventing . SysEventsEnabled = opts . withSysEventsEnabled
parsed . Eventing . ErrorEventsDisabled = ! opts . testWithErrorEventsEnabled
2020-02-12 13:53:27 -05:00
return parsed , nil
}
2024-11-01 13:27:40 -04:00
func DevCombined ( opt ... Option ) ( * Config , error ) {
opts , err := getOpts ( opt ... )
if err != nil {
return nil , fmt . Errorf ( "error parsing options: %w" , err )
}
2022-06-03 13:25:58 -07:00
controllerKey := DevKeyGeneration ()
workerAuthKey := DevKeyGeneration ()
workerAuthStorageKey := DevKeyGeneration ()
2023-04-02 17:45:43 -04:00
bsrKey := DevKeyGeneration ()
2022-06-03 13:25:58 -07:00
recoveryKey := DevKeyGeneration ()
2024-11-01 13:27:40 -04:00
2023-04-02 17:45:43 -04:00
hclStr := fmt . Sprintf ( devConfig + devControllerExtraConfig + devWorkerExtraConfig , controllerKey , workerAuthKey , bsrKey , recoveryKey , workerAuthStorageKey )
2024-11-01 13:27:40 -04:00
if opts . withIPv6Enabled {
hclStr = fmt . Sprintf ( devConfig + devIpv6ControllerExtraConfig + devIpv6WorkerExtraConfig , controllerKey , workerAuthKey , bsrKey , recoveryKey , workerAuthStorageKey )
}
2020-07-20 13:52:16 -04:00
parsed , err := Parse ( hclStr )
if err != nil {
return nil , fmt . Errorf ( "error parsing dev config: %w" , err )
}
parsed . DevController = true
2020-09-23 01:23:10 -04:00
parsed . DevControllerKey = controllerKey
parsed . DevWorkerAuthKey = workerAuthKey
2022-06-03 13:25:58 -07:00
parsed . DevWorkerAuthStorageKey = workerAuthStorageKey
2023-04-02 17:45:43 -04:00
parsed . DevBsrKey = bsrKey
2020-09-23 01:23:10 -04:00
parsed . DevRecoveryKey = recoveryKey
2020-07-20 13:52:16 -04:00
return parsed , nil
}
2020-03-10 15:48:04 -04:00
func New () * Config {
2020-02-12 13:53:27 -05:00
return & Config {
SharedConfig : new ( configutil . SharedConfig ),
}
}
2022-09-29 09:01:33 -04:00
// Load will create a Config from the given config files. It concatenates the
// contents of the files together. This allows for composing a config from
// separate files, but notably does not attempt any merging of configs. Thus it
// is possible that the concatenation results in an invalid config file. This
// also supports decrypting the config, either with a kms block in one of the
// given files, or as a separate file in wrapperPath.
//
// Note that having multiple config files is only supported properly if they are
// all hcl files. If they are json files, only the first file is used. A mix of
// hcl and json will result in an error.
func Load ( ctx context . Context , paths [] string , wrapperPath string ) ( * Config , error ) {
const op = "config.Load"
var err error
var cfg * Config
configStrs := make ([] string , 0 , len ( paths ))
for _ , path := range paths {
fileBytes , err := os . ReadFile ( path )
if err != nil {
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "could not read config file" , "path" , path , "error" , err ))
return nil , err
}
configStrs = append ( configStrs , string ( fileBytes ))
}
configString := strings . Join ( configStrs , "\n" )
wrapperString := configString
if wrapperPath != "" {
wrapperBytes , err := os . ReadFile ( wrapperPath )
if err != nil {
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "could not read kms config file" , "path" , wrapperPath , "error" , err ))
return nil , err
}
wrapperString = string ( wrapperBytes )
2020-02-12 13:53:27 -05:00
}
2022-09-29 09:01:33 -04:00
var configWrapper wrapping . Wrapper
var ifWrapper wrapping . InitFinalizer
var cleanupFunc func () error
if wrapperString != "" {
configWrapper , cleanupFunc , err = wrapper . GetWrapperFromHcl (
ctx ,
wrapperString ,
globals . KmsPurposeConfig ,
configutil . WithPluginOptions (
pluginutil . WithPluginsMap ( kms_plugin_assets . BuiltinKmsPlugins ()),
pluginutil . WithPluginsFilesystem ( kms_plugin_assets . KmsPluginPrefix , kms_plugin_assets . FileSystem ()),
),
// TODO: How would we want to expose this kind of log to users when
// using recovery configs? Generally with normal CLI commands we
// don't print out all of these logs. We may want a logger with a
// custom writer behind our existing gate where we print nothing
// unless there is an error, then dump all of it.
configutil . WithLogger ( hclog . NewNullLogger ()),
)
if err != nil {
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "could not get kms wrapper from config" , "path" , wrapperPath ))
return nil , err
}
if cleanupFunc != nil {
defer func () {
if err := cleanupFunc (); err != nil {
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "could not clean up kms wrapper" , "path" , wrapperPath ))
}
}()
}
if configWrapper != nil {
ifWrapper , _ = configWrapper .( wrapping . InitFinalizer )
}
}
if ifWrapper != nil {
if err := ifWrapper . Init ( ctx ); err != nil && ! errors . Is ( err , wrapping . ErrFunctionNotImplemented ) {
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "could not initialize kms" , "path" , wrapperPath ))
return nil , err
}
}
2020-06-24 13:08:13 -07:00
2022-09-29 09:01:33 -04:00
if configWrapper != nil {
configString , err = configutil . EncryptDecrypt ( configString , true , true , configWrapper )
2020-06-24 13:08:13 -07:00
if err != nil {
return nil , err
}
2020-02-12 13:53:27 -05:00
}
2022-09-29 09:01:33 -04:00
cfg , err = Parse ( configString )
if ifWrapper != nil {
if err := ifWrapper . Finalize ( context . Background ()); err != nil && ! errors . Is ( err , wrapping . ErrFunctionNotImplemented ) {
event . WriteError ( context . Background (), op , err , event . WithInfoMsg ( "could not finalize kms" , "path" , wrapperPath ))
return nil , err
}
}
return cfg , err
2020-02-12 13:53:27 -05:00
}
2020-03-10 15:48:04 -04:00
func Parse ( d string ) ( * Config , error ) {
2020-02-12 13:53:27 -05:00
obj , err := hcl . Parse ( d )
if err != nil {
return nil , err
}
2020-03-10 15:48:04 -04:00
result := New ()
2020-02-12 13:53:27 -05:00
if err := hcl . DecodeObject ( result , obj ); err != nil {
return nil , err
}
2020-11-16 10:39:18 -08:00
// Perform controller configuration overrides for auth token settings
if result . Controller != nil {
2021-07-14 17:50:15 -07:00
result . Controller . Name , err = parseutil . ParsePath ( result . Controller . Name )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
2021-05-04 08:37:56 -05:00
return nil , fmt . Errorf ( "Error parsing controller name: %w" , err )
}
2021-01-21 16:27:20 -05:00
if result . Controller . Name != strings . ToLower ( result . Controller . Name ) {
return nil , errors . New ( "Controller name must be all lower-case" )
}
if ! strutil . Printable ( result . Controller . Name ) {
return nil , errors . New ( "Controller name contains non-printable characters" )
}
2021-12-07 11:28:12 -05:00
result . Controller . Description , err = parseutil . ParsePath ( result . Controller . Description )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing controller description: %w" , err )
}
if ! strutil . Printable ( result . Controller . Description ) {
return nil , errors . New ( "Controller description contains non-printable characters" )
}
2020-11-16 10:39:18 -08:00
if result . Controller . AuthTokenTimeToLive != "" {
t , err := parseutil . ParseDurationSecond ( result . Controller . AuthTokenTimeToLive )
if err != nil {
return result , err
}
result . Controller . AuthTokenTimeToLiveDuration = t
}
if result . Controller . AuthTokenTimeToStale != "" {
t , err := parseutil . ParseDurationSecond ( result . Controller . AuthTokenTimeToStale )
if err != nil {
return result , err
}
result . Controller . AuthTokenTimeToStaleDuration = t
}
2021-12-08 15:44:13 -05:00
2022-03-28 17:16:39 +01:00
if result . Controller . GracefulShutdownWait != "" {
t , err := parseutil . ParseDurationSecond ( result . Controller . GracefulShutdownWait )
if err != nil {
return result , err
}
result . Controller . GracefulShutdownWaitDuration = t
}
2023-04-08 16:15:14 -07:00
if result . Controller . Scheduler . JobRunInterval != "" {
t , err := parseutil . ParseDurationSecond ( result . Controller . Scheduler . JobRunInterval )
if err != nil {
return result , err
2022-09-13 10:01:49 -07:00
}
2023-04-08 16:15:14 -07:00
result . Controller . Scheduler . JobRunIntervalDuration = t
}
2022-09-13 10:01:49 -07:00
2023-04-08 16:15:14 -07:00
if result . Controller . Scheduler . MonitorInterval != "" {
t , err := parseutil . ParseDurationSecond ( result . Controller . Scheduler . MonitorInterval )
if err != nil {
return result , err
2022-09-13 10:01:49 -07:00
}
2023-04-08 16:15:14 -07:00
result . Controller . Scheduler . MonitorIntervalDuration = t
2022-09-13 10:01:49 -07:00
}
2025-01-09 10:26:37 -08:00
workerRPCGracePeriod := result . Controller . WorkerRPCGracePeriod
if util . IsNil ( workerRPCGracePeriod ) {
// For backwards compatibility this is still called BOUNDARY_CONTROLLER_WORKER_STATUS_GRACE_PERIOD,
// though it is now used to control the SessionInfo and RoutingInfo RPCs.
workerRPCGracePeriod = os . Getenv ( "BOUNDARY_CONTROLLER_WORKER_STATUS_GRACE_PERIOD" )
2022-11-10 13:14:57 -05:00
}
2025-01-09 10:26:37 -08:00
if workerRPCGracePeriod != nil {
t , err := parseutil . ParseDurationSecond ( workerRPCGracePeriod )
2022-11-10 13:14:57 -05:00
if err != nil {
return result , err
}
2025-01-09 10:26:37 -08:00
result . Controller . WorkerRPCGracePeriodDuration = t
2022-11-10 13:14:57 -05:00
}
2025-01-09 10:26:37 -08:00
if result . Controller . WorkerRPCGracePeriodDuration < 0 {
return nil , errors . New ( "Controller worker RPC grace period value is negative" )
2022-11-10 13:14:57 -05:00
}
livenessTimeToStale := result . Controller . LivenessTimeToStale
if util . IsNil ( livenessTimeToStale ) {
livenessTimeToStale = os . Getenv ( "BOUNDARY_CONTROLLER_LIVENESS_TIME_TO_STALE" )
}
if livenessTimeToStale != nil {
t , err := parseutil . ParseDurationSecond ( livenessTimeToStale )
if err != nil {
return result , err
}
result . Controller . LivenessTimeToStaleDuration = t
}
if result . Controller . LivenessTimeToStaleDuration < 0 {
return nil , errors . New ( "Controller liveness time to stale value is negative" )
}
2024-08-13 14:27:39 -07:00
getDownstreamWorkersTimeout := result . Controller . GetDownstreamWorkersTimeout
if util . IsNil ( getDownstreamWorkersTimeout ) {
getDownstreamWorkersTimeout = os . Getenv ( "BOUNDARY_CONTROLLER_GET_DOWNSTREAM_WORKERS_TIMEOUT" )
}
if getDownstreamWorkersTimeout != nil {
t , err := parseutil . ParseDurationSecond ( getDownstreamWorkersTimeout )
if err != nil {
return result , fmt . Errorf ( "error trying to parse controller get_downstream_workers_timeout: %w" , err )
}
result . Controller . GetDownstreamWorkersTimeoutDuration = t
}
if result . Controller . GetDownstreamWorkersTimeoutDuration < 0 {
return nil , errors . New ( "get downstream workers timeout must be greater than 0" )
}
2023-10-23 16:35:35 -07:00
if result . Controller . MaxPageSizeRaw != nil {
switch t := result . Controller . MaxPageSizeRaw .( type ) {
case string :
maxPageSizeString , err := parseutil . ParsePath ( t )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing max page size: %w" , err )
}
pageSize , err := strconv . Atoi ( maxPageSizeString )
if err != nil {
return nil , fmt . Errorf ( "Max page size value is not an int: %w" , err )
}
if pageSize <= 0 {
return nil , fmt . Errorf ( "Max page size value must be at least 1, was %d" , pageSize )
}
result . Controller . MaxPageSize = uint ( pageSize )
case int :
if t <= 0 {
return nil , fmt . Errorf ( "Max page size value must be at least 1, was %d" , t )
}
result . Controller . MaxPageSize = uint ( t )
default :
return nil , fmt . Errorf ( "Max page size: unsupported type %q" , reflect . TypeOf ( t ). String ())
}
}
2021-12-08 15:44:13 -05:00
if result . Controller . Database != nil {
if result . Controller . Database . MaxOpenConnectionsRaw != nil {
switch t := result . Controller . Database . MaxOpenConnectionsRaw .( type ) {
case string :
maxOpenConnectionsString , err := parseutil . ParsePath ( t )
2022-07-05 15:25:55 -04:00
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
2021-12-08 15:44:13 -05:00
return nil , fmt . Errorf ( "Error parsing database max open connections: %w" , err )
}
result . Controller . Database . MaxOpenConnections , err = strconv . Atoi ( maxOpenConnectionsString )
if err != nil {
return nil , fmt . Errorf ( "Database max open connections value is not an int: %w" , err )
}
case int :
result . Controller . Database . MaxOpenConnections = t
default :
return nil , fmt . Errorf ( "Database max open connections: unsupported type %q" ,
reflect . TypeOf ( t ). String ())
}
}
2022-07-05 15:25:55 -04:00
if result . Controller . Database . MaxIdleConnectionsRaw != nil {
switch t := result . Controller . Database . MaxIdleConnectionsRaw .( type ) {
case string :
maxIdleConnectionsString , err := parseutil . ParsePath ( t )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing database max idle connections: %w" , err )
}
idleConns , err := strconv . Atoi ( maxIdleConnectionsString )
if err != nil {
return nil , fmt . Errorf ( "Database max idle connections value is not a uint: %w" , err )
}
result . Controller . Database . MaxIdleConnections = & idleConns
case int :
result . Controller . Database . MaxIdleConnections = & t
default :
return nil , fmt . Errorf ( "Database max idle connections: unsupported type %q" ,
reflect . TypeOf ( t ). String ())
}
}
if result . Controller . Database . ConnMaxIdleTime != nil {
switch t := result . Controller . Database . ConnMaxIdleTime .( type ) {
case string :
durationString , err := parseutil . ParsePath ( t )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing connection max idle time: %w" , err )
}
connMaxIdleTime , err := parseutil . ParseDurationSecond ( durationString )
if err != nil {
return nil , fmt . Errorf ( "Connection max idle time is not a duration: %w" , err )
}
result . Controller . Database . ConnMaxIdleTimeDuration = & connMaxIdleTime
default :
return nil , fmt . Errorf ( "Database connection max idle time: unsupported type %q" ,
reflect . TypeOf ( t ). String ())
}
}
2021-12-08 15:44:13 -05:00
}
2023-10-02 21:02:17 +00:00
result . Controller . ApiRateLimits , err = parseApiRateLimits ( obj . Node )
if err != nil {
return nil , err
}
2023-12-04 17:25:16 +00:00
if result . Controller . ApiRateLimiterMaxQuotas <= 0 {
result . Controller . ApiRateLimiterMaxQuotas = ratelimit . DefaultLimiterMaxQuotas ()
2023-10-02 21:02:17 +00:00
}
2025-01-22 13:31:42 -08:00
switch t := result . Controller . ConcurrentPasswordHashWorkersRaw .( type ) {
case string :
concurrentPasswordWorkersString , err := parseutil . ParsePath ( t )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing concurrent password hash workers: %w" , err )
}
concurrentWorkers , err := strconv . Atoi ( concurrentPasswordWorkersString )
if err != nil {
return nil , fmt . Errorf ( "Concurrent password hash workers value is not an int: %w" , err )
}
if concurrentWorkers <= 0 {
return nil , fmt . Errorf ( "Concurrent password hash workers value must be at least 1, was %d" , concurrentWorkers )
}
result . Controller . ConcurrentPasswordHashWorkers = uint ( concurrentWorkers )
case int :
if t <= 0 {
return nil , fmt . Errorf ( "Concurrent password hash workers value must be at least 1, was %d" , t )
}
result . Controller . ConcurrentPasswordHashWorkers = uint ( t )
case nil :
if envVal := os . Getenv ( "BOUNDARY_CONTROLLER_CONCURRENT_PASSWORD_HASH_WORKERS" ); envVal != "" {
concurrentPasswordWorkers , err := strconv . Atoi ( envVal )
if err != nil {
return nil , fmt . Errorf ( "BOUNDARY_CONTROLLER_CONCURRENT_PASSWORD_HASH_WORKERS value is not an int: %w" , err )
}
result . Controller . ConcurrentPasswordHashWorkers = uint ( concurrentPasswordWorkers )
}
default :
return nil , fmt . Errorf ( "Concurrent password hash workers: unsupported type %q" , reflect . TypeOf ( t ). String ())
}
2020-11-16 10:39:18 -08:00
}
2021-01-21 16:27:20 -05:00
// Parse worker tags
if result . Worker != nil {
2024-01-12 11:58:12 -05:00
if result . Worker . UseDeprecatedKmsAuthMethod {
return nil , fmt . Errorf ( "The flag 'use_deprecated_auth_method' is unsupported as of version 0.15." )
}
2021-07-14 17:50:15 -07:00
result . Worker . Name , err = parseutil . ParsePath ( result . Worker . Name )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
2021-05-04 08:37:56 -05:00
return nil , fmt . Errorf ( "Error parsing worker name: %w" , err )
}
2021-01-21 16:27:20 -05:00
if result . Worker . Name != strings . ToLower ( result . Worker . Name ) {
return nil , errors . New ( "Worker name must be all lower-case" )
}
if ! strutil . Printable ( result . Worker . Name ) {
return nil , errors . New ( "Worker name contains non-printable characters" )
}
2021-12-03 11:47:00 +00:00
2021-12-10 18:16:20 +00:00
result . Worker . Description , err = parseutil . ParsePath ( result . Worker . Description )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing worker description: %w" , err )
}
if ! strutil . Printable ( result . Worker . Description ) {
return nil , errors . New ( "Worker description contains non-printable characters" )
}
2022-09-02 14:32:41 -04:00
result . Worker . ControllerGeneratedActivationToken , err = parseutil . ParsePath ( result . Worker . ControllerGeneratedActivationToken )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing worker activation token: %w" , err )
}
2025-01-09 10:26:37 -08:00
statusCallTimeoutDuration := result . Worker . ControllerRPCCallTimeout
2022-11-10 13:14:57 -05:00
if util . IsNil ( statusCallTimeoutDuration ) {
2025-01-09 10:26:37 -08:00
// For backwards compatibility this is still called BOUNDARY_WORKER_STATUS_CALL_TIMEOUT,
// though it is now used to control the SessionInfo, Statistics and RoutingInfo RPCs.
2022-11-10 13:14:57 -05:00
statusCallTimeoutDuration = os . Getenv ( "BOUNDARY_WORKER_STATUS_CALL_TIMEOUT" )
}
if statusCallTimeoutDuration != nil {
t , err := parseutil . ParseDurationSecond ( statusCallTimeoutDuration )
if err != nil {
return result , err
}
2025-01-09 10:26:37 -08:00
result . Worker . ControllerRPCCallTimeoutDuration = t
2022-11-10 13:14:57 -05:00
}
2025-01-09 10:26:37 -08:00
if result . Worker . ControllerRPCCallTimeoutDuration < 0 {
return nil , errors . New ( "Controller RPC timeout value is negative" )
2022-11-10 13:14:57 -05:00
}
2024-08-13 14:27:39 -07:00
getDownstreamWorkersTimeoutDuration := result . Worker . GetDownstreamWorkersTimeout
if util . IsNil ( getDownstreamWorkersTimeoutDuration ) {
getDownstreamWorkersTimeoutDuration = os . Getenv ( "BOUNDARY_WORKER_GET_DOWNSTREAM_WORKERS_TIMEOUT" )
}
if getDownstreamWorkersTimeoutDuration != nil {
t , err := parseutil . ParseDurationSecond ( getDownstreamWorkersTimeoutDuration )
if err != nil {
return result , fmt . Errorf ( "error trying to parse worker get_downstream_workers_timeout: %w" , err )
}
result . Worker . GetDownstreamWorkersTimeoutDuration = t
}
if result . Worker . GetDownstreamWorkersTimeoutDuration < 0 {
return nil , errors . New ( "get downstream workers timeout must be greater than 0" )
}
2025-01-09 10:26:37 -08:00
successfulControllerRPCGracePeriod := result . Worker . SuccessfulControllerRPCGracePeriod
if util . IsNil ( successfulControllerRPCGracePeriod ) {
// For backwards compatibility this is still called BOUNDARY_WORKER_SUCCESSFUL_STATUS_GRACE_PERIOD,
// though it is now used to control the SessionInfo and RoutingInfo RPCs.
successfulControllerRPCGracePeriod = os . Getenv ( "BOUNDARY_WORKER_SUCCESSFUL_STATUS_GRACE_PERIOD" )
2022-11-10 13:14:57 -05:00
}
2025-01-09 10:26:37 -08:00
if successfulControllerRPCGracePeriod != nil {
t , err := parseutil . ParseDurationSecond ( successfulControllerRPCGracePeriod )
2022-11-10 13:14:57 -05:00
if err != nil {
return result , err
}
2025-01-09 10:26:37 -08:00
result . Worker . SuccessfulControllerRPCGracePeriodDuration = t
2022-11-10 13:14:57 -05:00
}
2025-01-09 10:26:37 -08:00
if result . Worker . SuccessfulControllerRPCGracePeriodDuration < 0 {
return nil , errors . New ( "Successful controller RPC grace period value is negative" )
2022-11-10 13:14:57 -05:00
}
2023-12-15 15:26:35 -08:00
if ! util . IsNil ( result . Worker . RecordingStorageMinimumAvailableCapacity ) {
if result . Worker . RecordingStoragePath == "" {
return nil , errors . New ( "recording_storage_path cannot be empty when providing recording_storage_minimum_available_capacity" )
}
recordingStorageMinimumAvailableDiskSpace , err := parseutil . ParseCapacityString ( result . Worker . RecordingStorageMinimumAvailableCapacity )
if err != nil {
return result , err
}
result . Worker . RecordingStorageMinimumAvailableDiskSpace = recordingStorageMinimumAvailableDiskSpace
}
// RecordingStorageMinimumAvailableDiskSpace defaults to 500MiB when not set by the user
if result . Worker . RecordingStoragePath != "" && result . Worker . RecordingStorageMinimumAvailableDiskSpace == 0 {
result . Worker . RecordingStorageMinimumAvailableDiskSpace = storage . DefaultMinimumAvailableDiskSpace
}
2022-11-10 13:14:57 -05:00
switch {
2025-01-09 10:26:37 -08:00
case result . Worker . ControllerRPCCallTimeoutDuration == 0 && result . Worker . SuccessfulControllerRPCGracePeriodDuration == 0 :
2022-11-10 13:14:57 -05:00
// Nothing
2025-01-09 10:26:37 -08:00
case result . Worker . ControllerRPCCallTimeoutDuration != 0 && result . Worker . SuccessfulControllerRPCGracePeriodDuration != 0 :
if result . Worker . ControllerRPCCallTimeoutDuration > result . Worker . SuccessfulControllerRPCGracePeriodDuration {
return nil , fmt . Errorf ( "Worker setting for controller rpc timeout duration must be less than or equal to successful controller rpc grace period duration" )
2022-11-10 13:14:57 -05:00
}
default :
2025-01-09 10:26:37 -08:00
return nil , fmt . Errorf ( "Worker settings for controller rpc call timeout duration and successful controller rpc grace period duration must either both be set or both be empty" )
2022-11-10 13:14:57 -05:00
}
2021-01-21 16:27:20 -05:00
if result . Worker . TagsRaw != nil {
switch t := result . Worker . TagsRaw .( type ) {
2021-12-03 11:47:00 +00:00
// We allow `tags` to be a simple string containing a URL with schema.
// See: https://github.com/hashicorp/go-secure-stdlib/blob/main/parseutil/parsepath.go
case string :
rawTags , err := parseutil . ParsePath ( t )
if err != nil {
return nil , fmt . Errorf ( "Error parsing worker tags: %w" , err )
}
2022-10-25 18:29:38 -05:00
var temp [] map [ string ] any
2021-12-03 11:47:00 +00:00
err = hcl . Decode ( & temp , rawTags )
if err != nil {
return nil , fmt . Errorf ( "Error decoding raw worker tags: %w" , err )
}
if err := mapstructure . WeakDecode ( temp , & result . Worker . Tags ); err != nil {
return nil , fmt . Errorf ( "Error decoding the worker's tags: %w" , err )
}
2021-01-21 16:27:20 -05:00
// HCL allows multiple labeled blocks with the same name, turning it
// into a slice of maps, hence the slice here. This format is the
// one that ends up matching the JSON that we use in the expression.
2022-10-25 18:29:38 -05:00
case [] map [ string ] any :
2021-12-03 11:47:00 +00:00
for _ , m := range t {
for k , v := range m {
// We allow the user to pass in only the keys in HCL, and
// then set the values to point to a URL with schema.
valStr , ok := v .( string )
if ! ok {
continue
}
parsed , err := parseutil . ParsePath ( valStr )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing worker tag values: %w" , err )
}
if valStr == parsed {
// Nothing was found, ignore.
// WeakDecode will still parse it though as we
// don't know if this could be a valid tag.
continue
}
var tags [] string
err = json . Unmarshal ([] byte ( parsed ), & tags )
if err != nil {
2022-08-03 19:29:17 -04:00
return nil , fmt . Errorf ( "Error unmarshaling env var/file contents: %w" , err )
2021-12-03 11:47:00 +00:00
}
m [ k ] = tags
}
}
2021-01-21 16:27:20 -05:00
if err := mapstructure . WeakDecode ( t , & result . Worker . Tags ); err != nil {
return nil , fmt . Errorf ( "Error decoding the worker's %q section: %w" , "tags" , err )
}
// However for those that are used to other systems, we also accept
// key=value pairs
2022-10-25 18:29:38 -05:00
case [] any :
2021-01-21 16:27:20 -05:00
var strs [] string
if err := mapstructure . WeakDecode ( t , & strs ); err != nil {
return nil , fmt . Errorf ( "Error decoding the worker's %q section: %w" , "tags" , err )
}
result . Worker . Tags = make ( map [ string ][] string , len ( strs ))
// Aggregate the values by key. We care about the first equal
// sign only, to allow equals to be in values if needed. This
// also means we don't support equal signs in keys.
for _ , str := range strs {
splitStr := strings . SplitN ( str , "=" , 2 )
switch len ( splitStr ) {
case 1 :
return nil , fmt . Errorf ( "Error decoding tag %q from string: must be in key = value format" , str )
case 2 :
key := splitStr [ 0 ]
v := result . Worker . Tags [ key ]
if len ( v ) == 0 {
v = make ([] string , 0 , 1 )
}
result . Worker . Tags [ key ] = append ( v , splitStr [ 1 ])
}
}
}
}
2021-12-03 11:47:00 +00:00
2021-01-21 16:27:20 -05:00
for k , v := range result . Worker . Tags {
if k != strings . ToLower ( k ) {
return nil , fmt . Errorf ( "Tag key %q is not all lower-case letters" , k )
}
if ! strutil . Printable ( k ) {
return nil , fmt . Errorf ( "Tag key %q contains non-printable characters" , k )
}
2022-08-26 13:37:26 -07:00
if strings . Contains ( k , "," ) {
return nil , fmt . Errorf ( "Tag key %q cannot contain commas" , k )
}
2021-01-21 16:27:20 -05:00
for _ , val := range v {
if val != strings . ToLower ( val ) {
return nil , fmt . Errorf ( "Tag value %q for tag key %q is not all lower-case letters" , val , k )
}
if ! strutil . Printable ( k ) {
return nil , fmt . Errorf ( "Tag value %q for tag key %q contains non-printable characters" , v , k )
}
2022-08-26 13:37:26 -07:00
if strings . Contains ( val , "," ) {
return nil , fmt . Errorf ( "Tag value %q for tag key %q cannot contain commas" , val , k )
}
2021-01-21 16:27:20 -05:00
}
}
2021-12-07 13:01:04 +00:00
2022-06-13 15:06:03 -07:00
result . Worker . InitialUpstreams , err = parseWorkerUpstreams ( result )
2021-12-07 13:01:04 +00:00
if err != nil {
2022-06-13 15:06:03 -07:00
return nil , fmt . Errorf ( "Failed to parse worker upstreams: %w" , err )
2021-12-07 13:01:04 +00:00
}
2021-01-21 16:27:20 -05:00
}
2023-03-30 16:20:28 -04:00
// Now that we can have multiple KMSes for downstream workers, allow an
// unlimited number of KMS blocks as we don't know how many might be defined
2023-05-29 13:30:16 -07:00
sharedConfig , err := configutil . ParseConfig (
d ,
configutil . WithMaxKmsBlocks ( - 1 ),
configutil . WithListenerOptions ( listenerutil . WithDefaultUiContentSecurityPolicyHeader ( defaultCsp )),
)
2020-02-12 13:53:27 -05:00
if err != nil {
return nil , err
}
result . SharedConfig = sharedConfig
2021-02-11 20:10:48 -05:00
for _ , listener := range result . SharedConfig . Listeners {
2021-04-14 15:26:21 -05:00
if strutil . StrListContains ( listener . Purpose , "api" ) &&
( listener . CorsDisableDefaultAllowedOriginValues == nil || ! * listener . CorsDisableDefaultAllowedOriginValues ) {
2021-05-21 12:33:02 -05:00
switch listener . CorsEnabled {
case nil :
// If CORS wasn't specified, enable default value of *, which allows
// both the admin UI (without the user having to explicitly set an
// origin) and the desktop origin
2021-02-11 20:10:48 -05:00
listener . CorsEnabled = new ( bool )
* listener . CorsEnabled = true
2021-05-21 12:33:02 -05:00
listener . CorsAllowedOrigins = [] string { "*" }
default :
// If not the wildcard and they haven't disabled us auto-adding
// origin values, add the desktop client origin
if * listener . CorsEnabled &&
! strutil . StrListContains ( listener . CorsAllowedOrigins , "*" ) {
listener . CorsAllowedOrigins = strutil . AppendIfMissing ( listener . CorsAllowedOrigins , desktopCorsOrigin )
}
2021-02-11 20:10:48 -05:00
}
}
}
2021-08-04 14:01:58 -04:00
list , ok := obj . Node .( * ast . ObjectList )
if ! ok {
return nil , fmt . Errorf ( "error parsing: file doesn't contain a root object" )
}
eventList := list . Filter ( "events" )
switch len ( eventList . Items ) {
case 0 :
2021-07-08 15:50:25 -04:00
result . Eventing = event . DefaultEventerConfig ()
2021-08-04 14:01:58 -04:00
case 1 :
if result . Eventing , err = parseEventing ( eventList . Items [ 0 ]); err != nil {
return nil , fmt . Errorf ( `error parsing "events": %w` , err )
2021-07-08 15:50:25 -04:00
}
2021-08-04 14:01:58 -04:00
default :
return nil , fmt . Errorf ( `too many "events" nodes (max 1, got %d)` , len ( eventList . Items ))
2021-07-08 15:50:25 -04:00
}
2021-12-08 09:39:04 -05:00
if result . Plugins . ExecutionDir != "" {
result . Plugins . ExecutionDir , err = parseutil . ParsePath ( result . Plugins . ExecutionDir )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return nil , fmt . Errorf ( "Error parsing plugins execution dir: %w" , err )
}
}
2023-03-07 16:29:34 -05:00
for _ , f := range extraParsingFuncs {
if err := f ( result ); err != nil {
return nil , err
}
}
2020-02-12 13:53:27 -05:00
return result , nil
}
2023-10-02 21:02:17 +00:00
func parseApiRateLimits ( node ast . Node ) ( ratelimit . Configs , error ) {
list , ok := node .( * ast . ObjectList )
if ! ok {
return nil , fmt . Errorf ( "error parsing: file doesn't contain a root object" )
}
controllerList := list . Filter ( "controller" )
configs := make ( ratelimit . Configs , 0 )
for _ , item := range controllerList . Items {
controller , ok := item . Val .( * ast . ObjectType )
if ! ok {
return nil , fmt . Errorf ( "error parsing: file doesn't contain controller object" )
}
apiRateLimitsList := controller . List . Filter ( "api_rate_limit" )
var err error
for i , item := range apiRateLimitsList . Items {
var a ratelimit . Config
if err := hcl . DecodeObject ( & a , item . Val ); err != nil {
return nil , fmt . Errorf ( "error decoding controller api_rate_limit entry %d" , i )
}
a . Period , err = parseutil . ParseDurationSecond ( a . PeriodHCL )
if err != nil {
return nil , fmt . Errorf ( "error decoding controller api_rate_limit period for entry %d" , i )
}
configs = append ( configs , & a )
}
}
return configs , nil
}
2022-06-13 15:06:03 -07:00
func parseWorkerUpstreams ( c * Config ) ([] string , error ) {
2021-12-07 13:01:04 +00:00
if c == nil || c . Worker == nil {
return nil , fmt . Errorf ( "config or worker field is nil" )
}
2024-09-23 11:45:34 -04:00
if c . Worker . InitialUpstreamsRaw == nil {
2022-06-13 15:06:03 -07:00
// return nil here so that other address sources can be provided outside of config
2021-12-07 13:01:04 +00:00
return nil , nil
}
2024-09-23 11:45:34 -04:00
switch t := c . Worker . InitialUpstreamsRaw .( type ) {
2022-10-25 18:29:38 -05:00
case [] any :
2022-06-13 15:06:03 -07:00
var upstreams [] string
2024-09-23 11:45:34 -04:00
err := mapstructure . WeakDecode ( c . Worker . InitialUpstreamsRaw , & upstreams )
2021-12-07 13:01:04 +00:00
if err != nil {
2022-06-13 15:06:03 -07:00
return nil , fmt . Errorf ( "failed to decode worker initial_upstreams block into config field: %w" , err )
2021-12-07 13:01:04 +00:00
}
2022-06-13 15:06:03 -07:00
return upstreams , nil
2021-12-07 13:01:04 +00:00
case string :
2022-06-13 15:06:03 -07:00
upstreamsStr , err := parseutil . ParsePath ( t )
2021-12-07 13:01:04 +00:00
if err != nil {
return nil , fmt . Errorf ( "bad env var or file pointer: %w" , err )
}
2022-06-13 15:06:03 -07:00
var upstreams [] string
err = json . Unmarshal ([] byte ( upstreamsStr ), & upstreams )
2021-12-07 13:01:04 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to unmarshal env/file contents: %w" , err )
}
2022-06-13 15:06:03 -07:00
return upstreams , nil
2021-12-07 13:01:04 +00:00
default :
typ := reflect . TypeOf ( t )
return nil , fmt . Errorf ( "unexpected type %q" , typ . String ())
}
}
2021-08-04 14:01:58 -04:00
func parseEventing ( eventObj * ast . ObjectItem ) ( * event . EventerConfig , error ) {
// Decode the outside struct
var result event . EventerConfig
if err := hcl . DecodeObject ( & result , eventObj . Val ); err != nil {
return nil , fmt . Errorf ( `error decoding "events" node: %w` , err )
}
// Now, find the sinks
eventObjType , ok := eventObj . Val .( * ast . ObjectType )
if ! ok {
return nil , fmt . Errorf ( `error interpreting "events" node as an object type` )
}
list := eventObjType . List
sinkList := list . Filter ( "sink" )
// Go through each sink and decode
for i , item := range sinkList . Items {
var s event . SinkConfig
if err := hcl . DecodeObject ( & s , item . Val ); err != nil {
return nil , fmt . Errorf ( "error decoding eventer sink entry %d" , i )
}
// Fix up type and validate
switch {
case s . Type != "" :
case len ( item . Keys ) == 1 :
s . Type = event . SinkType ( item . Keys [ 0 ]. Token . Value ().( string ))
default :
switch {
case s . StderrConfig != nil :
// If we haven't found the type any other way, they _must_
// specify this block even though there are no config parameters
s . Type = event . StderrSink
case s . FileConfig != nil :
s . Type = event . FileSink
default :
return nil , fmt . Errorf ( "sink type could not be determined" )
}
}
s . Type = event . SinkType ( strings . ToLower ( string ( s . Type )))
if s . Type == event . StderrSink && s . StderrConfig == nil {
// StderrConfig is optional as it has no values, but ensure it's
// always populated if it's the type
s . StderrConfig = new ( event . StderrSinkTypeConfig )
}
2021-08-04 17:57:36 -04:00
// parse the duration string specified in a file config into a time.Duration
if s . FileConfig != nil && s . FileConfig . RotateDurationHCL != "" {
var err error
2021-08-04 19:11:00 -04:00
s . FileConfig . RotateDuration , err = parseutil . ParseDurationSecond ( s . FileConfig . RotateDurationHCL )
2021-08-04 17:57:36 -04:00
if err != nil {
return nil , fmt . Errorf ( "can't parse rotation duration %s" , s . FileConfig . RotateDurationHCL )
}
}
2022-04-18 11:03:10 -04:00
// parse map into event types
if s . AuditConfig != nil && s . AuditConfig . FilterOverridesHCL != nil {
s . AuditConfig . FilterOverrides = make ( map [ event . DataClassification ] event . FilterOperation , len ( s . AuditConfig . FilterOverridesHCL ))
for k , v := range s . AuditConfig . FilterOverridesHCL {
s . AuditConfig . FilterOverrides [ event . DataClassification ( k )] = event . FilterOperation ( v )
}
}
2021-08-04 14:01:58 -04:00
if err := s . Validate (); err != nil {
return nil , err
}
// Append to result
2021-08-30 19:02:48 -04:00
result . Sinks = append ( result . Sinks , & s )
2021-08-04 14:01:58 -04:00
}
if len ( result . Sinks ) == 0 {
2021-08-30 19:02:48 -04:00
result . Sinks = [] * event . SinkConfig { event . DefaultSink ()}
2021-08-04 14:01:58 -04:00
}
return & result , nil
}
2020-02-12 13:53:27 -05:00
// Sanitized returns a copy of the config with all values that are considered
// sensitive stripped. It also strips all `*Raw` values that are mainly
// used for parsing.
//
// Specifically, the fields that this method strips are:
// - KMS.Config
// - Telemetry.CirconusAPIToken
2022-10-25 18:29:38 -05:00
func ( c * Config ) Sanitized () map [ string ] any {
2020-02-12 13:53:27 -05:00
// Create shared config if it doesn't exist (e.g. in tests) so that map
// keys are actually populated
if c . SharedConfig == nil {
c . SharedConfig = new ( configutil . SharedConfig )
}
sharedResult := c . SharedConfig . Sanitized ()
2022-10-25 18:29:38 -05:00
result := map [ string ] any {}
2020-02-12 13:53:27 -05:00
for k , v := range sharedResult {
result [ k ] = v
}
return result
}
2022-09-12 09:37:13 -04:00
// SetupControllerPublicClusterAddress will set the controller public address.
// If the flagValue is provided it will be used. Otherwise this will use the
// address from cluster listener. In either case it will check to see if no port
// is included, and if not it will set the default port of 9201.
//
// If there are any errors parsing the address from the flag or listener,
// and error is returned.
func ( c * Config ) SetupControllerPublicClusterAddress ( flagValue string ) error {
if c . Controller == nil {
c . Controller = new ( Controller )
}
if flagValue != "" {
c . Controller . PublicClusterAddr = flagValue
}
if c . Controller . PublicClusterAddr == "" {
FindAddr :
for _ , listener := range c . Listeners {
for _ , purpose := range listener . Purpose {
if purpose == "cluster" {
c . Controller . PublicClusterAddr = listener . Address
break FindAddr
}
}
}
} else {
var err error
c . Controller . PublicClusterAddr , err = parseutil . ParsePath ( c . Controller . PublicClusterAddr )
if err != nil && ! errors . Is ( err , parseutil . ErrNotAUrl ) {
return fmt . Errorf ( "Error parsing public cluster addr: %w" , err )
}
c . Controller . PublicClusterAddr , err = listenerutil . ParseSingleIPTemplate ( c . Controller . PublicClusterAddr )
if err != nil {
return fmt . Errorf ( "Error parsing IP template on controller public cluster addr: %w" , err )
}
}
2024-11-01 13:27:40 -04:00
host , port , err := util . SplitHostPort ( c . Controller . PublicClusterAddr )
2022-09-12 09:37:13 -04:00
if err != nil {
2024-11-01 13:27:40 -04:00
return fmt . Errorf ( "Error splitting public cluster adddress host/port: %w" , err )
2022-09-12 09:37:13 -04:00
}
2024-11-01 13:27:40 -04:00
if port == "" {
port = "9201"
}
c . Controller . PublicClusterAddr = util . JoinHostPort ( host , port )
2024-09-20 11:30:27 -07:00
2022-09-12 09:37:13 -04:00
return nil
}
// SetupWorkerInitialUpstreams will set the worker initial upstreams in cases
// where both a worker and controller stanza are provided. The initial upstreams
// will be:
// - The initialily provided value, if it is the same as the controller's cluster address
// - The controller's public cluster address if it it was set
// - The controller's cluster listener's address
//
// Any other value already set for iniital upstream will result in an error.
func ( c * Config ) SetupWorkerInitialUpstreams () error {
// nothing to do here
if c . Worker == nil || c . Controller == nil {
return nil
}
var clusterAddr string
for _ , lnConfig := range c . Listeners {
switch len ( lnConfig . Purpose ) {
case 0 :
return fmt . Errorf ( "Listener specified without a purpose" )
case 1 :
purpose := lnConfig . Purpose [ 0 ]
switch purpose {
case "cluster" :
clusterAddr = lnConfig . Address
if clusterAddr == "" {
clusterAddr = "127.0.0.1:9201"
lnConfig . Address = clusterAddr
}
}
default :
return fmt . Errorf ( "Specifying a listener with more than one purpose is not supported" )
}
}
switch len ( c . Worker . InitialUpstreams ) {
case 0 :
if c . Controller . PublicClusterAddr != "" {
clusterAddr = c . Controller . PublicClusterAddr
}
c . Worker . InitialUpstreams = [] string { clusterAddr }
case 1 :
if c . Worker . InitialUpstreams [ 0 ] == clusterAddr {
break
}
if c . Controller . PublicClusterAddr != "" &&
c . Worker . InitialUpstreams [ 0 ] == c . Controller . PublicClusterAddr {
break
}
// Best effort see if it's a domain name and if not assume it must match
2024-11-01 13:27:40 -04:00
host , _ , err := util . SplitHostPort ( c . Worker . InitialUpstreams [ 0 ])
2022-09-12 09:37:13 -04:00
if err == nil {
ip := net . ParseIP ( host )
if ip == nil {
// Assume it's a domain name
break
}
}
fallthrough
default :
return fmt . Errorf ( `When running a combined controller and worker, it's invalid to specify a "initial_upstreams" or "controllers" key in the worker block with any values other than the controller cluster or upstream worker address/port when using IPs rather than DNS names` )
}
return nil
}