11package auto
22
33import (
4+ "bytes"
45 "context"
6+ "crypto"
7+ "crypto/ecdsa"
8+ "crypto/ed25519"
9+ "crypto/rsa"
10+ "crypto/tls"
11+ "crypto/x509"
12+ "encoding/pem"
13+ "errors"
14+ "fmt"
15+ "log"
16+ "os"
17+ "time"
518
19+ "go.uber.org/zap"
20+ corev1 "k8s.io/api/core/v1"
21+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
22+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
623 "sigs.k8s.io/controller-runtime/pkg/client"
724
825 interfaces "go.etcd.io/etcd-operator/pkg/certificate/interfaces"
26+ "go.etcd.io/etcd/client/pkg/v3/transport"
27+ )
28+
29+ const (
30+ DefaultValidity = 365 * 24 * time .Hour
931)
1032
1133type AutoCertProvider struct {
@@ -22,23 +44,252 @@ func New(c client.Client) interfaces.Provider {
2244
2345func (ac * AutoCertProvider ) EnsureCertificateSecret (ctx context.Context , secretName , namespace string ,
2446 cfg * interfaces.Config ) error {
47+ var secret corev1.Secret
48+ err := ac .Get (ctx , client.ObjectKey {Name : secretName , Namespace : namespace }, & secret )
49+ if err != nil {
50+ if k8serrors .IsNotFound (err ) {
51+ err := ac .createNewSecret (ctx , secretName , namespace , cfg )
52+ if err != nil {
53+ return err
54+ }
55+ } else {
56+ return err
57+ }
58+ }
59+
60+ err = ac .ValidateCertificateSecret (ctx , secretName , namespace , cfg )
61+ if err != nil {
62+ if k8serrors .IsNotFound (err ) {
63+ return err
64+ } else {
65+ return fmt .Errorf ("invalid certificate secret: %s present in namespace: %s, please delete and try again.\n Error: %s" ,
66+ secretName , namespace , err )
67+ }
68+ }
69+
70+ log .Printf ("Valid certificate secret: %s already present in namespace: %s" , secretName , namespace )
2571 return nil
2672}
2773
2874func (ac * AutoCertProvider ) ValidateCertificateSecret (ctx context.Context , secretName , namespace string ,
2975 _ * interfaces.Config ) error {
76+ var err error
77+ secret := & corev1.Secret {}
78+ err = ac .Get (ctx , client.ObjectKey {Name : secretName , Namespace : namespace }, secret )
79+ if err != nil && k8serrors .IsNotFound (err ) {
80+ for try := range interfaces .MaxRetries {
81+ // Wait for the certificate secret to get created
82+ log .Printf ("Valid certificate secret: retry attempt %v, after %v, error: %v" , try + 1 , interfaces .RetryInterval , err )
83+ time .Sleep (interfaces .RetryInterval )
84+ err = ac .Get (ctx , client.ObjectKey {Name : secretName , Namespace : namespace }, secret )
85+ if err == nil {
86+ break
87+ }
88+ }
89+ if err != nil {
90+ return err
91+ }
92+ } else {
93+ return err
94+ }
95+
96+ certificateData , exists := secret .Data ["tls.crt" ]
97+ if ! exists {
98+ return interfaces .ErrTLSCert
99+ }
100+
101+ decodeCertificatePem , _ := pem .Decode (certificateData )
102+ if decodeCertificatePem == nil {
103+ return interfaces .ErrDecodeCert
104+ }
105+
106+ privateKeyData , keyExists := secret .Data ["tls.key" ]
107+ if ! keyExists {
108+ return interfaces .ErrTLSKey
109+ }
110+
111+ parseCert , err := x509 .ParseCertificate (decodeCertificatePem .Bytes )
112+ if err != nil {
113+ return fmt .Errorf ("failed to parse certificate: %w" , err )
114+ }
115+
116+ if parseCert .NotAfter .Before (time .Now ()) {
117+ return interfaces .ErrCertExpired
118+ }
119+
120+ privateKey , err := parsePrivateKey (privateKeyData )
121+ if err != nil {
122+ return fmt .Errorf ("failed to parse private key: %w" , err )
123+ }
124+
125+ if checkKeyPairErr := checkKeyPair (parseCert , privateKey ); checkKeyPairErr != nil {
126+ return fmt .Errorf ("private key does not match certificate: %w" , checkKeyPairErr )
127+ }
128+
30129 return nil
31130}
32131
33132func (ac * AutoCertProvider ) DeleteCertificateSecret (ctx context.Context , secretName , namespace string ) error {
34- return nil
133+ var secret corev1.Secret
134+ err := ac .Get (ctx , client.ObjectKey {Name : secretName , Namespace : namespace }, & secret )
135+ if k8serrors .IsNotFound (err ) {
136+ return nil
137+ }
138+ if err != nil {
139+ return err
140+ }
141+ return ac .Delete (ctx , & secret )
35142}
36143
37144func (ac * AutoCertProvider ) RevokeCertificate (ctx context.Context , secretName string , namespace string ) error {
38- return nil
145+ return ac . DeleteCertificateSecret ( ctx , secretName , namespace )
39146}
40147
41148func (ac * AutoCertProvider ) GetCertificateConfig (ctx context.Context ,
42149 secretName , namespace string ) (* interfaces.Config , error ) {
150+ var autoCertSecret corev1.Secret
151+ err := ac .Get (ctx , client.ObjectKey {Name : secretName , Namespace : namespace }, & autoCertSecret )
152+ if err != nil {
153+ return nil , fmt .Errorf ("failed to get certificate: %w" , err )
154+ }
155+
43156 return & interfaces.Config {}, nil
44157}
158+
159+ // parsePrivateKey parses the private key from the PEM-encoded data.
160+ func parsePrivateKey (privateKeyData []byte ) (crypto.PrivateKey , error ) {
161+ block , _ := pem .Decode (privateKeyData )
162+ if block == nil {
163+ return nil , errors .New ("failed to decode private key: invalid PEM" )
164+ }
165+
166+ // Parse the private key from the PEM block
167+ privateKey , err := x509 .ParseECPrivateKey (block .Bytes )
168+ if err != nil {
169+ if err != nil {
170+ return nil , fmt .Errorf ("failed to parse private key: %w" , err )
171+ }
172+ }
173+
174+ return privateKey , nil
175+ }
176+
177+ // checkKeyPair checks if the private key matches the certificate by validating the public key
178+ func checkKeyPair (cert * x509.Certificate , privateKey crypto.PrivateKey ) error {
179+ switch key := privateKey .(type ) {
180+ case * rsa.PrivateKey :
181+ pub , ok := cert .PublicKey .(* rsa.PublicKey )
182+ if ! ok || ! key .PublicKey .Equal (pub ) {
183+ return interfaces .ErrRSAKeyPair
184+ }
185+ case * ecdsa.PrivateKey :
186+ pub , ok := cert .PublicKey .(* ecdsa.PublicKey )
187+ if ! ok || ! key .PublicKey .Equal (pub ) {
188+ return interfaces .ErrECDSAKeyPair
189+ }
190+ case * ed25519.PrivateKey :
191+ pub , ok := cert .PublicKey .(ed25519.PublicKey )
192+ if ! ok || ! bytes .Equal (key .Public ().(ed25519.PublicKey ), pub ) {
193+ return interfaces .ErrED25519KeyPair
194+ }
195+ default :
196+ return fmt .Errorf ("unsupported private key type: %T" , key )
197+ }
198+
199+ return nil
200+ }
201+
202+ // createNewSecret generates or updates a Kubernetes TLS Secret
203+ // with a self-signed certificate in the specified namespace.
204+ // DNSNames and IPAddresses if not user-defined, will be set to default value in runtime:
205+ // fmt.Sprintf("%s-%d.%s.%s.svc.cluster.local", ec.Name, index, ec.Name, ec.Namespace)
206+ // minimum validityDuration is 365 days
207+ // as per: https://github.com/etcd-io/etcd/blob/main/client/pkg/transport/listener.go#L275
208+ func (ac * AutoCertProvider ) createNewSecret (ctx context.Context , secretName , namespace string ,
209+ cfg * interfaces.Config ) error {
210+ validity := DefaultValidity
211+ if cfg .ValidityDuration != 0 {
212+ validity = cfg .ValidityDuration * time .Hour
213+ }
214+
215+ tmpDir , err := os .MkdirTemp ("" , fmt .Sprintf ("etcd-auto-cert-%s-*" , secretName ))
216+ if err != nil {
217+ return fmt .Errorf ("failed to create temp dir for certificate generation: %w" , err )
218+ }
219+
220+ defer func () {
221+ _ = os .RemoveAll (tmpDir )
222+ }()
223+
224+ var hosts []string
225+ if cfg != nil {
226+ if cfg .CommonName != "" {
227+ hosts = append (hosts , cfg .CommonName )
228+ }
229+ if len (cfg .AltNames .DNSNames ) > 0 {
230+ hosts = append (hosts , cfg .AltNames .DNSNames ... )
231+ }
232+ }
233+
234+ tlsInfo , selfCertErr := transport .SelfCert (zap .NewNop (), tmpDir , hosts , uint (validity / DefaultValidity ))
235+ if selfCertErr != nil {
236+ return fmt .Errorf ("certificate creation via transport.SelfCert failed: %w" , selfCertErr )
237+ }
238+
239+ certPath := tlsInfo .CertFile
240+ keyPath := tlsInfo .KeyFile
241+ caPath := tlsInfo .TrustedCAFile
242+
243+ certPEM , err := os .ReadFile (certPath )
244+ if err != nil {
245+ return fmt .Errorf ("failed to read generated cert file %s: %w" , certPath , err )
246+ }
247+
248+ keyPEM , err := os .ReadFile (keyPath )
249+ if err != nil {
250+ return fmt .Errorf ("failed to read generated key file %s: %w" , keyPath , err )
251+ }
252+
253+ caPEM , err := os .ReadFile (caPath )
254+ if err != nil || len (caPEM ) == 0 {
255+ // use certPEM when CA file is not found or empty
256+ caPEM = certPEM
257+ }
258+
259+ // Validate cert and key pair
260+ if _ , err := tls .X509KeyPair (certPEM , keyPEM ); err != nil {
261+ return fmt .Errorf ("generated keypair invalid: %w" , err )
262+ }
263+
264+ secret := & corev1.Secret {
265+ ObjectMeta : metav1.ObjectMeta {
266+ Name : secretName ,
267+ Namespace : namespace ,
268+ },
269+ Type : corev1 .SecretTypeTLS ,
270+ Data : map [string ][]byte {
271+ "tls.crt" : certPEM ,
272+ "tls.key" : keyPEM ,
273+ "ca.crt" : caPEM ,
274+ },
275+ }
276+
277+ // Create or Update certificate Secret
278+ if err := ac .Create (ctx , secret ); err != nil {
279+ if k8serrors .IsAlreadyExists (err ) {
280+ var existing corev1.Secret
281+ if getErr := ac .Get (ctx , client.ObjectKey {Name : secretName , Namespace : namespace }, & existing ); getErr != nil {
282+ return fmt .Errorf ("failed to get existing secret for update: %w" , getErr )
283+ }
284+ existing .Data = secret .Data
285+
286+ if updateErr := ac .Update (ctx , & existing ); updateErr != nil {
287+ return fmt .Errorf ("failed to update existing secret: %w" , updateErr )
288+ }
289+ return nil
290+ }
291+ return fmt .Errorf ("failed to create secret: %w" , err )
292+ }
293+
294+ return nil
295+ }
0 commit comments