Browse Source

First pass at a certificate deployer

master
Chris Smith 5 years ago
parent
commit
51d5c4b6de
3 changed files with 107 additions and 1 deletions
  1. 102
    0
      certs/deployer.go
  2. 4
    1
      dotege.go
  3. 1
    0
      model/model.go

+ 102
- 0
certs/deployer.go View File

@@ -0,0 +1,102 @@
1
+package certs
2
+
3
+import (
4
+	"github.com/csmith/dotege/model"
5
+	"go.uber.org/zap"
6
+	"io/ioutil"
7
+	"os"
8
+	"path"
9
+	"time"
10
+)
11
+
12
+// CertificateDeployer deploys certificates according to their configuration.
13
+type CertificateDeployer struct {
14
+	logger        *zap.SugaredLogger
15
+	certChannel   <-chan model.FoundCertificate
16
+	deployChannel chan bool
17
+	certs         map[string]model.FoundCertificate
18
+	hostnames     map[string]*model.Hostname
19
+}
20
+
21
+// NewCertificateDeployer creates a new CertificateDeployer.
22
+func NewCertificateDeployer(logger *zap.SugaredLogger, channel <-chan model.FoundCertificate) *CertificateDeployer {
23
+	deployer := &CertificateDeployer{
24
+		logger:        logger,
25
+		certChannel:   channel,
26
+		deployChannel: make(chan bool, 1),
27
+		certs:         make(map[string]model.FoundCertificate),
28
+	}
29
+
30
+	go deployer.monitor()
31
+	go deployer.deployAll()
32
+
33
+	return deployer
34
+}
35
+
36
+func (c *CertificateDeployer) monitor() {
37
+	select {
38
+	case cert := <-c.certChannel:
39
+		c.certs[cert.Hostname] = cert
40
+		c.deployChannel <- true
41
+	}
42
+}
43
+
44
+func (c *CertificateDeployer) deployAll() {
45
+	select {
46
+	case <-c.deployChannel:
47
+		c.logger.Debug("Checking for certificates requiring deploySingle")
48
+		for _, hostname := range c.hostnames {
49
+			if cert, ok := c.certs[hostname.Name]; ok {
50
+				c.deploySingle(cert, hostname)
51
+			}
52
+		}
53
+	}
54
+}
55
+
56
+func (c *CertificateDeployer) deploySingle(cert model.FoundCertificate, hostname *model.Hostname) {
57
+	if (hostname.CertActions & model.COMBINE) == model.COMBINE {
58
+		chain := c.readFile(cert.FullChain)
59
+		pkey := c.readFile(cert.PrivateKey)
60
+		c.deployFile("combined.pem", append(chain, pkey...), cert.ModTime, hostname)
61
+	} else {
62
+		c.deployFile("cert.pem", c.readFile(cert.Cert), cert.ModTime, hostname)
63
+		c.deployFile("chain.pem", c.readFile(cert.Chain), cert.ModTime, hostname)
64
+		c.deployFile("fullchain.pem", c.readFile(cert.FullChain), cert.ModTime, hostname)
65
+		c.deployFile("privkey.pem", c.readFile(cert.PrivateKey), cert.ModTime, hostname)
66
+	}
67
+}
68
+
69
+func (c *CertificateDeployer) deployFile(name string, content []byte, modTime time.Time, hostname *model.Hostname) {
70
+	var target string
71
+	if (hostname.CertActions & model.FLATTEN) == model.FLATTEN {
72
+		target = path.Join(hostname.CertDestination, hostname.Name+".pem")
73
+	} else {
74
+		target = path.Join(hostname.CertDestination, hostname.Name, name)
75
+	}
76
+
77
+	info, err := os.Stat(target)
78
+	if err != nil && info.ModTime().After(modTime) {
79
+		c.logger.Debugf("Not writing %s as it was modified more recently than our cert", target)
80
+		return
81
+	}
82
+
83
+	err = ioutil.WriteFile(target, content, 0700)
84
+	if err != nil {
85
+		c.logger.Warnf("Unable to write certificate %s - %s", target, err.Error())
86
+	} else {
87
+		c.logger.Info("Updated certificate file %s", target)
88
+	}
89
+}
90
+
91
+func (c *CertificateDeployer) readFile(name string) []byte {
92
+	data, err := ioutil.ReadFile(name)
93
+	if err != nil {
94
+		c.logger.Errorf("Unable to read certificate file %s - %s", name, err.Error())
95
+	}
96
+	return data
97
+}
98
+
99
+func (c *CertificateDeployer) UpdateHostnames(hostnames map[string]*model.Hostname) {
100
+	c.hostnames = hostnames
101
+	c.deployChannel <- true
102
+}

+ 4
- 1
dotege.go View File

@@ -61,6 +61,7 @@ func main() {
61 61
 
62 62
 	certMonitor := certs.NewCertificateManager(sugar, certChan)
63 63
 	certMonitor.AddDirectory("/data/certrequests/certs")
64
+	certDeployer := certs.NewCertificateDeployer(sugar, certChan)
64 65
 
65 66
 	templateGenerator := NewTemplateGenerator(sugar)
66 67
 	templateGenerator.AddTemplate(model.TemplateConfig{
@@ -89,10 +90,12 @@ func main() {
89 90
 				delete(containers, name)
90 91
 				timer.Reset(100 * time.Millisecond)
91 92
 			case <-timer.C:
93
+				hostnames := getHostnames(containers, config)
92 94
 				templateGenerator.Generate(Context{
93 95
 					Containers: containers,
94
-					Hostnames:  getHostnames(containers, config),
96
+					Hostnames:  hostnames,
95 97
 				})
98
+				certDeployer.UpdateHostnames(hostnames)
96 99
 			}
97 100
 		}
98 101
 	}()

+ 1
- 0
model/model.go View File

@@ -52,6 +52,7 @@ type TemplateConfig struct {
52 52
 
53 53
 // FoundCertificate describes a certificate we've located on disk.
54 54
 type FoundCertificate struct {
55
+	Hostname   string
55 56
 	Cert       string
56 57
 	Chain      string
57 58
 	FullChain  string

Loading…
Cancel
Save