You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/cmd/config/config.go

308 lines
7.6 KiB

package config
import (
"bytes"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"strings"
"time"
wrapping "github.com/hashicorp/go-kms-wrapping"
"github.com/hashicorp/hcl"
"github.com/hashicorp/shared-secure-libs/configutil"
"github.com/hashicorp/vault/sdk/helper/parseutil"
)
const (
devConfig = `
disable_mlock = true
telemetry {
prometheus_retention_time = "24h"
disable_hostname = true
}
`
devControllerExtraConfig = `
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 = "recovery"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_recovery"
}
listener "tcp" {
purpose = "api"
tls_disable = true
cors_enabled = true
cors_allowed_origins = ["*"]
}
listener "tcp" {
purpose = "cluster"
}
`
devWorkerExtraConfig = `
listener "tcp" {
purpose = "proxy"
}
worker {
name = "dev-worker"
description = "A default worker created in dev mode"
controllers = ["127.0.0.1"]
}
`
)
// Config is the configuration for the boundary controller
type Config struct {
*configutil.SharedConfig `hcl:"-"`
Worker *Worker `hcl:"worker"`
Controller *Controller `hcl:"controller"`
// Dev-related options
DevController bool `hcl:"-"`
PassthroughDirectory string `hcl:"-"`
DevControllerKey string `hcl:"-"`
DevWorkerAuthKey string `hcl:"-"`
DevRecoveryKey string `hcl:"-"`
}
type Controller struct {
Name string `hcl:"name"`
Description string `hcl:"description"`
Database *Database `hcl:"database"`
PublicClusterAddr string `hcl:"public_cluster_addr"`
// AuthTokenTimeToLive is the total valid lifetime of a token denoted by time.Duration
AuthTokenTimeToLive interface{} `hcl:"auth_token_time_to_live"`
AuthTokenTimeToLiveDuration time.Duration
// AuthTokenTimeToStale is the total time a token can go unused before becoming invalid
// denoted by time.Duration
AuthTokenTimeToStale interface{} `hcl:"auth_token_time_to_stale"`
AuthTokenTimeToStaleDuration time.Duration
}
type Worker struct {
Name string `hcl:"name"`
Description string `hcl:"description"`
Controllers []string `hcl:"controllers"`
PublicAddr string `hcl:"public_addr"`
}
type Database struct {
Url string `hcl:"url"`
MigrationUrl string `hcl:"migration_url"`
}
// DevWorker is a Config that is used for dev mode of Boundary
// workers
func DevWorker() (*Config, error) {
parsed, err := Parse(devConfig + devWorkerExtraConfig)
if err != nil {
return nil, fmt.Errorf("error parsing dev config: %w", err)
}
return parsed, nil
}
func devKeyGeneration() (string, string, string) {
var numBytes int64 = 96
randBuf := new(bytes.Buffer)
n, err := randBuf.ReadFrom(&io.LimitedReader{
R: rand.Reader,
N: numBytes,
})
if err != nil {
panic(err)
}
if n != numBytes {
panic(fmt.Errorf("expected to read 64 bytes, read %d", n))
}
controllerKey := base64.StdEncoding.EncodeToString(randBuf.Bytes()[0:32])
workerAuthKey := base64.StdEncoding.EncodeToString(randBuf.Bytes()[32:64])
recoveryKey := base64.StdEncoding.EncodeToString(randBuf.Bytes()[64:numBytes])
return controllerKey, workerAuthKey, recoveryKey
}
// DevController is a Config that is used for dev mode of Boundary
// controllers
func DevController() (*Config, error) {
controllerKey, workerAuthKey, recoveryKey := devKeyGeneration()
hclStr := fmt.Sprintf(devConfig+devControllerExtraConfig, controllerKey, workerAuthKey, recoveryKey)
parsed, err := Parse(hclStr)
if err != nil {
return nil, fmt.Errorf("error parsing dev config: %w", err)
}
parsed.DevController = true
parsed.DevControllerKey = controllerKey
parsed.DevWorkerAuthKey = workerAuthKey
parsed.DevRecoveryKey = recoveryKey
return parsed, nil
}
func DevCombined() (*Config, error) {
controllerKey, workerAuthKey, recoveryKey := devKeyGeneration()
hclStr := fmt.Sprintf(devConfig+devControllerExtraConfig+devWorkerExtraConfig, controllerKey, workerAuthKey, recoveryKey)
parsed, err := Parse(hclStr)
if err != nil {
return nil, fmt.Errorf("error parsing dev config: %w", err)
}
parsed.DevController = true
parsed.DevControllerKey = controllerKey
parsed.DevWorkerAuthKey = workerAuthKey
parsed.DevRecoveryKey = recoveryKey
return parsed, nil
}
func New() *Config {
return &Config{
SharedConfig: new(configutil.SharedConfig),
}
}
// LoadFile loads the configuration from the given file.
func LoadFile(path string, wrapper wrapping.Wrapper) (*Config, error) {
d, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
raw := string(d)
if wrapper != nil {
raw, err = configutil.EncryptDecrypt(raw, true, true, wrapper)
if err != nil {
return nil, err
}
}
return Parse(raw)
}
func Parse(d string) (*Config, error) {
obj, err := hcl.Parse(d)
if err != nil {
return nil, err
}
// Nothing to do here right now
result := New()
if err := hcl.DecodeObject(result, obj); err != nil {
return nil, err
}
// Perform controller configuration overrides for auth token settings
if result.Controller != nil {
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
}
}
sharedConfig, err := configutil.ParseConfig(d)
if err != nil {
return nil, err
}
result.SharedConfig = sharedConfig
return result, nil
}
// 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
func (c *Config) Sanitized() map[string]interface{} {
// 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()
result := map[string]interface{}{}
for k, v := range sharedResult {
result[k] = v
}
return result
}
var ErrNotAUrl = errors.New("not a url")
// ParseAddress parses a URL with schemes file://, env://, or any other.
// Depending on the scheme it will return specific types of data:
//
// * file:// will return a string with the file's contents * env:// will return
// a string with the env var's contents * anything else will return the string
// as it was
//
// On error, we return the original string along with the error. The caller can
// switch on ErrNotAUrl to understand whether it was the parsing step that
// errored or something else. This is useful to attempt to read a non-URL string
// from some resource, but where the original input may simply be a valid string
// of that type.
func ParseAddress(addr string) (string, error) {
addr = strings.TrimSpace(addr)
parsed, err := url.Parse(addr)
if err != nil {
return addr, ErrNotAUrl
}
switch parsed.Scheme {
case "file":
contents, err := ioutil.ReadFile(strings.TrimPrefix(addr, "file://"))
if err != nil {
return addr, fmt.Errorf("error reading file at %s: %w", addr, err)
}
return string(contents), nil
case "env":
return os.Getenv(strings.TrimPrefix(addr, "env://")), nil
}
return addr, nil
}