-
Notifications
You must be signed in to change notification settings - Fork 34
/
simplecert.go
203 lines (161 loc) · 5.5 KB
/
simplecert.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
//
// simplecert
//
// Created by Philipp Mieden
// Contact: dreadl0ck@protonmail.ch
// Copyright © 2018 bestbytes. All rights reserved.
//
package simplecert
import (
"encoding/json"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"github.com/go-acme/lego/v3/certificate"
)
const (
logFileName = "simplecert.log"
certResourceFileName = "CertResource.json"
certFileName = "cert.pem"
keyFileName = "key.pem"
)
var local bool
// Init obtains a new LetsEncrypt cert for the specified domains if there is none in cacheDir
// or loads an existing one. Certs will be auto renewed in the configured interval.
// 1. Check if we have a cached certificate, if yes kickoff renewal routine and return
// 2. No Cached Certificate found - make sure the supplied cacheDir exists
// 3. Create a new SSLUser and ACME Client
// 4. Obtain a new certificate
// 5. Save To Disk
// 6. Kickoff Renewal Routine
func Init(cfg *Config, cleanup func()) (*CertReloader, error) {
// validate config
err := CheckConfig(cfg)
if err != nil {
return nil, err
}
// config ok.
// update global config
c = cfg
// make sure the cacheDir exists
ensureCacheDirExists(c.CacheDir)
// open logfile handle
logFile, err := os.OpenFile(filepath.Join(c.CacheDir, logFileName), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
if err != nil {
log.Fatal("[FATAL] simplecert: failed to create logfile: ", err)
}
// configure log pkg to log to stdout and into the logfile
log.SetOutput(io.MultiWriter(os.Stdout, logFile))
if c.Local {
// Status() needs to know whether simplecert is running locally
// since there is no need to expose the entire configuration for this
// we will only make local accessible within simplecert
local = true
// update the cachedir path
// certs used in local mode are stored in the "local" subfolder
// to avoid overwriting a production certificate
c.CacheDir = filepath.Join(c.CacheDir, "local")
// make sure the cacheDir/local folder exists
ensureCacheDirExists(c.CacheDir)
var (
certFilePath = filepath.Join(c.CacheDir, certFileName)
keyFilePath = filepath.Join(c.CacheDir, keyFileName)
)
// check if a local cert is already cached
if certCached(c.CacheDir) {
// cert cached! Did the domains change?
// If the domains have been modified we need to generate a new certificate
if domainsChanged(certFilePath, keyFilePath) {
log.Println("[INFO] cert cached but domains have changed. generating a new one...")
createLocalCert(certFilePath, keyFilePath)
}
} else {
// nothing there yet. create a new one
createLocalCert(certFilePath, keyFilePath)
}
// create entries in /etc/hosts if necessary
if c.UpdateHosts {
updateHosts()
}
// return a cert reloader for the local cert
return NewCertReloader(certFilePath, keyFilePath, logFile, cleanup)
}
var (
certFilePath = filepath.Join(c.CacheDir, certFileName)
keyFilePath = filepath.Join(c.CacheDir, keyFileName)
)
// do we have a certificate in cacheDir?
if certCached(c.CacheDir) {
/*
* Cert Found. Load it
*/
if domainsChanged(certFilePath, keyFilePath) {
log.Println("[INFO] domains have changed. Obtaining a new certificate...")
goto obtainNewCert
}
log.Println("[INFO] simplecert: found cert in cacheDir")
// read cert resource from disk
b, err := ioutil.ReadFile(filepath.Join(c.CacheDir, certResourceFileName))
if err != nil {
log.Fatal("[FATAL] simplecert: failed to read CertResource.json from disk: ", err)
}
// unmarshal certificate resource
var cr CR
err = json.Unmarshal(b, &cr)
if err != nil {
log.Fatal("[FATAL] simplecert: failed to unmarshal certificate resource: ", err)
}
var (
// CertReloader must be created before starting the renewal check
// since a renewal might result in receiving a SIGHUP for triggering the reload
// the goroutine for handling the signal and taking action is started when creating the reloader
certReloader, errReloader = NewCertReloader(certFilePath, keyFilePath, logFile, cleanup)
cert = getACMECertResource(cr)
)
// renew cert if necessary
errRenew := renew(cert)
if errRenew != nil {
log.Fatal("[FATAL] failed to renew cached cert on startup: ", errRenew)
}
// kickoff renewal routine
go renewalRoutine(cert)
return certReloader, errReloader
}
obtainNewCert:
/*
* No Cert Found. Register a new one
*/
u, err := getUser()
if err != nil {
log.Fatal("[FATAL] failed to get ACME user: ", err)
}
// get ACME Client
client, err := createClient(u, c.DNSServers)
if err != nil {
log.Fatal("[FATAL] failed to create lego.Client: ", err)
}
// bundle CA with certificate to avoid "transport: x509: certificate signed by unknown authority" error
request := certificate.ObtainRequest{
Domains: c.Domains,
Bundle: true,
}
// Obtain a new certificate
// The acme library takes care of completing the challenges to obtain the certificate(s).
// The domains must resolve to this machine or you have to use the DNS challenge.
cert, err := client.Certificate.Obtain(request)
if err != nil {
log.Fatal("[FATAL] simplecert: failed to obtain cert: ", err)
}
log.Println("[INFO] simplecert: client obtained cert for domain: ", cert.Domain)
// Save cert to disk
err = saveCertToDisk(cert, c.CacheDir)
if err != nil {
log.Fatal("[FATAL] simplecert: failed to write cert to disk")
}
log.Println("[INFO] simplecert: wrote new cert to disk!")
// kickoff renewal routine
go renewalRoutine(cert)
return NewCertReloader(certFilePath, keyFilePath, logFile, cleanup)
}