From 93b7d5bcf1fc6c0b7d25628bb643e816fe510b84 Mon Sep 17 00:00:00 2001 From: Kenneth Jenkins <51246568+kenjenkins@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:33:21 -0700 Subject: [PATCH] sync: do not clobber bootstrap settings In the all-in-one mode, certain bootstrap settings are passed to Pomerium Core using an in-memory ConfigSource. Currently the APIReconciler is unaware of these bootstrap settings, and when it syncs a settings entity via the API it will fill in the Core defaults. This is a problem for the 'address' setting, which is set to :8443 for all-in-one mode, and is different from the Core default of :443. If the bootstrap setting is overridden, Pomerium Core won't be listening on the correct port and the pomerium-proxy Service in the kustomize configuration won't work correctly. Instead, let's add a parameter to NewAPIReconciler() to make these bootstrap settings available to the API sync logic. The sync logic will apply all of the Pomerium CRD settings on top of these base settings. The standalone mode can continue to use the Core defaults as before. --- cmd/all_in_one.go | 2 +- cmd/controller.go | 4 +++- pomerium/sync_api.go | 14 +++++++------ pomerium/sync_api_test.go | 44 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/cmd/all_in_one.go b/cmd/all_in_one.go index 547e88fb..2dfc873d 100644 --- a/cmd/all_in_one.go +++ b/cmd/all_in_one.go @@ -339,7 +339,7 @@ func (s *allCmdParam) buildController(ctx context.Context, cfg *config.Config) ( client := databroker.NewDataBrokerServiceClient(conn) var reconciler pomerium.Reconciler if s.syncAPIURL != "" { - reconciler = pomerium.NewAPIReconciler(s.syncAPIURL, s.syncAPIToken) + reconciler = pomerium.NewAPIReconciler(s.syncAPIURL, s.syncAPIToken, s.cfg.Options) } else { reconciler = pomerium.NewDataBrokerReconciler(client, s.dumpConfigDiff) } diff --git a/cmd/controller.go b/cmd/controller.go index 041ff352..892e5508 100644 --- a/cmd/controller.go +++ b/cmd/controller.go @@ -14,6 +14,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/config" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + pomerium_config "github.com/pomerium/pomerium/config" "github.com/pomerium/pomerium/pkg/grpc/databroker" "github.com/pomerium/pomerium/pkg/grpcutil" @@ -167,7 +168,8 @@ func (s *controllerCmd) buildController(ctx context.Context) (*controllers.Contr } if s.SyncAPIURL != "" { - c.Reconciler = pomerium.NewAPIReconciler(s.SyncAPIURL, s.SyncAPIToken) + c.Reconciler = pomerium.NewAPIReconciler( + s.SyncAPIURL, s.SyncAPIToken, pomerium_config.NewDefaultOptions()) c.MgrOpts.LeaderElection = true c.MgrOpts.LeaderElectionID = s.leaderElectionID c.MgrOpts.LeaderElectionNamespace = s.leaderElectionNamespace diff --git a/pomerium/sync_api.go b/pomerium/sync_api.go index 2e567729..8a7c9982 100644 --- a/pomerium/sync_api.go +++ b/pomerium/sync_api.go @@ -35,14 +35,15 @@ import ( // NewAPIReconciler initializes a reconciler that syncs using the unified API, // for the given API url and API token. func NewAPIReconciler( - url, token string, + url, token string, baseOptions *config.Options, ) Reconciler { client := sdk.NewClient( sdk.WithURL(url), sdk.WithAPIToken(token)) return &APIReconciler{ - apiClient: client, - secretsMap: model.NewTLSSecretsMap(), + apiClient: client, + baseOptions: baseOptions, + secretsMap: model.NewTLSSecretsMap(), } } @@ -57,7 +58,8 @@ type APIReconciler struct { apiClient sdk.Client k8sClient client.Client - secretsMap *model.TLSSecretsMap + baseOptions *config.Options + secretsMap *model.TLSSecretsMap } const ( @@ -341,8 +343,8 @@ func (r *APIReconciler) SetConfig(ctx context.Context, cfg *model.Config) (chang pbConfig.Settings.Certificates = nil pbConfig.Settings.CertificateAuthority = nil - // Apply all Core defaults. - mergedSettings := config.NewDefaultOptions() + // Layer settings on top of the baseOptions. + mergedSettings := *r.baseOptions mergedSettings.ApplySettings(ctx, nil, pbConfig.Settings) settings, err := convertProto[*configpb.Settings](mergedSettings.ToProto().GetSettings()) diff --git a/pomerium/sync_api_test.go b/pomerium/sync_api_test.go index 2389d3ab..65c55878 100644 --- a/pomerium/sync_api_test.go +++ b/pomerium/sync_api_test.go @@ -58,8 +58,9 @@ func setupReconciler(t *testing.T) ( k8sClient := controllers_mock.NewMockClient(ctrl) r := &APIReconciler{ - apiClient: apiClient, - secretsMap: model.NewTLSSecretsMap(), + apiClient: apiClient, + baseOptions: config.NewDefaultOptions(), + secretsMap: model.NewTLSSecretsMap(), } r.SetK8sClient(k8sClient) return apiClient, k8sClient, r @@ -1099,6 +1100,45 @@ func TestAPIReconciler_SetConfig(t *testing.T) { assert.False(t, changes) assert.NoError(t, err) }) + + t.Run("base options", func(t *testing.T) { + apiClient, _, r := setupReconciler(t) + r.baseOptions = &config.Options{ + Addr: ":8443", + } + ctx := t.Context() + + apiClient.EXPECT().GetSettings(ctx, RequestEq(&configpb.GetSettingsRequest{})). + Return(&connect.Response[configpb.GetSettingsResponse]{ + Msg: &configpb.GetSettingsResponse{ + Settings: &configpb.Settings{ + Id: new("settings-id-123"), + }, + }, + }, nil) + + // The address from the base options should be preserved. + expectedSettings := &configpb.Settings{ + Id: new("settings-id-123"), + Address: new(":8443"), + AuthenticateServiceUrl: new("https://authenticate.localhost.pomerium.io"), + IdpClientId: new("CLIENT_ID"), + IdpClientSecret: new("CLIENT_SECRET"), + IdpProvider: new("oidc"), + IdpProviderUrl: new("https://idp.example.com"), + PassIdentityHeaders: new(true), + } + + apiClient.EXPECT().UpdateSettings(ctx, RequestEq(&configpb.UpdateSettingsRequest{ + Settings: expectedSettings, + })).Return(&connect.Response[configpb.UpdateSettingsResponse]{ + Msg: &configpb.UpdateSettingsResponse{}, + }, nil) + + changes, err := r.SetConfig(ctx, cfg) + assert.True(t, changes) + assert.NoError(t, err) + }) } func TestAPIReconciler_syncOneSecret(t *testing.T) {