[fix] allow IPv6 controllers, workers, and targets (#5221)

Co-authored-by: Damian Debkowski <damian.debkowski@hashicorp.com>
This commit is contained in:
daniandDamian Debkowski authored and GitHub committed 2024-11-01 13:27:40 -04:00
1 parent 680bb02af9
commit 62c41191ff
42 files changed
+1996 -546

No files matched your search

+18 -14
View File
@@ -23,6 +23,7 @@ import (
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/boundary/internal/util"
"github.com/hashicorp/boundary/testing/dbtest"
capoidc "github.com/hashicorp/cap/oidc"
"github.com/jimlambrt/gldap"
@@ -242,13 +243,9 @@ func (b *Server) CreateDevLdapAuthMethod(ctx context.Context) error {
if purpose != "api" {
continue
}
host, _, err = net.SplitHostPort(ln.Config.Address)
host, _, err = util.SplitHostPort(ln.Config.Address)
if err != nil {
if strings.Contains(err.Error(), "missing port") {
host = ln.Config.Address
} else {
return fmt.Errorf("error splitting host/port: %w", err)
}
return fmt.Errorf("error splitting host/port: %w", err)
}
}
if host == "" {
@@ -259,6 +256,16 @@ func (b *Server) CreateDevLdapAuthMethod(ctx context.Context) error {
tb := &oidcLogger{}
port = testdirectory.FreePort(tb)
// The util.SplitHostPort() method removes the square brackets that enclose the
// host address when the address type is ipv6. The square brackets must be
// added back, otherwise the gldap server will fail to start due to a parsing
// error.
if ip := net.ParseIP(host); ip != nil {
if ip.To16() != nil {
host = fmt.Sprintf("[%s]", host)
}
}
b.DevLdapSetup.testDirectory = testdirectory.Start(tb,
testdirectory.WithNoTLS(tb),
testdirectory.WithHost(tb, host),
@@ -455,15 +462,12 @@ func (b *Server) CreateDevOidcAuthMethod(ctx context.Context) error {
if purpose != "api" {
continue
}
b.DevOidcSetup.hostAddr, b.DevOidcSetup.callbackPort, err = net.SplitHostPort(ln.Config.Address)
b.DevOidcSetup.hostAddr, b.DevOidcSetup.callbackPort, err = util.SplitHostPort(ln.Config.Address)
if err != nil {
if strings.Contains(err.Error(), "missing port") {
b.DevOidcSetup.hostAddr = ln.Config.Address
// Use the default API port in the callback
b.DevOidcSetup.callbackPort = "9200"
} else {
return fmt.Errorf("error splitting host/port: %w", err)
}
return fmt.Errorf("error splitting host/port: %w", err)
}
if b.DevOidcSetup.callbackPort == "" {
b.DevOidcSetup.callbackPort = "9200"
}
}
if b.DevOidcSetup.hostAddr == "" {
+16 -17
View File
@@ -16,6 +16,7 @@ import (
_ "crypto/sha512"
"crypto/tls"
"github.com/hashicorp/boundary/internal/util"
"github.com/hashicorp/go-secure-stdlib/listenerutil"
"github.com/hashicorp/go-secure-stdlib/reloadutil"
"github.com/mitchellh/cli"
@@ -137,24 +138,22 @@ func tcpListenerFactory(purpose string, l *listenerutil.ListenerConfig, ui cli.U
}
}
host, port, err := net.SplitHostPort(l.Address)
host, port, err := util.SplitHostPort(l.Address)
if err != nil {
if strings.Contains(err.Error(), "missing port") {
switch purpose {
case "api":
port = "9200"
case "cluster":
port = "9201"
case "proxy":
port = "9202"
case "ops":
port = "9203"
default:
return "", nil, errors.New("no purpose provided for listener and no port discoverable")
}
host = l.Address
} else {
return "", nil, fmt.Errorf("error splitting host/port: %w", err)
return "", nil, fmt.Errorf("error splitting host/port: %w", err)
}
if port == "" {
switch purpose {
case "api":
port = "9200"
case "cluster":
port = "9201"
case "proxy":
port = "9202"
case "ops":
port = "9203"
default:
return "", nil, errors.New("no purpose provided for listener and no port discoverable")
}
}
+6 -18
View File
@@ -10,11 +10,9 @@ import (
"errors"
"fmt"
"io"
"net"
"os"
"os/signal"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@@ -58,10 +56,6 @@ const (
WorkerAuthReqFile = "auth_request_token"
)
// This regular expression is used to find all instances of square brackets within a string.
// This regular expression is used to remove the square brackets from an IPv6 address.
var squareBrackets = regexp.MustCompile("\\[|\\]")
func init() {
metric.InitializeBuildInfo(prometheus.DefaultRegisterer)
}
@@ -841,20 +835,14 @@ func (b *Server) SetupWorkerPublicAddress(conf *config.Config, flagValue string)
}
}
host, port, err := net.SplitHostPort(conf.Worker.PublicAddr)
host, port, err := util.SplitHostPort(conf.Worker.PublicAddr)
if err != nil {
if strings.Contains(err.Error(), "missing port") {
port = "9202"
host = conf.Worker.PublicAddr
} else {
return fmt.Errorf("Error splitting public adddress host/port: %w", err)
}
return fmt.Errorf("Error splitting public adddress host/port: %w", err)
}
// remove the square brackets from the ipv6 address because the method
// net.JoinHostPort() will add a second pair of square brackets.
host = squareBrackets.ReplaceAllString(host, "")
conf.Worker.PublicAddr = net.JoinHostPort(host, port)
if port == "" {
port = "9202"
}
conf.Worker.PublicAddr = util.JoinHostPort(host, port)
return nil
}
+8 -16
View File
@@ -10,7 +10,6 @@ import (
"fmt"
"io"
"math"
"net"
"net/netip"
"os"
"strconv"
@@ -22,6 +21,7 @@ import (
apiproxy "github.com/hashicorp/boundary/api/proxy"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/internal/cmd/base"
"github.com/hashicorp/boundary/internal/util"
"github.com/mitchellh/cli"
"github.com/posener/complete"
"go.uber.org/atomic"
@@ -476,14 +476,10 @@ func (c *Command) Run(args []string) (retCode int) {
proxyAddr := clientProxy.ListenerAddress(context.Background())
var clientProxyHost, clientProxyPort string
clientProxyHost, clientProxyPort, err = net.SplitHostPort(proxyAddr)
clientProxyHost, clientProxyPort, err = util.SplitHostPort(proxyAddr)
if err != nil {
if strings.Contains(err.Error(), "missing port") {
clientProxyHost = proxyAddr
} else {
c.PrintCliError(fmt.Errorf("error splitting listener addr: %w", err))
return base.CommandCliError
}
c.PrintCliError(fmt.Errorf("error splitting listener addr: %w", err))
return base.CommandCliError
}
c.sessInfo.Address = clientProxyHost
@@ -605,15 +601,11 @@ func (c *Command) handleExec(clientProxy *apiproxy.ClientProxy, passthroughArgs
addr := clientProxy.ListenerAddress(context.Background())
var host, port string
var err error
host, port, err = net.SplitHostPort(addr)
host, port, err = util.SplitHostPort(addr)
if err != nil {
if strings.Contains(err.Error(), "missing port") {
host = addr
} else {
c.PrintCliError(fmt.Errorf("Error splitting listener addr: %w", err))
c.execCmdReturnValue.Store(int32(3))
return
}
c.PrintCliError(fmt.Errorf("Error splitting listener addr: %w", err))
c.execCmdReturnValue.Store(int32(3))
return
}
var args []string
+4 -7
View File
@@ -8,7 +8,6 @@ import (
"errors"
"fmt"
"math/rand"
"net"
"os"
"runtime"
"strings"
@@ -27,6 +26,7 @@ import (
"github.com/hashicorp/boundary/internal/server"
"github.com/hashicorp/boundary/internal/server/store"
"github.com/hashicorp/boundary/internal/types/scope"
"github.com/hashicorp/boundary/internal/util"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/go-secure-stdlib/strutil"
"github.com/hashicorp/nodeenrollment"
@@ -592,13 +592,10 @@ func (c *Command) Run(args []string) int {
return base.CommandUserError
}
host, port, err := net.SplitHostPort(c.flagHostAddress)
host, port, err := util.SplitHostPort(c.flagHostAddress)
if err != nil {
if !strings.Contains(err.Error(), "missing port") {
c.UI.Error(fmt.Errorf("Invalid host address specified: %w", err).Error())
return base.CommandUserError
}
host = c.flagHostAddress
c.UI.Error(fmt.Errorf("Invalid host address specified: %w", err).Error())
return base.CommandUserError
}
if port != "" {
c.UI.Error(`Port must not be specified as part of the dev host address`)
+7 -14
View File
@@ -26,6 +26,7 @@ import (
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/event"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/util"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-secure-stdlib/mlock"
"github.com/hashicorp/go-secure-stdlib/parseutil"
@@ -355,14 +356,10 @@ func (c *Command) Run(args []string) int {
}
}
for _, upstream := range c.Config.Worker.InitialUpstreams {
host, _, err := net.SplitHostPort(upstream)
host, _, err := util.SplitHostPort(upstream)
if err != nil {
if strings.Contains(err.Error(), globals.MissingPortErrStr) {
host = upstream
} else {
c.UI.Error(fmt.Errorf("Invalid worker upstream address %q: %w", upstream, err).Error())
return base.CommandUserError
}
c.UI.Error(fmt.Errorf("Invalid worker upstream address %q: %w", upstream, err).Error())
return base.CommandUserError
}
ip := net.ParseIP(host)
if ip != nil {
@@ -413,14 +410,10 @@ func (c *Command) Run(args []string) int {
if purpose != "cluster" {
continue
}
host, _, err := net.SplitHostPort(ln.Address)
host, _, err := util.SplitHostPort(ln.Address)
if err != nil {
if strings.Contains(err.Error(), globals.MissingPortErrStr) {
host = ln.Address
} else {
c.UI.Error(fmt.Errorf("Invalid cluster listener address %q: %w", ln.Address, err).Error())
return base.CommandUserError
}
c.UI.Error(fmt.Errorf("Invalid cluster listener address %q: %w", ln.Address, err).Error())
return base.CommandUserError
}
ip := net.ParseIP(host)
if ip != nil {
+111 -32
View File
@@ -15,7 +15,6 @@ import (
"net"
"os"
"reflect"
"regexp"
"strconv"
"strings"
"time"
@@ -103,6 +102,84 @@ listener "tcp" {
purpose = "ops"
tls_disable = true
}
`
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"
}
`
devWorkerExtraConfig = `
@@ -133,10 +210,6 @@ kms "aead" {
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'"
)
// This regular expression is used to find all instances of square brackets within a string.
// This regular expression is used to remove the square brackets from an IPv6 address.
var squareBrackets = regexp.MustCompile("\\[|\\]")
// Config is the configuration for the boundary controller
type Config struct {
*configutil.SharedConfig `hcl:"-"`
@@ -378,15 +451,18 @@ type License struct {
// WithAuditEventsEnabled, TestWithErrorEventsEnabled
func DevWorker(opt ...Option) (*Config, error) {
workerAuthStorageKey := DevKeyGeneration()
hclStr := fmt.Sprintf(devConfig+devWorkerExtraConfig, workerAuthStorageKey)
parsed, err := Parse(hclStr)
if err != nil {
return nil, fmt.Errorf("error parsing dev config: %w", err)
}
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("error parsing options: %w", err)
}
hclStr := fmt.Sprintf(devConfig+devWorkerExtraConfig, workerAuthStorageKey)
if opts.withIPv6Enabled {
hclStr = fmt.Sprintf(devConfig+devIpv6WorkerExtraConfig, workerAuthStorageKey)
}
parsed, err := Parse(hclStr)
if err != nil {
return nil, fmt.Errorf("error parsing dev config: %w", err)
}
parsed.Eventing.AuditEnabled = opts.withAuditEventsEnabled
parsed.Eventing.ObservationsEnabled = opts.withObservationsEnabled
parsed.Eventing.SysEventsEnabled = opts.withSysEventsEnabled
@@ -414,12 +490,20 @@ func DevKeyGeneration() string {
// DevController is a Config that is used for dev mode of Boundary
// controllers
func DevController(opt ...Option) (*Config, error) {
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("error parsing options: %w", err)
}
controllerKey := DevKeyGeneration()
workerAuthKey := DevKeyGeneration()
bsrKey := DevKeyGeneration()
recoveryKey := DevKeyGeneration()
hclStr := fmt.Sprintf(devConfig+devControllerExtraConfig, controllerKey, workerAuthKey, bsrKey, recoveryKey)
if opts.withIPv6Enabled {
hclStr = fmt.Sprintf(devConfig+devIpv6ControllerExtraConfig, controllerKey, workerAuthKey, bsrKey, recoveryKey)
}
parsed, err := Parse(hclStr)
if err != nil {
return nil, fmt.Errorf("error parsing dev config: %w", err)
@@ -429,10 +513,6 @@ func DevController(opt ...Option) (*Config, error) {
parsed.DevWorkerAuthKey = workerAuthKey
parsed.DevBsrKey = bsrKey
parsed.DevRecoveryKey = recoveryKey
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("error parsing options: %w", err)
}
parsed.Eventing.AuditEnabled = opts.withAuditEventsEnabled
parsed.Eventing.ObservationsEnabled = opts.withObservationsEnabled
parsed.Eventing.SysEventsEnabled = opts.withSysEventsEnabled
@@ -440,13 +520,22 @@ func DevController(opt ...Option) (*Config, error) {
return parsed, nil
}
func DevCombined() (*Config, error) {
func DevCombined(opt ...Option) (*Config, error) {
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("error parsing options: %w", err)
}
controllerKey := DevKeyGeneration()
workerAuthKey := DevKeyGeneration()
workerAuthStorageKey := DevKeyGeneration()
bsrKey := DevKeyGeneration()
recoveryKey := DevKeyGeneration()
hclStr := fmt.Sprintf(devConfig+devControllerExtraConfig+devWorkerExtraConfig, controllerKey, workerAuthKey, bsrKey, recoveryKey, workerAuthStorageKey)
if opts.withIPv6Enabled {
hclStr = fmt.Sprintf(devConfig+devIpv6ControllerExtraConfig+devIpv6WorkerExtraConfig, controllerKey, workerAuthKey, bsrKey, recoveryKey, workerAuthStorageKey)
}
parsed, err := Parse(hclStr)
if err != nil {
return nil, fmt.Errorf("error parsing dev config: %w", err)
@@ -1255,20 +1344,14 @@ func (c *Config) SetupControllerPublicClusterAddress(flagValue string) error {
}
}
host, port, err := net.SplitHostPort(c.Controller.PublicClusterAddr)
host, port, err := util.SplitHostPort(c.Controller.PublicClusterAddr)
if err != nil {
if strings.Contains(err.Error(), "missing port") {
port = "9201"
host = c.Controller.PublicClusterAddr
} else {
return fmt.Errorf("Error splitting public cluster adddress host/port: %w", err)
}
return fmt.Errorf("Error splitting public cluster adddress host/port: %w", err)
}
// remove the square brackets from the ipv6 address because the method
// net.JoinHostPort() will add a second pair of square brackets.
host = squareBrackets.ReplaceAllString(host, "")
c.Controller.PublicClusterAddr = net.JoinHostPort(host, port)
if port == "" {
port = "9201"
}
c.Controller.PublicClusterAddr = util.JoinHostPort(host, port)
return nil
}
@@ -1322,11 +1405,7 @@ func (c *Config) SetupWorkerInitialUpstreams() error {
break
}
// Best effort see if it's a domain name and if not assume it must match
host, _, err := net.SplitHostPort(c.Worker.InitialUpstreams[0])
if err != nil && strings.Contains(err.Error(), globals.MissingPortErrStr) {
err = nil
host = c.Worker.InitialUpstreams[0]
}
host, _, err := util.SplitHostPort(c.Worker.InitialUpstreams[0])
if err == nil {
ip := net.ParseIP(host)
if ip == nil {
+97
View File
@@ -6,6 +6,7 @@ package config
import (
"encoding/base64"
"fmt"
"net"
"net/http"
"os"
"testing"
@@ -13,6 +14,7 @@ import (
"github.com/hashicorp/boundary/internal/event"
"github.com/hashicorp/boundary/internal/ratelimit"
"github.com/hashicorp/boundary/internal/util"
configutil "github.com/hashicorp/go-secure-stdlib/configutil/v2"
"github.com/hashicorp/go-secure-stdlib/listenerutil"
"github.com/hashicorp/go-secure-stdlib/parseutil"
@@ -779,6 +781,101 @@ func TestDevWorkerRecordingStoragePath(t *testing.T) {
}
}
func TestDevControllerIpv6(t *testing.T) {
require, assert := require.New(t), assert.New(t)
// This test only validates that all listeners are utilizing an IPv6 address.
// Other dev controller configurations are validates in TestDevController.
actual, err := DevController(WithIPv6Enabled(true))
require.NoError(err)
// expected an error here because we purposely did not provide a port number
// to allow randomly assigned port values
_, _, err = net.SplitHostPort(actual.Controller.PublicClusterAddr)
require.Error(err)
// assert the square brackets are removed from the host ipv6 address and that the port value is empty
publicAddr, port, err := util.SplitHostPort(actual.Controller.PublicClusterAddr)
require.NoError(err)
assert.Empty(port)
assert.Empty(publicAddr)
require.NotEmpty(actual.Listeners)
for _, l := range actual.Listeners {
addr, _, err := util.SplitHostPort(l.Address)
require.NoError(err)
ip := net.ParseIP(addr)
assert.NotNil(ip, "failed to parse listener address for %v", l.Purpose)
assert.NotNil(ip.To16(), "failed to convert address to IPv6 for %v, found %v", l.Purpose, addr)
}
}
func TestDevWorkerIpv6(t *testing.T) {
require, assert := require.New(t), assert.New(t)
// This test only validates that all listeners are utilizing an IPv6 address.
// Other dev worker configurations are validates in TestDevWorker.
actual, err := DevWorker(WithIPv6Enabled(true))
require.NoError(err)
// expected an error here because we purposely did not provide a port number
// to allow randomly assigned port values
_, _, err = net.SplitHostPort(actual.Worker.PublicAddr)
require.Error(err)
// assert the square brackets are removed from the worker ipv6 address and that the port value is empty
publicAddr, port, err := util.SplitHostPort(actual.Worker.PublicAddr)
require.NoError(err)
assert.Empty(port)
ip := net.ParseIP(publicAddr)
assert.NotNil(ip, "failed to parse worker public address")
assert.NotNil(ip.To16(), "worker public address is not IPv6 %s", actual.Worker.PublicAddr)
require.NotEmpty(actual.Listeners)
for _, l := range actual.Listeners {
addr, _, err := util.SplitHostPort(l.Address)
require.NoError(err)
ip := net.ParseIP(addr)
assert.NotNil(ip, "failed to parse listener address for %v", l.Purpose)
assert.NotNil(ip.To16(), "failed to convert address to IPv6 for %v, found %v", l.Purpose, addr)
}
}
func TestDevCombinedIpv6(t *testing.T) {
require, assert := require.New(t), assert.New(t)
// This test only validates that all listeners are utilizing an IPv6 address.
actual, err := DevCombined(WithIPv6Enabled(true))
require.NoError(err)
// expected an error here because we purposely did not provide a port number
// to allow randomly assigned port values for the worker and controller
_, _, err = net.SplitHostPort(actual.Worker.PublicAddr)
require.Error(err)
_, _, err = net.SplitHostPort(actual.Controller.PublicClusterAddr)
require.Error(err)
// assert the square brackets are removed from the host ipv6 address and that the port value is empty
publicAddr, port, err := util.SplitHostPort(actual.Worker.PublicAddr)
require.NoError(err)
assert.Empty(port)
ip := net.ParseIP(publicAddr)
assert.NotNil(ip, "failed to parse worker public address")
assert.NotNil(ip.To16(), "worker public address is not IPv6 %s", actual.Worker.PublicAddr)
// assert the square brackets are removed from the controller ipv6 address and that the port value is empty
publicAddr, port, err = util.SplitHostPort(actual.Controller.PublicClusterAddr)
require.NoError(err)
assert.Empty(port)
assert.Empty(publicAddr)
require.NotEmpty(actual.Listeners)
for _, l := range actual.Listeners {
addr, _, err := util.SplitHostPort(l.Address)
require.NoError(err)
ip := net.ParseIP(addr)
assert.NotNil(ip, "failed to parse listener address for %v", l.Purpose)
assert.NotNil(ip.To16(), "failed to convert address to IPv6 for %v, found %v", l.Purpose, addr)
}
}
func TestDevKeyGeneration(t *testing.T) {
t.Parallel()
dk := DevKeyGeneration()
+15
View File
@@ -35,6 +35,7 @@ type options struct {
withSysEventsEnabled bool
withAuditEventsEnabled bool
withObservationsEnabled bool
withIPv6Enabled bool
testWithErrorEventsEnabled bool
}
@@ -59,6 +60,12 @@ func getDefaultOptions() (options, error) {
}
opts.withObservationsEnabled = obs
ipv6, err := parseutil.ParseBool(os.Getenv("BOUNDARY_ENABLE_TEST_IPV6"))
if err != nil {
return opts, err
}
opts.withIPv6Enabled = ipv6
errEvents, err := parseutil.ParseBool(os.Getenv("BOUNDARY_ENABLE_TEST_ERROR_EVENTS"))
if err != nil {
return opts, err
@@ -92,6 +99,14 @@ func WithObservationsEnabled(enable bool) Option {
}
}
// WithIPv6Enabled provides an option for enabling network ipv6 addresses
func WithIPv6Enabled(enable bool) Option {
return func(o *options) error {
o.withIPv6Enabled = enable
return nil
}
}
// TestWithErrorEventsEnabled provides an option for enabling error events
// during tests.
func TestWithErrorEventsEnabled(_ testing.TB, enable bool) Option {