Skip to content

Commit 17b9212

Browse files
authored
Storage Encryption (#445)
* Generate random string (instead of base64 encoded random data) * Switch to crypto/rand * Make random storage encryption secret configurable * Add finalizer to prevent manual deletion * qualify finalizer name
1 parent 618c94d commit 17b9212

File tree

2 files changed

+193
-69
lines changed

2 files changed

+193
-69
lines changed

controllers/postgres_controller.go

Lines changed: 127 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ package controllers
99
import (
1010
"bytes"
1111
"context"
12+
"crypto/rand"
1213
"encoding/json"
1314
"errors"
1415
"fmt"
16+
"math/big"
1517
"net"
1618
"net/http"
1719
"net/url"
@@ -49,6 +51,8 @@ const (
4951
postgresExporterServicePortName string = "metrics"
5052
postgresExporterServiceTenantAnnotationName string = pg.TenantLabelName
5153
postgresExporterServiceProjectIDAnnotationName string = pg.ProjectIDLabelName
54+
storageEncryptionKeyName string = "storage-encryption-key"
55+
storageEncryptionKeyFinalizerName string = "postgres.database.fits.cloud/secret-finalizer"
5256
)
5357

5458
// requeue defines in how many seconds a requeue should happen
@@ -65,16 +69,17 @@ type PostgresReconciler struct {
6569
PartitionID, Tenant, StorageClass string
6670
*operatormanager.OperatorManager
6771
*lbmanager.LBManager
68-
recorder record.EventRecorder
69-
PgParamBlockList map[string]bool
70-
StandbyClustersSourceRanges []string
71-
PostgresletNamespace string
72-
SidecarsConfigMapName string
73-
EnableNetPol bool
74-
EtcdHost string
75-
PatroniTTL uint32
76-
PatroniLoopWait uint32
77-
PatroniRetryTimeout uint32
72+
recorder record.EventRecorder
73+
PgParamBlockList map[string]bool
74+
StandbyClustersSourceRanges []string
75+
PostgresletNamespace string
76+
SidecarsConfigMapName string
77+
EnableNetPol bool
78+
EtcdHost string
79+
PatroniTTL uint32
80+
PatroniLoopWait uint32
81+
PatroniRetryTimeout uint32
82+
EnableRandomStorageEncryptionSecret bool
7883
}
7984

8085
// Reconcile is the entry point for postgres reconciliation.
@@ -165,6 +170,11 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
165170
}
166171
log.Info("corresponding passwords secret deleted")
167172

173+
if err := r.deleteStorageEncryptionSecret(ctx, instance); err != nil {
174+
return ctrl.Result{}, fmt.Errorf("error while deleting storage encryption secret: %w", err)
175+
}
176+
log.Info("storage encryption secret removed")
177+
168178
instance.RemoveFinalizer(pg.PostgresFinalizerName)
169179
if err := r.CtrlClient.Update(ctx, instance); err != nil {
170180
r.recorder.Eventf(instance, "Warning", "Self-Reconcilation", "failed to remove finalizer: %v", err)
@@ -251,6 +261,12 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
251261
return ctrl.Result{}, fmt.Errorf("error while creating sidecars servicemonitor %v: %w", namespace, err)
252262
}
253263

264+
// Make sure the storage secret exist, if neccessary
265+
if err := r.ensureStorageEncryptionSecret(ctx, instance); err != nil {
266+
r.recorder.Eventf(instance, "Warning", "Error", "failed to create storage secret: %v", err)
267+
return ctrl.Result{}, fmt.Errorf("error while creating storage secret: %w", err)
268+
}
269+
254270
if err := r.createOrUpdateZalandoPostgresql(ctx, instance, log, globalSidecarsCM, r.PatroniTTL, r.PatroniLoopWait, r.PatroniRetryTimeout); err != nil {
255271
r.recorder.Eventf(instance, "Warning", "Error", "failed to create Zalando resource: %v", err)
256272
return ctrl.Result{}, fmt.Errorf("failed to create or update zalando postgresql: %w", err)
@@ -1189,3 +1205,104 @@ func (r *PostgresReconciler) deleteExporterSidecarService(ctx context.Context, n
11891205

11901206
return nil
11911207
}
1208+
1209+
func (r *PostgresReconciler) ensureStorageEncryptionSecret(ctx context.Context, instance *pg.Postgres) error {
1210+
1211+
if !r.EnableRandomStorageEncryptionSecret {
1212+
r.Log.Info("storage secret disabled, no action needed")
1213+
return nil
1214+
}
1215+
1216+
// Check if secrets exist local in SERVICE Cluster
1217+
n := storageEncryptionKeyName
1218+
ns := instance.ToPeripheralResourceNamespace()
1219+
s := &corev1.Secret{}
1220+
r.Log.Info("checking for storage secret", "namespace", ns, "name", n)
1221+
err := r.SvcClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: n}, s)
1222+
if err == nil {
1223+
r.Log.Info("storage secret found, no action needed")
1224+
return nil
1225+
}
1226+
1227+
// we got an error other than not found, so we cannot continue!
1228+
if !apierrors.IsNotFound(err) {
1229+
return fmt.Errorf("error while fetching storage secret from service cluster: %w", err)
1230+
}
1231+
1232+
r.Log.Info("creating storage secret")
1233+
1234+
k, err := r.generateRandomString()
1235+
if err != nil {
1236+
return fmt.Errorf("error while generating random storage secret: %w", err)
1237+
}
1238+
1239+
postgresSecret := &corev1.Secret{
1240+
ObjectMeta: metav1.ObjectMeta{
1241+
Name: n,
1242+
Namespace: ns,
1243+
Finalizers: []string{storageEncryptionKeyFinalizerName},
1244+
},
1245+
StringData: map[string]string{
1246+
"host-encryption-passphrase": k,
1247+
},
1248+
}
1249+
1250+
if err := r.SvcClient.Create(ctx, postgresSecret); err != nil {
1251+
return fmt.Errorf("error while creating storage secret in service cluster: %w", err)
1252+
}
1253+
r.Log.Info("created storage secret", "secret", postgresSecret)
1254+
1255+
return nil
1256+
1257+
}
1258+
1259+
func (r *PostgresReconciler) generateRandomString() (string, error) {
1260+
const chars string = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
1261+
var size *big.Int = big.NewInt(int64(len(chars)))
1262+
b := make([]byte, 64)
1263+
for i := range b {
1264+
x, err := rand.Int(rand.Reader, size)
1265+
if err != nil {
1266+
return "", err
1267+
}
1268+
b[i] = chars[x.Int64()]
1269+
}
1270+
return string(b), nil
1271+
}
1272+
1273+
func (r *PostgresReconciler) deleteStorageEncryptionSecret(ctx context.Context, instance *pg.Postgres) error {
1274+
1275+
// Fetch secret
1276+
n := storageEncryptionKeyName
1277+
ns := instance.ToPeripheralResourceNamespace()
1278+
s := &corev1.Secret{}
1279+
r.Log.Info("Fetching storage secret", "namespace", ns, "name", n)
1280+
err := r.SvcClient.Get(ctx, types.NamespacedName{Namespace: ns, Name: n}, s)
1281+
if err != nil {
1282+
// TODO this would be blocking if we couldn't remove the finalizer!
1283+
return fmt.Errorf("error while fetching storage secret from service cluster: %w", err)
1284+
}
1285+
1286+
// Remove finalizer
1287+
s.ObjectMeta.Finalizers = removeElem(s.ObjectMeta.Finalizers, storageEncryptionKeyFinalizerName)
1288+
if err := r.SvcClient.Update(ctx, s); err != nil {
1289+
return fmt.Errorf("error while removing finalizer from storage secret in service cluster: %w", err)
1290+
}
1291+
1292+
// Delete secret
1293+
if err := r.SvcClient.Delete(ctx, s); err != nil {
1294+
return fmt.Errorf("error while deleting storage secret in service cluster: %w", err)
1295+
}
1296+
1297+
return nil
1298+
}
1299+
1300+
func removeElem(ss []string, s string) (out []string) {
1301+
for _, elem := range ss {
1302+
if elem == s {
1303+
continue
1304+
}
1305+
out = append(out, elem)
1306+
}
1307+
return
1308+
}

