From c7fd5a4752eb2dea85b36713378318b297b7a9ed Mon Sep 17 00:00:00 2001 From: Denis Mishin Date: Tue, 8 Aug 2023 14:47:32 -0400 Subject: [PATCH] restructure --- internal/zero/bootstrap/bootstrap.go | 68 ++++++++++------------------ internal/zero/bootstrap/cloud.go | 17 ++----- internal/zero/bootstrap/file.go | 42 +++++------------ internal/zero/bootstrap/file_test.go | 36 ++++++++------- internal/zero/bootstrap/new.go | 17 ++----- internal/zero/bootstrap/new_test.go | 3 +- internal/zero/bootstrap/source.go | 25 +++++++--- 7 files changed, 86 insertions(+), 122 deletions(-) diff --git a/internal/zero/bootstrap/bootstrap.go b/internal/zero/bootstrap/bootstrap.go index d36531e01..1dbfb0efa 100644 --- a/internal/zero/bootstrap/bootstrap.go +++ b/internal/zero/bootstrap/bootstrap.go @@ -1,3 +1,4 @@ +// Package bootstrap fetches the very initial configuration for Pomerium Core to start. package bootstrap /* @@ -12,14 +13,11 @@ package bootstrap import ( "context" - "crypto/cipher" "fmt" "time" "golang.org/x/sync/errgroup" - - "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/internal/log" cluster_api "github.com/pomerium/zero-sdk/cluster" connect_mux "github.com/pomerium/zero-sdk/connect-mux" @@ -35,7 +33,16 @@ const ( ) // Run initializes the bootstrap config source -func (svc *BootstrapConfigSource) Run(ctx context.Context) error { +func (svc *Source) Run( + ctx context.Context, + clusterAPI cluster_api.ClientWithResponsesInterface, + mux *connect_mux.Mux, + fileCachePath string, +) error { + svc.clusterAPI = clusterAPI + svc.connectMux = mux + svc.fileCachePath = fileCachePath + svc.tryLoadInitial(ctx) eg, ctx := errgroup.WithContext(ctx) @@ -45,7 +52,7 @@ func (svc *BootstrapConfigSource) Run(ctx context.Context) error { return eg.Wait() } -func (svc *BootstrapConfigSource) watchUpdates(ctx context.Context) error { +func (svc *Source) watchUpdates(ctx context.Context) error { return svc.connectMux.Watch(ctx, connect_mux.WithOnConnected(func(_ context.Context) { svc.triggerUpdate(DefaultCheckForUpdateIntervalWhenConnected) @@ -59,7 +66,7 @@ func (svc *BootstrapConfigSource) watchUpdates(ctx context.Context) error { ) } -func (svc *BootstrapConfigSource) updateLoop(ctx context.Context) error { +func (svc *Source) updateLoop(ctx context.Context) error { ticker := time.NewTicker(svc.updateInterval.Load()) defer ticker.Stop() @@ -72,7 +79,7 @@ func (svc *BootstrapConfigSource) updateLoop(ctx context.Context) error { } ticker.Reset(svc.updateInterval.Load()) - err := svc.update(ctx) + err := svc.tryUpdateAndSave(ctx) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("failed to update bootstrap config") } @@ -81,7 +88,7 @@ func (svc *BootstrapConfigSource) updateLoop(ctx context.Context) error { // triggerUpdate triggers an update of the bootstrap config // and sets the interval for the next update -func (svc *BootstrapConfigSource) triggerUpdate(newUpdateInterval time.Duration) { +func (svc *Source) triggerUpdate(newUpdateInterval time.Duration) { svc.updateInterval.Store(newUpdateInterval) select { @@ -90,62 +97,37 @@ func (svc *BootstrapConfigSource) triggerUpdate(newUpdateInterval time.Duration) } } -func (svc *BootstrapConfigSource) update(ctx context.Context) error { - current := svc.GetConfig() - cfg := current.Clone() - - err := tryUpdateAndSave(ctx, cfg.Options, svc.clusterAPI, svc.fileCachePath, svc.fileCipher) - if err != nil { - return err - } - - _ = svc.SetConfig(ctx, cfg) - - return nil -} - -func tryUpdateAndSave( - ctx context.Context, - dst *config.Options, - clusterAPI cluster_api.ClientWithResponsesInterface, - fileCachePath string, - fileCipher cipher.AEAD, -) error { - err := LoadBootstrapConfigFromAPI(ctx, dst, clusterAPI) +func (svc *Source) tryUpdateAndSave(ctx context.Context) error { + cfg, err := LoadBootstrapConfigFromAPI(ctx, svc.clusterAPI) if err != nil { return fmt.Errorf("load bootstrap config from API: %w", err) } - err = SaveBootstrapConfigToFile(dst, fileCachePath, fileCipher) + err = SaveBootstrapConfigToFile(cfg, svc.fileCachePath, svc.fileCipher) if err != nil { log.Ctx(ctx).Error().Err(err). Msg("failed to save bootstrap config to file, note it may prevent Pomerium from starting up in case of connectivity issues") } + svc.UpdateBootstrap(ctx, *cfg) return nil } -func (src *BootstrapConfigSource) tryLoadInitial(ctx context.Context) { - dst := src.GetConfig() - - err := tryUpdateAndSave(ctx, dst.Options, src.clusterAPI, src.fileCachePath, src.fileCipher) +func (svc *Source) tryLoadInitial(ctx context.Context) { + err := svc.tryUpdateAndSave(ctx) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("failed to load bootstrap config") - src.tryLoadFromFile(ctx) + svc.tryLoadFromFile(ctx) return } - - src.SetConfig(ctx, dst) } -func (src *BootstrapConfigSource) tryLoadFromFile(ctx context.Context) { - dst := src.GetConfig() - - err := LoadBootstrapConfigFromFile(dst.Options, src.fileCachePath, src.fileCipher) +func (svc *Source) tryLoadFromFile(ctx context.Context) { + cfg, err := LoadBootstrapConfigFromFile(svc.fileCachePath, svc.fileCipher) if err != nil { log.Ctx(ctx).Error().Err(err).Msg("failed to load bootstrap config from file") return } - src.SetConfig(ctx, dst) + svc.UpdateBootstrap(ctx, *cfg) } diff --git a/internal/zero/bootstrap/cloud.go b/internal/zero/bootstrap/cloud.go index 0f0302f9f..fa2e1c0c7 100644 --- a/internal/zero/bootstrap/cloud.go +++ b/internal/zero/bootstrap/cloud.go @@ -4,30 +4,21 @@ import ( "context" "fmt" - "github.com/pomerium/pomerium/config" cluster_api "github.com/pomerium/zero-sdk/cluster" ) // LoadBootstrapConfigFromAPI loads the bootstrap configuration from the cluster API. func LoadBootstrapConfigFromAPI( ctx context.Context, - dst *config.Options, client cluster_api.ClientWithResponsesInterface, -) error { +) (*cluster_api.BootstrapConfig, error) { resp, err := client.GetClusterBootstrapConfigWithResponse(ctx) if err != nil { - return fmt.Errorf("get: %w", err) + return nil, fmt.Errorf("get: %w", err) } if resp.JSON200 == nil { - return fmt.Errorf("unexpected response: %d/%v", resp.StatusCode(), resp.Status()) + return nil, fmt.Errorf("unexpected response: %d/%v", resp.StatusCode(), resp.Status()) } - v := cluster_api.BootstrapConfig(*resp.JSON200) - - if v.DatabrokerStorageConnection != nil { - dst.DataBrokerStorageType = "postgres" - dst.DataBrokerStorageConnectionString = *v.DatabrokerStorageConnection - } - - return nil + return resp.JSON200, nil } diff --git a/internal/zero/bootstrap/file.go b/internal/zero/bootstrap/file.go index 9cadcde9f..8b248a2ae 100644 --- a/internal/zero/bootstrap/file.go +++ b/internal/zero/bootstrap/file.go @@ -14,33 +14,33 @@ import ( "fmt" "os" - "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/pkg/cryptutil" + cluster_api "github.com/pomerium/zero-sdk/cluster" ) -func LoadBootstrapConfigFromFile(dst *config.Options, fp string, cipher cipher.AEAD) error { +// LoadBootstrapConfigFromFile loads the bootstrap configuration from a file. +func LoadBootstrapConfigFromFile(fp string, cipher cipher.AEAD) (*cluster_api.BootstrapConfig, error) { ciphertext, err := os.ReadFile(fp) if err != nil { - return fmt.Errorf("read bootstrap config: %w", err) + return nil, fmt.Errorf("read bootstrap config: %w", err) } plaintext, err := cryptutil.Decrypt(cipher, ciphertext, nil) if err != nil { - return fmt.Errorf("decrypt bootstrap config: %w", err) + return nil, fmt.Errorf("decrypt bootstrap config: %w", err) } - fc := fileConfig{} - err = json.Unmarshal(plaintext, &fc) + var dst cluster_api.BootstrapConfig + err = json.Unmarshal(plaintext, &dst) if err != nil { - return fmt.Errorf("unmarshal bootstrap config: %w", err) + return nil, fmt.Errorf("unmarshal bootstrap config: %w", err) } - applyFileConfig(dst, fc) - - return nil + return &dst, nil } -func SaveBootstrapConfigToFile(src *config.Options, fp string, cipher cipher.AEAD) error { - plaintext, err := json.Marshal(getFileConfig(src)) +// SaveBootstrapConfigToFile saves the bootstrap configuration to a file. +func SaveBootstrapConfigToFile(src *cluster_api.BootstrapConfig, fp string, cipher cipher.AEAD) error { + plaintext, err := json.Marshal(src) if err != nil { return fmt.Errorf("marshal file config: %w", err) } @@ -52,21 +52,3 @@ func SaveBootstrapConfigToFile(src *config.Options, fp string, cipher cipher.AEA } return nil } - -type fileConfig struct { - PostgresDSN *string `json:"postgres_dsn"` -} - -func getFileConfig(src *config.Options) fileConfig { - fc := fileConfig{} - if src.DataBrokerStorageConnectionString != "" { - fc.PostgresDSN = &src.DataBrokerStorageConnectionString - } - return fc -} - -func applyFileConfig(dst *config.Options, fc fileConfig) { - if fc.PostgresDSN != nil { - dst.DataBrokerStorageConnectionString = *fc.PostgresDSN - } -} diff --git a/internal/zero/bootstrap/file_test.go b/internal/zero/bootstrap/file_test.go index 55e57d686..93aa410e8 100644 --- a/internal/zero/bootstrap/file_test.go +++ b/internal/zero/bootstrap/file_test.go @@ -1,29 +1,33 @@ package bootstrap_test import ( + "os" "testing" - "github.com/pomerium/pomerium/internal/zero/bootstrap" "github.com/stretchr/testify/require" + + "github.com/pomerium/pomerium/internal/zero/bootstrap" + "github.com/pomerium/pomerium/pkg/cryptutil" + cluster_api "github.com/pomerium/zero-sdk/cluster" ) func TestFile(t *testing.T) { - secret := []byte("secret") - - src, err := bootstrap.New(secret) - require.NoError(t, err) - cfg := src.GetConfig() - require.NotNil(t, cfg) - - // test that the config is valid - require.NoError(t, cfg.Options.Validate()) - - // test that the config is deterministic - src2, err := bootstrap.New(secret) + cipher, err := cryptutil.NewAEADCipher(cryptutil.NewKey()) require.NoError(t, err) - cfg2 := src2.GetConfig() - require.NotNil(t, cfg2) + txt := "test" + src := cluster_api.BootstrapConfig{ + DatabrokerStorageConnection: &txt, + } - require.Equal(t, cfg.Options, cfg2.Options) + fd, err := os.CreateTemp(t.TempDir(), "test.data") + require.NoError(t, err) + require.NoError(t, fd.Close()) + + require.NoError(t, bootstrap.SaveBootstrapConfigToFile(&src, fd.Name(), cipher)) + + dst, err := bootstrap.LoadBootstrapConfigFromFile(fd.Name(), cipher) + require.NoError(t, err) + + require.Equal(t, src, *dst) } diff --git a/internal/zero/bootstrap/new.go b/internal/zero/bootstrap/new.go index a2ec9b21a..7909170ea 100644 --- a/internal/zero/bootstrap/new.go +++ b/internal/zero/bootstrap/new.go @@ -20,8 +20,8 @@ import ( connect_mux "github.com/pomerium/zero-sdk/connect-mux" ) -// BootstrapConfigSource is a base config layer for Pomerium -type BootstrapConfigSource struct { +// Source is a base config layer for Pomerium +type Source struct { source clusterAPI cluster_api.ClientWithResponsesInterface @@ -34,12 +34,8 @@ type BootstrapConfigSource struct { updateInterval atomicutil.Value[time.Duration] } -func New( - clusterAPI cluster_api.ClientWithResponsesInterface, - mux *connect_mux.Mux, - secret []byte, - fileCachePath string, -) (*BootstrapConfigSource, error) { +// New creates a new bootstrap config source +func New(secret []byte) (*Source, error) { cfg := new(config.Config) err := setConfigDefaults(cfg) @@ -59,13 +55,10 @@ func New( return nil, fmt.Errorf("init secrets: %w", err) } - svc := &BootstrapConfigSource{ + svc := &Source{ source: source{ready: make(chan struct{})}, fileCipher: cipher, checkForUpdate: make(chan struct{}, 1), - fileCachePath: fileCachePath, - clusterAPI: clusterAPI, - connectMux: mux, } svc.cfg.Store(cfg) svc.updateInterval.Store(DefaultCheckForUpdateInterval) diff --git a/internal/zero/bootstrap/new_test.go b/internal/zero/bootstrap/new_test.go index 5392e7e30..8a2a75517 100644 --- a/internal/zero/bootstrap/new_test.go +++ b/internal/zero/bootstrap/new_test.go @@ -3,8 +3,9 @@ package bootstrap_test import ( "testing" - "github.com/pomerium/pomerium/internal/zero/bootstrap" "github.com/stretchr/testify/require" + + "github.com/pomerium/pomerium/internal/zero/bootstrap" ) func TestConfigDeterministic(t *testing.T) { diff --git a/internal/zero/bootstrap/source.go b/internal/zero/bootstrap/source.go index 135935cca..1cd3f69a2 100644 --- a/internal/zero/bootstrap/source.go +++ b/internal/zero/bootstrap/source.go @@ -9,6 +9,7 @@ import ( "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/internal/atomicutil" + cluster_api "github.com/pomerium/zero-sdk/cluster" ) var ( @@ -43,7 +44,7 @@ func (src *source) WaitReady(ctx context.Context) error { // GetConfig implements config.Source func (src *source) GetConfig() *config.Config { - return src.cfg.Load().Clone() + return src.cfg.Load() } // OnConfigChange implements config.Source @@ -53,16 +54,19 @@ func (src *source) OnConfigChange(_ context.Context, l config.ChangeListener) { src.listenerLock.Unlock() } -// setConfig updates the underlying configuration -// its only called by the updater -func (src *source) SetConfig(ctx context.Context, cfg *config.Config) bool { +// UpdateBootstrap updates the underlying configuration options +func (src *source) UpdateBootstrap(ctx context.Context, cfg cluster_api.BootstrapConfig) bool { current := src.cfg.Load() - if cmp.Equal(cfg.Options, current.Options, cmpOpts...) { + incoming := current.Clone() + applyBootstrapConfig(incoming.Options, &cfg) + + if cmp.Equal(incoming.Options, current.Options, cmpOpts...) { return false } - src.cfg.Store(cfg) - src.notifyListeners(ctx, cfg) + src.cfg.Store(incoming) + + src.notifyListeners(ctx, incoming) return true } @@ -80,3 +84,10 @@ func (src *source) notifyListeners(ctx context.Context, cfg *config.Config) { l(ctx, cfg) } } + +func applyBootstrapConfig(dst *config.Options, src *cluster_api.BootstrapConfig) { + if src.DatabrokerStorageConnection != nil { + dst.DataBrokerStorageType = "postgres" + dst.DataBrokerStorageConnectionString = *src.DatabrokerStorageConnection + } +}