Skip to content

Commit e6bce5a

Browse files
committed
Simplify the PKI implementation
The original implementation dynamically assigns functions that return errors so we can swap them under test. Errors from these calls are wrapped in sentinels so they can be identified at runtime. In practice, however, these errors are never examined. - Sentinel errors are removed. The "encoding/pem.Decode" function does not return errors, so we still generate our own in two places. - All "Parse" functions are removed and replaced by their "Unmarshal" equivalents. - Most "New" functions are removed. One remains to generate a fresh root CA certificate and private key pair. - IP addresses are removed. Fields on the "Certificate" and "PrivateKey" types are not exported, making them opaque to consumers except for the PEM marshaling methods. This provides a few benefits: - The algorithms for keys and signatures can change without affecting callers. - Certificates are parsed as they are generated and unmarshaled. Their values are always either zero or fully parsed. - The root CA is parsed once per reconcile loop rather than once per leaf. - Getter methods return copies so that certificate fields cannot change.
1 parent 18367c3 commit e6bce5a

25 files changed

+850
-1764
lines changed

internal/controller/postgrescluster/instance.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1363,7 +1363,7 @@ func (r *Reconciler) reconcileInstanceCertificates(
13631363
}
13641364
if err == nil {
13651365
err = pgbackrest.InstanceCertificates(ctx, cluster,
1366-
*root.Certificate, *leafCert.Certificate, *leafCert.PrivateKey,
1366+
root.Certificate, leafCert.Certificate, leafCert.PrivateKey,
13671367
instanceCerts)
13681368
}
13691369
if err == nil {

internal/controller/postgrescluster/patroni.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -352,22 +352,17 @@ func (r *Reconciler) reconcileReplicationSecret(
352352
err := errors.WithStack(client.IgnoreNotFound(
353353
r.Client.Get(ctx, client.ObjectKeyFromObject(existing), existing)))
354354

355-
clientLeaf := pki.NewLeafCertificate("", nil, nil)
356-
clientLeaf.DNSNames = []string{postgres.ReplicationUser}
357-
clientLeaf.CommonName = clientLeaf.DNSNames[0]
355+
clientLeaf := &pki.LeafCertificate{}
356+
_ = clientLeaf.Certificate.UnmarshalText(existing.Data[naming.ReplicationCert])
357+
_ = clientLeaf.PrivateKey.UnmarshalText(existing.Data[naming.ReplicationPrivateKey])
358358

359-
if data, ok := existing.Data[naming.ReplicationCert]; err == nil && ok {
360-
clientLeaf.Certificate, err = pki.ParseCertificate(data)
361-
err = errors.WithStack(err)
362-
}
363-
if data, ok := existing.Data[naming.ReplicationPrivateKey]; err == nil && ok {
364-
clientLeaf.PrivateKey, err = pki.ParsePrivateKey(data)
365-
err = errors.WithStack(err)
366-
}
359+
commonName := postgres.ReplicationUser
360+
dnsNames := []string{commonName}
367361

368362
// if there is an error or the client leaf certificate is bad, generate a new one
369363
if err != nil || pki.LeafCertIsBad(ctx, clientLeaf, rootCACert, cluster.Namespace) {
370-
err = errors.WithStack(clientLeaf.Generate(rootCACert))
364+
clientLeaf, err = rootCACert.GenerateLeafCertificate(commonName, dnsNames)
365+
err = errors.WithStack(err)
371366
}
372367

373368
intent := &corev1.Secret{ObjectMeta: naming.ReplicationClientCertSecret(cluster)}

internal/controller/postgrescluster/pgbackrest_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ func TestReconcilePGBackRest(t *testing.T) {
244244
Type: condition, Reason: "testing", Status: status})
245245
}
246246

247-
rootCA := pki.NewRootCertificateAuthority()
248-
assert.NilError(t, rootCA.Generate())
247+
rootCA, err := pki.NewRootCertificateAuthority()
248+
assert.NilError(t, err)
249249