main.go

Lines changed: 66 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -36,39 +36,40 @@ import (
3636

3737
const (
3838
// envPrefix = "pg"
39-
metricsAddrSvcMgrFlg = "metrics-addr-svc-mgr"
40-
metricsAddrCtrlMgrFlg = "metrics-addr-ctrl-mgr"
41-
enableLeaderElectionFlg = "enable-leader-election"
42-
partitionIDFlg = "partition-id"
43-
tenantFlg = "tenant"
44-
ctrlPlaneKubeConfifgFlg = "controlplane-kubeconfig"
45-
loadBalancerIPFlg = "load-balancer-ip"
46-
portRangeStartFlg = "port-range-start"
47-
portRangeSizeFlg = "port-range-size"
48-
customPSPNameFlg = "custom-psp-name"
49-
storageClassFlg = "storage-class"
50-
postgresImageFlg = "postgres-image"
51-
etcdHostFlg = "etcd-host"
52-
crdValidationFlg = "enable-crd-validation"
53-
operatorImageFlg = "operator-image"
54-
pgParamBlockListFlg = "postgres-param-blocklist"
55-
majorVersionUpgradeModeFlg = "major-version-upgrade-mode"
56-
standbyClustersSourceRangesFlg = "standby-clusters-source-ranges"
57-
postgresletNamespaceFlg = "postgreslet-namespace"
58-
sidecarsCMNameFlg = "sidecars-configmap-name"
59-
enableNetPolFlg = "enable-netpol"
60-
enablePodAntiaffinityFlg = "enable-pod-antiaffinity"
61-
patroniRetryTimeoutFlg = "patroni-retry-timeout"
62-
enableStandbyLeaderSelectorFlg = "enable-standby-leader-selector"
63-
ControlPlaneNamespaceFlg = "control-plane-namespace"
64-
enableLegacyStandbySelectorFlg = "enable-legacy-standby-selector"
65-
deployEtcdFlg = "deploy-etcd"
66-
etcdImageFlg = "etcd-image"
67-
etcdBackupSidecarImageFlg = "etcd-backup-sidecar-image"
68-
etcdBackupSecretNameFlg = "etcd-backup-secret-name" // nolint
69-
etcdPSPNameFlg = "etcd-psp-name"
70-
postgresletFullnameFlg = "postgreslet-fullname"
71-
enableLBSourceRangesFlg = "enable-lb-source-ranges"
39+
metricsAddrSvcMgrFlg = "metrics-addr-svc-mgr"
40+
metricsAddrCtrlMgrFlg = "metrics-addr-ctrl-mgr"
41+
enableLeaderElectionFlg = "enable-leader-election"
42+
partitionIDFlg = "partition-id"
43+
tenantFlg = "tenant"
44+
ctrlPlaneKubeConfifgFlg = "controlplane-kubeconfig"
45+
loadBalancerIPFlg = "load-balancer-ip"
46+
portRangeStartFlg = "port-range-start"
47+
portRangeSizeFlg = "port-range-size"
48+
customPSPNameFlg = "custom-psp-name"
49+
storageClassFlg = "storage-class"
50+
postgresImageFlg = "postgres-image"
51+
etcdHostFlg = "etcd-host"
52+
crdValidationFlg = "enable-crd-validation"
53+
operatorImageFlg = "operator-image"
54+
pgParamBlockListFlg = "postgres-param-blocklist"
55+
majorVersionUpgradeModeFlg = "major-version-upgrade-mode"
56+
standbyClustersSourceRangesFlg = "standby-clusters-source-ranges"
57+
postgresletNamespaceFlg = "postgreslet-namespace"
58+
sidecarsCMNameFlg = "sidecars-configmap-name"
59+
enableNetPolFlg = "enable-netpol"
60+
enablePodAntiaffinityFlg = "enable-pod-antiaffinity"
61+
patroniRetryTimeoutFlg = "patroni-retry-timeout"
62+
enableStandbyLeaderSelectorFlg = "enable-standby-leader-selector"
63+
ControlPlaneNamespaceFlg = "control-plane-namespace"
64+
enableLegacyStandbySelectorFlg = "enable-legacy-standby-selector"
65+
deployEtcdFlg = "deploy-etcd"
66+
etcdImageFlg = "etcd-image"
67+
etcdBackupSidecarImageFlg = "etcd-backup-sidecar-image"
68+
etcdBackupSecretNameFlg = "etcd-backup-secret-name" // nolint
69+
etcdPSPNameFlg = "etcd-psp-name"
70+
postgresletFullnameFlg = "postgreslet-fullname"
71+
enableLBSourceRangesFlg = "enable-lb-source-ranges"
72+
enableRandomStorageEncrytionSecretFlg = "enable-random-storage-encryption-secret"
7273
)
7374

7475
var (
@@ -110,14 +111,15 @@ func main() {
110111
etcdPSPName string
111112
postgresletFullname string
112113

113-
enableLeaderElection bool
114-
enableCRDValidation bool
115-
enableNetPol bool
116-
enablePodAntiaffinity bool
117-
enableStandbyLeaderSelector bool
118-
enableLegacyStandbySelector bool
119-
deployEtcd bool
120-
enableLBSourceRanges bool
114+
enableLeaderElection bool
115+
enableCRDValidation bool
116+
enableNetPol bool
117+
enablePodAntiaffinity bool
118+
enableStandbyLeaderSelector bool
119+
enableLegacyStandbySelector bool
120+
deployEtcd bool
121+
enableLBSourceRanges bool
122+
enableRandomStorageEncrytionSecret bool
121123

122124
portRangeStart int
123125
portRangeSize int
@@ -245,6 +247,9 @@ func main() {
245247
viper.SetDefault(enableLBSourceRangesFlg, true)
246248
enableLBSourceRanges = viper.GetBool(enableLBSourceRangesFlg)
247249

250+
viper.SetDefault(enableRandomStorageEncrytionSecretFlg, false)
251+
enableRandomStorageEncrytionSecret = viper.GetBool(enableRandomStorageEncrytionSecretFlg)
252+
248253
ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
249254

250255
ctrl.Log.Info("flag",
@@ -281,6 +286,7 @@ func main() {
281286
etcdPSPNameFlg, etcdPSPName,
282287
postgresletFullnameFlg, postgresletFullname,
283288
enableLBSourceRangesFlg, enableLBSourceRanges,
289+
enableRandomStorageEncrytionSecretFlg, enableRandomStorageEncrytionSecret,
284290
)
285291

286292
svcClusterConf := ctrl.GetConfigOrDie()
@@ -366,24 +372,25 @@ func main() {
366372
EnableLBSourceRanges: enableLBSourceRanges,
367373
}
368374
if err = (&controllers.PostgresReconciler{
369-
CtrlClient: ctrlPlaneClusterMgr.GetClient(),
370-
SvcClient: svcClusterMgr.GetClient(),
371-
Log: ctrl.Log.WithName("controllers").WithName("Postgres"),
372-
Scheme: ctrlPlaneClusterMgr.GetScheme(),
373-
PartitionID: partitionID,
374-
Tenant: tenant,
375-
StorageClass: storageClass,
376-
OperatorManager: opMgr,
377-
LBManager: lbmanager.New(svcClusterMgr.GetClient(), lbMgrOpts),
378-
PgParamBlockList: pgParamBlockList,
379-
StandbyClustersSourceRanges: standbyClusterSourceRanges,
380-
PostgresletNamespace: postgresletNamespace,
381-
SidecarsConfigMapName: sidecarsCMName,
382-
EnableNetPol: enableNetPol,
383-
EtcdHost: etcdHost,
384-
PatroniTTL: patroniTTL,
385-
PatroniLoopWait: patroniLoopWait,
386-
PatroniRetryTimeout: patroniRetryTimeout,
375+
CtrlClient: ctrlPlaneClusterMgr.GetClient(),
376+
SvcClient: svcClusterMgr.GetClient(),
377+
Log: ctrl.Log.WithName("controllers").WithName("Postgres"),
378+
Scheme: ctrlPlaneClusterMgr.GetScheme(),
379+
PartitionID: partitionID,
380+
Tenant: tenant,
381+
StorageClass: storageClass,
382+
OperatorManager: opMgr,
383+
LBManager: lbmanager.New(svcClusterMgr.GetClient(), lbMgrOpts),
384+
PgParamBlockList: pgParamBlockList,
385+
StandbyClustersSourceRanges: standbyClusterSourceRanges,
386+
PostgresletNamespace: postgresletNamespace,
387+
SidecarsConfigMapName: sidecarsCMName,
388+
EnableNetPol: enableNetPol,
389+
EtcdHost: etcdHost,
390+
PatroniTTL: patroniTTL,
391+
PatroniLoopWait: patroniLoopWait,
392+
PatroniRetryTimeout: patroniRetryTimeout,
393+
EnableRandomStorageEncryptionSecret: enableRandomStorageEncrytionSecret,
387394
}).SetupWithManager(ctrlPlaneClusterMgr); err != nil {
388395
setupLog.Error(err, "unable to create controller", "controller", "Postgres")
389396
os.Exit(1)

0 commit comments

Comments
 (0)