diff --git a/config/config_source.go b/config/config_source.go index 0f549b01e..a5ca1a9ba 100644 --- a/config/config_source.go +++ b/config/config_source.go @@ -281,6 +281,7 @@ func getAllConfigFilePaths(cfg *Config) []string { cfg.Options.CertFile, cfg.Options.ClientSecretFile, cfg.Options.CookieSecretFile, + cfg.Options.DataBrokerStorageConnectionStringFile, cfg.Options.DataBrokerStorageCAFile, cfg.Options.DataBrokerStorageCertFile, cfg.Options.DataBrokerStorageCertKeyFile, diff --git a/config/options.go b/config/options.go index 7c975404d..e9f7fd2ed 100644 --- a/config/options.go +++ b/config/options.go @@ -249,11 +249,12 @@ type Options struct { // Supported type: memory, postgres DataBrokerStorageType string `mapstructure:"databroker_storage_type" yaml:"databroker_storage_type,omitempty"` // DataBrokerStorageConnectionString is the data source name for storage backend. - DataBrokerStorageConnectionString string `mapstructure:"databroker_storage_connection_string" yaml:"databroker_storage_connection_string,omitempty"` - DataBrokerStorageCertFile string `mapstructure:"databroker_storage_cert_file" yaml:"databroker_storage_cert_file,omitempty"` - DataBrokerStorageCertKeyFile string `mapstructure:"databroker_storage_key_file" yaml:"databroker_storage_key_file,omitempty"` - DataBrokerStorageCAFile string `mapstructure:"databroker_storage_ca_file" yaml:"databroker_storage_ca_file,omitempty"` - DataBrokerStorageCertSkipVerify bool `mapstructure:"databroker_storage_tls_skip_verify" yaml:"databroker_storage_tls_skip_verify,omitempty"` + DataBrokerStorageConnectionString string `mapstructure:"databroker_storage_connection_string" yaml:"databroker_storage_connection_string,omitempty"` + DataBrokerStorageConnectionStringFile string `mapstructure:"databroker_storage_connection_string_file" yaml:"databroker_storage_connection_string_file,omitempty"` + DataBrokerStorageCertFile string `mapstructure:"databroker_storage_cert_file" yaml:"databroker_storage_cert_file,omitempty"` + DataBrokerStorageCertKeyFile string `mapstructure:"databroker_storage_key_file" yaml:"databroker_storage_key_file,omitempty"` + DataBrokerStorageCAFile string `mapstructure:"databroker_storage_ca_file" yaml:"databroker_storage_ca_file,omitempty"` + DataBrokerStorageCertSkipVerify bool `mapstructure:"databroker_storage_tls_skip_verify" yaml:"databroker_storage_tls_skip_verify,omitempty"` // DownstreamMTLS holds all downstream mTLS settings. DownstreamMTLS DownstreamMTLSSettings `mapstructure:"downstream_mtls" yaml:"downstream_mtls,omitempty"` @@ -592,7 +593,7 @@ func (o *Options) Validate() error { switch o.DataBrokerStorageType { case StorageInMemoryName: case StoragePostgresName: - if o.DataBrokerStorageConnectionString == "" { + if o.DataBrokerStorageConnectionString == "" && o.DataBrokerStorageConnectionStringFile == "" { return errors.New("config: missing databroker storage backend dsn") } default: @@ -1084,6 +1085,17 @@ func (o *Options) GetDataBrokerCertificate() (*tls.Certificate, error) { return cryptutil.CertificateFromFile(o.DataBrokerStorageCertFile, o.DataBrokerStorageCertKeyFile) } +// GetDataBrokerStorageConnectionString gets the databroker storage connection string from either a file +// or the config option directly. If from a file spaces are trimmed off the ends. +func (o *Options) GetDataBrokerStorageConnectionString() (string, error) { + if o.DataBrokerStorageConnectionStringFile != "" { + bs, err := os.ReadFile(o.DataBrokerStorageConnectionStringFile) + return strings.TrimSpace(string(bs)), err + } + + return o.DataBrokerStorageConnectionString, nil +} + // GetCertificates gets all the certificates from the options. func (o *Options) GetCertificates() ([]tls.Certificate, error) { var certs []tls.Certificate diff --git a/config/options_test.go b/config/options_test.go index c1da27587..b5ae56c25 100644 --- a/config/options_test.go +++ b/config/options_test.go @@ -1300,6 +1300,66 @@ func TestOptions_RuntimeFlags(t *testing.T) { } } +func TestOptions_GetDataBrokerStorageConnectionString(t *testing.T) { + t.Parallel() + + t.Run("validate", func(t *testing.T) { + t.Parallel() + + o := NewDefaultOptions() + o.Services = "databroker" + o.DataBrokerStorageType = "postgres" + o.SharedKey = cryptutil.NewBase64Key() + + assert.ErrorContains(t, o.Validate(), "missing databroker storage backend dsn", + "should validate DSN") + + o.DataBrokerStorageConnectionString = "DSN" + assert.NoError(t, o.Validate(), + "should have no error when the dsn is set") + + o.DataBrokerStorageConnectionString = "" + o.DataBrokerStorageConnectionStringFile = "DSN_FILE" + assert.NoError(t, o.Validate(), + "should have no error when the dsn file is set") + }) + t.Run("literal", func(t *testing.T) { + t.Parallel() + + o := NewDefaultOptions() + o.DataBrokerStorageConnectionString = "DSN" + + dsn, err := o.GetDataBrokerStorageConnectionString() + assert.NoError(t, err) + assert.Equal(t, "DSN", dsn) + }) + t.Run("file", func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + fp := filepath.Join(dir, "DSN_FILE") + + o := NewDefaultOptions() + o.DataBrokerStorageConnectionStringFile = fp + o.DataBrokerStorageConnectionString = "IGNORED" + + dsn, err := o.GetDataBrokerStorageConnectionString() + assert.Error(t, err, + "should return an error when the file doesn't exist") + assert.Empty(t, dsn) + + os.WriteFile(fp, []byte(` + DSN + `), 0o644) + + dsn, err = o.GetDataBrokerStorageConnectionString() + assert.NoError(t, err, + "should not return an error when the file exists") + assert.Equal(t, "DSN", dsn, + "should return the trimmed contents of the file") + }) +} + func encodeCert(cert *tls.Certificate) []byte { return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert.Certificate[0]}) } diff --git a/databroker/cache.go b/databroker/cache.go index 79c40c7f2..b76131e62 100644 --- a/databroker/cache.go +++ b/databroker/cache.go @@ -91,7 +91,10 @@ func New(cfg *config.Config, eventsMgr *events.Manager) (*DataBroker, error) { return nil, err } - dataBrokerServer := newDataBrokerServer(cfg) + dataBrokerServer, err := newDataBrokerServer(cfg) + if err != nil { + return nil, err + } c := &DataBroker{ dataBrokerServer: dataBrokerServer, diff --git a/databroker/databroker.go b/databroker/databroker.go index 39c459425..6fa3a7f43 100644 --- a/databroker/databroker.go +++ b/databroker/databroker.go @@ -3,12 +3,14 @@ package databroker import ( "context" + "fmt" "google.golang.org/protobuf/types/known/emptypb" "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/internal/atomicutil" "github.com/pomerium/pomerium/internal/databroker" + "github.com/pomerium/pomerium/internal/log" databrokerpb "github.com/pomerium/pomerium/pkg/grpc/databroker" registrypb "github.com/pomerium/pomerium/pkg/grpc/registry" "github.com/pomerium/pomerium/pkg/grpcutil" @@ -21,31 +23,48 @@ type dataBrokerServer struct { } // newDataBrokerServer creates a new databroker service server. -func newDataBrokerServer(cfg *config.Config) *dataBrokerServer { +func newDataBrokerServer(cfg *config.Config) (*dataBrokerServer, error) { srv := &dataBrokerServer{ sharedKey: atomicutil.NewValue([]byte{}), } - srv.server = databroker.New(srv.getOptions(cfg)...) + + opts, err := srv.getOptions(cfg) + if err != nil { + return nil, err + } + + srv.server = databroker.New(opts...) srv.setKey(cfg) - return srv + return srv, nil } // OnConfigChange updates the underlying databroker server whenever configuration is changed. -func (srv *dataBrokerServer) OnConfigChange(_ context.Context, cfg *config.Config) { - srv.server.UpdateConfig(srv.getOptions(cfg)...) +func (srv *dataBrokerServer) OnConfigChange(ctx context.Context, cfg *config.Config) { + opts, err := srv.getOptions(cfg) + if err != nil { + log.Error(ctx).Err(err).Msg("databroker: error updating config changes") + return + } + + srv.server.UpdateConfig(opts...) srv.setKey(cfg) } -func (srv *dataBrokerServer) getOptions(cfg *config.Config) []databroker.ServerOption { +func (srv *dataBrokerServer) getOptions(cfg *config.Config) ([]databroker.ServerOption, error) { + dataBrokerStorageConnectionString, err := cfg.Options.GetDataBrokerStorageConnectionString() + if err != nil { + return nil, fmt.Errorf("error loading databroker storage connection string: %w", err) + } + cert, _ := cfg.Options.GetDataBrokerCertificate() return []databroker.ServerOption{ databroker.WithGetSharedKey(cfg.Options.GetSharedKey), databroker.WithStorageType(cfg.Options.DataBrokerStorageType), - databroker.WithStorageConnectionString(cfg.Options.DataBrokerStorageConnectionString), + databroker.WithStorageConnectionString(dataBrokerStorageConnectionString), databroker.WithStorageCAFile(cfg.Options.DataBrokerStorageCAFile), databroker.WithStorageCertificate(cert), databroker.WithStorageCertSkipVerify(cfg.Options.DataBrokerStorageCertSkipVerify), - } + }, nil } func (srv *dataBrokerServer) setKey(cfg *config.Config) {