1
16
17 package certificate
18
19 import (
20 "crypto/tls"
21 "crypto/x509"
22 "encoding/pem"
23 "fmt"
24 "os"
25 "path/filepath"
26 "time"
27
28 certutil "k8s.io/client-go/util/cert"
29 "k8s.io/klog/v2"
30 )
31
32 const (
33 keyExtension = ".key"
34 certExtension = ".crt"
35 pemExtension = ".pem"
36 currentPair = "current"
37 updatedPair = "updated"
38 )
39
40 type fileStore struct {
41 pairNamePrefix string
42 certDirectory string
43 keyDirectory string
44 certFile string
45 keyFile string
46 }
47
48
49
50 type FileStore interface {
51 Store
52
53
54 CurrentPath() string
55 }
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 func NewFileStore(
71 pairNamePrefix string,
72 certDirectory string,
73 keyDirectory string,
74 certFile string,
75 keyFile string) (FileStore, error) {
76
77 s := fileStore{
78 pairNamePrefix: pairNamePrefix,
79 certDirectory: certDirectory,
80 keyDirectory: keyDirectory,
81 certFile: certFile,
82 keyFile: keyFile,
83 }
84 if err := s.recover(); err != nil {
85 return nil, err
86 }
87 return &s, nil
88 }
89
90
91 func (s *fileStore) CurrentPath() string {
92 return filepath.Join(s.certDirectory, s.filename(currentPair))
93 }
94
95
96
97 func (s *fileStore) recover() error {
98
99 currentPath := filepath.Join(s.certDirectory, s.filename(currentPair))
100 if exists, err := fileExists(currentPath); err != nil {
101 return err
102 } else if exists {
103 return nil
104 }
105
106
107
108 updatedPath := filepath.Join(s.certDirectory, s.filename(updatedPair))
109 if fi, err := os.Lstat(updatedPath); err != nil {
110 if os.IsNotExist(err) {
111 return nil
112 }
113 return err
114 } else if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
115 return fmt.Errorf("expected %q to be a symlink but it is a file", updatedPath)
116 }
117
118
119 if err := os.Rename(updatedPath, currentPath); err != nil {
120 return fmt.Errorf("unable to rename %q to %q: %v", updatedPath, currentPath, err)
121 }
122 return nil
123 }
124
125 func (s *fileStore) Current() (*tls.Certificate, error) {
126 pairFile := filepath.Join(s.certDirectory, s.filename(currentPair))
127 if pairFileExists, err := fileExists(pairFile); err != nil {
128 return nil, err
129 } else if pairFileExists {
130 klog.Infof("Loading cert/key pair from %q.", pairFile)
131 return loadFile(pairFile)
132 }
133
134 certFileExists, err := fileExists(s.certFile)
135 if err != nil {
136 return nil, err
137 }
138 keyFileExists, err := fileExists(s.keyFile)
139 if err != nil {
140 return nil, err
141 }
142 if certFileExists && keyFileExists {
143 klog.Infof("Loading cert/key pair from (%q, %q).", s.certFile, s.keyFile)
144 return loadX509KeyPair(s.certFile, s.keyFile)
145 }
146
147 c := filepath.Join(s.certDirectory, s.pairNamePrefix+certExtension)
148 k := filepath.Join(s.keyDirectory, s.pairNamePrefix+keyExtension)
149 certFileExists, err = fileExists(c)
150 if err != nil {
151 return nil, err
152 }
153 keyFileExists, err = fileExists(k)
154 if err != nil {
155 return nil, err
156 }
157 if certFileExists && keyFileExists {
158 klog.Infof("Loading cert/key pair from (%q, %q).", c, k)
159 return loadX509KeyPair(c, k)
160 }
161
162 noKeyErr := NoCertKeyError(
163 fmt.Sprintf("no cert/key files read at %q, (%q, %q) or (%q, %q)",
164 pairFile,
165 s.certFile,
166 s.keyFile,
167 s.certDirectory,
168 s.keyDirectory))
169 return nil, &noKeyErr
170 }
171
172 func loadFile(pairFile string) (*tls.Certificate, error) {
173
174
175 cert, err := tls.LoadX509KeyPair(pairFile, pairFile)
176 if err != nil {
177 return nil, fmt.Errorf("could not convert data from %q into cert/key pair: %v", pairFile, err)
178 }
179 certs, err := x509.ParseCertificates(cert.Certificate[0])
180 if err != nil {
181 return nil, fmt.Errorf("unable to parse certificate data: %v", err)
182 }
183 cert.Leaf = certs[0]
184 return &cert, nil
185 }
186
187 func (s *fileStore) Update(certData, keyData []byte) (*tls.Certificate, error) {
188 ts := time.Now().Format("2006-01-02-15-04-05")
189 pemFilename := s.filename(ts)
190
191 if err := os.MkdirAll(s.certDirectory, 0755); err != nil {
192 return nil, fmt.Errorf("could not create directory %q to store certificates: %v", s.certDirectory, err)
193 }
194 certPath := filepath.Join(s.certDirectory, pemFilename)
195
196 f, err := os.OpenFile(certPath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0600)
197 if err != nil {
198 return nil, fmt.Errorf("could not open %q: %v", certPath, err)
199 }
200 defer f.Close()
201
202
203 certs, err := certutil.ParseCertsPEM(certData)
204 if err != nil {
205 return nil, fmt.Errorf("invalid certificate data: %v", err)
206 }
207 for _, c := range certs {
208 pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: c.Raw})
209 }
210
211 keyBlock, _ := pem.Decode(keyData)
212 if keyBlock == nil {
213 return nil, fmt.Errorf("invalid key data")
214 }
215 pem.Encode(f, keyBlock)
216
217 cert, err := loadFile(certPath)
218 if err != nil {
219 return nil, err
220 }
221
222 if err := s.updateSymlink(certPath); err != nil {
223 return nil, err
224 }
225 return cert, nil
226 }
227
228
229
230
231 func (s *fileStore) updateSymlink(filename string) error {
232
233
234 currentPath := filepath.Join(s.certDirectory, s.filename(currentPair))
235 currentPathExists := false
236 if fi, err := os.Lstat(currentPath); err != nil {
237 if !os.IsNotExist(err) {
238 return err
239 }
240 } else if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
241 return fmt.Errorf("expected %q to be a symlink but it is a file", currentPath)
242 } else {
243 currentPathExists = true
244 }
245
246
247
248 updatedPath := filepath.Join(s.certDirectory, s.filename(updatedPair))
249 if fi, err := os.Lstat(updatedPath); err != nil {
250 if !os.IsNotExist(err) {
251 return err
252 }
253 } else if fi.Mode()&os.ModeSymlink != os.ModeSymlink {
254 return fmt.Errorf("expected %q to be a symlink but it is a file", updatedPath)
255 } else {
256 if err := os.Remove(updatedPath); err != nil {
257 return fmt.Errorf("unable to remove %q: %v", updatedPath, err)
258 }
259 }
260
261
262
263 if filenameExists, err := fileExists(filename); err != nil {
264 return err
265 } else if !filenameExists {
266 return fmt.Errorf("file %q does not exist so it can not be used as the currently selected cert/key", filename)
267 }
268
269
270
271 filename, err := filepath.Abs(filename)
272 if err != nil {
273 return err
274 }
275
276
277 if err := os.Symlink(filename, updatedPath); err != nil {
278 return fmt.Errorf("unable to create a symlink from %q to %q: %v", updatedPath, filename, err)
279 }
280
281
282 if currentPathExists {
283 if err := os.Remove(currentPath); err != nil {
284 return fmt.Errorf("unable to remove %q: %v", currentPath, err)
285 }
286 }
287 if err := os.Rename(updatedPath, currentPath); err != nil {
288 return fmt.Errorf("unable to rename %q to %q: %v", updatedPath, currentPath, err)
289 }
290 return nil
291 }
292
293 func (s *fileStore) filename(qualifier string) string {
294 return s.pairNamePrefix + "-" + qualifier + pemExtension
295 }
296
297 func loadX509KeyPair(certFile, keyFile string) (*tls.Certificate, error) {
298 cert, err := tls.LoadX509KeyPair(certFile, keyFile)
299 if err != nil {
300 return nil, err
301 }
302 certs, err := x509.ParseCertificates(cert.Certificate[0])
303 if err != nil {
304 return nil, fmt.Errorf("unable to parse certificate data: %v", err)
305 }
306 cert.Leaf = certs[0]
307 return &cert, nil
308 }
309
310
311 func fileExists(filename string) (bool, error) {
312 if _, err := os.Stat(filename); os.IsNotExist(err) {
313 return false, nil
314 } else if err != nil {
315 return false, err
316 }
317 return true, nil
318 }
319
View as plain text