250250
result, err := r.reconcilePGBackRest(ctx, postgresCluster, instances, rootCA)
251251
if err != nil || result != (reconcile.Result{}) {
@@ -1780,8 +1780,8 @@ func TestReconcilePostgresClusterDataSource(t *testing.T) {
17801780
t.Cleanup(func() { teardownManager(cancel, t) })
17811781

17821782
namespace := setupNamespace(t, tClient).Name
1783-
rootCA := pki.NewRootCertificateAuthority()
1784-
assert.NilError(t, rootCA.Generate())
1783+
rootCA, err := pki.NewRootCertificateAuthority()
1784+
assert.NilError(t, err)
17851785

17861786
type testResult struct {
17871787
configCount, jobCount, pvcCount int

internal/controller/postgrescluster/pki.go

Lines changed: 20 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,14 @@ func (r *Reconciler) reconcileRootCertificate(
5555
err := errors.WithStack(client.IgnoreNotFound(
5656
r.Client.Get(ctx, client.ObjectKeyFromObject(existing), existing)))
5757

58-
root := pki.NewRootCertificateAuthority()
59-
60-
if data, ok := existing.Data[keyCertificate]; err == nil && ok {
61-
root.Certificate, err = pki.ParseCertificate(data)
62-
err = errors.WithStack(err)
63-
}
64-
if data, ok := existing.Data[keyPrivateKey]; err == nil && ok {
65-
root.PrivateKey, err = pki.ParsePrivateKey(data)
66-
err = errors.WithStack(err)
67-
}
58+
root := &pki.RootCertificateAuthority{}
59+
_ = root.Certificate.UnmarshalText(existing.Data[keyCertificate])
60+
_ = root.PrivateKey.UnmarshalText(existing.Data[keyPrivateKey])
6861

6962
// if there is an error or the root CA is bad, generate a new one
7063
if err != nil || pki.RootCAIsBad(root) {
71-
err = errors.WithStack(root.Generate())
64+
root, err = pki.NewRootCertificateAuthority()
65+
err = errors.WithStack(err)
7266
}
7367

7468
intent := &corev1.Secret{}
@@ -133,22 +127,17 @@ func (r *Reconciler) reconcileClusterCertificate(
133127
err := errors.WithStack(client.IgnoreNotFound(
134128
r.Client.Get(ctx, client.ObjectKeyFromObject(existing), existing)))
135129

136-
leaf := pki.NewLeafCertificate("", nil, nil)
137-
leaf.DNSNames = naming.ServiceDNSNames(ctx, primaryService)
138-
leaf.CommonName = leaf.DNSNames[0] // FQDN
130+
leaf := &pki.LeafCertificate{}
131+
_ = leaf.Certificate.UnmarshalText(existing.Data[keyCertificate])
132+
_ = leaf.PrivateKey.UnmarshalText(existing.Data[keyPrivateKey])
139133

140-
if data, ok := existing.Data[keyCertificate]; err == nil && ok {
141-
leaf.Certificate, err = pki.ParseCertificate(data)
142-
err = errors.WithStack(err)
143-
}
144-
if data, ok := existing.Data[keyPrivateKey]; err == nil && ok {
145-
leaf.PrivateKey, err = pki.ParsePrivateKey(data)
146-
err = errors.WithStack(err)
147-
}
134+
dnsNames := naming.ServiceDNSNames(ctx, primaryService)
135+
dnsFQDN := dnsNames[0]
148136

149137
// if there is an error or the leaf certificate is bad, generate a new one
150138
if err != nil || pki.LeafCertIsBad(ctx, leaf, rootCACert, cluster.Namespace) {
151-
err = errors.WithStack(leaf.Generate(rootCACert))
139+
leaf, err = rootCACert.GenerateLeafCertificate(dnsFQDN, dnsNames)
140+
err = errors.WithStack(err)
152141
}
153142

154143
intent := &corev1.Secret{ObjectMeta: naming.PostgresTLSSecret(cluster)}
@@ -212,24 +201,19 @@ func (*Reconciler) instanceCertificate(
212201
var err error
213202
const keyCertificate, keyPrivateKey = "dns.crt", "dns.key"
214203

204+
leaf := &pki.LeafCertificate{}
205+
_ = leaf.Certificate.UnmarshalText(existing.Data[keyCertificate])
206+
_ = leaf.PrivateKey.UnmarshalText(existing.Data[keyPrivateKey])
207+
215208
// RFC 2818 states that the certificate DNS names must be used to verify
216209
// HTTPS identity.
217-
leaf := pki.NewLeafCertificate("", nil, nil)
218-
leaf.DNSNames = naming.InstancePodDNSNames(ctx, instance)
219-
leaf.CommonName = leaf.DNSNames[0] // FQDN
220-
221-
if data, ok := existing.Data[keyCertificate]; err == nil && ok {
222-
leaf.Certificate, err = pki.ParseCertificate(data)
223-
err = errors.WithStack(err)
224-
}
225-
if data, ok := existing.Data[keyPrivateKey]; err == nil && ok {
226-
leaf.PrivateKey, err = pki.ParsePrivateKey(data)
227-
err = errors.WithStack(err)
228-
}
210+
dnsNames := naming.InstancePodDNSNames(ctx, instance)
211+
dnsFQDN := dnsNames[0]
229212

230213
// if there is an error or the leaf certificate is bad, generate a new one
231214
if err != nil || pki.LeafCertIsBad(ctx, leaf, rootCACert, instance.Namespace) {
232-
err = errors.WithStack(leaf.Generate(rootCACert))
215+
leaf, err = rootCACert.GenerateLeafCertificate(dnsFQDN, dnsNames)
216+
err = errors.WithStack(err)
233217
}
234218

235219
if err == nil {

internal/controller/postgrescluster/pki_test.go

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package postgrescluster
1919

2020
import (
2121
"context"
22-
"crypto/x509"
2322
"fmt"
2423
"os"
2524
"reflect"
@@ -145,7 +144,7 @@ func TestReconcileCerts(t *testing.T) {
145144
assert.NilError(t, err)
146145

147146
// assert returned certificate matches the one created earlier
148-
assert.DeepEqual(t, fromSecret, initialRoot.Certificate)
147+
assert.DeepEqual(t, *fromSecret, initialRoot.Certificate)
149148
})
150149

151150
t.Run("root certificate changes", func(t *testing.T) {
@@ -166,10 +165,10 @@ func TestReconcileCerts(t *testing.T) {
166165
assert.NilError(t, err)
167166

168167
// check that the cert from the secret does not equal the initial certificate
169-
assert.Assert(t, !fromSecret.Equal(*initialRoot.Certificate))
168+
assert.Assert(t, !fromSecret.Equal(initialRoot.Certificate))
170169

171170
// check that the returned cert matches the cert from the secret
172-
assert.DeepEqual(t, fromSecret, returnedRoot.Certificate)
171+
assert.DeepEqual(t, *fromSecret, returnedRoot.Certificate)
173172
})
174173

175174
})
@@ -201,13 +200,11 @@ func TestReconcileCerts(t *testing.T) {
201200
initialLeafCert, err := r.instanceCertificate(ctx, instance, existing, intent, initialRoot)
202201
assert.NilError(t, err)
203202

204-
certificate, err := pki.ParseCertificate(intent.Data["dns.crt"])
205-
assert.NilError(t, err)
206-
privateKey, err := pki.ParsePrivateKey(intent.Data["dns.key"])
207-
assert.NilError(t, err)
203+
fromSecret := &pki.LeafCertificate{}
204+
assert.NilError(t, fromSecret.Certificate.UnmarshalText(intent.Data["dns.crt"]))
205+
assert.NilError(t, fromSecret.PrivateKey.UnmarshalText(intent.Data["dns.key"]))
208206

209-
assert.DeepEqual(t, certificate, initialLeafCert.Certificate)
210-
assert.DeepEqual(t, privateKey, initialLeafCert.PrivateKey)
207+
assert.DeepEqual(t, fromSecret, initialLeafCert)
211208
})
212209

213210
t.Run("check that the leaf certs update when root changes", func(t *testing.T) {
@@ -235,14 +232,14 @@ func TestReconcileCerts(t *testing.T) {
235232
assert.NilError(t, err)
236233

237234
// assert old leaf cert does not match the newly reconciled one
238-
assert.Assert(t, !initialLeaf.Certificate.Equal(*newLeaf.Certificate))
235+
assert.Assert(t, !initialLeaf.Certificate.Equal(newLeaf.Certificate))
239236

240237
// 'reconcile' the certificate when the secret does not change. The returned leaf certificate should not change
241238
newLeaf2, err := r.instanceCertificate(ctx, instance, intent, intent, newRootCert)
242239
assert.NilError(t, err)
243240

244241
// check that the leaf cert did not change after another reconciliation
245-
assert.DeepEqual(t, newLeaf2.Certificate, newLeaf.Certificate)
242+
assert.DeepEqual(t, newLeaf2, newLeaf)
246243

247244
})
248245

@@ -364,17 +361,16 @@ func TestReconcileCerts(t *testing.T) {
364361

365362
assert.Assert(t, !reflect.DeepEqual(initialClusterCertSecret, newClusterCertSecret))
366363

367-
pkiCert, err := pki.ParseCertificate(newClusterCertSecret.Data["tls.crt"])
368-
assert.NilError(t, err)
364+
leaf := &pki.LeafCertificate{}
365+
assert.NilError(t, leaf.Certificate.UnmarshalText(newClusterCertSecret.Data["tls.crt"]))
366+
assert.NilError(t, leaf.PrivateKey.UnmarshalText(newClusterCertSecret.Data["tls.key"]))
369367

370-
x509Cert, err := x509.ParseCertificate(pkiCert.Certificate)
371-
assert.NilError(t, err)
372368
assert.Assert(t,
373-
strings.HasPrefix(x509Cert.Subject.CommonName, "the-primary."+namespace+".svc."),
374-
"got %q", x509Cert.Subject.CommonName)
369+
strings.HasPrefix(leaf.Certificate.CommonName(), "the-primary."+namespace+".svc."),
370+
"got %q", leaf.Certificate.CommonName())
375371

376-
if assert.Check(t, len(x509Cert.DNSNames) > 1) {
377-
assert.DeepEqual(t, x509Cert.DNSNames[1:], []string{
372+
if dnsNames := leaf.Certificate.DNSNames(); assert.Check(t, len(dnsNames) > 1) {
373+
assert.DeepEqual(t, dnsNames[1:], []string{
378374
"the-primary." + namespace + ".svc",
379375
"the-primary." + namespace,
380376
"the-primary",
@@ -404,9 +400,6 @@ func getCertFromSecret(
404400
}
405401

406402
// parse the cert from binary encoded data
407-
if fromSecret, err := pki.ParseCertificate(secretCRT); fromSecret == nil || err != nil {
408-
return nil, fmt.Errorf("error parsing %s", dataKey)
409-
} else {
410-
return fromSecret, nil
411-
}
403+
fromSecret := &pki.Certificate{}
404+
return fromSecret, fromSecret.UnmarshalText(secretCRT)
412405
}

internal/patroni/certificates.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const (
3030
)
3131

3232
// certAuthorities encodes roots in a format suitable for Patroni's TLS verification.
33-
func certAuthorities(roots ...*pki.Certificate) ([]byte, error) {
33+
func certAuthorities(roots ...pki.Certificate) ([]byte, error) {
3434
var out []byte
3535

3636
for i := range roots {
@@ -46,7 +46,7 @@ func certAuthorities(roots ...*pki.Certificate) ([]byte, error) {
4646

4747
// certFile encodes cert and key as a combination suitable for
4848
// Patroni's TLS identification. It can be used by both the client and the server.
49-
func certFile(key *pki.PrivateKey, cert *pki.Certificate) ([]byte, error) {
49+
func certFile(key pki.PrivateKey, cert pki.Certificate) ([]byte, error) {
5050
var out []byte
5151

5252
if b, err := key.MarshalText(); err == nil {

internal/patroni/certificates_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ BY8V4Ag=
3838
-----END CERTIFICATE-----`
3939

4040
func TestCertAuthorities(t *testing.T) {
41-
root, err := pki.ParseCertificate([]byte(rootPEM))
42-
assert.NilError(t, err)
41+
root := pki.Certificate{}
42+
assert.NilError(t, root.UnmarshalText([]byte(rootPEM)))
4343

4444
data, err := certAuthorities(root)
4545
assert.NilError(t, err)
@@ -49,11 +49,11 @@ func TestCertAuthorities(t *testing.T) {
4949
}
5050

5151
func TestCertFile(t *testing.T) {
52-
root := pki.NewRootCertificateAuthority()
53-
assert.NilError(t, root.Generate())
52+
root, err := pki.NewRootCertificateAuthority()
53+
assert.NilError(t, err)
5454

55-
instance := pki.NewLeafCertificate("instance.pod-dns", nil, nil)
56-
assert.NilError(t, instance.Generate(root))
55+
instance, err := root.GenerateLeafCertificate("instance.pod-dns", nil)
56+
assert.NilError(t, err)
5757

5858
data, err := certFile(instance.PrivateKey, instance.Certificate)
5959
assert.NilError(t, err)

internal/patroni/reconcile.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ func InstanceConfigMap(ctx context.Context,
7474

7575
// InstanceCertificates populates the shared Secret with certificates needed to run Patroni.
7676
func InstanceCertificates(ctx context.Context,
77-
inRoot *pki.Certificate, inDNS *pki.Certificate,
78-
inDNSKey *pki.PrivateKey, outInstanceCertificates *corev1.Secret,
77+
inRoot pki.Certificate, inDNS pki.Certificate,
78+
inDNSKey pki.PrivateKey, outInstanceCertificates *corev1.Secret,
7979
) error {
8080
initialize.ByteMap(&outInstanceCertificates.Data)
8181

internal/patroni/reconcile_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ func TestClusterConfigMap(t *testing.T) {
5555
func TestReconcileInstanceCertificates(t *testing.T) {
5656
t.Parallel()
5757

58-
root := pki.NewRootCertificateAuthority()
59-
assert.NilError(t, root.Generate(), "bug in test")
58+
root, err := pki.NewRootCertificateAuthority()
59+
assert.NilError(t, err, "bug in test")
6060

61-
leaf := pki.NewLeafCertificate("any", nil, nil)
62-
assert.NilError(t, leaf.Generate(root), "bug in test")
61+
leaf, err := root.GenerateLeafCertificate("any", nil)
62+
assert.NilError(t, err, "bug in test")
6363

6464
ctx := context.Background()
6565
secret := new(corev1.Secret)

0 commit comments

Comments
 (0)