Skip to content

Commit f4240bd

Browse files
committed
feat: support separate datastore URIs for migrations
Add MigrationDatastoreURI credential ref to ClusterCredentials, enabling least-privilege credentials for the application while using elevated credentials only for migration jobs. When MigrationDatastoreURI is configured, migration jobs use that URI instead of the application's DatastoreURI. When not set, migrations continue using the same DatastoreURI as the application (existing behavior). Fixes #338
1 parent ccf8db6 commit f4240bd

3 files changed

Lines changed: 55 additions & 2 deletions

File tree

pkg/apis/authzed/v1alpha1/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ type ClusterCredentials struct {
124124
// MigrationSecrets configures the source for the migration secrets.
125125
// +optional
126126
MigrationSecrets *CredentialRef `json:"migrationSecrets,omitempty"`
127+
128+
// MigrationDatastoreURI configures a separate connection string for migrations.
129+
// When set, migration jobs use this URI instead of DatastoreURI, enabling
130+
// least-privilege credentials for the application while using elevated
131+
// credentials only for migrations.
132+
// +optional
133+
MigrationDatastoreURI *CredentialRef `json:"migrationDatastoreURI,omitempty"`
127134
}
128135

129136
// CredentialRef describes where to read a single credential value.

pkg/config/config.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ type SpiceConfig struct {
176176
DatastoreURIRef ResolvedCredentialRef
177177
PresharedKeyRef ResolvedCredentialRef
178178
MigrationSecretsRef ResolvedCredentialRef
179+
MigrationDatastoreURIRef ResolvedCredentialRef
179180
ExtraPodLabels map[string]string
180181
ExtraPodAnnotations map[string]string
181182
ExtraServiceAccountAnnotations map[string]string
@@ -367,6 +368,24 @@ func NewConfig(cluster *v1alpha1.SpiceDBCluster, globalConfig *OperatorConfig, s
367368
Key: key,
368369
}
369370
}
371+
372+
// Resolve MigrationDatastoreURI credential.
373+
// If not specified, migrations use the same DatastoreURI.
374+
if credentials.MigrationDatastoreURI == nil {
375+
// Default: use the same datastore URI for migrations
376+
spiceConfig.MigrationDatastoreURIRef = spiceConfig.DatastoreURIRef
377+
} else if credentials.MigrationDatastoreURI.Skip {
378+
spiceConfig.MigrationDatastoreURIRef = ResolvedCredentialRef{Skip: true}
379+
} else {
380+
key := credentials.MigrationDatastoreURI.Key
381+
if key == "" {
382+
key = "migration_datastore_uri"
383+
}
384+
spiceConfig.MigrationDatastoreURIRef = ResolvedCredentialRef{
385+
SecretName: credentials.MigrationDatastoreURI.SecretName,
386+
Key: key,
387+
}
388+
}
370389
}
371390

