// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package base import ( "context" "fmt" "sync" "testing" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/cmd/config" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/event" "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/hashicorp/go-kms-wrapping/v2/extras/multi" "github.com/hashicorp/go-secure-stdlib/configutil/v2" "github.com/hashicorp/go-secure-stdlib/listenerutil" "github.com/mitchellh/cli" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func Test_NewServer(t *testing.T) { t.Run("assert-require-no-nil-fields", func(t *testing.T) { assert := assert.New(t) s := NewServer(&Command{}) assert.Equal(s.Command, &Command{}) assert.NotNil(s.InfoKeys) assert.NotNil(s.Info) assert.NotNil(s.SecureRandomReader) assert.NotNil(s.ReloadFuncsLock) assert.NotNil(s.ReloadFuncs) assert.NotNil(s.StderrLock) }) } func TestServer_SetupKMSes_Purposes(t *testing.T) { tests := []struct { name string purposes []string wantErrContains string }{ { name: "nil purposes", }, { name: "empty purpose", purposes: []string{""}, wantErrContains: "KMS block missing 'purpose'", }, { name: "unknown purpose", purposes: []string{"foobar"}, wantErrContains: "Unknown KMS purpose", }, { name: "single purpose", purposes: []string{globals.KmsPurposeRoot}, }, { name: "multi purpose", purposes: []string{ globals.KmsPurposeRoot, globals.KmsPurposeRecovery, globals.KmsPurposeWorkerAuth, globals.KmsPurposeDownstreamWorkerAuth, globals.KmsPurposeWorkerAuthStorage, globals.KmsPurposeConfig, globals.KmsPurposeBsr, }, }, { name: "previous root without root", purposes: []string{globals.KmsPurposePreviousRoot}, wantErrContains: fmt.Sprintf("KMS block contains '%s' without '%s'", globals.KmsPurposePreviousRoot, globals.KmsPurposeRoot), }, { name: "root and previous in the same stanza", purposes: []string{globals.KmsPurposeRoot, globals.KmsPurposePreviousRoot}, wantErrContains: fmt.Sprintf("KMS blocks with purposes '%s' and '%s' must have different key IDs", globals.KmsPurposeRoot, globals.KmsPurposePreviousRoot), }, { name: "duplicate root purposes", purposes: []string{globals.KmsPurposeRoot, globals.KmsPurposeRoot}, wantErrContains: fmt.Sprintf("Duplicate KMS block for purpose '%s'", globals.KmsPurposeRoot), }, { name: "duplicate previous root purposes", purposes: []string{globals.KmsPurposePreviousRoot, globals.KmsPurposePreviousRoot}, wantErrContains: fmt.Sprintf("Duplicate KMS block for purpose '%s'", globals.KmsPurposePreviousRoot), }, { name: "duplicate worker auth purposes", purposes: []string{globals.KmsPurposeWorkerAuth, globals.KmsPurposeWorkerAuth}, wantErrContains: fmt.Sprintf("Duplicate KMS block for purpose '%s'", globals.KmsPurposeWorkerAuth), }, { name: "duplicate worker auth storage purposes", purposes: []string{globals.KmsPurposeWorkerAuthStorage, globals.KmsPurposeWorkerAuthStorage}, wantErrContains: fmt.Sprintf("Duplicate KMS block for purpose '%s'", globals.KmsPurposeWorkerAuthStorage), }, { name: "duplicate bsr kms purposes", purposes: []string{globals.KmsPurposeBsr, globals.KmsPurposeBsr}, wantErrContains: fmt.Sprintf("Duplicate KMS block for purpose '%s'", globals.KmsPurposeBsr), }, { name: "duplicate recovery purposes", purposes: []string{globals.KmsPurposeRecovery, globals.KmsPurposeRecovery}, wantErrContains: fmt.Sprintf("Duplicate KMS block for purpose '%s'", globals.KmsPurposeRecovery), }, } logger := hclog.Default() serLock := new(sync.Mutex) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) conf := &configutil.SharedConfig{ Seals: []*configutil.KMS{ { Type: "aead", Purpose: tt.purposes, }, }, } s := NewServer(&Command{Context: context.Background()}) require.NoError(s.SetupEventing(s.Context, logger, serLock, "setup-kms-testing")) err := s.SetupKMSes(s.Context, cli.NewMockUi(), &config.Config{SharedConfig: conf}) if tt.wantErrContains != "" { require.Error(err) assert.Contains(err.Error(), tt.wantErrContains) return } require.NoError(err) for _, purpose := range tt.purposes { switch purpose { case globals.KmsPurposeRoot: assert.NotNil(s.RootKms) case globals.KmsPurposeWorkerAuth: assert.NotNil(s.WorkerAuthKms) case globals.KmsPurposeWorkerAuthStorage: assert.NotNil(s.WorkerAuthStorageKms) case globals.KmsPurposeRecovery: assert.NotNil(s.RecoveryKms) case globals.KmsPurposeBsr: assert.NotNil(s.BsrKms) } } }) } } func TestServer_SetupKMSes_RootMigration(t *testing.T) { t.Parallel() t.Run("correctly-pools-root-and-previous", func(t *testing.T) { t.Parallel() assert, require := assert.New(t), require.New(t) logger := hclog.Default() serLock := new(sync.Mutex) conf := &configutil.SharedConfig{ Seals: []*configutil.KMS{ { Type: "aead", Purpose: []string{ globals.KmsPurposeRoot, }, Config: map[string]string{ "key_id": "root", }, }, { Type: "aead", Purpose: []string{ globals.KmsPurposePreviousRoot, }, Config: map[string]string{ "key_id": "previous_root", }, }, }, } s := NewServer(&Command{Context: context.Background()}) require.NoError(s.SetupEventing(s.Context, logger, serLock, "setup-kms-testing")) err := s.SetupKMSes(s.Context, cli.NewMockUi(), &config.Config{SharedConfig: conf}) require.NoError(err) require.NotNil(s.RootKms) typ, err := s.RootKms.Type(s.Context) require.NoError(err) assert.Equal(wrapping.WrapperTypePooled, typ) // Ensure that the root is the encryptor keyId, err := s.RootKms.KeyId(s.Context) require.NoError(err) assert.Equal("root", keyId) // Ensure that the previous root is in the wrapper too assert.Equal([]string{"previous_root", "root"}, s.RootKms.(*multi.PooledWrapper).AllKeyIds()) }) t.Run("errors-on-previous-without-root", func(t *testing.T) { t.Parallel() require := require.New(t) logger := hclog.Default() serLock := new(sync.Mutex) conf := &configutil.SharedConfig{ Seals: []*configutil.KMS{ { Type: "aead", Purpose: []string{ globals.KmsPurposePreviousRoot, }, }, }, } s := NewServer(&Command{Context: context.Background()}) require.NoError(s.SetupEventing(s.Context, logger, serLock, "setup-kms-testing")) err := s.SetupKMSes(s.Context, cli.NewMockUi(), &config.Config{SharedConfig: conf}) require.Error(err) }) t.Run("errors-on-previous-and-root-with-same-key-id", func(t *testing.T) { t.Parallel() require := require.New(t) logger := hclog.Default() serLock := new(sync.Mutex) conf := &configutil.SharedConfig{ Seals: []*configutil.KMS{ { Type: "aead", Purpose: []string{ globals.KmsPurposeRoot, }, Config: map[string]string{ "key_id": "root", }, }, { Type: "aead", Purpose: []string{ globals.KmsPurposePreviousRoot, }, Config: map[string]string{ "key_id": "root", }, }, }, } s := NewServer(&Command{Context: context.Background()}) require.NoError(s.SetupEventing(s.Context, logger, serLock, "setup-kms-testing")) err := s.SetupKMSes(s.Context, cli.NewMockUi(), &config.Config{SharedConfig: conf}) require.Error(err) }) } func TestServer_SetupEventing(t *testing.T) { // DO NOT run these test in parallel since they have a dependency on // event.sysEventer testLock := &sync.Mutex{} testLogger := hclog.New(&hclog.LoggerOptions{ Mutex: testLock, }) setTrue := true setFalse := false tests := []struct { name string s *Server logger hclog.Logger lock *sync.Mutex opt []Option want event.EventerConfig wantErrMatch *errors.Template wantErrIs error wantErrContains string }{ { name: "missing-logger", s: &Server{}, lock: testLock, wantErrMatch: errors.T(errors.InvalidParameter), wantErrContains: "missing logger", }, { name: "missing-serialization-lock", s: &Server{}, logger: testLogger, wantErrMatch: errors.T(errors.InvalidParameter), wantErrContains: "missing serialization lock", }, { name: "opts-none", s: &Server{}, logger: testLogger, lock: testLock, want: *event.DefaultEventerConfig(), }, { name: "opts-event-flags", s: &Server{}, logger: testLogger, lock: testLock, opt: []Option{WithEventFlags(&EventFlags{ Format: event.JSONSinkFormat, AuditEnabled: &setTrue, ObservationsEnabled: &setFalse, SysEventsEnabled: &setFalse, TelemetryEnabled: &setFalse, })}, want: func() event.EventerConfig { c := event.DefaultEventerConfig() c.AuditEnabled = true c.ObservationsEnabled = false c.SysEventsEnabled = false c.TelemetryEnabled = false return *c }(), }, { name: "opts-event-flags-invalid", s: &Server{}, logger: testLogger, lock: testLock, opt: []Option{WithEventFlags(&EventFlags{ Format: "invalid-format", })}, wantErrIs: event.ErrInvalidParameter, wantErrContains: "not a valid sink format", }, { name: "opts-eventer-config", s: &Server{}, logger: testLogger, lock: testLock, opt: []Option{WithEventerConfig(&event.EventerConfig{ ObservationsEnabled: false, SysEventsEnabled: false, AuditEnabled: true, TelemetryEnabled: false, })}, want: func() event.EventerConfig { c := event.DefaultEventerConfig() c.AuditEnabled = true c.ObservationsEnabled = false c.SysEventsEnabled = false c.TelemetryEnabled = false return *c }(), }, { name: "opts-eventer-config-invalid", s: &Server{}, logger: testLogger, lock: testLock, opt: []Option{WithEventerConfig(&event.EventerConfig{ Sinks: []*event.SinkConfig{ { Format: "invalid-format", }, }, })}, wantErrIs: event.ErrInvalidParameter, wantErrContains: "sink 0 is invalid", }, { name: "opts-eventer-config-observation-telemetry-invalid", s: &Server{}, logger: testLogger, lock: testLock, opt: []Option{WithEventFlags(&EventFlags{ Format: event.JSONSinkFormat, AuditEnabled: &setTrue, ObservationsEnabled: &setFalse, SysEventsEnabled: &setFalse, TelemetryEnabled: &setTrue, })}, wantErrIs: event.ErrInvalidParameter, wantErrContains: "telemetry events require observation event to be enabled", }, { name: "opts-eventer-config-observation-on-telemetry-off", s: &Server{}, logger: testLogger, lock: testLock, opt: []Option{WithEventFlags(&EventFlags{ Format: event.JSONSinkFormat, AuditEnabled: &setFalse, ObservationsEnabled: &setTrue, SysEventsEnabled: &setFalse, TelemetryEnabled: &setFalse, })}, want: func() event.EventerConfig { c := event.DefaultEventerConfig() c.AuditEnabled = false c.ObservationsEnabled = true c.SysEventsEnabled = false c.TelemetryEnabled = false return *c }(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) event.TestResetSystEventer(t) err := tt.s.SetupEventing(context.Background(), tt.logger, tt.lock, tt.name, tt.opt...) if tt.wantErrMatch != nil || tt.wantErrIs != nil { require.Error(err) assert.Nil(tt.s.Eventer) assert.Nil(event.SysEventer()) if tt.wantErrMatch != nil { assert.Truef(errors.Match(tt.wantErrMatch, err), "want %q and got %q", tt.wantErrMatch.Code, err.Error()) } if tt.wantErrIs != nil { assert.ErrorIs(err, tt.wantErrIs) } if tt.wantErrContains != "" { assert.Contains(err.Error(), tt.wantErrContains) } return } require.NoError(err) assert.Equal(tt.want, event.TestGetEventerConfig(t, tt.s.Eventer)) }) } } func TestServer_AddEventerToContext(t *testing.T) { testLock := &sync.Mutex{} testLogger := hclog.New(&hclog.LoggerOptions{ Mutex: testLock, }) testEventer, err := event.NewEventer(testLogger, testLock, "TestServer_AddEventerToContext", event.EventerConfig{}) require.NoError(t, err) tests := []struct { name string s Server ctx context.Context wantErrMatch *errors.Template wantErrContains string }{ { name: "missing-eventer", s: Server{}, ctx: context.Background(), wantErrMatch: errors.T(errors.InvalidParameter), wantErrContains: "missing server eventer", }, { name: "valid", s: Server{Eventer: testEventer}, ctx: context.Background(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) gotCtx, err := tt.s.AddEventerToContext(tt.ctx) if tt.wantErrMatch != nil { require.Error(err) assert.Nil(gotCtx) assert.Truef(errors.Match(tt.wantErrMatch, err), "want %q and got %q", tt.wantErrMatch.Code, err.Error()) if tt.wantErrContains != "" { assert.Contains(err.Error(), tt.wantErrContains) } return } require.NoError(err) e, ok := event.EventerFromContext(gotCtx) require.Truef(ok, "unable to get eventer from context") assert.NotNil(e) assert.Equal(tt.s.Eventer, e) }) } } func TestSetupWorkerPublicAddress(t *testing.T) { tests := []struct { name string inputConfig *config.Config inputFlagValue string stateFn func(t *testing.T) expErr bool expErrStr string expPublicAddress string }{ { name: "nil worker", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: nil, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: ":9202", }, { name: "setting public address directly with ipv4", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: "127.0.0.1", }, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "setting public address directly with ipv4:port", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: "127.0.0.1:8080", }, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:8080", }, { name: "setting public address directly with ipv6", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: "[2001:4860:4860:0:0:0:0:8888]", }, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: "[2001:4860:4860:0:0:0:0:8888]:9202", }, { name: "setting public address directly with ipv6:port", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: "[2001:4860:4860:0:0:0:0:8888]:8080", }, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: "[2001:4860:4860:0:0:0:0:8888]:8080", }, { name: "setting public address directly with abbreviated ipv6", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: "[2001:4860:4860::8888]", }, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: "[2001:4860:4860::8888]:9202", }, { name: "setting public address directly with abbreviated ipv6:port", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: "[2001:4860:4860::8888]:8080", }, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: "[2001:4860:4860::8888]:8080", }, { name: "setting public address to env var", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: "env://TEST_ENV_VAR_FOR_WORKER_ADDR", }, }, inputFlagValue: "", stateFn: func(t *testing.T) { t.Setenv("TEST_ENV_VAR_FOR_WORKER_ADDR", "127.0.0.1:8080") }, expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:8080", }, { name: "setting public address to env var that points to template", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: "env://TEST_ENV_VAR_FOR_WORKER_ADDR", }, }, inputFlagValue: "", stateFn: func(t *testing.T) { t.Setenv("TEST_ENV_VAR_FOR_WORKER_ADDR", `{{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }}`) }, expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "setting public address to ip template", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: `{{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }}`, }, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "setting public address to multiline ip template", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{ PublicAddr: `{{ with $local := GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" -}} {{- $local | join "address" " " -}} {{- end }}`, }, }, inputFlagValue: "", expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "using flag value with ip only", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: "127.0.0.1", expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "using flag value with ip:port", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: "127.0.0.1:8080", expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:8080", }, { name: "using flag value to point to env var with ip only", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: "env://TEST_ENV_VAR_FOR_WORKER_ADDR", stateFn: func(t *testing.T) { t.Setenv("TEST_ENV_VAR_FOR_WORKER_ADDR", "127.0.0.1") }, expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "using flag value to point to env var with ip:port", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: "env://TEST_ENV_VAR_FOR_WORKER_ADDR", stateFn: func(t *testing.T) { t.Setenv("TEST_ENV_VAR_FOR_WORKER_ADDR", "127.0.0.1:8080") }, expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:8080", }, { name: "using flag value with ip template", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: `{{ GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" | join "address" " " }}`, expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "using flag value with multiline ip template", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: `{{ with $local := GetAllInterfaces | include "flags" "loopback" | include "type" "IPV4" -}} {{- $local | join "address" " " -}} {{- end }}`, expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "read address from listeners ip only", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{ {Purpose: []string{"proxy"}, Address: "127.0.0.1"}, }, }, Worker: &config.Worker{}, }, expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:9202", }, { name: "read address from listeners ip:port", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{ {Purpose: []string{"proxy"}, Address: "127.0.0.1:8080"}, }, }, Worker: &config.Worker{}, }, expErr: false, expErrStr: "", expPublicAddress: "127.0.0.1:8080", }, { name: "read address from listeners is ignored on different purpose", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{ {Purpose: []string{"somethingelse"}, Address: "127.0.0.1:8080"}, }, }, Worker: &config.Worker{}, }, expErr: false, expErrStr: "", expPublicAddress: ":9202", }, { name: "using flag value to point to nonexistent file", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: "file://this_doesnt_exist_for_sure", expErr: true, expErrStr: "Error parsing public addr: error reading file at file://this_doesnt_exist_for_sure: open this_doesnt_exist_for_sure: no such file or directory", expPublicAddress: "", }, { name: "using flag value to provoke error in SplitHostPort", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: "abc::123", expErr: true, expErrStr: "Error splitting public adddress host/port: address abc::123: too many colons in address", expPublicAddress: "", }, { name: "bad ip template", inputConfig: &config.Config{ SharedConfig: &configutil.SharedConfig{ Listeners: []*listenerutil.ListenerConfig{}, }, Worker: &config.Worker{}, }, inputFlagValue: "{{ somethingthatdoesntexist }}", expErr: true, expErrStr: "Error parsing IP template on worker public addr: unable to parse address template \"{{ somethingthatdoesntexist }}\": unable to parse template \"{{ somethingthatdoesntexist }}\": template: sockaddr.Parse:1: function \"somethingthatdoesntexist\" not defined", expPublicAddress: "", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.stateFn != nil { tt.stateFn(t) } s := Server{} err := s.SetupWorkerPublicAddress(tt.inputConfig, tt.inputFlagValue) if tt.expErr { require.EqualError(t, err, tt.expErrStr) return } require.NoError(t, err) require.NotNil(t, tt.inputConfig.Worker) require.Equal(t, tt.expPublicAddress, tt.inputConfig.Worker.PublicAddr) }) } }