mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-28 00:17:34 +02:00
restructure
This commit is contained in:
parent
40efa26257
commit
c7fd5a4752
7 changed files with 86 additions and 122 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue