kubelatte-ce

Форк
2
Форк от sbertech/kubelatte-ce
/
reloadcerts.go 
241 строка · 6.2 Кб
1
package util
2

3
import (
4
	"crypto/tls"
5
	"crypto/x509"
6
	"fmt"
7
	"sync"
8
	"time"
9

10
	"github.com/spf13/afero"
11
)
12

13
//TODO Make the Reloader its own thing and have a special case for the Cert one?
14

15
// A tool to reload certificates automatically
16
type CertificateReloader interface {
17
	Start() error                              // Start the monitoring of the key file
18
	Stop() chan struct{}                       // Stop the monitoring
19
	IsRunning() bool                           // Returns true if the reloader is running
20
	GetCertificate() (*tls.Certificate, error) // Returns the latest certs available and errors if latest cert has expired
21
}
22

23
type CertificatePKIReloader struct {
24
	refreshInterval time.Duration
25
	lock            sync.RWMutex
26
	stopCh          chan struct{}
27
	stoppedCh       chan struct{}
28
	started         bool
29
	lastModTime     time.Time
30
	certExpiry      time.Time
31
	fs              afero.Fs
32
	certFilename    string
33
	keyFilename     string
34
	cert            *tls.Certificate
35
	errHandler      func(error)
36
}
37

38
// FileError indicates there was a problem inspecting or reading the files
39
// being monitored.
40
type FileError struct {
41
	error
42
}
43

44
// TLSError indicates there was a problem converting the contents of the
45
// monitored files into x509 certificate/key pair.
46
type TLSError struct {
47
	error
48
}
49

50
// Creates a CertificateReloader based on the files and afero FS.
51
func NewCertificatePKIReloaderFull(fs afero.Fs, certFilename, keyFilename string, refreshInterval time.Duration) *CertificatePKIReloader {
52
	return newCertificatePKIReloaderFull(
53
		fs,
54
		certFilename,
55
		keyFilename,
56
		refreshInterval,
57
		nil,
58
	)
59
}
60

61
// Creates a CertificateReloader based on the files and afero FS.
62
// Calls the given error handler when there are problems reading the given
63
// files. The error passed to the handler will be a FileError, TLSError, or
64
// error.
65
// If errHandler is nil, the default behavior is to do nothing on error.
66
func NewCertificatePKIReloaderFullWithErrHandler(fs afero.Fs, certFilename, keyFilename string, refreshInterval time.Duration, errHandler func(error)) *CertificatePKIReloader {
67
	return newCertificatePKIReloaderFull(
68
		fs,
69
		certFilename,
70
		keyFilename,
71
		refreshInterval,
72
		errHandler,
73
	)
74
}
75

76
// A simplified version of NewCertificatePKIReloaderFull where the fs is the OS fs by default
77
func NewCertificatePKIReloader(certFilename, keyFilename string, refreshInterval time.Duration) *CertificatePKIReloader {
78
	return NewCertificatePKIReloaderFull(
79
		afero.NewOsFs(),
80
		certFilename,
81
		keyFilename,
82
		refreshInterval)
83
}
84

85
// A simplified version of NewCertificatePKIReloaderFullWithErrHandler where the
86
// fs is the OS fs by default.
87
// Calls the given error handler when there are problems reading the given
88
// files. The error passed to the handler will be a FileError, TLSError, or
89
// error.
90
// If errHandler is nil, the default behavior is to do nothing on error.
91
func NewCertificatePKIReloaderWithErrHandler(certFilename, keyFilename string, refreshInterval time.Duration, errHandler func(error)) *CertificatePKIReloader {
92
	return newCertificatePKIReloaderFull(
93
		afero.NewOsFs(),
94
		certFilename,
95
		keyFilename,
96
		refreshInterval,
97
		errHandler,
98
	)
99
}
100

101
func newCertificatePKIReloaderFull(fs afero.Fs, certFilename, keyFilename string, refreshInterval time.Duration, errHandler func(error)) *CertificatePKIReloader {
102
	if errHandler == nil {
103
		errHandler = func(_ error) { /* Do nothing */ }
104
	}
105

106
	return &CertificatePKIReloader{
107
		fs:              fs,
108
		certFilename:    certFilename,
109
		keyFilename:     keyFilename,
110
		refreshInterval: refreshInterval,
111
		started:         false,
112
		errHandler:      errHandler,
113
	}
114
}
115

