forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
certmanager.go
211 lines (179 loc) · 7.12 KB
/
certmanager.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package templaterouter
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/golang/glog"
routev1 "github.com/openshift/api/route/v1"
)
// certificateFile represents a certificate file.
type certificateFile struct {
certDir string
id string
}
// Tag generates a certificate file tag/name. This is used to index into the
// the map of deleted certificates.
func (cf certificateFile) Tag() string {
return filepath.Join(cf.certDir, cf.id+".pem")
}
// simpleCertificateManager is the default implementation of a certificateManager
type simpleCertificateManager struct {
cfg *certificateManagerConfig
w certificateWriter
deletedCertificates map[string]certificateFile
}
// newSimpleCertificateManager should be used to create a new cert manager. It will return an error
// if the config is not valid
func newSimpleCertificateManager(cfg *certificateManagerConfig, w certificateWriter) (certificateManager, error) {
if err := validateCertManagerConfig(cfg); err != nil {
return nil, err
}
if w == nil {
return nil, fmt.Errorf("certificate manager requires a certificate writer")
}
return &simpleCertificateManager{cfg, w, make(map[string]certificateFile, 0)}, nil
}
// validateCertManagerConfig ensures that the key functions and directories are set as well as
// ensuring that the two configured directories are set to different values
func validateCertManagerConfig(cfg *certificateManagerConfig) error {
if cfg.certKeyFunc == nil || cfg.caCertKeyFunc == nil ||
cfg.destCertKeyFunc == nil || len(cfg.certDir) == 0 ||
len(cfg.caCertDir) == 0 {
return fmt.Errorf("certificate manager requires all config items to be set")
}
if cfg.certDir == cfg.caCertDir {
return fmt.Errorf("certificate manager requires different directories for certDir and caCertDir")
}
return nil
}
// CertificateWriter provides direct access to the underlying writer if required
func (cm *simpleCertificateManager) CertificateWriter() certificateWriter {
return cm.w
}
// WriteCertificatesForConfig write certificates for edge and reencrypt termination by appending the
// key, cert, and ca cert into a single <host>.pem file. Also write <host>_pod.pem file if it is
// reencrypt termination
func (cm *simpleCertificateManager) WriteCertificatesForConfig(config *ServiceAliasConfig) error {
if config == nil {
return nil
}
if config.Status == ServiceAliasConfigStatusSaved {
glog.V(4).Infof("skipping certificate write for %s%s since its status is already %s", config.Host, config.Path, ServiceAliasConfigStatusSaved)
return nil
}
if len(config.Certificates) > 0 {
if config.TLSTermination == routev1.TLSTerminationEdge || config.TLSTermination == routev1.TLSTerminationReencrypt {
certKey := cm.cfg.certKeyFunc(config)
certObj, ok := config.Certificates[certKey]
if ok {
newLine := []byte("\n")
//initialize with key and append the newline and cert
buffer := bytes.NewBuffer([]byte(certObj.PrivateKey))
buffer.Write(newLine)
buffer.Write([]byte(certObj.Contents))
caCertKey := cm.cfg.caCertKeyFunc(config)
caCertObj, caOk := config.Certificates[caCertKey]
if caOk {
buffer.Write(newLine)
buffer.Write([]byte(caCertObj.Contents))
}
certFile := certificateFile{certDir: cm.cfg.certDir, id: certObj.ID}
delete(cm.deletedCertificates, certFile.Tag())
if err := cm.w.WriteCertificate(cm.cfg.certDir, certObj.ID, buffer.Bytes()); err != nil {
return err
}
}
}
if config.TLSTermination == routev1.TLSTerminationReencrypt {
destCertKey := cm.cfg.destCertKeyFunc(config)
destCert, ok := config.Certificates[destCertKey]
if ok {
destCertFile := certificateFile{certDir: cm.cfg.caCertDir, id: destCert.ID}
delete(cm.deletedCertificates, destCertFile.Tag())
if err := cm.w.WriteCertificate(cm.cfg.caCertDir, destCert.ID, []byte(destCert.Contents)); err != nil {
return err
}
}
}
}
return nil
}
// DeleteCertificatesForConfig will delete all certificates for the ServiceAliasConfig
func (cm *simpleCertificateManager) DeleteCertificatesForConfig(config *ServiceAliasConfig) error {
if config == nil {
return nil
}
if len(config.Certificates) > 0 {
if config.TLSTermination == routev1.TLSTerminationEdge || config.TLSTermination == routev1.TLSTerminationReencrypt {
certKey := cm.cfg.certKeyFunc(config)
certObj, ok := config.Certificates[certKey]
if ok {
certFile := certificateFile{certDir: cm.cfg.certDir, id: certObj.ID}
cm.deletedCertificates[certFile.Tag()] = certFile
}
}
if config.TLSTermination == routev1.TLSTerminationReencrypt {
destCertKey := cm.cfg.destCertKeyFunc(config)
destCert, ok := config.Certificates[destCertKey]
if ok {
destCertFile := certificateFile{certDir: cm.cfg.caCertDir, id: destCert.ID}
cm.deletedCertificates[destCertFile.Tag()] = destCertFile
}
}
}
return nil
}
// Commit applies any pending changes made to the certificateManager.
func (cm *simpleCertificateManager) Commit() error {
// Deletion of certificates that are being referenced in backends or
// config is problematic in that the template router will not
// reload because the config is invalid, so we _do_ need to "stage"
// or commit the removals. Remove all the deleted certificates.
for _, certFile := range cm.deletedCertificates {
err := cm.w.DeleteCertificate(certFile.certDir, certFile.id)
if err != nil {
// Log a warning if the delete fails but proceed on.
glog.Warningf("Ignoring error deleting certificate file %v: %v", certFile.Tag(), err)
}
}
cm.deletedCertificates = make(map[string]certificateFile, 0)
// If we decide to stage the certificate writes, we can flush the
// write to the disk here. Today, the certificate writes are done
// just before this function is called. The tradeoff is storing a
// copy in memory until we commit.
return nil
}
// simpleCertificateWriter is the default implementation of a certificateWriter
type simpleCertificateWriter struct{}
// NewSimpleCertificateWriter provides a new instance of simpleCertificateWriter
func newSimpleCertificateWriter() certificateWriter {
return &simpleCertificateWriter{}
}
// WriteCertificate creates and writes the file identified by <id> in <directory>. The file extension
// .pem will be added to id.
func (cm *simpleCertificateWriter) WriteCertificate(directory string, id string, cert []byte) error {
fileName := filepath.Join(directory, id+".pem")
err := ioutil.WriteFile(fileName, cert, 0644)
if err != nil {
glog.Errorf("Error writing certificate file %v: %v", fileName, err)
return err
}
return nil
}
// DeleteCertificate deletes certificates identified by <id> in <directory> with the .pem extension added.
// this will not return an error if the file does not exist
func (cm *simpleCertificateWriter) DeleteCertificate(directory, id string) error {
fileName := filepath.Join(directory, id+".pem")
if _, err := os.Stat(fileName); os.IsNotExist(err) {
glog.V(4).Infof("attempted to delete file %s but it does not exist", fileName)
return nil
}
err := os.Remove(fileName)
if os.IsNotExist(err) {
glog.V(4).Infof("%s passed the existence check but it was gone when os.Remove was called", fileName)
return nil
}
return err
}