diff --git a/.gitignore b/.gitignore index 33268717c..30dc3d2eb 100644 --- a/.gitignore +++ b/.gitignore @@ -33,7 +33,6 @@ temp/ # Icon must end with two \r Icon - # Thumbnails ._* @@ -53,6 +52,10 @@ Icon *_REMOTE_*.txt ### Go ### +# Files that shouldn't be shared +go.work +go.work.sum + # Binaries for programs and plugins *.exe *.exe~ diff --git a/go.mod b/go.mod index bc53f7161..bd1ac29d3 100644 --- a/go.mod +++ b/go.mod @@ -73,7 +73,7 @@ require ( golang.org/x/sync v0.3.0 golang.org/x/sys v0.13.0 golang.org/x/term v0.13.0 - golang.org/x/tools v0.9.1 + golang.org/x/tools v0.13.0 google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 google.golang.org/grpc v1.58.3 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 @@ -99,6 +99,7 @@ require ( github.com/jackc/pgx/v5 v5.4.3 github.com/jimlambrt/gldap v0.1.7 github.com/kelseyhightower/envconfig v1.4.0 + github.com/miekg/dns v1.1.56 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 golang.org/x/net v0.17.0 @@ -194,7 +195,7 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xo/dburl v0.16.0 // indirect go.uber.org/goleak v1.1.12 // indirect - golang.org/x/mod v0.10.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/oauth2 v0.10.0 // indirect golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.3.0 // indirect diff --git a/go.sum b/go.sum index b02149432..175b2f266 100644 --- a/go.sum +++ b/go.sum @@ -557,6 +557,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182aff github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/miekg/dns v1.1.56 h1:5imZaSeoRNvpM9SzWNhEcP9QliKiz20/dA2QabIGVnE= +github.com/miekg/dns v1.1.56/go.mod h1:cRm6Oo2C8TY9ZS/TqsSrseAcncm74lfK5G+ikN2SWWY= github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE= github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -841,8 +843,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20211102181907-3a5865c02020/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1063,8 +1065,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index b313d4aa8..988907e1c 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -132,6 +132,9 @@ type Server struct { DevOidcSetup oidcSetup DevLdapSetup ldapSetup + SkipPlugins bool // Useful when running on platforms that we can't easily compile plugins on, like Windows + WorkerDnsServer string + DatabaseUrl string DatabaseMaxOpenConnections int DatabaseMaxIdleConnections *int diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index c10dc7baa..7fe615068 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -101,6 +101,8 @@ type Command struct { flagEveryEventDenyFilters []string flagCreateLoopbackPlugin bool flagPluginExecutionDir string + flagSkipPlugins bool + flagWorkerDnsServer string flagWorkerAuthMethod string flagWorkerAuthStorageDir string flagWorkerAuthStorageSkipCleanup bool @@ -373,6 +375,18 @@ func (c *Command) Flags() *base.FlagSets { EnvVar: "BOUNDARY_DEV_PLUGIN_EXECUTION_DIR", Usage: "Specifies where Boundary should write plugins that it is executing; if not set defaults to system temp directory.", }) + f.BoolVar(&base.BoolVar{ + Name: "skip-plugins", + Target: &c.flagSkipPlugins, + Usage: "Skip loading compiled-in plugins. This does not prevent loopback plugins from being loaded if enabled.", + Hidden: true, + }) + f.StringVar(&base.StringVar{ + Name: "worker-dns-server", + Target: &c.flagWorkerDnsServer, + Usage: "Use a custom DNS server when workers resolve endpoints.", + Hidden: true, + }) f.StringVar(&base.StringVar{ Name: "worker-auth-method", @@ -570,6 +584,9 @@ func (c *Command) Run(args []string) int { c.Config.DevUiPassthroughDir = c.flagUiPassthroughDir + c.SkipPlugins = c.flagSkipPlugins + c.WorkerDnsServer = c.flagWorkerDnsServer + for _, l := range c.Config.Listeners { if len(l.Purpose) != 1 { c.UI.Error("Only one purpose supported for each listener") @@ -707,14 +724,18 @@ func (c *Command) Run(args []string) int { } } - c.InfoKeys = append(c.InfoKeys, "[Controller] AEAD Key Bytes") - c.Info["[Controller] AEAD Key Bytes"] = c.Config.DevControllerKey + c.InfoKeys = append(c.InfoKeys, "[Root] AEAD Key Bytes") + c.Info["[Root] AEAD Key Bytes"] = c.Config.DevControllerKey c.InfoKeys = append(c.InfoKeys, "[Recovery] AEAD Key Bytes") c.Info["[Recovery] AEAD Key Bytes"] = c.Config.DevRecoveryKey c.InfoKeys = append(c.InfoKeys, "[Worker-Auth] AEAD Key Bytes") c.Info["[Worker-Auth] AEAD Key Bytes"] = c.Config.DevWorkerAuthKey c.InfoKeys = append(c.InfoKeys, "[Bsr] AEAD Key Bytes") c.Info["[Bsr] AEAD Key Bytes"] = c.Config.DevBsrKey + if c.Config.DevWorkerAuthStorageKey != "" { + c.InfoKeys = append(c.InfoKeys, "[Worker-Auth-Storage] AEAD Key Bytes") + c.Info["[Worker-Auth-Storage] AEAD Key Bytes"] = c.Config.DevWorkerAuthStorageKey + } // Initialize the listeners if err := c.SetupListeners(c.UI, c.Config.SharedConfig, []string{"api", "cluster", "proxy", "ops"}); err != nil { diff --git a/internal/cmd/commands/server/server.go b/internal/cmd/commands/server/server.go index 8a0702a1a..92c49913f 100644 --- a/internal/cmd/commands/server/server.go +++ b/internal/cmd/commands/server/server.go @@ -55,11 +55,13 @@ type Command struct { controller *controller.Controller worker *worker.Worker - flagConfig []string - flagConfigKms string - flagLogLevel string - flagLogFormat string - flagCombineLogs bool + flagConfig []string + flagConfigKms string + flagLogLevel string + flagLogFormat string + flagCombineLogs bool + flagSkipPlugins bool + flagWorkerDnsServer string reloadedCh chan struct{} // for tests startedCh chan struct{} // for tests @@ -138,6 +140,19 @@ func (c *Command) Flags() *base.FlagSets { Hidden: true, }) + f.BoolVar(&base.BoolVar{ + Name: "skip-plugins", + Target: &c.flagSkipPlugins, + Usage: "Skip loading compiled-in plugins. This does not prevent loopback plugins from being loaded if enabled.", + Hidden: true, + }) + f.StringVar(&base.StringVar{ + Name: "worker-dns-server", + Target: &c.flagWorkerDnsServer, + Usage: "Use a custom DNS server when workers resolve endpoints.", + Hidden: true, + }) + return set } @@ -255,6 +270,9 @@ func (c *Command) Run(args []string) int { "in a Docker container, provide the IPC_LOCK cap to the container.")) } + c.SkipPlugins = c.flagSkipPlugins + c.WorkerDnsServer = c.flagWorkerDnsServer + // Perform controller-specific listener checks here before setup var clusterAddr string var foundApi bool diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index de032c90e..a56ed92bc 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -253,8 +253,8 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { return nil, fmt.Errorf("error creating host catalog plugin logger: %w", err) } } - switch enabledPlugin { - case base.EnabledPluginLoopback: + switch { + case enabledPlugin == base.EnabledPluginLoopback: lp, err := loopback.NewLoopbackPlugin() if err != nil { return nil, fmt.Errorf("error creating loopback plugin: %w", err) @@ -267,7 +267,7 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { if _, err = conf.RegisterPlugin(ctx, "loopback", plg, []plugin.PluginType{plugin.PluginTypeHost, plugin.PluginTypeStorage}, opts...); err != nil { return nil, err } - case base.EnabledPluginHostAzure: + case enabledPlugin == base.EnabledPluginHostAzure && !c.conf.SkipPlugins: pluginType := strings.ToLower(enabledPlugin.String()) client, cleanup, err := external_plugins.CreateHostPlugin( ctx, @@ -285,7 +285,7 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { if _, err := conf.RegisterPlugin(ctx, pluginType, client, []plugin.PluginType{plugin.PluginTypeHost}, plugin.WithDescription(fmt.Sprintf("Built-in %s host plugin", enabledPlugin.String()))); err != nil { return nil, fmt.Errorf("error registering %s host plugin: %w", pluginType, err) } - case base.EnabledPluginAws: + case enabledPlugin == base.EnabledPluginAws && !c.conf.SkipPlugins: pluginType := strings.ToLower(enabledPlugin.String()) client, cleanup, err := external_plugins.CreateHostPlugin( ctx, diff --git a/internal/daemon/worker/handler.go b/internal/daemon/worker/handler.go index 83ea6e0ba..0dd157345 100644 --- a/internal/daemon/worker/handler.go +++ b/internal/daemon/worker/handler.go @@ -275,7 +275,7 @@ func (w *Worker) handleProxy(listenerCfg *listenerutil.ListenerConfig, sessionMa } } - pDialer, err := proxyHandlers.GetEndpointDialer(ctx, endpointUrl.Host, workerId, acResp, w.downstreamReceiver) + pDialer, err := proxyHandlers.GetEndpointDialer(ctx, endpointUrl.Host, workerId, acResp, w.downstreamReceiver, proxyHandlers.WithDnsServerAddress(w.conf.WorkerDnsServer)) if err != nil { conn.Close(proxyHandlers.WebsocketStatusProtocolSetupError, "unable to get endpoint dialer") event.WriteError(ctx, op, err) diff --git a/internal/daemon/worker/proxy/options.go b/internal/daemon/worker/proxy/options.go index f3bbb59af..3c01f2306 100644 --- a/internal/daemon/worker/proxy/options.go +++ b/internal/daemon/worker/proxy/options.go @@ -25,6 +25,7 @@ func GetOpts(opt ...Option) Options { type Options struct { WithInjectedApplicationCredentials []*serverpb.Credential WithPostConnectionHook func(net.Conn) + WithDnsServerAddress string } func getDefaultOptions() Options { @@ -51,3 +52,12 @@ func WithPostConnectionHook(fn func(net.Conn)) Option { o.WithPostConnectionHook = fn } } + +// WithDnsServerAddress allows specifying lookup of the endpoint to happen via +// an alternate DNS server. Must be in scheme://host:port form where scheme is +// "udp" or "tcp". +func WithDnsServerAddress(with string) Option { + return func(o *Options) { + o.WithDnsServerAddress = with + } +} diff --git a/internal/daemon/worker/proxy/proxydialer.go b/internal/daemon/worker/proxy/proxydialer.go index ecc861c8a..96ec60ff7 100644 --- a/internal/daemon/worker/proxy/proxydialer.go +++ b/internal/daemon/worker/proxy/proxydialer.go @@ -7,9 +7,12 @@ import ( "context" "fmt" "net" + "net/netip" + "net/url" "sync/atomic" "github.com/hashicorp/boundary/internal/errors" + "github.com/miekg/dns" "google.golang.org/protobuf/proto" ) @@ -19,19 +22,69 @@ var GetEndpointDialer = directDialer // directDialer returns a ProxyDialer which tcp dials directly to the provided // endpoint. -func directDialer(ctx context.Context, endpoint string, _ string, _ proto.Message, _ interface{}) (*ProxyDialer, error) { +func directDialer(ctx context.Context, endpoint string, _ string, _ proto.Message, _ interface{}, opt ...Option) (*ProxyDialer, error) { const op = "proxy.directDialer" if len(endpoint) == 0 { return nil, errors.New(ctx, errors.InvalidParameter, op, "endpoint is empty") } - d, err := NewProxyDialer(ctx, func(opt ...Option) (net.Conn, error) { + opts := GetOpts(opt...) + dnsServer := opts.WithDnsServerAddress + parsedDnsServer, err := url.Parse(dnsServer) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + d, err := NewProxyDialer(ctx, func(dialerOpt ...Option) (net.Conn, error) { + if dnsServer != "" { + host, port, err := net.SplitHostPort(endpoint) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error splitting host/port")) + } + _, err = netip.ParseAddr(host) + // Only try resolving if it's not an IP address (err is not nil) or + // not something we definitely can't resolve via an upstream DNS + // server (localhost) + if err != nil && host != "localhost" && host != "localhost.localdomain" { + dnsClient := new(dns.Client) + dnsClient.Net = parsedDnsServer.Scheme + m := new(dns.Msg) + m.Id = dns.Id() + m.RecursionDesired = true + m.Question = make([]dns.Question, 1) + m.Question[0] = dns.Question{ + Name: fmt.Sprintf("%s.", host), + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + } + resp, _, err := dnsClient.Exchange(m, parsedDnsServer.Host) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error performing dns exchange")) + } + if len(resp.Answer) == 0 { + return nil, errors.New(ctx, errors.Internal, op, "no answer returned from dns") + } + var found bool + for _, ans := range resp.Answer { + ans, ok := ans.(*dns.A) + if !ok { + continue + } + endpoint = net.JoinHostPort(ans.A.String(), port) + found = true + break + } + if !found { + return nil, errors.New(ctx, errors.Internal, op, "no A records in dns answer") + } + } + } remoteConn, err := net.Dial("tcp", endpoint) if err != nil { return nil, errors.Wrap(ctx, err, op) } - opts := GetOpts(opt...) - if opts.WithPostConnectionHook != nil { - opts.WithPostConnectionHook(remoteConn) + dialerOpts := GetOpts(dialerOpt...) + if dialerOpts.WithPostConnectionHook != nil { + dialerOpts.WithPostConnectionHook(remoteConn) } return remoteConn, nil }) diff --git a/internal/daemon/worker/worker.go b/internal/daemon/worker/worker.go index cd7a28ec2..4d5a598c5 100644 --- a/internal/daemon/worker/worker.go +++ b/internal/daemon/worker/worker.go @@ -235,8 +235,8 @@ func New(ctx context.Context, conf *Config) (*Worker, error) { var enableStorageLoopback bool for _, enabledPlugin := range w.conf.Server.EnabledPlugins { - switch enabledPlugin { - case base.EnabledPluginAws: + switch { + case enabledPlugin == base.EnabledPluginAws && !w.conf.SkipPlugins: pluginType := strings.ToLower(enabledPlugin.String()) client, cleanup, err := external_plugins.CreateStoragePlugin( ctx, @@ -252,7 +252,7 @@ func New(ctx context.Context, conf *Config) (*Worker, error) { } conf.ShutdownFuncs = append(conf.ShutdownFuncs, cleanup) plgClients[pluginType] = client - case base.EnabledPluginLoopback: + case enabledPlugin == base.EnabledPluginLoopback: enableStorageLoopback = true } } diff --git a/internal/host/plugin/job_set_sync.go b/internal/host/plugin/job_set_sync.go index a5fe1fa2a..04887c3f0 100644 --- a/internal/host/plugin/job_set_sync.go +++ b/internal/host/plugin/job_set_sync.go @@ -54,7 +54,7 @@ func newSetSyncJob(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, return nil, errors.New(ctx, errors.InvalidParameter, op, "missing db.Writer") case kms == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing kms") - case len(plgm) == 0: + case plgm == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing plugin manager") }