blob: 4d8668d1a77dc8f2403e4ee7e7fd829a86a0889d [file] [log] [blame]
Serge Bazanskibee272f2022-09-13 13:52:42 +02001package component
2
3import (
4 "crypto/ed25519"
5 "crypto/rand"
6 "crypto/tls"
7 "crypto/x509"
8 "encoding/pem"
9 "fmt"
10 "math/big"
11 "os"
12 "time"
13
14 "k8s.io/klog/v2"
15
16 "source.monogon.dev/metropolis/pkg/pki"
17)
18
19// GetDevCerts returns paths to this component's development certificate, key
20// and CA, or panics if unavailable.
Serge Bazanskia5baa872022-09-15 18:49:35 +020021func (c *ComponentConfig) GetDevCerts() (certPath, keyPath, caPath string) {
Serge Bazanskibee272f2022-09-13 13:52:42 +020022 klog.Infof("Using developer certificates at %s", c.DevCertsPath)
23
24 caPath = c.ensureDevCA()
25 certPath, keyPath = c.ensureDevComponent()
26 return
27}
28
29// ensureDevComponent ensures that a development certificate/key exists for this
30// component and returns paths to them. This data is either read from disk if it
31// already exists, or is generated when this function is called. If any problem
32// occurs, the code panics.
Serge Bazanskia5baa872022-09-15 18:49:35 +020033func (c *ComponentConfig) ensureDevComponent() (certPath, keyPath string) {
Serge Bazanskibee272f2022-09-13 13:52:42 +020034 caKeyPath := c.DevCertsPath + "/ca.key"
35 caCertPath := c.DevCertsPath + "/ca.cert"
36
37 // Load CA. By convention, we are always called after ensureDevCA.
38 ca, err := tls.LoadX509KeyPair(caCertPath, caKeyPath)
39 if err != nil {
40 klog.Exitf("Could not load Dev CA: %v", err)
41 }
42 caCert, err := x509.ParseCertificate(ca.Certificate[0])
43 if err != nil {
44 klog.Exitf("Could not parse Dev CA: %v", err)
45 }
46
47 // Check if we have keys already.
48 keyPath = c.DevCertsPath + fmt.Sprintf("/%s.key", c.ComponentName)
49 certPath = c.DevCertsPath + fmt.Sprintf("/%s.crt", c.ComponentName)
50 noKey := false
51 if _, err := os.Stat(keyPath); os.IsNotExist(err) {
52 noKey = true
53 }
54 noCert := false
55 if _, err := os.Stat(certPath); os.IsNotExist(err) {
56 noCert = true
57 }
58
59 if noKey || noCert {
60 klog.Infof("Generating developer %s certificate...", c.ComponentName)
61 } else {
62 return
63 }
64
65 // Generate key/certificate.
66 cert := pki.Server([]string{
67 fmt.Sprintf("%s.local", c.ComponentName),
68 }, nil)
69
70 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
71 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
72 if err != nil {
73 klog.Exitf("Failed to generate %s serial number: %v", c.ComponentName, err)
74 }
75 cert.SerialNumber = serialNumber
76 cert.NotBefore = time.Now()
77 cert.NotAfter = pki.UnknownNotAfter
78 cert.BasicConstraintsValid = true
79
80 pub, priv, err := ed25519.GenerateKey(rand.Reader)
81 if err != nil {
82 klog.Exitf("Failed to generate %s key: %v", c.ComponentName, err)
83 }
84 certBytes, err := x509.CreateCertificate(rand.Reader, &cert, caCert, pub, ca.PrivateKey)
85 if err != nil {
86 klog.Exitf("Failed to generate %s certificate: %v", c.ComponentName, err)
87 }
88
89 // And marshal them to disk.
90 privPKCS, err := x509.MarshalPKCS8PrivateKey(priv)
91 if err != nil {
92 klog.Exitf("Failed to marshal %s private key: %v", c.ComponentName, err)
93 }
94 err = os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{
95 Type: "PRIVATE KEY",
96 Bytes: privPKCS,
97 }), 0600)
98 if err != nil {
99 klog.Exitf("Failed to write %s private key: %v", c.ComponentName, err)
100 }
101 err = os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{
102 Type: "CERTIFICATE",
103 Bytes: certBytes,
104 }), 0644)
105 if err != nil {
106 klog.Exitf("Failed to write %s certificate: %v", c.ComponentName, err)
107 }
108
109 return
110}
111
112// ensureDevCA ensures that a development CA certificate/key exists and returns
113// paths to them. This data is either read from disk if it already exists, or is
114// generated when this function is called. If any problem occurs, the code
115// panics.
Serge Bazanskia5baa872022-09-15 18:49:35 +0200116func (c *ComponentConfig) ensureDevCA() (caCertPath string) {
Serge Bazanskibee272f2022-09-13 13:52:42 +0200117 caKeyPath := c.DevCertsPath + "/ca.key"
118 caCertPath = c.DevCertsPath + "/ca.cert"
119
120 if err := os.MkdirAll(c.DevCertsPath, 0700); err != nil {
121 klog.Exitf("Failed to make developer certificate directory: %v", err)
122 }
123
124 // Check if we already have a key/certificate.
125 noKey := false
126 if _, err := os.Stat(caKeyPath); os.IsNotExist(err) {
127 noKey = true
128 }
129 noCert := false
130 if _, err := os.Stat(caCertPath); os.IsNotExist(err) {
131 noCert = true
132 }
133
134 if noKey || noCert {
135 klog.Infof("Generating developer CA certificate...")
136 } else {
137 return
138 }
139 hostname, err := os.Hostname()
140 if err != nil {
141 hostname = "unknown"
142 }
143
144 // No key/certificate, generate them.
145 ca := pki.CA(fmt.Sprintf("monogon dev certs CA (%s)", hostname))
146
147 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
148 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
149 if err != nil {
150 klog.Exitf("Failed to generate CA serial number: %v", err)
151 }
152 ca.SerialNumber = serialNumber
153 ca.NotBefore = time.Now()
154 ca.NotAfter = pki.UnknownNotAfter
155 ca.BasicConstraintsValid = true
156
157 caPub, caPriv, err := ed25519.GenerateKey(rand.Reader)
158 if err != nil {
159 klog.Exitf("Failed to generate CA key: %v", err)
160 }
161 caBytes, err := x509.CreateCertificate(rand.Reader, &ca, &ca, caPub, caPriv)
162 if err != nil {
163 klog.Exitf("Failed to generate CA certificate: %v", err)
164 }
165
166 // And marshal them to disk.
167 caPrivPKCS, err := x509.MarshalPKCS8PrivateKey(caPriv)
168 if err != nil {
169 klog.Exitf("Failed to marshal %s private key: %v", c.ComponentName, err)
170 }
171 err = os.WriteFile(caKeyPath, pem.EncodeToMemory(&pem.Block{
172 Type: "PRIVATE KEY",
173 Bytes: caPrivPKCS,
174 }), 0600)
175 if err != nil {
176 klog.Exitf("Failed to write CA private key: %v", err)
177 }
178 err = os.WriteFile(caCertPath, pem.EncodeToMemory(&pem.Block{
179 Type: "CERTIFICATE",
180 Bytes: caBytes,
181 }), 0644)
182 if err != nil {
183 klog.Exitf("Failed to write CA certificate: %v", err)
184 }
185
186 return
187}