372391
if len(migrationConfig.SpannerCredsSecretRef) > 0 {
@@ -752,9 +771,14 @@ func (c *Config) unpatchedMigrationJob(migrationHash string) *applybatchv1.JobAp
752771
envVars := []*applycorev1.EnvVarApplyConfiguration{
753772
applycorev1.EnvVar().WithName(envPrefix + "_LOG_LEVEL").WithValue(c.MigrationLogLevel),
754773
}
755-
if !c.DatastoreURIRef.Skip {
774+
// Use MigrationDatastoreURIRef for migrations if set, otherwise fall back to DatastoreURIRef.
775+
migrationURIRef := c.MigrationDatastoreURIRef
776+
if migrationURIRef.SecretName == "" && !migrationURIRef.Skip {
777+
migrationURIRef = c.DatastoreURIRef
778+
}
779+
if !migrationURIRef.Skip {
756780
envVars = append(envVars,
757-
applycorev1.EnvVar().WithName(envPrefix+"_DATASTORE_CONN_URI").WithValueFrom(applycorev1.EnvVarSource().WithSecretKeyRef(applycorev1.SecretKeySelector().WithName(c.DatastoreURIRef.SecretName).WithKey(c.DatastoreURIRef.Key))),
781+
applycorev1.EnvVar().WithName(envPrefix+"_DATASTORE_CONN_URI").WithValueFrom(applycorev1.EnvVarSource().WithSecretKeyRef(applycorev1.SecretKeySelector().WithName(migrationURIRef.SecretName).WithKey(migrationURIRef.Key))),
758782
)
759783
}
760784
if !c.MigrationSecretsRef.Skip {

pkg/config/config_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ func TestNewConfig(t *testing.T) {
170170
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
171171
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
172172
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
173+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
173174
ProjectLabels: true,
174175
ProjectAnnotations: true,
175176
Passthrough: map[string]string{
@@ -257,6 +258,7 @@ func TestNewConfig(t *testing.T) {
257258
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
258259
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
259260
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
261+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
260262
ProjectLabels: true,
261263
ProjectAnnotations: true,
262264
Passthrough: map[string]string{
@@ -342,6 +344,7 @@ func TestNewConfig(t *testing.T) {
342344
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
343345
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
344346
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
347+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
345348
ProjectLabels: true,
346349
ProjectAnnotations: true,
347350
Passthrough: map[string]string{
@@ -408,6 +411,7 @@ func TestNewConfig(t *testing.T) {
408411
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
409412
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
410413
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
414+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
411415
ProjectLabels: true,
412416
ProjectAnnotations: true,
413417
Passthrough: map[string]string{
@@ -476,6 +480,7 @@ func TestNewConfig(t *testing.T) {
476480
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
477481
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
478482
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
483+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
479484
ProjectLabels: true,
480485
ProjectAnnotations: true,
481486
Passthrough: map[string]string{
@@ -563,6 +568,7 @@ func TestNewConfig(t *testing.T) {
563568
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
564569
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
565570
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
571+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
566572
ProjectLabels: true,
567573
ProjectAnnotations: true,
568574
Passthrough: map[string]string{
@@ -650,6 +656,7 @@ func TestNewConfig(t *testing.T) {
650656
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
651657
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
652658
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
659+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
653660
ProjectLabels: true,
654661
ProjectAnnotations: true,
655662
Passthrough: map[string]string{
@@ -741,6 +748,7 @@ func TestNewConfig(t *testing.T) {
741748
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
742749
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
743750
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
751+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
744752
ProjectLabels: true,
745753
ProjectAnnotations: true,
746754
Passthrough: map[string]string{
@@ -835,6 +843,7 @@ func TestNewConfig(t *testing.T) {
835843
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
836844
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
837845
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
846+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
838847
ProjectLabels: true,
839848
ProjectAnnotations: true,
840849
Passthrough: map[string]string{
@@ -924,6 +933,7 @@ func TestNewConfig(t *testing.T) {
924933
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
925934
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
926935
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
936+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
927937
ProjectLabels: true,
928938
ProjectAnnotations: true,
929939
Passthrough: map[string]string{
@@ -1013,6 +1023,7 @@ func TestNewConfig(t *testing.T) {
10131023
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
10141024
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
10151025
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1026+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
10161027
ProjectLabels: true,
10171028
ProjectAnnotations: true,
10181029
Passthrough: map[string]string{
@@ -1104,6 +1115,7 @@ func TestNewConfig(t *testing.T) {
11041115
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
11051116
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
11061117
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1118+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
11071119
ProjectLabels: true,
11081120
ProjectAnnotations: true,
11091121
Passthrough: map[string]string{
@@ -1198,6 +1210,7 @@ func TestNewConfig(t *testing.T) {
11981210
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
11991211
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
12001212
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1213+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
12011214
ProjectLabels: true,
12021215
ProjectAnnotations: true,
12031216
Passthrough: map[string]string{
@@ -1289,6 +1302,7 @@ func TestNewConfig(t *testing.T) {
12891302
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
12901303
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
12911304
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1305+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
12921306
ProjectLabels: true,
12931307
ProjectAnnotations: true,
12941308
Passthrough: map[string]string{
@@ -1382,6 +1396,7 @@ func TestNewConfig(t *testing.T) {
13821396
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
13831397
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
13841398
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1399+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
13851400
ProjectLabels: true,
13861401
ProjectAnnotations: true,
13871402
Passthrough: map[string]string{
@@ -1473,6 +1488,7 @@ func TestNewConfig(t *testing.T) {
14731488
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
14741489
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
14751490
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1491+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
14761492
ProjectLabels: true,
14771493
ProjectAnnotations: true,
14781494
Passthrough: map[string]string{
@@ -1563,6 +1579,7 @@ func TestNewConfig(t *testing.T) {
15631579
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
15641580
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
15651581
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1582+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
15661583
ProjectLabels: true,
15671584
ProjectAnnotations: true,
15681585
Passthrough: map[string]string{
@@ -1654,6 +1671,7 @@ func TestNewConfig(t *testing.T) {
16541671
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
16551672
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
16561673
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1674+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
16571675
ProjectLabels: true,
16581676
ProjectAnnotations: true,
16591677
Passthrough: map[string]string{
@@ -1750,6 +1768,7 @@ func TestNewConfig(t *testing.T) {
17501768
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
17511769
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
17521770
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1771+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
17531772
ProjectLabels: true,
17541773
ProjectAnnotations: true,
17551774
Passthrough: map[string]string{
@@ -1850,6 +1869,7 @@ func TestNewConfig(t *testing.T) {
18501869
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
18511870
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
18521871
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1872+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
18531873
ProjectLabels: true,
18541874
ProjectAnnotations: true,
18551875
Passthrough: map[string]string{
@@ -1940,6 +1960,7 @@ func TestNewConfig(t *testing.T) {
19401960
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
19411961
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
19421962
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
1963+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
19431964
ProjectLabels: true,
19441965
ProjectAnnotations: true,
19451966
Passthrough: map[string]string{ //nolint:gosec // this is a test
@@ -2031,6 +2052,7 @@ func TestNewConfig(t *testing.T) {
20312052
DatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
20322053
PresharedKeyRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "preshared_key"},
20332054
MigrationSecretsRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "migration_secrets"},
2055+
MigrationDatastoreURIRef: ResolvedCredentialRef{SecretName: "test-secret", Key: "datastore_uri"},
20342056
ProjectLabels: true,
20352057
ProjectAnnotations: true,
20362058
Passthrough: map[string]string{

0 commit comments

Comments
 (0)