Skip to content

Commit bd9bc14

Browse files
pooknullhors
andauthored
K8SPG-555: fix updating CA secrets to 2.5.0 (#906)
* K8SPG-555: fix updating CA secrets to 2.5.0 https://perconadev.atlassian.net/browse/K8SPG-555 * fix * add validation for custom certificates * improve validation * break into multiple lines * fix * small fix --------- Co-authored-by: Viacheslav Sarzhan <slava.sarzhan@percona.com>
1 parent 58f70d1 commit bd9bc14

File tree

4 files changed

+160
-8
lines changed

4 files changed

+160
-8
lines changed

internal/controller/postgrescluster/pki.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import (
2121
"github.com/pkg/errors"
2222
appsv1 "k8s.io/api/apps/v1"
2323
corev1 "k8s.io/api/core/v1"
24+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2426
"sigs.k8s.io/controller-runtime/pkg/client"
2527

2628
"github.com/percona/percona-postgresql-operator/internal/naming"
@@ -69,8 +71,22 @@ func (r *Reconciler) reconcileRootCertificate(
6971
}
7072
}
7173

72-
err := errors.WithStack(client.IgnoreNotFound(
73-
r.Client.Get(ctx, client.ObjectKeyFromObject(existing), existing)))
74+
err := errors.WithStack(
75+
r.Client.Get(ctx, client.ObjectKeyFromObject(existing), existing))
76+
// K8SPG-555: we need to check ca certificate from old operator versions
77+
// TODO: remove when 2.4.0 will become unsupported
78+
if k8serrors.IsNotFound(err) {
79+
nn := client.ObjectKeyFromObject(existing)
80+
nn.Name = naming.RootCertSecret
81+
err = errors.WithStack(
82+
r.Client.Get(ctx, nn, existing))
83+
if err == nil {
84+
existing.Name = naming.RootCertSecret
85+
}
86+
}
87+
if k8serrors.IsNotFound(err) {
88+
err = nil
89+
}
7490

7591
root := &pki.RootCertificateAuthority{}
7692

@@ -81,17 +97,21 @@ func (r *Reconciler) reconcileRootCertificate(
8197
_ = root.Certificate.UnmarshalText(existing.Data[certificateKey])
8298
_ = root.PrivateKey.UnmarshalText(existing.Data[privateKey])
8399

100+
if cluster.Spec.CustomRootCATLSSecret != nil {
101+
return root, err
102+
}
103+
84104
if !pki.RootIsValid(root) {
85105
root, err = pki.NewRootCertificateAuthority()
86106
err = errors.WithStack(err)
87107
}
88108
}
89-
if cluster.Spec.CustomRootCATLSSecret != nil {
90-
return root, err
91-
}
92109

93110
intent := &corev1.Secret{
94-
ObjectMeta: naming.PostgresRootCASecret(cluster),
111+
ObjectMeta: metav1.ObjectMeta{
112+
Name: existing.Name,
113+
Namespace: existing.Namespace,
114+
},
95115
}
96116
intent.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Secret"))
97117
intent.Data = make(map[string][]byte)

internal/naming/names.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,11 @@ const (
9090
)
9191

9292
const (
93-
// K8SPG-555: use PostgresRootCASecret instead.
93+
// Deprecated: K8SPG-555: use PostgresRootCASecret instead.
94+
// Currently it's used to update certificates from older operator version
9495
// RootCertSecret is the default root certificate secret name
95-
// RootCertSecret = "pgo-root-cacert" /* #nosec */
96+
// TODO: remove when 2.4.0 will become unsupported
97+
RootCertSecret = "pgo-root-cacert" /* #nosec */
9698

9799
// ClusterCertSecret is the default cluster leaf certificate secret name
98100
ClusterCertSecret = "%s-cluster-cert" /* #nosec */

percona/controller/pgcluster/controller.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,10 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R
213213
return reconcile.Result{}, nil
214214
}
215215

216+
if err := r.reconcileTLS(ctx, cr); err != nil {
217+
return reconcile.Result{}, errors.Wrap(err, "reconcile TLS")
218+
}
219+
216220
if err := r.reconcileExternalWatchers(ctx, cr); err != nil {
217221
return reconcile.Result{}, errors.Wrap(err, "start external watchers")
218222
}
@@ -282,6 +286,131 @@ func (r *PGClusterReconciler) Reconcile(ctx context.Context, request reconcile.R
282286
return ctrl.Result{}, nil
283287
}
284288

289+
func (r *PGClusterReconciler) reconcileTLS(ctx context.Context, cr *v2.PerconaPGCluster) error {
290+
checkSecretProjection := func(p *corev1.SecretProjection, requiredPaths ...string) error {
291+
if p == nil {
292+
return nil
293+
}
294+
295+
if p.Name == "" {
296+
return errors.New("secret name is not specified")
297+
}
298+
299+
secret := new(corev1.Secret)
300+
nn := types.NamespacedName{Name: p.Name, Namespace: cr.Namespace}
301+
if err := r.Client.Get(ctx, nn, secret); err != nil {
302+
return errors.Wrapf(err, "failed to get secret %s", nn.Name)
303+
}
304+
305+
pathMap := make(map[string]struct{})
306+
for _, item := range p.Items {
307+
if _, ok := secret.Data[item.Key]; !ok {
308+
return errors.Errorf("key %s doesn't exist in secret %s", item.Key, secret.Name)
309+
}
310+
pathMap[item.Path] = struct{}{}
311+
}
312+
313+
for _, path := range requiredPaths {
314+
if _, ok := pathMap[path]; !ok {
315+
if _, ok := secret.Data[path]; !ok {
316+
return errors.Errorf("required path %s was not found both in secret %s and in the .items section", path, secret.Name)
317+
}
318+
}
319+
}
320+
321+
return nil
322+
}
323+
324+
if err := checkSecretProjection(cr.Spec.Secrets.CustomRootCATLSSecret, "root.crt", "root.key"); err != nil {
325+
return errors.Wrap(err, "failed to validate .spec.customRootCATLSSecret")
326+
}
327+
328+
certPaths := []string{"tls.key", "tls.crt"}
329+
if cr.Spec.Secrets.CustomRootCATLSSecret == nil {
330+
certPaths = append(certPaths, "ca.crt")
331+
}
332+
if err := checkSecretProjection(cr.Spec.Secrets.CustomTLSSecret, certPaths...); err != nil {
333+
return errors.Wrap(err, "failed to validate .spec.customTLSSecret")
334+
}
335+
if err := checkSecretProjection(cr.Spec.Secrets.CustomReplicationClientTLSSecret, certPaths...); err != nil {
336+
return errors.Wrap(err, "failed to validate .spec.customReplicationTLSSecret")
337+
}
338+
339+
if cr.Spec.Secrets.CustomRootCATLSSecret != nil {
340+
return nil
341+
}
342+
343+
if err := r.reconcileOldCACert(ctx, cr); err != nil {
344+
return errors.Wrap(err, "reconcile old CA")
345+
}
346+
347+
return nil
348+
}
349+
350+
func (r *PGClusterReconciler) reconcileOldCACert(ctx context.Context, cr *v2.PerconaPGCluster) error {
351+
oldCASecret := &corev1.Secret{
352+
ObjectMeta: metav1.ObjectMeta{
353+
Name: naming.RootCertSecret,
354+
Namespace: cr.Namespace,
355+
},
356+
}
357+
err := r.Client.Get(ctx, client.ObjectKeyFromObject(oldCASecret), oldCASecret)
358+
if client.IgnoreNotFound(err) != nil {
359+
return errors.Wrap(err, "failed to get old ca secret")
360+
}
361+
362+
if cr.CompareVersion("2.5.0") < 0 {
363+
if k8serrors.IsNotFound(err) {
364+
// K8SPG-555: We should create an empty secret with old name, so that crunchy part can populate it
365+
// instead of creating secrets unique to the cluster
366+
// TODO: remove when 2.4.0 will become unsupported
367+
if err := r.Client.Create(ctx, oldCASecret); err != nil {
368+
return errors.Wrap(err, "failed to create ca secret")
369+
}
370+
}
371+
return nil
372+
}
373+
if k8serrors.IsNotFound(err) {
374+
return nil
375+
}
376+
377+
// K8SPG-555: Previously we used a single CA secret for all clusters in a namespace.
378+
// We should copy the contents of the old CA secret, if it exists, to the new one, which is unique for each cluster.
379+
// TODO: remove when 2.4.0 will become unsupported
380+
newCASecret := &corev1.Secret{
381+
ObjectMeta: naming.PostgresRootCASecret(
382+
&v1beta1.PostgresCluster{
383+
ObjectMeta: metav1.ObjectMeta{
384+
Name: cr.Name,
385+
Namespace: cr.Namespace,
386+
},
387+
}),
388+
}
389+
err = r.Client.Get(ctx, client.ObjectKeyFromObject(newCASecret), new(corev1.Secret))
390+
if client.IgnoreNotFound(err) != nil {
391+
return errors.Wrap(err, "failed to get new ca secret")
392+
}
393+
394+
if k8serrors.IsNotFound(err) {
395+
err := r.Client.Get(ctx, types.NamespacedName{
396+
Name: cr.Name,
397+
Namespace: cr.Namespace,
398+
}, new(v1beta1.PostgresCluster))
399+
if client.IgnoreNotFound(err) != nil {
400+
return errors.Wrap(err, "failed to get crunchy cluster")
401+
}
402+
// If the cluster is new, we should not copy the old CA secret.
403+
// We should create an empty secret instead, so that crunchy part can populate it.
404+
if !k8serrors.IsNotFound(err) {
405+
newCASecret.Data = oldCASecret.Data
406+
}
407+
if err := r.Client.Create(ctx, newCASecret); err != nil {
408+
return errors.Wrap(err, "failed to create updated CA secret")
409+
}
410+
}
411+
return nil
412+
}
413+
285414
func (r *PGClusterReconciler) reconcilePMM(ctx context.Context, cr *v2.PerconaPGCluster) error {
286415
if !cr.PMMEnabled() {
287416
return nil

percona/controller/pgcluster/finalizer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func (r *PGClusterReconciler) deleteTLSSecrets(ctx context.Context, cr *v2.Perco
8282
naming.ClusterPGBouncer(crunchyCluster),
8383
}
8484
if cr.Spec.Secrets.CustomRootCATLSSecret == nil {
85+
secretsMeta = append(secretsMeta, metav1.ObjectMeta{Namespace: crunchyCluster.Namespace, Name: naming.RootCertSecret})
8586
secretsMeta = append(secretsMeta, naming.PostgresRootCASecret(crunchyCluster))
8687
}
8788
if cr.Spec.Secrets.CustomTLSSecret == nil {

0 commit comments

Comments
 (0)