Skip to content

Commit 4b994b0

Browse files
committed
refactor(operator): use Secret for database passwords
1 parent 081a434 commit 4b994b0

File tree

8 files changed

+112
-25
lines changed

8 files changed

+112
-25
lines changed

operator/controller/src/main/deploy/rbac/cluster/cluster-role.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,12 @@ rules:
5757
- poddisruptionbudgets
5858
verbs:
5959
- '*'
60+
61+
- apiGroups:
62+
- ''
63+
resources:
64+
- secrets
65+
verbs:
66+
- list
67+
- get
68+
- create

operator/controller/src/main/java/io/apicurio/registry/operator/ApicurioRegistry3Reconciler.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,49 @@
11
package io.apicurio.registry.operator;
22

33
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
4-
import io.apicurio.registry.operator.resource.app.*;
5-
import io.apicurio.registry.operator.resource.ui.*;
4+
import io.apicurio.registry.operator.resource.app.AppDeploymentResource;
5+
import io.apicurio.registry.operator.resource.app.AppIngressResource;
6+
import io.apicurio.registry.operator.resource.app.AppNetworkPolicyResource;
7+
import io.apicurio.registry.operator.resource.app.AppPodDisruptionBudgetResource;
8+
import io.apicurio.registry.operator.resource.app.AppServiceResource;
9+
import io.apicurio.registry.operator.resource.ui.UIDeploymentResource;
10+
import io.apicurio.registry.operator.resource.ui.UIIngressResource;
11+
import io.apicurio.registry.operator.resource.ui.UINetworkPolicyResource;
12+
import io.apicurio.registry.operator.resource.ui.UIPodDisruptionBudgetResource;
13+
import io.apicurio.registry.operator.resource.ui.UIServiceResource;
614
import io.apicurio.registry.operator.status.OperatorErrorConditionManager;
715
import io.apicurio.registry.operator.status.StatusManager;
816
import io.apicurio.registry.operator.updater.IngressCRUpdater;
917
import io.apicurio.registry.operator.updater.KafkaSqlCRUpdater;
1018
import io.apicurio.registry.operator.updater.SqlCRUpdater;
11-
import io.javaoperatorsdk.operator.api.reconciler.*;
19+
import io.javaoperatorsdk.operator.api.reconciler.Cleaner;
20+
import io.javaoperatorsdk.operator.api.reconciler.Context;
21+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
22+
import io.javaoperatorsdk.operator.api.reconciler.DeleteControl;
23+
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusHandler;
24+
import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
25+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
26+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
1227
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
1328
import org.slf4j.Logger;
1429
import org.slf4j.LoggerFactory;
1530

16-
import static io.apicurio.registry.operator.resource.ActivationConditions.*;
17-
import static io.apicurio.registry.operator.resource.ResourceKey.*;
31+
import static io.apicurio.registry.operator.resource.ActivationConditions.AppIngressActivationCondition;
32+
import static io.apicurio.registry.operator.resource.ActivationConditions.AppNetworkPolicyActivationCondition;
33+
import static io.apicurio.registry.operator.resource.ActivationConditions.AppPodDisruptionBudgetActivationCondition;
34+
import static io.apicurio.registry.operator.resource.ActivationConditions.UIIngressActivationCondition;
35+
import static io.apicurio.registry.operator.resource.ActivationConditions.UINetworkPolicyActivationCondition;
36+
import static io.apicurio.registry.operator.resource.ActivationConditions.UIPodDisruptionBudgetActivationCondition;
37+
import static io.apicurio.registry.operator.resource.ResourceKey.APP_DEPLOYMENT_ID;
38+
import static io.apicurio.registry.operator.resource.ResourceKey.APP_INGRESS_ID;
39+
import static io.apicurio.registry.operator.resource.ResourceKey.APP_NETWORK_POLICY_ID;
40+
import static io.apicurio.registry.operator.resource.ResourceKey.APP_POD_DISRUPTION_BUDGET_ID;
41+
import static io.apicurio.registry.operator.resource.ResourceKey.APP_SERVICE_ID;
42+
import static io.apicurio.registry.operator.resource.ResourceKey.UI_DEPLOYMENT_ID;
43+
import static io.apicurio.registry.operator.resource.ResourceKey.UI_INGRESS_ID;
44+
import static io.apicurio.registry.operator.resource.ResourceKey.UI_NETWORK_POLICY_ID;
45+
import static io.apicurio.registry.operator.resource.ResourceKey.UI_POD_DISRUPTION_BUDGET_ID;
46+
import static io.apicurio.registry.operator.resource.ResourceKey.UI_SERVICE_ID;
1847

