Skip to content

Commit 187c2f8

Browse files
wip
Signed-off-by: Mayank Shah <mayank.shah@percona.com>
1 parent 7f9450b commit 187c2f8

13 files changed

Lines changed: 1214 additions & 98 deletions

File tree

internal/controller/postgrescluster/controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ func (r *Reconciler) Reconcile(
300300
rootCA, err = r.reconcileRootCertificate(ctx, cluster)
301301
}
302302

303-
if err == nil && rootCA != nil {
303+
if err == nil {
304304
certManagerManaged, certErr := r.isRootCACertManagerManaged(ctx, cluster)
305305
if certErr != nil {
306306
log.V(1).Info("failed to check if root CA is cert-manager managed, will retry on next reconcile",

internal/controller/postgrescluster/instance.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,15 +1575,20 @@ func (r *Reconciler) reconcileCertManagerInstanceCertificates(
15751575
_ = leafCert.Certificate.UnmarshalText(instanceCerts.Data["dns.crt"])
15761576
_ = leafCert.PrivateKey.UnmarshalText(instanceCerts.Data["dns.key"])
15771577

1578+
caCert, err := instanceCACert(rootCertificateAuth, existing)
1579+
if err != nil {
1580+
return nil, err
1581+
}
1582+
15781583
err = patroni.InstanceCertificates(ctx,
1579-
rootCertificateAuth.Certificate, leafCert.Certificate,
1584+
caCert, leafCert.Certificate,
15801585
leafCert.PrivateKey, instanceCerts)
15811586
if err != nil {
15821587
return nil, errors.Wrap(err, "failed to add patroni certificates")
15831588
}
15841589

15851590
err = pgbackrest.InstanceCertificates(ctx, cluster,
1586-
rootCertificateAuth.Certificate, leafCert.Certificate, leafCert.PrivateKey,
1591+
caCert, leafCert.Certificate, leafCert.PrivateKey,
15871592
instanceCerts)
15881593
if err != nil {
15891594
return nil, errors.Wrap(err, "failed to add pgbackrest certificates")
@@ -1597,6 +1602,28 @@ func (r *Reconciler) reconcileCertManagerInstanceCertificates(
15971602
return instanceCerts, nil
15981603
}
15991604

1605+
// instanceCACert returns the CA certificate to embed alongside an instance's
1606+
// leaf certificate. When rootCertificateAuth is set (the operator manages
1607+
// the CA itself), it's the source of truth. When it's nil (external issuer —
1608+
// see K8SPG-951), this reads the ca.crt cert-manager wrote into the
1609+
// instance's own just-issued secret.
1610+
func instanceCACert(rootCertificateAuth *pki.RootCertificateAuthority, issuedSecret *corev1.Secret) (pki.Certificate, error) {
1611+
if rootCertificateAuth != nil {
1612+
return rootCertificateAuth.Certificate, nil
1613+
}
1614+
1615+
ca := issuedSecret.Data[corev1.ServiceAccountRootCAKey]
1616+
if len(ca) == 0 {
1617+
return pki.Certificate{}, errors.New("external issuer did not return a CA certificate for the instance")
1618+
}
1619+
1620+
var caCert pki.Certificate
1621+
if err := caCert.UnmarshalText(ca); err != nil {
1622+
return pki.Certificate{}, errors.Wrap(err, "failed to parse CA certificate from cert-manager secret")
1623+
}
1624+
return caCert, nil
1625+
}
1626+
16001627
// reconcileInternalInstanceCertificates creates instance certificates using internal PKI.
16011628
func (r *Reconciler) reconcileInternalInstanceCertificates(
16021629
ctx context.Context, cluster *v1beta1.PostgresCluster,

internal/controller/postgrescluster/instance_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/percona/percona-postgresql-operator/v2/internal/controller/runtime"
3838
"github.com/percona/percona-postgresql-operator/v2/internal/logging"
3939
"github.com/percona/percona-postgresql-operator/v2/internal/naming"
40+
"github.com/percona/percona-postgresql-operator/v2/internal/pki"
4041
"github.com/percona/percona-postgresql-operator/v2/internal/testing/cmp"
4142
"github.com/percona/percona-postgresql-operator/v2/internal/testing/events"
4243
"github.com/percona/percona-postgresql-operator/v2/internal/testing/require"
@@ -2158,3 +2159,45 @@ func TestCleanupDisruptionBudgets(t *testing.T) {
21582159
})
21592160
})
21602161
}
2162+
2163+
func TestInstanceCACert(t *testing.T) {
2164+
t.Run("uses rootCertificateAuth when present", func(t *testing.T) {
2165+
root, err := pki.NewRootCertificateAuthority()
2166+
assert.NilError(t, err)
2167+
2168+
caCert, err := instanceCACert(root, &corev1.Secret{})
2169+
assert.NilError(t, err)
2170+
2171+
want, err := root.Certificate.MarshalText()
2172+
assert.NilError(t, err)
2173+
got, err := caCert.MarshalText()
2174+
assert.NilError(t, err)
2175+
assert.DeepEqual(t, want, got)
2176+
})
2177+
2178+
t.Run("parses ca.crt from issued secret when rootCertificateAuth is nil", func(t *testing.T) {
2179+
root, err := pki.NewRootCertificateAuthority()
2180+
assert.NilError(t, err)
2181+
caBytes, err := root.Certificate.MarshalText()
2182+
assert.NilError(t, err)
2183+
2184+
issuedSecret := &corev1.Secret{Data: map[string][]byte{corev1.ServiceAccountRootCAKey: caBytes}}
2185+
2186+
caCert, err := instanceCACert(nil, issuedSecret)
2187+
assert.NilError(t, err)
2188+
got, err := caCert.MarshalText()
2189+
assert.NilError(t, err)
2190+
assert.DeepEqual(t, caBytes, got)
2191+
})
2192+
2193+
t.Run("errors when issued secret has no ca.crt", func(t *testing.T) {
2194+
_, err := instanceCACert(nil, &corev1.Secret{})
2195+
assert.ErrorContains(t, err, "did not return a CA certificate")
2196+
})
2197+
2198+
t.Run("errors when ca.crt is not a valid certificate", func(t *testing.T) {
2199+
issuedSecret := &corev1.Secret{Data: map[string][]byte{corev1.ServiceAccountRootCAKey: []byte("not a cert")}}
2200+
_, err := instanceCACert(nil, issuedSecret)
2201+
assert.ErrorContains(t, err, "failed to parse CA certificate")
2202+
})
2203+
}

internal/controller/postgrescluster/pgbackrest.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2322,9 +2322,9 @@ func (r *Reconciler) reconcileCertManagerPGBackRestSecret(
23222322
// Populate the pgBackRest secret from cert-manager-issued certs.
23232323
initialize.Map(&intent.Data)
23242324

2325-
caCert, err := rootCA.Certificate.MarshalText()
2325+
caCert, err := pgBackRestCACert(rootCA, clientSecret, repoSecret)
23262326
if err != nil {
2327-
return errors.Wrap(err, "failed to marshal root CA certificate")
2327+
return err
23282328
}
23292329
intent.Data[pgbackrest.CertAuthoritySecretKey] = caCert
23302330
intent.Data[pgbackrest.CertClientSecretKey] = clientSecret.Data[corev1.TLSCertKey]
@@ -2335,6 +2335,29 @@ func (r *Reconciler) reconcileCertManagerPGBackRestSecret(
23352335
return nil
23362336
}
23372337

2338+
// pgBackRestCACert returns the CA certificate bytes to trust for pgBackRest's
2339+
// client/repo TLS. When rootCA is set (the operator manages the CA itself),
2340+
// it's the source of truth. When rootCA is nil (external issuer — see
2341+
// K8SPG-951), there is no operator-tracked CA; instead this reads the ca.crt
2342+
// cert-manager wrote into one of the just-issued leaf secrets, which is
2343+
// present as long as the external issuer returns CA data in its response
2344+
// (true for CA-backed issuers; not guaranteed for e.g. some ACME issuers).
2345+
func pgBackRestCACert(rootCA *pki.RootCertificateAuthority, clientSecret, repoSecret *corev1.Secret) ([]byte, error) {
2346+
if rootCA != nil {
2347+
caCert, err := rootCA.Certificate.MarshalText()
2348+
return caCert, errors.Wrap(err, "failed to marshal root CA certificate")
2349+
}
2350+
2351+
if ca := clientSecret.Data[corev1.ServiceAccountRootCAKey]; len(ca) > 0 {
2352+
return ca, nil
2353+
}
2354+
if ca := repoSecret.Data[corev1.ServiceAccountRootCAKey]; len(ca) > 0 {
2355+
return ca, nil
2356+
}
2357+
2358+
return nil, errors.New("external issuer did not return a CA certificate for pgBackRest")
2359+
}
2360+
23382361
// +kubebuilder:rbac:groups="",resources="serviceaccounts",verbs={create,patch}
23392362
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources="roles",verbs={create,patch}
23402363
// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources="rolebindings",verbs={create,patch}

internal/controller/postgrescluster/pgbackrest_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4571,3 +4571,40 @@ func TestBackupsEnabled(t *testing.T) {
45714571
assert.Assert(t, backupsReconciliationAllowed)
45724572
})
45734573
}
4574+
4575+
func TestPgBackRestCACert(t *testing.T) {
4576+
t.Run("uses rootCA when present", func(t *testing.T) {
4577+
root, err := pki.NewRootCertificateAuthority()
4578+
assert.NilError(t, err)
4579+
4580+
caCert, err := pgBackRestCACert(root, &corev1.Secret{}, &corev1.Secret{})
4581+
assert.NilError(t, err)
4582+
4583+
want, err := root.Certificate.MarshalText()
4584+
assert.NilError(t, err)
4585+
assert.DeepEqual(t, want, caCert)
4586+
})
4587+
4588+
t.Run("falls back to client secret ca.crt when rootCA is nil", func(t *testing.T) {
4589+
clientSecret := &corev1.Secret{Data: map[string][]byte{corev1.ServiceAccountRootCAKey: []byte("client-ca-bytes")}}
4590+
repoSecret := &corev1.Secret{}
4591+
4592+
caCert, err := pgBackRestCACert(nil, clientSecret, repoSecret)
4593+
assert.NilError(t, err)
4594+
assert.DeepEqual(t, []byte("client-ca-bytes"), caCert)
4595+
})
4596+
4597+
t.Run("falls back to repo secret ca.crt when client secret has none", func(t *testing.T) {
4598+
clientSecret := &corev1.Secret{}
4599+
repoSecret := &corev1.Secret{Data: map[string][]byte{corev1.ServiceAccountRootCAKey: []byte("repo-ca-bytes")}}
4600+
4601+
caCert, err := pgBackRestCACert(nil, clientSecret, repoSecret)
4602+
assert.NilError(t, err)
4603+
assert.DeepEqual(t, []byte("repo-ca-bytes"), caCert)
4604+
})
4605+
4606+
t.Run("errors when neither secret has a CA cert", func(t *testing.T) {
4607+
_, err := pgBackRestCACert(nil, &corev1.Secret{}, &corev1.Secret{})
4608+
assert.ErrorContains(t, err, "did not return a CA certificate")
4609+
})
4610+
}

internal/controller/postgrescluster/pki.go

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,23 @@ func (r *Reconciler) reconcileRootCertificate(
4242
) (
4343
*pki.RootCertificateAuthority, error,
4444
) {
45+
mode, err := certmanager.ResolveIssuerMode(ctx, r.Client, cluster)
46+
if err != nil {
47+
return nil, errors.Wrap(err, "failed to resolve issuer mode")
48+
}
49+
if mode == certmanager.IssuerModeExternal {
50+
return nil, nil
51+
}
52+
4553
const keyCertificate, keyPrivateKey = "root.crt", "root.key"
4654

4755
// K8SPG-553
4856
existing := &corev1.Secret{
4957
ObjectMeta: naming.PostgresRootCASecret(cluster),
5058
}
59+
if mode == certmanager.IssuerModeManagedCluster {
60+
existing.ObjectMeta = naming.ClusterCACertSecret(cluster, certmanager.CertManagerNamespace())
61+
}
5162

5263
privateKey := keyPrivateKey
5364
certificateKey := keyCertificate
@@ -64,11 +75,11 @@ func (r *Reconciler) reconcileRootCertificate(
6475
}
6576
}
6677

67-
err := errors.WithStack(
78+
err = errors.WithStack(
6879
r.Client.Get(ctx, client.ObjectKeyFromObject(existing), existing))
6980
// K8SPG-555: we need to check ca certificate from old operator versions
7081
// TODO: remove when 2.4.0 will become unsupported
71-
if k8serrors.IsNotFound(err) {
82+
if k8serrors.IsNotFound(err) && mode == certmanager.IssuerModeManagedNamespaced {
7283
nn := client.ObjectKeyFromObject(existing)
7384
nn.Name = naming.RootCertSecret
7485
err = errors.WithStack(
@@ -104,6 +115,12 @@ func (r *Reconciler) reconcileRootCertificate(
104115
return nil, errors.New("waiting for cert-manager to issue a valid CA certificate")
105116
}
106117

118+
if mode == certmanager.IssuerModeManagedCluster {
119+
// The cluster-scoped CA cert/secret is entirely cert-manager's
120+
// responsibility; there is no internal-PKI fallback for it.
121+
return nil, errors.New("waiting for cert-manager to issue a valid CA certificate")
122+
}
123+
107124
root := &pki.RootCertificateAuthority{}
108125

109126
if err == nil {
@@ -186,8 +203,17 @@ func (r *Reconciler) reconcileCertManagerRootCertificate(
186203
return nil, errors.Wrap(err, "error applying CA certificate")
187204
}
188205

206+
mode, err := certmanager.ResolveIssuerMode(ctx, r.Client, cluster)
207+
if err != nil {
208+
return nil, errors.Wrap(err, "failed to resolve issuer mode")
209+
}
210+
secretMeta := naming.PostgresRootCASecret(cluster)
211+
if mode == certmanager.IssuerModeManagedCluster {
212+
secretMeta = naming.ClusterCACertSecret(cluster, certmanager.CertManagerNamespace())
213+
}
214+
189215
// Try to fetch the CA secret created by cert-manager.
190-
secret := &corev1.Secret{ObjectMeta: naming.PostgresRootCASecret(cluster)}
216+
secret := &corev1.Secret{ObjectMeta: secretMeta}
191217
if err := r.Client.Get(ctx, client.ObjectKeyFromObject(secret), secret); err != nil {
192218
if k8serrors.IsNotFound(err) {
193219
log.Info("waiting for cert-manager to issue CA certificate")
@@ -335,9 +361,14 @@ func (r *Reconciler) reconcileCertManagerClusterCertificate(
335361
) {
336362
c := r.CertManagerCtrlFunc(r.Client, r.Scheme, false)
337363

338-
err := c.ApplyIssuer(ctx, cluster)
364+
mode, err := certmanager.ResolveIssuerMode(ctx, r.Client, cluster)
339365
if err != nil {
340-
return nil, errors.Wrap(err, "failed to apply TLS issuer")
366+
return nil, errors.Wrap(err, "failed to resolve issuer mode")
367+
}
368+
if mode != certmanager.IssuerModeExternal {
369+
if err := c.ApplyIssuer(ctx, cluster); err != nil {
370+
return nil, errors.Wrap(err, "failed to apply TLS issuer")
371+
}
341372
}
342373

343374
primaryDNSNames, err := naming.ServiceDNSNames(ctx, primaryService, cluster.Spec.ClusterServiceDNSSuffix)
@@ -385,11 +416,27 @@ func (r *Reconciler) isRootCACertManagerManaged(ctx context.Context, cluster *v1
385416
return false, nil
386417
}
387418

419+
mode, err := certmanager.ResolveIssuerMode(ctx, r.Client, cluster)
420+
if err != nil {
421+
return false, errors.Wrap(err, "failed to resolve issuer mode")
422+
}
423+
388424
installed, err := r.isCertManagerInstalled(ctx, cluster.Namespace)
389-
if err != nil || !installed {
425+
if err != nil {
390426
return false, err
391427
}
392428

429+
if mode != certmanager.IssuerModeManagedNamespaced {
430+
if !installed {
431+
return false, errors.New("cert-manager is required when spec.tls.issuerConf is set")
432+
}
433+
return true, nil
434+
}
435+
436+
if !installed {
437+
return false, nil
438+
}
439+
393440
rootSecret := &corev1.Secret{ObjectMeta: naming.PostgresRootCASecret(cluster)}
394441
err = r.Client.Get(ctx, client.ObjectKeyFromObject(rootSecret), rootSecret)
395442
if err != nil {

0 commit comments

Comments
 (0)