Skip to content

Commit 0100a75

Browse files
committed
Create TLS-capable clients if security enabled
Signed-off-by: Timofei Larkin <[email protected]>
1 parent 0716ee0 commit 0100a75

File tree

1 file changed

+70
-2
lines changed

1 file changed

+70
-2
lines changed

internal/controller/factory/etcd_client.go

Lines changed: 70 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"
@@ -57,8 +61,72 @@ func configFromCluster(ctx context.Context, cluster *v1alpha1.EtcdCluster, cli c
5761
}
5862
}
5963
for name := range names {
60-
urls = append(urls, fmt.Sprintf("%s.%s.%s:%s", name, ep.Name, ep.Namespace, "2379"))
64+
urls = append(urls, fmt.Sprintf("%s.%s.%s.svc:%s", name, ep.Name, ep.Namespace, "2379"))
6165
}
66+
etcdClient := clientv3.Config{Endpoints: urls, DialTimeout: 5 * time.Second}
6267

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

0 commit comments

Comments
 (0)