1948
@ControllerConfiguration(
2049
dependents = {
@@ -91,7 +120,7 @@ public UpdateControl<ApicurioRegistry3> reconcile(ApicurioRegistry3 primary,
91120
// Operator will attempt to update the CR to use the newer fields if possible.
92121
// This has to be done first, so subsequent functionality can deal with new fields only.
93122
var update = IngressCRUpdater.update(primary);
94-
update = SqlCRUpdater.update(primary) || update;
123+
update = SqlCRUpdater.update(primary, context) || update;
95124
update = KafkaSqlCRUpdater.update(primary) || update;
96125
if (update) {
97126
return UpdateControl.updateResource(primary);

operator/controller/src/main/java/io/apicurio/registry/operator/feat/PostgresSql.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
66
import io.apicurio.registry.operator.api.v1.spec.SqlSpec;
77
import io.apicurio.registry.operator.api.v1.spec.StorageSpec;
8+
import io.apicurio.registry.operator.utils.SecretKeyRefTool;
89
import io.fabric8.kubernetes.api.model.EnvVar;
910
import io.fabric8.kubernetes.api.model.EnvVarBuilder;
1011

@@ -35,8 +36,11 @@ public static void configureDatasource(ApicurioRegistry3 primary, Map<String, En
3536
.withValue(dataSource.getUrl()).build());
3637
addEnvVar(env, new EnvVarBuilder().withName(ENV_APICURIO_DATASOURCE_USERNAME)
3738
.withValue(dataSource.getUsername()).build());
38-
addEnvVar(env, new EnvVarBuilder().withName(ENV_APICURIO_DATASOURCE_PASSWORD)
39-
.withValue(dataSource.getPassword()).build());
39+
40+
var password = new SecretKeyRefTool(dataSource.getPassword(), "password");
41+
if (password.isValid()) {
42+
password.applySecretEnvVar(env, ENV_APICURIO_DATASOURCE_PASSWORD);
43+
}
4044
});
4145
}
4246
}

operator/controller/src/main/java/io/apicurio/registry/operator/updater/SqlCRUpdater.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,21 @@
22

33
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
44
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec;
5-
import io.apicurio.registry.operator.api.v1.spec.*;
5+
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
6+
import io.apicurio.registry.operator.api.v1.spec.DataSourceSpec;
7+
import io.apicurio.registry.operator.api.v1.spec.DeprecatedDataSource;
8+
import io.apicurio.registry.operator.api.v1.spec.DeprecatedSqlSpec;
9+
import io.apicurio.registry.operator.api.v1.spec.SecretKeyRef;
10+
import io.apicurio.registry.operator.api.v1.spec.SqlSpec;
11+
import io.apicurio.registry.operator.api.v1.spec.StorageSpec;
12+
import io.apicurio.registry.operator.api.v1.spec.StorageType;
13+
import io.fabric8.kubernetes.api.model.SecretBuilder;
14+
import io.javaoperatorsdk.operator.api.reconciler.Context;
615
import org.slf4j.Logger;
716
import org.slf4j.LoggerFactory;
817

18+
import java.util.UUID;
19+
920
import static io.apicurio.registry.operator.utils.Utils.isBlank;
1021
import static java.util.Optional.ofNullable;
1122

