Skip to content

Commit 4bc99b5

Browse files
authored
Bugs: wrong address and memory leak (#285)
* Several `defer c.Close()` added to clean up etcd clients after reconciliation. * DNS names targeting etcd cluster members fixed to resolve properly. * TLS capability added to etcd clients created during reconciliation.
2 parents b6e4ea9 + d76c473 commit 4bc99b5

File tree

2 files changed

+88
-5
lines changed

2 files changed

+88
-5
lines changed

internal/controller/etcdcluster_controller.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,18 @@ func (r *EtcdClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
111111
if err != nil {
112112
return ctrl.Result{}, err
113113
}
114+
defer func() {
115+
if clusterClient != nil {
116+
// nolint:errcheck
117+
clusterClient.Close()
118+
}
119+
for i := range singleClients {
120+
if singleClients[i] != nil {
121+
// nolint:errcheck
122+
singleClients[i].Close()
123+
}
124+
}
125+
}()
114126
state.endpointsFound = clusterClient != nil && singleClients != nil
115127

116128
if !state.endpointsFound {
@@ -286,6 +298,8 @@ func (r *EtcdClusterReconciler) ensureConditionalClusterObjects(
286298
}
287299

288300
// updateStatusOnErr wraps error and updates EtcdCluster status
301+
// TODO: refactor this so the linter doesn't complain
302+
// nolint:unparam
289303
func (r *EtcdClusterReconciler) updateStatusOnErr(ctx context.Context, cluster *etcdaenixiov1alpha1.EtcdCluster, err error) (ctrl.Result, error) {
290304
// The function 'updateStatusOnErr' will always return non-nil error. Hence, the ctrl.Result will always be ignored.
291305
// Therefore, the ctrl.Result returned by 'updateStatus' function can be discarded.
@@ -341,9 +355,8 @@ func (r *EtcdClusterReconciler) configureAuth(ctx context.Context, cluster *etcd
341355
return err
342356
}
343357

344-
defer func() {
345-
err = cli.Close()
346-
}()
358+
// nolint:errcheck
359+
defer cli.Close()
347360

348361
err = testMemberList(ctx, cli)
349362
if err != nil {

internal/controller/factory/etcd_client.go

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package factory
22

33
import (
44
"context"
5+
"crypto/tls"
6+
"crypto/x509"
7+
"encoding/pem"
58
"fmt"
9+
"time"
610

711
"github.com/aenix-io/etcd-operator/api/v1alpha1"
812
clientv3 "go.etcd.io/etcd/client/v3"
@@ -29,6 +33,8 @@ func NewEtcdClientSet(ctx context.Context, cluster *v1alpha1.EtcdCluster, cli cl
2933
cfg.Endpoints = []string{ep}
3034
singleClients[i], err = clientv3.New(cfg)
3135
if err != nil {
36+
// nolint:errcheck
37+
clusterClient.Close()
3238
return nil, nil, fmt.Errorf("error building etcd single-endpoint client for endpoint %s: %w", ep, err)
3339
}
3440
}
@@ -56,8 +62,72 @@ func configFromCluster(ctx context.Context, cluster *v1alpha1.EtcdCluster, cli c
5662
}
5763
}
5864
for name := range names {
59-
urls = append(urls, fmt.Sprintf("%s:%s", name, "2379"))
65+
urls = append(urls, fmt.Sprintf("%s.%s.%s.svc:%s", name, ep.Name, ep.Namespace, "2379"))
6066
}
67+
etcdClient := clientv3.Config{Endpoints: urls, DialTimeout: 5 * time.Second}
6168

62-
return clientv3.Config{Endpoints: urls}, nil
69+
if s := cluster.Spec.Security; s != nil {
70+
if s.TLS.ClientSecret == "" {
71+
return clientv3.Config{}, fmt.Errorf("cannot configure client: security enabled, but client certificates not set")
72+
}
73+
clientSecret := &v1.Secret{}
74+
err := cli.Get(ctx, types.NamespacedName{Namespace: cluster.Namespace, Name: s.TLS.ClientSecret}, clientSecret)
75+
if err != nil {
76+
return clientv3.Config{}, fmt.Errorf("cannot configure client: failed to get client certificate: %w", err)
77+
}
78+
cert, err := parseTLSSecret(clientSecret)
79+
if err != nil {
80+
return clientv3.Config{}, fmt.Errorf("cannot configure client: failed to extract keypair from client secret: %w", err)
81+
}
82+
etcdClient.TLS = &tls.Config{}
83+
etcdClient.TLS.Certificates = []tls.Certificate{cert}
84+
etcdClient.TLS.GetClientCertificate = func(cri *tls.CertificateRequestInfo) (*tls.Certificate, error) {
85+
return &etcdClient.TLS.Certificates[0], nil
86+
}
87+
caSecret := &v1.Secret{}
88+
err = cli.Get(ctx, types.NamespacedName{Namespace: cluster.Namespace, Name: s.TLS.ServerSecret}, caSecret)
89+
if err != nil {
90+
return clientv3.Config{}, fmt.Errorf("cannot configure client: failed to get server CA secret: %w", err)
91+
}
92+
ca, err := parseTLSSecretCA(caSecret)
93+
if err != nil {
94+
return clientv3.Config{}, fmt.Errorf("cannot configure client: failed to extract Root CA from server secret: %w", err)
95+
}
96+
pool := x509.NewCertPool()
97+
pool.AddCert(ca)
98+
etcdClient.TLS.RootCAs = pool
99+
}
100+
101+
return etcdClient, nil
102+
}
103+
104+
func parseTLSSecret(secret *v1.Secret) (cert tls.Certificate, err error) {
105+
certPem, ok := secret.Data["tls.crt"]
106+
if !ok {
107+
return tls.Certificate{}, fmt.Errorf("tls.crt not found in secret")
108+
}
109+
keyPem, ok := secret.Data["tls.key"]
110+
if !ok {
111+
return tls.Certificate{}, fmt.Errorf("tls.key not found in secret")
112+
}
113+
cert, err = tls.X509KeyPair(certPem, keyPem)
114+
if err != nil {
115+
err = fmt.Errorf("failed to load x509 key pair: %w", err)
116+
}
117+
return
118+
}
119+
120+
func parseTLSSecretCA(secret *v1.Secret) (ca *x509.Certificate, err error) {
121+
caPemBytes, ok := secret.Data["ca.crt"]
122+
if !ok {
123+
err = fmt.Errorf("secret does not contain ca.crt")
124+
return
125+
}
126+
block, _ := pem.Decode(caPemBytes)
127+
if block == nil {
128+
err = fmt.Errorf("failed to decode PEM bytes")
129+
return
130+
}
131+
ca, err = x509.ParseCertificate(block.Bytes)
132+
return
63133
}

0 commit comments

Comments
 (0)