@@ -2,7 +2,11 @@ package factory
22
33import (
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