@@ -16,7 +27,7 @@ public class SqlCRUpdater {
1627
/**
1728
* @return true if the CR has been updated
1829
*/
19-
public static boolean update(ApicurioRegistry3 primary) {
30+
public static boolean update(ApicurioRegistry3 primary, Context context) {
2031
var updated = false;
2132
var prevDataSource = ofNullable(primary.getSpec()).map(ApicurioRegistry3Spec::getApp)
2233
.map(AppSpec::getSql).map(DeprecatedSqlSpec::getDataSource);
@@ -44,7 +55,7 @@ public static boolean update(ApicurioRegistry3 primary) {
4455
} else {
4556
log.warn(
4657
"Automatic update cannot be performed, "
47-
+ "because the field `app.storage.type` is already set and is not '{}'.",
58+
+ "because the field `app.storage.type` is already set and is not '{}'.",
4859
StorageType.POSTGRESQL.getValue());
4960
}
5061
} else {
@@ -67,7 +78,7 @@ public static boolean update(ApicurioRegistry3 primary) {
6778
} else {
6879
log.warn(
6980
"Automatic update cannot be performed, "
70-
+ "because the field `app.storage.type` is already set and is not '{}'.",
81+
+ "because the field `app.storage.type` is already set and is not '{}'.",
7182
StorageType.POSTGRESQL.getValue());
7283
}
7384
} else {
@@ -82,15 +93,27 @@ public static boolean update(ApicurioRegistry3 primary) {
8293
if (storageType.isEmpty() || StorageType.POSTGRESQL.equals(storageType.orElse(null))) {
8394
log.info(
8495
"Performing automatic CR update from `app.sql.dataSource.password` to `app.storage.sql.dataSource.password`.");
96+
// Create the Secret
97+
var secretName = primary.getMetadata().getName() + "-datasource-password-" + UUID.randomUUID().toString().substring(0, 7);
98+
// @formatter:off
99+
var secret = new SecretBuilder()
100+
.withNewMetadata()
101+
.withNamespace(primary.getMetadata().getNamespace())
102+
.withName(secretName)
103+
.endMetadata()
104+
.addToData("password", prevPassword.get())
105+
.build();
106+
// @formatter:on
107+
context.getClient().resource(secret).create();
85108
primary.getSpec().withApp().withStorage().setType(StorageType.POSTGRESQL);
86109
primary.getSpec().getApp().getSql().getDataSource().setPassword(null);
87110
primary.getSpec().getApp().getStorage().withSql().withDataSource()
88-
.setPassword(prevPassword.get());
111+
.setPassword(SecretKeyRef.builder().name(secretName).build());
89112
updated = true;
90113
} else {
91114
log.warn(
92115
"Automatic update cannot be performed, "
93-
+ "because the field `app.storage.type` is already set and is not '{}'.",
116+
+ "because the field `app.storage.type` is already set and is not '{}'.",
94117
StorageType.POSTGRESQL.getValue());
95118
}
96119
} else {

operator/controller/src/test/java/io/apicurio/registry/operator/it/CRUpdateITTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.time.Duration;
1212
import java.util.List;
1313

14+
import static io.apicurio.registry.operator.utils.Utils.isBlank;
1415
import static org.assertj.core.api.Assertions.assertThat;
1516
import static org.awaitility.Awaitility.await;
1617

@@ -53,6 +54,10 @@ void testCRUpdate() {
5354
assertThat(updated).hasSize(1);
5455
// We do not care about the operand here, just about the CR structure
5556
assertThat(updated.get(0).getSpec()).usingRecursiveComparison()
57+
// We have to specially handle generated Secret name, since we do not know it in advance.
58+
// It should be enough to just make sure it's not blank.
59+
.withEqualsForFields((l, r) -> !isBlank((String) l) && !isBlank((String) r),
60+
"app.storage.sql.dataSource.password.name")
5661
.isEqualTo(updatedExpected.getSpec());
5762
});
5863
});

operator/controller/src/test/resources/k8s/examples/postgresql/example-postgresql-database.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ spec:
3030
value: apicurio
3131
volumes:
3232
- name: cache-volume
33-
emptyDir: {}
33+
emptyDir: { }
3434
---
3535
# PostgreSQL StatefulSet Service
3636
apiVersion: v1
@@ -43,3 +43,12 @@ spec:
4343
ports:
4444
- port: 5432
4545
targetPort: 5432
46+
---
47+
# PostgreSQL Password Secret
48+
apiVersion: v1
49+
kind: Secret
50+
metadata:
51+
name: example-postgresql-database-password
52+
type: Opaque
53+
data:
54+
password: cGFzc3dvcmQ=

operator/controller/src/test/resources/k8s/examples/postgresql/example-postgresql.apicurioregistry3.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ spec:
1010
dataSource:
1111
url: jdbc:postgresql://example-postgresql-database:5432/apicurio
1212
username: username
13-
password: password
13+
password:
14+
name: example-postgresql-database-password
1415
ingress:
1516
host: example-postgresql-app.apps.cluster.example
1617
ui:

operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/DataSourceSpec.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
package io.apicurio.registry.operator.api.v1.spec;
22

3-
import com.fasterxml.jackson.annotation.*;
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
6+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
7+
import com.fasterxml.jackson.annotation.JsonSetter;
48
import com.fasterxml.jackson.databind.JsonDeserializer.None;
59
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
6-
import lombok.*;
10+
import lombok.AllArgsConstructor;
11+
import lombok.EqualsAndHashCode;
12+
import lombok.Getter;
13+
import lombok.NoArgsConstructor;
14+
import lombok.Setter;
15+
import lombok.ToString;
716
import lombok.experimental.SuperBuilder;
817

918
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
@@ -12,7 +21,7 @@
1221

1322
@JsonDeserialize(using = None.class)
1423
@JsonInclude(NON_NULL)
15-
@JsonPropertyOrder({ "url", "username", "password" })
24+
@JsonPropertyOrder({"url", "username", "password"})
1625
@NoArgsConstructor
1726
@AllArgsConstructor(access = PRIVATE)
1827
@SuperBuilder(toBuilder = true)
@@ -47,15 +56,13 @@ public class DataSourceSpec {
4756
/**
4857
* Configure SQL data source password.
4958
* <p>
50-
* If you want to reference a Secret, you can set the <code>APICURIO_DATASOURCE_PASSWORD</code>
51-
* environment variable directly using the <code>app.env</code> field.
59+
* References name of a Secret that contains the password. Key <code>password</code> is assumed by default.
5260
*/
5361
@JsonProperty("password")
5462
@JsonPropertyDescription("""
5563
Configure SQL data source password.
56-
57-
If you want to reference a Secret, you can set the `APICURIO_DATASOURCE_PASSWORD` environment variable \
58-
directly using the `app.env` field.""")
64+
65+
References name of a Secret that contains the password. Key `password` is assumed by default.""")
5966
@JsonSetter(nulls = SKIP)
60-
private String password;
67+
private SecretKeyRef password;
6168
}

0 commit comments

Comments
 (0)