Add TLS certificate and worker tag reloading on SIGHUP (ICU-1142) (ICU-1143) (#959)

This commit is contained in:
Jeff Mitchell authored and GitHub committed 2021-02-25 17:57:10 -05:00
1 parent 64e6053278
commit 4738ceee5d
14 files changed
+677 -107

No files matched your search

+11 -1
View File
@@ -2,7 +2,7 @@
Canonical reference for changes, improvements, and bugfixes for Boundary.
## Next
## Next (Unreleased)
### Changes/Deprecations
@@ -22,6 +22,16 @@ All of these changes are from [PR
* cli: All errors, including API errors, are now written to `stderr`. Previously
in the default table format, API errors would be written to `stdout`.
### New and Improved
* server: Officially support reloading TLS parameters on `SIGHUP`. (This likely
worked before but wasn't fully tested.)
([PR](https://github.com/hashicorp/boundary/pull/959))
* server: On `SIGHUP`, [worker
tags](https://www.boundaryproject.io/docs/configuration/worker#tags) will be
re-parsed and new values used
([PR](https://github.com/hashicorp/boundary/pull/959))
## 0.1.7 (2021/02/16)
*Note* This release fixes an upgrade issue affecting users on Postgres 11
+3 -3
View File
@@ -56,10 +56,10 @@ func NewListener(l *configutil.Listener, logger hclog.Logger, ui cli.Ui) (*alpnm
return nil, nil, nil, fmt.Errorf("unknown listener type: %q", l.Type)
}
var purpose string
if len(l.Purpose) == 1 {
purpose = l.Purpose[0]
if len(l.Purpose) != 1 {
return nil, nil, nil, fmt.Errorf("Expected single listener purpose, found %d", len(l.Purpose))
}
purpose := l.Purpose[0]
switch purpose {
case "cluster":
+39 -19
View File
@@ -9,10 +9,12 @@ import (
"io"
"net"
"os"
"os/signal"
"sort"
"strconv"
"strings"
"sync"
"syscall"
"github.com/armon/go-metrics"
"github.com/hashicorp/boundary/globals"
@@ -269,25 +271,26 @@ func (b *Server) SetupListeners(ui cli.Ui, config *configutil.SharedConfig, allo
defer b.ReloadFuncsLock.Unlock()
for i, lnConfig := range config.Listeners {
var purpose string
for _, p := range lnConfig.Purpose {
purpose = strings.ToLower(p)
if !strutil.StrListContains(allowedPurposes, purpose) {
return fmt.Errorf("Unknown listener purpose %q", purpose)
}
if len(lnConfig.Purpose) != 1 {
return fmt.Errorf("Invalid size of listener purposes (%d)", len(lnConfig.Purpose))
}
purpose := strings.ToLower(lnConfig.Purpose[0])
if !strutil.StrListContains(allowedPurposes, purpose) {
return fmt.Errorf("Unknown listener purpose %q", purpose)
}
lnConfig.Purpose[0] = purpose
// Override for now
// TODO: Way to configure
lnConfig.TLSCipherSuites = []uint16{
// 1.3
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
// 1.2
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
if lnConfig.TLSCipherSuites == nil {
lnConfig.TLSCipherSuites = []uint16{
// 1.3
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,
// 1.2
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
}
}
lnMux, props, reloadFunc, err := NewListener(lnConfig, b.Logger, ui)
@@ -320,9 +323,9 @@ func (b *Server) SetupListeners(ui cli.Ui, config *configutil.SharedConfig, allo
}
if reloadFunc != nil {
relSlice := b.ReloadFuncs["listener|"+lnConfig.Type]
relSlice := b.ReloadFuncs["listeners"]
relSlice = append(relSlice, reloadFunc)
b.ReloadFuncs["listener|"+lnConfig.Type] = relSlice
b.ReloadFuncs["listeners"] = relSlice
}
if lnConfig.MaxRequestSize == 0 {
@@ -708,3 +711,20 @@ func (b *Server) SetupWorkerPublicAddress(conf *config.Config, flagValue string)
conf.Worker.PublicAddr = net.JoinHostPort(host, port)
return nil
}
// MakeSighupCh returns a channel that can be used for SIGHUP
// reloading. This channel will send a message for every
// SIGHUP received.
func MakeSighupCh() chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, syscall.SIGHUP)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}
+2 -23
View File
@@ -1,10 +1,6 @@
package cmd
import (
"os"
"os/signal"
"syscall"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/commands/accountscmd"
"github.com/hashicorp/boundary/internal/cmd/commands/authenticate"
@@ -37,14 +33,14 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
"server": func() (cli.Command, error) {
return &server.Command{
Server: base.NewServer(base.NewCommand(serverCmdUi)),
SighupCh: MakeSighupCh(),
SighupCh: base.MakeSighupCh(),
SigUSR2Ch: MakeSigUSR2Ch(),
}, nil
},
"dev": func() (cli.Command, error) {
return &dev.Command{
Server: base.NewServer(base.NewCommand(serverCmdUi)),
SighupCh: MakeSighupCh(),
SighupCh: base.MakeSighupCh(),
SigUSR2Ch: MakeSigUSR2Ch(),
}, nil
},
@@ -767,20 +763,3 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) {
},
}
}
// MakeSighupCh returns a channel that can be used for SIGHUP
// reloading. This channel will send a message for every
// SIGHUP received.
func MakeSighupCh() chan struct{} {
resultCh := make(chan struct{})
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, syscall.SIGHUP)
go func() {
for {
<-signalCh
resultCh <- struct{}{}
}
}()
return resultCh
}
@@ -0,0 +1,163 @@
// +build !hsm
// NOTE on the NOTE: This is from Vault, but that doesn't mean it's not valid
// going forward with us.
//
// NOTE: we can't use this with HSM. We can't set testing mode on and it's not
// safe to use env vars since that provides an attack vector in the real world.
package server
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"os"
"sync"
"testing"
"time"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
)
const reloadConfig = `
disable_mlock = true
telemetry {
prometheus_retention_time = "24h"
disable_hostname = true
}
controller {
name = "dev-controller"
description = "A default controller created in dev mode"
database {
url = "%s"
}
}
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_cert_file = "%s/bundle.pem"
tls_key_file = "%s/bundle.pem"
cors_enabled = true
cors_allowed_origins = ["*"]
}
listener "tcp" {
purpose = "cluster"
}
listener "tcp" {
purpose = "proxy"
}
`
func TestServer_ReloadListener(t *testing.T) {
t.Parallel()
require := require.New(t)
wg := &sync.WaitGroup{}
wd, _ := os.Getwd()
wd += "/test-fixtures/reload/"
td, err := ioutil.TempDir("", "boundary-test-")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(td)
controllerKey, workerAuthKey, recoveryKey := config.DevKeyGeneration()
cmd := testServerCommand(t, controllerKey)
defer func() {
if cmd.DevDatabaseCleanupFunc != nil {
require.NoError(cmd.DevDatabaseCleanupFunc())
}
}()
require.NoError(err)
// Setup initial certs
inBytes, err := ioutil.ReadFile(wd + "bundle1.pem")
require.NoError(err)
require.NoError(ioutil.WriteFile(td+"/bundle.pem", inBytes, 0777))
relHcl := fmt.Sprintf(reloadConfig, cmd.DatabaseUrl, controllerKey, workerAuthKey, recoveryKey, td, td)
require.NoError(ioutil.WriteFile(td+"/reload.hcl", []byte(relHcl), 0777))
// Populate CA pool
inBytes, _ = ioutil.ReadFile(td + "/bundle.pem")
certPool := x509.NewCertPool()
require.True(certPool.AppendCertsFromPEM(inBytes))
wg.Add(1)
args := []string{"-config", td + "/reload.hcl"}
go func() {
defer wg.Done()
if code := cmd.Run(args); code != 0 {
output := cmd.UI.(*cli.MockUi).ErrorWriter.String() + cmd.UI.(*cli.MockUi).OutputWriter.String()
t.Errorf("got a non-zero exit status: %s", output)
}
}()
testCertificateSerial := func(serial string) {
conn, err := tls.Dial("tcp", "127.0.0.1:9200", &tls.Config{
RootCAs: certPool,
})
require.NoError(err)
defer conn.Close()
require.NoError(conn.Handshake())
ser := conn.ConnectionState().PeerCertificates[0].SerialNumber.String()
require.Equal(ser, serial)
}
select {
case <-cmd.startedCh:
case <-time.After(15 * time.Second):
t.Fatalf("timeout")
}
testCertificateSerial("142541707881583626546634262782315760343015820827")
inBytes, err = ioutil.ReadFile(wd + "bundle2.pem")
require.NoError(err)
require.NoError(ioutil.WriteFile(td+"/bundle.pem", inBytes, 0777))
cmd.SighupCh <- struct{}{}
select {
case <-cmd.reloadedCh:
case <-time.After(5 * time.Second):
t.Fatalf("timeout")
}
testCertificateSerial("193080739105342897219784862820114567438786419504")
close(cmd.ShutdownCh)
wg.Wait()
}
+71 -49
View File
@@ -25,6 +25,7 @@ import (
"github.com/hashicorp/vault/sdk/helper/mlock"
"github.com/mitchellh/cli"
"github.com/posener/complete"
"go.uber.org/atomic"
)
var (
@@ -35,10 +36,8 @@ var (
type Command struct {
*base.Server
ExtShutdownCh chan struct{}
SighupCh chan struct{}
ReloadedCh chan struct{}
SigUSR2Ch chan struct{}
SighupCh chan struct{}
SigUSR2Ch chan struct{}
Config *config.Config
controller *controller.Controller
@@ -51,6 +50,14 @@ type Command struct {
flagLogLevel string
flagLogFormat string
flagCombineLogs bool
reloadedCh chan struct{} // for tests
startedCh chan struct{} // for tests
presetConfig *atomic.String // for tests
// for tests -- Prometheus seems to use a global collector so if package
// tests are run it errors out even if they're not run in parallel 🙄 cause
// Go runs the tests within the same overall process
skipMetrics bool
}
func (c *Command) Synopsis() string {
@@ -145,9 +152,11 @@ func (c *Command) Run(args []string) int {
base.StartMemProfiler(c.Logger)
if err := c.SetupMetrics(c.UI, c.Config.Telemetry); err != nil {
c.UI.Error(err.Error())
return 1
if !c.skipMetrics {
if err := c.SetupMetrics(c.UI, c.Config.Telemetry); err != nil {
c.UI.Error(err.Error())
return 1
}
}
if err := c.SetupKMSes(c.UI, c.Config); err != nil {
@@ -438,6 +447,11 @@ func (c *Command) Run(args []string) int {
}
}
// Inform any tests that the server is ready
if c.startedCh != nil {
close(c.startedCh)
}
return c.WaitForInterrupt()
}
@@ -451,29 +465,37 @@ func (c *Command) ParseFlagsAndConfig(args []string) int {
return 1
}
wrapperPath := c.flagConfig
if c.flagConfigKms != "" {
wrapperPath = c.flagConfigKms
}
wrapper, err := wrapper.GetWrapperFromPath(wrapperPath, "config")
if err != nil {
c.UI.Error(err.Error())
return 1
}
if wrapper != nil {
c.configWrapper = wrapper
if err := wrapper.Init(c.Context); err != nil {
c.UI.Error(fmt.Errorf("Could not initialize kms: %w", err).Error())
return 1
}
}
if len(c.flagConfig) == 0 {
if len(c.flagConfig) == 0 && c.presetConfig == nil {
c.UI.Error("Must specify a config file using -config")
return 1
}
c.Config, err = config.LoadFile(c.flagConfig, wrapper)
switch {
case c.presetConfig != nil:
c.Config, err = config.Parse(c.presetConfig.Load())
default:
wrapperPath := c.flagConfig
if c.flagConfigKms != "" {
wrapperPath = c.flagConfigKms
}
var configWrapper wrapping.Wrapper
if wrapperPath != "" {
configWrapper, err = wrapper.GetWrapperFromPath(wrapperPath, "config")
if err != nil {
c.UI.Error(err.Error())
return 1
}
if configWrapper != nil {
c.configWrapper = configWrapper
if err := configWrapper.Init(c.Context); err != nil {
c.UI.Error(fmt.Errorf("Could not initialize kms: %w", err).Error())
return 1
}
}
}
c.Config, err = config.LoadFile(c.flagConfig, configWrapper)
}
if err != nil {
c.UI.Error("Error parsing config: " + err.Error())
return 1
@@ -547,14 +569,9 @@ func (c *Command) WaitForInterrupt() int {
// Wait for shutdown
shutdownTriggered := false
shutdownCh := c.ShutdownCh
if c.ExtShutdownCh != nil {
shutdownCh = c.ExtShutdownCh
}
for !shutdownTriggered {
select {
case <-shutdownCh:
case <-c.ShutdownCh:
c.UI.Output("==> Boundary server shutdown triggered")
if c.Config.Worker != nil {
@@ -579,11 +596,15 @@ func (c *Command) WaitForInterrupt() int {
var err error
var newConf *config.Config
if c.flagConfig == "" {
if c.flagConfig == "" && c.presetConfig == nil {
goto RUNRELOADFUNCS
}
newConf, err = config.LoadFile(c.flagConfig, c.configWrapper)
if c.presetConfig != nil {
newConf, err = config.Parse(c.presetConfig.Load())
} else {
newConf, err = config.LoadFile(c.flagConfig, c.configWrapper)
}
if err != nil {
c.Logger.Error("could not reload config", "path", c.flagConfig, "error", err)
goto RUNRELOADFUNCS
@@ -616,8 +637,8 @@ func (c *Command) WaitForInterrupt() int {
}
RUNRELOADFUNCS:
if err := c.Reload(); err != nil {
c.UI.Error(fmt.Errorf("Error(s) were encountered during server reload: %w", err).Error())
if err := c.Reload(newConf); err != nil {
c.UI.Error(fmt.Errorf("Error(s) were encountered during reload: %w", err).Error())
}
case <-c.SigUSR2Ch:
@@ -630,30 +651,31 @@ func (c *Command) WaitForInterrupt() int {
return 0
}
func (c *Command) Reload() error {
func (c *Command) Reload(newConf *config.Config) error {
c.ReloadFuncsLock.RLock()
defer c.ReloadFuncsLock.RUnlock()
var reloadErrors *multierror.Error
for k, relFuncs := range c.ReloadFuncs {
switch {
case strings.HasPrefix(k, "listener|"):
for _, relFunc := range relFuncs {
if relFunc != nil {
if err := relFunc(); err != nil {
reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("error encountered reloading listener: %w", err))
}
}
for _, relFunc := range c.ReloadFuncs["listeners"] {
if relFunc != nil {
if err := relFunc(); err != nil {
reloadErrors = multierror.Append(reloadErrors, fmt.Errorf("error encountered reloading listener: %w", err))
}
}
}
if newConf != nil && c.worker != nil {
c.worker.ParseAndStoreTags(newConf.Worker.Tags)
}
// Send a message that we reloaded. This prevents "guessing" sleep times
// in tests.
select {
case c.ReloadedCh <- struct{}{}:
default:
if c.reloadedCh != nil {
select {
case c.reloadedCh <- struct{}{}:
default:
}
}
return reloadErrors.ErrorOrNil()
@@ -0,0 +1,48 @@
package server
import (
"fmt"
"testing"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
)
const rootKmsConfig = `
kms "aead" {
purpose = "root"
aead_type = "aes-gcm"
key = "%s"
key_id = "global_root"
}`
func testServerCommand(t *testing.T, controllerKey string) *Command {
require := require.New(t)
t.Helper()
cmd := &Command{
Server: base.NewServer(base.NewCommand(cli.NewMockUi())),
SighupCh: base.MakeSighupCh(),
startedCh: make(chan struct{}),
reloadedCh: make(chan struct{}, 5),
skipMetrics: true,
}
require.NoError(cmd.SetupLogging("trace", "", "", ""))
kmsHcl := fmt.Sprintf(rootKmsConfig, controllerKey)
parsedKmsConfig, err := config.Parse(kmsHcl)
require.NoError(err)
require.NoError(cmd.SetupKMSes(cmd.UI, parsedKmsConfig))
err = cmd.CreateDevDatabase(cmd.Context, "postgres")
if err != nil {
if cmd.DevDatabaseCleanupFunc != nil {
require.NoError(cmd.DevDatabaseCleanupFunc())
}
require.NoError(err)
}
return cmd
}
@@ -0,0 +1,87 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA6xABm4SRwtOZMYMulP1rOOmTSxz6j7TBgpDEk6NqEf9K1XL6
VQhACAKf0IJbBGtlKcX6MA5xzIZSzBrarujRYRMX4bM5qNqXYa8c9L9q+fhjeV+y
rTINalWnuh5+K9Aw63qPO5dW7ZYsU/R/ZatCxngvA4GB/bufZh9XfE13/o2HP64q
r2ljMz+mhM/e77K39c3H27C/co3q9wa6GLHL+4njwMZcIVPFk7uXoku0PpPEkl3h
rgrLux+XoHQIbojllcqEIth3X1twIROm+eMh0XL0Fo+WKLvO1U3KLQF1AP/Fe7h8
jcqLGkMC6BhCVxmaGf8PazDyR0K3kAEGHgvHFwIDAQABAoIBAQCFMVGn3It3WMzS
OGHZvGrYGcoDYs07RpHruLoaahs9TWoK3paoPf+CSnJiBKssKupsrCiiuFyVJnud
+Y12VKfnmJkMo7CFVC85A6+lox0rvSooaShmaRke6qAanxKhItvFYnHss0lnYJOH
kt/pLQyuRwVkuCp/KqQcip/Y6FCY5GTE7ILMi1pFa9hVqtpkXr9KVjONFifHEinM
rKWeEgFvYqIBD1r90xgj8tnce0qUrpwyCEVHH2CQZyj/wLg12HnO8t7E/6A7FwXi
do4Ii0sWZeRx2+SgK0KIE7MgHxnNxvgCEenETB4hVbChXQU+Hbgar03TqTv2yK0A
2MgeeujJAoGBAP+kvXyPbLcf7LLg72FeYZfYLvVCuoHOkOEiHYL1CXZNOXO8VQtJ
4Z0P24WfNyGfmOUDP4UL/eYKQ6ssgT9rcJSDBm0duB88e0PUwbSvdvYypLMrokL1
5KLTP/wVsQj9DmuMqn7pRbrkUasxjb5BAAc4WNrf8DdhZlaDv5b/IbddAoGBAOtj
60jFvVqs6pTv29R0g4cyln0ltP/GPLB/GekzSAvFKODiW31hhwhnV4+yZs0YGGEj
AeGuE1MKkc5VrEMhmiDx612bL+bk8LmjPZXysnY2h9zH0liqOHyC13eMR3QjGOb+
aFk3zg5/6gIF7p28seWaleJZjiZPPc9mUTbRTBUDAoGAceHmk7Wk3ZM4M5ump/6O
hUFF8fZO9YKa4d/7u5oDJBNU060DSasY+YnV5hm+Spk5WFPxGQj1i2PpLzj9Ps5S
8aXI2BUUL74Wv2no/N/po9V9r19xwlieCLcI2viiy/lHUjvwu+aZLJ+HjjbeCPIV
3CGK275E2bbnCwVX9SEk2PUCgYEAsGg6nVgsF3d9ek7j5a3IIJtF8tY+dm0/cFpg
tccL2F5Sy254eFeN1jt0gKmHp27GXo3YzCQkMVi7evHz3n+hPc1vwcAavySWT9lJ
v0Waf0NQwKI0PuSa3p1EE0DbD+t5ryu8UhijYXR1kDyX3jja7aLTb0YEZSt/vB4H
AVkLdrcCgYEAq+NN1+aKPbZJatOmj1zn8kMrA0KuG8pMVNubQBlA6o8IF1J6TNkB
s2IE2WLhKpVSqhTpqPBgh2sxOmsDs3syLo6fHpLPB2+56TG4HkgTcpzhVjy/FUuv
TRNTnK5e0UnkB0mI1mQCXoyu777tEtX6B6e8pxiHVEZh0oGkqskuohI=
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDRzCCAi+gAwIBAgIUGPfKju2r/0rReB6jpRy+NRzVDhswDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTIxMDIyMjIzNDc1MVoYDzIwNTEw
MjIyMTU0ODIwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDrEAGbhJHC05kxgy6U/Ws46ZNLHPqPtMGCkMSTo2oR
/0rVcvpVCEAIAp/QglsEa2UpxfowDnHMhlLMGtqu6NFhExfhszmo2pdhrxz0v2r5
+GN5X7KtMg1qVae6Hn4r0DDreo87l1btlixT9H9lq0LGeC8DgYH9u59mH1d8TXf+
jYc/riqvaWMzP6aEz97vsrf1zcfbsL9yjer3BroYscv7iePAxlwhU8WTu5eiS7Q+
k8SSXeGuCsu7H5egdAhuiOWVyoQi2HdfW3AhE6b54yHRcvQWj5You87VTcotAXUA
/8V7uHyNyosaQwLoGEJXGZoZ/w9rMPJHQreQAQYeC8cXAgMBAAGjgY4wgYswDgYD
VR0PAQH/BAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV
HQ4EFgQUJ6eWLXRfSpu9bf5NBkL9LcK4s3gwHwYDVR0jBBgwFoAUpoUESIm3Tlju
/UzFlkYY8Hu8TMAwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3
DQEBCwUAA4IBAQCUrHC2TVB5ZLfduppFzgKo/YB3iYAiJ/k+LUKqWYO4lsSva2vs
4gSNuDLKvEnNgnuiGfp7/43DsSwjHXQD4pGOY/7i1DSS+T58rVtJ5HrhYyXOxGph
prtzbpN+2QKUkOC+MX6fqbeBu8nhk3bjDQcDLqqSOKToxNdGcD3Pyk8QnS0uv37Z
yLbnAKhINcEdCXKs9QCjPMlpkfaSm+XK6FKvyq6aNRgbXdzWSwLDTmbbXHxS276k
YFGaSN3bMfrvuLCEpxq+t3OvgZxR6rZWE7xRdJXOCHKOVlMA40ST4GlwV8By7NPN
jgY5VUk+2jrML05bG+GbIIUR1eDsBaRrtlY5
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDMTCCAhmgAwIBAgIUaxppPIC88WI7kcFaAE2A/6CpmsowDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTIxMDIyMjIzNDU0MloYDzIwNTEw
MjIzMDE0NjEyWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC75epj3j6t72LiHpnEvSEPKSy43uZVtQyuBX7u4Okw
AYyqFPXB1PTtcXNZNO6pDNpTxTTx9NwKfuwRonfTQuDeBQWJdetnMCvO2Jata2Gh
EtrHJE+aOpuWnNOU9qb360kM84SUAKKGZwSINGXKiRg5+OOoxcZ6k6xOYba8HQh+
MP0VQ3eC9SqtWRNuWM7kvvaS9YEQMlT0NBbLZBD2Oxh2xhQJiWXbwFif5LIcXuRN
K6f5Jc+eW5gXL2GJbIHzEAYWwZYB70vwVJZxRj5ZVtKQtVXSWkkI/Dlbx2t7uIE2
dxfAoRcUBBxgp/4OXyac++3iV3MVac1JAdOG8iXbXYLdAgMBAAGjeTB3MA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSmhQRIibdOWO79
TMWWRhjwe7xMwDAfBgNVHSMEGDAWgBTVUAc7emL2spJPGcdPWjffywuptTAUBgNV
HREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBACZV4xcWyA8Tsp+Q
IpzjHXaPqxfbX6tF9GzXRI3drqwf5KRRJ/FBG310cOkIdHwUIqmaeXK8H0IqIm1m
/kSB2MG9bguMLN7aW8erCPQ/A5f0ucERYrqT+vuqnruWstPoF1JhCYvMRLPvhcgc
dUXXr99KA/ONtvnvjf1GdV0ujwECgZZVGuLW+ZxPS48eYJQNHRvxzSdv2lDxZLK4
PRNXMjlTL2S/uFKou6I83G4oWahLtdG+9Eno4n2UuEIqhwO5rvnDOFk/pO3K0AVT
8L0+UwCARAVSQYV8fWd6hMlQLR0FpMxsDSBw5kHeueln/NxjbL6vIUrRj2Uq5OHN
Zot6haQ=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDMTCCAhmgAwIBAgIUaxppPIC88WI7kcFaAE2A/6CpmsowDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTIxMDIyMjIzNDU0MloYDzIwNTEw
MjIzMDE0NjEyWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC75epj3j6t72LiHpnEvSEPKSy43uZVtQyuBX7u4Okw
AYyqFPXB1PTtcXNZNO6pDNpTxTTx9NwKfuwRonfTQuDeBQWJdetnMCvO2Jata2Gh
EtrHJE+aOpuWnNOU9qb360kM84SUAKKGZwSINGXKiRg5+OOoxcZ6k6xOYba8HQh+
MP0VQ3eC9SqtWRNuWM7kvvaS9YEQMlT0NBbLZBD2Oxh2xhQJiWXbwFif5LIcXuRN
K6f5Jc+eW5gXL2GJbIHzEAYWwZYB70vwVJZxRj5ZVtKQtVXSWkkI/Dlbx2t7uIE2
dxfAoRcUBBxgp/4OXyac++3iV3MVac1JAdOG8iXbXYLdAgMBAAGjeTB3MA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSmhQRIibdOWO79
TMWWRhjwe7xMwDAfBgNVHSMEGDAWgBTVUAc7emL2spJPGcdPWjffywuptTAUBgNV
HREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBACZV4xcWyA8Tsp+Q
IpzjHXaPqxfbX6tF9GzXRI3drqwf5KRRJ/FBG310cOkIdHwUIqmaeXK8H0IqIm1m
/kSB2MG9bguMLN7aW8erCPQ/A5f0ucERYrqT+vuqnruWstPoF1JhCYvMRLPvhcgc
dUXXr99KA/ONtvnvjf1GdV0ujwECgZZVGuLW+ZxPS48eYJQNHRvxzSdv2lDxZLK4
PRNXMjlTL2S/uFKou6I83G4oWahLtdG+9Eno4n2UuEIqhwO5rvnDOFk/pO3K0AVT
8L0+UwCARAVSQYV8fWd6hMlQLR0FpMxsDSBw5kHeueln/NxjbL6vIUrRj2Uq5OHN
Zot6haQ=
-----END CERTIFICATE-----
@@ -0,0 +1,67 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAnz8qCV/AVyMr0aErfjiVJhNOqWomHEu7AOX6DvlWEs4O2YLY
TheY0k4zAEch7wkfUE+K9yM80c2HwzrR16ve03ak04zEBAZwEn4dkh/hYs2VtIbE
eC1Ya8ZpmarrRJLoEWNt+CvnbY8gIkAhDfP+7J73jVUPiR3E+HOTkLSKpOnseCC3
kTbaOPZZF8KAcose/uRrxG8FrgrqToLW7zMFUagBSghxrSW2Msn0flyTRh6VIFw4
eANuz5MOLFmSVFFVpgP58lywn2t32ppfL/yF47J/TqXk3Y1vlldZiB3NlH/WIOU2
xmkglgaMk2XSXnRESBVm9tUMqwWcnOzG8VQECwIDAQABAoIBAE9G8LRt+KDon33i
0HYr0QR9hCGO+J4JjXUrCEvAEPuXnYjnPKvTz36S84zl3JzVW8Ntde8GmdaQjU/B
zomhxHJdlDzEzSh70/tA0ZPXTysS0b5ngROa73Pn+NZJYopm4CUX7L0pjjLal9K1
Pj9mArjlgrPXbr6CFXbigsn9NzBVgxSn14nKpvupkzRfOtqlUOEIrD7Q3MUkOno4
zuMZxxox5DOSmjbSGdE994zhhe7f4++sf4H/7qeL+vOJIKhXaWUXyOElRLIUzGTx
M16aiqhGwR0mPwlxw/WCLSCQX0EHXK8gSuHVSulK+t1NixZcbbXkOuBd7gbWh0yT
vkZWh9ECgYEAxl1qP9Ws+lTz9g6xJAo+m5NR5B42qeH9SEfuN/RUyyaWrKUbi6GJ
WvM+XqLXIKAsQeYv4l3UNl+lnrwBL+gfo84r8OvKJWV6RCZtjKzV+FI8rzwGeu28
sKDCE3tkReI32VSKWrvTBGk/za5WEmWPGVNvSvG8Sh5WC6artSJtRwcCgYEAzYQZ
iT+0HTNlVxPohKUvuG2tw01roYFgywsvKDNHeoaEy/JZLplA3Hg24k6jAc/5TViF
Ij+p6z9WQru31++fXAeisegQZys959iJ+Tp+IDJ9ofeqyi81d+QxtlPNYhXBG720
p6Q0Lgtt+BfzY7rBMujVHqLOGvE1+cxYfhbc9d0CgYAAppfNo87Qd2zhdsTgAGSa
MA6JuAun3QTussW2S0fn7rV51YTUnXAM61VeDBy2j6/1e2LW1EX487FgZu9+D73i
1K9SOAPOwZl4FcQwPPTqvqJSVNkUmCkRT0WzuKjojchvHxbhG1Ki5hF+hD7mCo1A
oQRxc3+Roq3XlfFkQ7umCwKBgDb3Nb03hHyq2lfHw1B2vaSd8PNjKX4wlCq5+wrN
bDig8bN6Rc/ovSg6HrO4RHCfpxXArxrR3ChG8FM40Zq3Q+/hOupKHtj4/72adIxr
0H/cAS3oh407wxkltwYEs9Gf793zhvWvPSVzeR35iecKc9Gh5B4lfOpHyR/eP2y5
wK8hAoGAWDYYV1aMsZ+VB14qt/OZitx9y9FFj83uZiHSPIvQHUS88AhyOTvRIUyr
/cY8bHcTDH7h0EQW/nMJl3b/TrpzBuUA+GrJUShfp7JB/Qw2BSYfphnR4/XckyVj
/U26EPr/pL5sm/gnth/CZVzmTdK2uAoZ6bwYqS6c/I4isgztMAM=
-----END RSA PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDRzCCAi+gAwIBAgIUIdIKLy1j8a+dy5ldqey8QBbA2zAwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTIxMDIyMjIzNTg1OFoYDzIwNTEw
MjIyMTU1OTI4WjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCfPyoJX8BXIyvRoSt+OJUmE06paiYcS7sA5foO+VYS
zg7ZgthOF5jSTjMARyHvCR9QT4r3IzzRzYfDOtHXq97TdqTTjMQEBnASfh2SH+Fi
zZW0hsR4LVhrxmmZqutEkugRY234K+dtjyAiQCEN8/7snveNVQ+JHcT4c5OQtIqk
6ex4ILeRNto49lkXwoByix7+5GvEbwWuCupOgtbvMwVRqAFKCHGtJbYyyfR+XJNG
HpUgXDh4A27Pkw4sWZJUUVWmA/nyXLCfa3faml8v/IXjsn9OpeTdjW+WV1mIHc2U
f9Yg5TbGaSCWBoyTZdJedERIFWb21QyrBZyc7MbxVAQLAgMBAAGjgY4wgYswDgYD
VR0PAQH/BAQDAgOoMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNV
HQ4EFgQUFpqSoCLFGcEatR0BOusWP+ZcEfYwHwYDVR0jBBgwFoAUpoUESIm3Tlju
/UzFlkYY8Hu8TMAwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3
DQEBCwUAA4IBAQAYiRoBiUM41YAQXDbp7gFAE9HaOecDT5KQ+tLBBskunWJJg4Pq
2TKVkOrrNziP29oexrqXbMuSqi+jvyjsq76ZNRm0YeYopXQL9f6K2tjVdfMSkMRW
dwcxriTj7IC6OInXalJARpmagpNMcNj5y8XLBfJH+U7ejprTsaXY8tVNsIGG3JAI
vFIjajaStCrYuIHIDehetP+tBB+KAfo/YnTxQWvOE752KlsnevxDIBEwXtqCc4Lm
dUcZdzFsyf711nMdjOCxI9YVRvOOZ4wsc1okNTRYlDRZcByGTd0+o8h50Cx2wTG2
b6aq8q0R/FVPssTikjUqoC4+BDxntZ3qVRT+
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDMTCCAhmgAwIBAgIUaxppPIC88WI7kcFaAE2A/6CpmsowDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAxMJbG9jYWxob3N0MCAXDTIxMDIyMjIzNDU0MloYDzIwNTEw
MjIzMDE0NjEyWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQC75epj3j6t72LiHpnEvSEPKSy43uZVtQyuBX7u4Okw
AYyqFPXB1PTtcXNZNO6pDNpTxTTx9NwKfuwRonfTQuDeBQWJdetnMCvO2Jata2Gh
EtrHJE+aOpuWnNOU9qb360kM84SUAKKGZwSINGXKiRg5+OOoxcZ6k6xOYba8HQh+
MP0VQ3eC9SqtWRNuWM7kvvaS9YEQMlT0NBbLZBD2Oxh2xhQJiWXbwFif5LIcXuRN
K6f5Jc+eW5gXL2GJbIHzEAYWwZYB70vwVJZxRj5ZVtKQtVXSWkkI/Dlbx2t7uIE2
dxfAoRcUBBxgp/4OXyac++3iV3MVac1JAdOG8iXbXYLdAgMBAAGjeTB3MA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSmhQRIibdOWO79
TMWWRhjwe7xMwDAfBgNVHSMEGDAWgBTVUAc7emL2spJPGcdPWjffywuptTAUBgNV
HREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBACZV4xcWyA8Tsp+Q
IpzjHXaPqxfbX6tF9GzXRI3drqwf5KRRJ/FBG310cOkIdHwUIqmaeXK8H0IqIm1m
/kSB2MG9bguMLN7aW8erCPQ/A5f0ucERYrqT+vuqnruWstPoF1JhCYvMRLPvhcgc
dUXXr99KA/ONtvnvjf1GdV0ujwECgZZVGuLW+ZxPS48eYJQNHRvxzSdv2lDxZLK4
PRNXMjlTL2S/uFKou6I83G4oWahLtdG+9Eno4n2UuEIqhwO5rvnDOFk/pO3K0AVT
8L0+UwCARAVSQYV8fWd6hMlQLR0FpMxsDSBw5kHeueln/NxjbL6vIUrRj2Uq5OHN
Zot6haQ=
-----END CERTIFICATE-----
@@ -0,0 +1,165 @@
// +build !hsm
// NOTE on the NOTE: This is from Vault, but that doesn't mean it's not valid
// going forward with us.
//
// NOTE: we can't use this with HSM. We can't set testing mode on and it's not
// safe to use env vars since that provides an attack vector in the real world.
package server
import (
"fmt"
"sync"
"testing"
"time"
"github.com/hashicorp/boundary/internal/cmd/config"
"github.com/mitchellh/cli"
"github.com/stretchr/testify/require"
"go.uber.org/atomic"
)
const workerBaseConfig = `
disable_mlock = true
telemetry {
prometheus_retention_time = "24h"
disable_hostname = true
}
controller {
name = "dev-controller"
description = "A default controller created in dev mode"
database {
url = "%s"
}
}
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"
address = "127.0.0.1:9203"
tls_disable = true
cors_enabled = true
cors_allowed_origins = ["*"]
}
listener "tcp" {
purpose = "cluster"
address = "127.0.0.1:9204"
}
listener "tcp" {
purpose = "proxy"
address = "127.0.0.1:9205"
}
`
const tag1Config = `
worker {
name = "dev-worker"
description = "A default worker created in dev mode"
controllers = ["127.0.0.1:9204"]
tags {
type = ["dev", "local"]
}
}
`
const tag2Config = `
worker {
name = "dev-worker"
description = "A default worker created in dev mode"
controllers = ["127.0.0.1:9204"]
tags {
foo = ["bar", "baz"]
}
}
`
func TestServer_ReloadWorkerTags(t *testing.T) {
t.Parallel()
require := require.New(t)
wg := &sync.WaitGroup{}
controllerKey, workerAuthKey, recoveryKey := config.DevKeyGeneration()
cmd := testServerCommand(t, controllerKey)
defer func() {
if cmd.DevDatabaseCleanupFunc != nil {
require.NoError(cmd.DevDatabaseCleanupFunc())
}
}()
cmd.presetConfig = atomic.NewString(fmt.Sprintf(workerBaseConfig+tag1Config, cmd.DatabaseUrl, controllerKey, workerAuthKey, recoveryKey))
wg.Add(1)
go func() {
defer wg.Done()
if code := cmd.Run(nil); code != 0 {
output := cmd.UI.(*cli.MockUi).ErrorWriter.String() + cmd.UI.(*cli.MockUi).OutputWriter.String()
t.Errorf("got a non-zero exit status: %s", output)
}
}()
select {
case <-cmd.startedCh:
case <-time.After(15 * time.Second):
t.Fatalf("timeout")
}
fetchWorkerTags := func(name string, key string, values []string) {
t.Helper()
serversRepo, err := cmd.controller.ServersRepoFn()
require.NoError(err)
tags, err := serversRepo.ListTagsForServers(cmd.Context, []string{name})
require.NoError(err)
require.Len(tags, 2)
require.Equal(key, tags[0].Key)
require.Equal(values[0], tags[0].Value)
require.Equal(key, tags[1].Key)
require.Equal(values[1], tags[1].Value)
}
// Give time to populate up to the controller
time.Sleep(10 * time.Second)
fetchWorkerTags("dev-worker", "type", []string{"dev", "local"})
cmd.presetConfig.Store(fmt.Sprintf(workerBaseConfig+tag2Config, cmd.DatabaseUrl, controllerKey, workerAuthKey, recoveryKey))
cmd.SighupCh <- struct{}{}
select {
case <-cmd.reloadedCh:
case <-time.After(15 * time.Second):
t.Fatalf("timeout")
}
time.Sleep(10 * time.Second)
fetchWorkerTags("dev-worker", "foo", []string{"bar", "baz"})
close(cmd.ShutdownCh)
wg.Wait()
}
+3 -3
View File
@@ -145,7 +145,7 @@ func DevWorker() (*Config, error) {
return parsed, nil
}
func devKeyGeneration() (string, string, string) {
func DevKeyGeneration() (string, string, string) {
var numBytes int64 = 96
randBuf := new(bytes.Buffer)
n, err := randBuf.ReadFrom(&io.LimitedReader{
@@ -168,7 +168,7 @@ func devKeyGeneration() (string, string, string) {
// DevController is a Config that is used for dev mode of Boundary
// controllers
func DevController() (*Config, error) {
controllerKey, workerAuthKey, recoveryKey := devKeyGeneration()
controllerKey, workerAuthKey, recoveryKey := DevKeyGeneration()
hclStr := fmt.Sprintf(devConfig+devControllerExtraConfig, controllerKey, workerAuthKey, recoveryKey)
parsed, err := Parse(hclStr)
@@ -183,7 +183,7 @@ func DevController() (*Config, error) {
}
func DevCombined() (*Config, error) {
controllerKey, workerAuthKey, recoveryKey := devKeyGeneration()
controllerKey, workerAuthKey, recoveryKey := DevKeyGeneration()
hclStr := fmt.Sprintf(devConfig+devControllerExtraConfig+devWorkerExtraConfig, controllerKey, workerAuthKey, recoveryKey)
parsed, err := Parse(hclStr)
if err != nil {
+3 -3
View File
@@ -59,8 +59,7 @@ func New(conf *Config) (*Worker, error) {
w.lastStatusSuccess.Store((*LastStatusInformation)(nil))
w.controllerResolver.Store((*manual.Resolver)(nil))
w.parseAndStoreTags(conf.RawConfig.Worker.Tags)
w.updateTags.Store(true)
w.ParseAndStoreTags(conf.RawConfig.Worker.Tags)
if conf.SecureRandomReader == nil {
conf.SecureRandomReader = rand.Reader
@@ -148,7 +147,7 @@ func (w *Worker) Resolver() *manual.Resolver {
return raw.(*manual.Resolver)
}
func (w *Worker) parseAndStoreTags(incoming map[string][]string) {
func (w *Worker) ParseAndStoreTags(incoming map[string][]string) {
if len(incoming) == 0 {
w.tags.Store(map[string]*servers.TagValues{})
return
@@ -160,4 +159,5 @@ func (w *Worker) parseAndStoreTags(incoming map[string][]string) {
}
}
w.tags.Store(tags)
w.updateTags.Store(true)
}
@@ -63,15 +63,21 @@ allowed.
assumes TLS by default, so you must explicitly disable TLS to opt-in to
insecure communication.
- `tls_cert_file` `(string: <required-if-enabled>` –
- `tls_cert_file` `(string: <required-if-enabled, reloads-on-SIGHUP>` –
Specifies the path to the certificate for TLS. To configure the listener to
use a CA certificate, concatenate the primary certificate and the CA
certificate together. The primary certificate should appear first in the
combined file.
combined file. On `SIGHUP`, the path set here _at Boundary startup_ will be
used for reloading the certificate; modifying this value while Boundary is
running will have no effect for `SIGHUP`s.
- `tls_key_file` `(string: <required-if-enabled>` –
Specifies the path to the private key for the certificate. If the key file
is encrypted, you will be prompted to enter the passphrase on server startup.
- `tls_key_file` `(string: <required-if-enabled, reloads-on-SIGHUP>` –
Specifies the path to the private key for the certificate. If the key file is
encrypted, you will be prompted to enter the passphrase on server startup. The
passphrase must stay the same between key files when reloading your
configuration using `SIGHUP`. On `SIGHUP`, the path set here _at Boundary
startup_ will be used for reloading the certificate; modifying this value
while Boundary is running will have no effect for `SIGHUP`s.
- `tls_min_version` `(string: "tls12")` – Specifies the minimum supported
version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13".
@@ -32,7 +32,10 @@ worker {
- `controllers` - A list of hosts/IP addresses and optionally ports for reaching
controllers. The port will default to :9201 if not specified.
- `tags` - A map of key-value pairs where values are an array of strings. Most commonly used for [filtering](/docs/concepts/filtering) targets a worker can proxy via [worker tags](/docs/concepts/filtering/worker-tags).
- `tags` - A map of key-value pairs where values are an array of strings. Most
commonly used for [filtering](/docs/concepts/filtering) targets a worker can
proxy via [worker tags](/docs/concepts/filtering/worker-tags). On `SIGHUP`, the
tags set here will be re-parsed and new values used..
## KMS Configuration