-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
certwrapper.go
168 lines (144 loc) · 4.2 KB
/
certwrapper.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
package main
import (
"errors"
"flag"
"fmt"
"io/fs"
"os"
"os/exec"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"github.com/csmith/envflag"
"github.com/go-acme/lego/v4/certcrypto"
"golang.org/x/sys/unix"
)
var (
keyType = flag.String("key-type", "P384", "Type of private key to use when generating a certificate.")
acmeEndpoint = flag.String("acme-endpoint", "https://acme-v02.api.letsencrypt.org/directory", "ACME endpoint to request certificates from.")
dnsProvider = flag.String("dns-provider", "", "DNS provider to use. See https://go-acme.github.io/lego/dns/.")
userPath = flag.String("user-path", "cert/user.json", "Path to save user registration data.")
privateKeyPath = flag.String("private-key-path", "cert/privatekey.pem", "Path to save the private key.")
certificatePath = flag.String("certificate-path", "cert/certificate.pem", "Path to save the certificate.")
issuerCertPath = flag.String("issuer-path", "cert/issuer.pem", "Path to save the issuer's certificate.")
acmeEmail = flag.String("acme-email", "", "E-mail address to supply to the ACME server.")
domains = flag.String("domains", "", "Comma-separated list of domains to request on the certificate.")
)
func parseFlags() {
envflag.Parse(envflag.WithPrefix("CERTWRAPPER_"))
if *dnsProvider == "" {
fmt.Fprintf(os.Stderr, "DNS provider must be configured\n\n")
flag.Usage()
os.Exit(1)
}
if *domains == "" {
fmt.Fprintf(os.Stderr, "Domains must be configured\n\n")
flag.Usage()
os.Exit(1)
}
if *acmeEmail == "" {
fmt.Fprintf(os.Stderr, "ACME e-mail address must be configured\n\n")
flag.Usage()
os.Exit(1)
}
}
func main() {
parseFlags()
checkFilePermissions()
cm, err := NewCertificateManager(
certcrypto.KeyType(*keyType),
*acmeEndpoint,
*dnsProvider,
*userPath,
*privateKeyPath,
*certificatePath,
*issuerCertPath,
*acmeEmail,
strings.Split(*domains, ","),
)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create certificate manager: %v\n", err)
os.Exit(2)
}
// See if we need to get a certificate before starting the process
if cm.NeedsCertificate() {
if err := cm.ObtainCertificate(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain certificate: %v\n", err)
os.Exit(3)
}
}
args := flag.Args()
cmd := exec.Command(args[0], args[1:]...)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
if err := cmd.Start(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to run application: %v\n", err)
os.Exit(4)
}
go waitForCommandToExit(cmd)
go monitorCertificate(cm, cmd)
proxySignals(cmd)
}
func proxySignals(cmd *exec.Cmd) {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2)
for {
sig := <-sigs
if err := cmd.Process.Signal(sig); err != nil {
fmt.Fprintf(os.Stderr, "Failed to signal child process: %v\n", err)
os.Exit(7)
}
}
}
func waitForCommandToExit(cmd *exec.Cmd) {
if err := cmd.Wait(); err != nil {
fmt.Fprintf(os.Stderr, "Application failed: %v\n", err)
os.Exit(5)
}
os.Exit(0)
}
func checkCertificate(cm *CertificateManager, cmd *exec.Cmd) {
if cm.NeedsCertificate() {
if err := cm.ObtainCertificate(); err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain certificate: %v\n", err)
os.Exit(6)
}
if err := cmd.Process.Signal(syscall.SIGHUP); err != nil {
fmt.Fprintf(os.Stderr, "Failed to signal child process: %v\n", err)
os.Exit(7)
}
}
}
func monitorCertificate(cm *CertificateManager, cmd *exec.Cmd) {
ticker := time.NewTicker(time.Hour * 24)
for {
select {
case <-ticker.C:
checkCertificate(cm, cmd)
}
}
}
func checkFilePermissions() {
canWrite := func(p string) error {
if _, err := os.Stat(p); errors.Is(err, fs.ErrNotExist) {
// If the file doesn't exist we need to check write perms on the directory
return syscall.Access(filepath.Dir(p), unix.W_OK)
}
return syscall.Access(p, unix.W_OK)
}
paths := []string{
*userPath,
*privateKeyPath,
*certificatePath,
*issuerCertPath,
}
for i := range paths {
if err := canWrite(paths[i]); err != nil {
fmt.Fprintf(os.Stderr, "Insufficient permissions to write to path '%s': %v\n", paths[i], err)
os.Exit(8)
}
}
}