blob: 32d809cb4575f9db863d367a08d72c6c9259717b [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 }
Serge Bazanski26c18172023-04-24 17:40:48 +020075 cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
Serge Bazanskibee272f2022-09-13 13:52:42 +020076 cert.SerialNumber = serialNumber
77 cert.NotBefore = time.Now()
78 cert.NotAfter = pki.UnknownNotAfter
79 cert.BasicConstraintsValid = true
80
81 pub, priv, err := ed25519.GenerateKey(rand.Reader)
82 if err != nil {
83 klog.Exitf("Failed to generate %s key: %v", c.ComponentName, err)
84 }
85 certBytes, err := x509.CreateCertificate(rand.Reader, &cert, caCert, pub, ca.PrivateKey)
86 if err != nil {
87 klog.Exitf("Failed to generate %s certificate: %v", c.ComponentName, err)
88 }
89
90 // And marshal them to disk.
91 privPKCS, err := x509.MarshalPKCS8PrivateKey(priv)
92 if err != nil {
93 klog.Exitf("Failed to marshal %s private key: %v", c.ComponentName, err)
94 }
95 err = os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{
96 Type: "PRIVATE KEY",
97 Bytes: privPKCS,
98 }), 0600)
99 if err != nil {
100 klog.Exitf("Failed to write %s private key: %v", c.ComponentName, err)
101 }
102 err = os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{
103 Type: "CERTIFICATE",
104 Bytes: certBytes,
105 }), 0644)
106 if err != nil {
107 klog.Exitf("Failed to write %s certificate: %v", c.ComponentName, err)
108 }
109
110 return
111}
112
113// ensureDevCA ensures that a development CA certificate/key exists and returns
114// paths to them. This data is either read from disk if it already exists, or is
115// generated when this function is called. If any problem occurs, the code
116// panics.
Serge Bazanskia5baa872022-09-15 18:49:35 +0200117func (c *ComponentConfig) ensureDevCA() (caCertPath string) {
Serge Bazanskibee272f2022-09-13 13:52:42 +0200118 caKeyPath := c.DevCertsPath + "/ca.key"
119 caCertPath = c.DevCertsPath + "/ca.cert"
120
121 if err := os.MkdirAll(c.DevCertsPath, 0700); err != nil {
122 klog.Exitf("Failed to make developer certificate directory: %v", err)
123 }
124
125 // Check if we already have a key/certificate.
126 noKey := false
127 if _, err := os.Stat(caKeyPath); os.IsNotExist(err) {
128 noKey = true
129 }
130 noCert := false
131 if _, err := os.Stat(caCertPath); os.IsNotExist(err) {
132 noCert = true
133 }
134
135 if noKey || noCert {
136 klog.Infof("Generating developer CA certificate...")
137 } else {
138 return
139 }
140 hostname, err := os.Hostname()
141 if err != nil {
142 hostname = "unknown"
143 }
144
145 // No key/certificate, generate them.
146 ca := pki.CA(fmt.Sprintf("monogon dev certs CA (%s)", hostname))
147
148 serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
149 serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
150 if err != nil {
151 klog.Exitf("Failed to generate CA serial number: %v", err)
152 }
153 ca.SerialNumber = serialNumber
154 ca.NotBefore = time.Now()
155 ca.NotAfter = pki.UnknownNotAfter
156 ca.BasicConstraintsValid = true
157
158 caPub, caPriv, err := ed25519.GenerateKey(rand.Reader)
159 if err != nil {
160 klog.Exitf("Failed to generate CA key: %v", err)
161 }
162 caBytes, err := x509.CreateCertificate(rand.Reader, &ca, &ca, caPub, caPriv)
163 if err != nil {
164 klog.Exitf("Failed to generate CA certificate: %v", err)
165 }
166
167 // And marshal them to disk.
168 caPrivPKCS, err := x509.MarshalPKCS8PrivateKey(caPriv)
169 if err != nil {
170 klog.Exitf("Failed to marshal %s private key: %v", c.ComponentName, err)
171 }
172 err = os.WriteFile(caKeyPath, pem.EncodeToMemory(&pem.Block{
173 Type: "PRIVATE KEY",
174 Bytes: caPrivPKCS,
175 }), 0600)
176 if err != nil {
177 klog.Exitf("Failed to write CA private key: %v", err)
178 }
179 err = os.WriteFile(caCertPath, pem.EncodeToMemory(&pem.Block{
180 Type: "CERTIFICATE",
181 Bytes: caBytes,
182 }), 0644)
183 if err != nil {
184 klog.Exitf("Failed to write CA certificate: %v", err)
185 }
186
187 return
188}