116
func (r *CertificatePKIReloader) Start() error {
117
	if r == nil {
118
		panic("Calling Start on uninit CertificatePKIReloader")
119
	}
120
	if !r.started {
121
		r.runRefresh()
122
		if _, err := r.GetCertificate(); err != nil {
123
			return err
124
		}
125
		r.stopCh = make(chan struct{})
126
		r.stoppedCh = make(chan struct{})
127
		r.started = true
128
		go r.runRefreshLoop()
129
	}
130

131
	return nil
132
}
133

134
func (r *CertificatePKIReloader) Stop() chan struct{} {
135
	if r == nil {
136
		panic("Calling Start on uninit CertificatePKIReloader")
137
	}
138
	r.lock.Lock()
139
	defer r.lock.Unlock()
140

141
	if !r.started {
142
		stoppedCh := make(chan struct{})
143
		close(stoppedCh)
144
		return stoppedCh
145
	}
146

147
	close(r.stopCh)
148
	r.started = false
149
	return r.stoppedCh
150
}
151

152
func (r *CertificatePKIReloader) IsRunning() bool {
153
	if r == nil {
154
		return false
155
	}
156
	r.lock.RLock()
157
	defer r.lock.RUnlock()
158

159
	return r.started
160
}
161

162
func (r *CertificatePKIReloader) GetCertificate() (*tls.Certificate, error) {
163
	if r == nil {
164
		panic("Calling Start on uninit CertificatePKIReloader")
165
	}
166
	r.lock.RLock()
167
	defer r.lock.RUnlock()
168
	// return error if certificate in cache has expired
169
	if r.certExpiry.Before(time.Now()) {
170
		return nil, fmt.Errorf("certificate expired at %v", r.certExpiry)
171
	}
172
	return r.cert, nil
173
}
174

175
func readCert(fs afero.Fs, certFilename, keyFilename string) (*tls.Certificate, error) {
176
	certPEMBlock, err := afero.ReadFile(fs, certFilename)
177
	if err != nil {
178
		return &tls.Certificate{}, FileError{error: err}
179
	}
180

181
	keyPEMBlock, err := afero.ReadFile(fs, keyFilename)
182
	if err != nil {
183
		return &tls.Certificate{}, FileError{error: err}
184
	}
185

186
	cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
187
	if err != nil {
188
		return &tls.Certificate{}, TLSError{error: err}
189
	}
190
	return &cert, nil
191
}
192

193
func readModTime(fs afero.Fs, filename string) (time.Time, error) {
194
	f, err := fs.Stat(filename)
195
	if err != nil {
196
		return time.Time{}, nil
197
	}
198

199
	return f.ModTime(), nil
200
}
201

202
func (r *CertificatePKIReloader) runRefresh() {
203
	modTime, err := readModTime(r.fs, r.keyFilename)
204
	if err != nil {
205
		r.errHandler(err)
206
		return
207
	}
208

209
	if r.lastModTime.Before(modTime) {
210
		cert, err := readCert(r.fs, r.certFilename, r.keyFilename)
211
		if err != nil {
212
			r.errHandler(err)
213
			return
214
		}
215
		clientCert, err := x509.ParseCertificate(cert.Certificate[0])
216
		if err != nil {
217
			r.errHandler(err)
218
			return
219
		}
220
		r.lock.Lock()
221
		// cert, lastModTime, certExpiry are not updated in case of errors reading the cert
222
		r.lastModTime = modTime
223
		r.cert = cert
224
		r.certExpiry = clientCert.NotAfter
225
		r.lock.Unlock()
226
	}
227
}
228

229
func (r *CertificatePKIReloader) runRefreshLoop() {
230
	defer close(r.stoppedCh)
231

232
	ticker := time.NewTicker(r.refreshInterval)
233
	for {
234
		select {
235
		case <-ticker.C:
236
			r.runRefresh()
237
		case <-r.stopCh:
238
			return
239
		}
240
	}
241
}
242

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.