blob: 32d809cb4575f9db863d367a08d72c6c9259717b [file] [log] [blame]
package component
import (
"crypto/ed25519"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"os"
"time"
"k8s.io/klog/v2"
"source.monogon.dev/metropolis/pkg/pki"
)
// GetDevCerts returns paths to this component's development certificate, key
// and CA, or panics if unavailable.
func (c *ComponentConfig) GetDevCerts() (certPath, keyPath, caPath string) {
klog.Infof("Using developer certificates at %s", c.DevCertsPath)
caPath = c.ensureDevCA()
certPath, keyPath = c.ensureDevComponent()
return
}
// ensureDevComponent ensures that a development certificate/key exists for this
// component and returns paths to them. This data is either read from disk if it
// already exists, or is generated when this function is called. If any problem
// occurs, the code panics.
func (c *ComponentConfig) ensureDevComponent() (certPath, keyPath string) {
caKeyPath := c.DevCertsPath + "/ca.key"
caCertPath := c.DevCertsPath + "/ca.cert"
// Load CA. By convention, we are always called after ensureDevCA.
ca, err := tls.LoadX509KeyPair(caCertPath, caKeyPath)
if err != nil {
klog.Exitf("Could not load Dev CA: %v", err)
}
caCert, err := x509.ParseCertificate(ca.Certificate[0])
if err != nil {
klog.Exitf("Could not parse Dev CA: %v", err)
}
// Check if we have keys already.
keyPath = c.DevCertsPath + fmt.Sprintf("/%s.key", c.ComponentName)
certPath = c.DevCertsPath + fmt.Sprintf("/%s.crt", c.ComponentName)
noKey := false
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
noKey = true
}
noCert := false
if _, err := os.Stat(certPath); os.IsNotExist(err) {
noCert = true
}
if noKey || noCert {
klog.Infof("Generating developer %s certificate...", c.ComponentName)
} else {
return
}
// Generate key/certificate.
cert := pki.Server([]string{
fmt.Sprintf("%s.local", c.ComponentName),
}, nil)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
klog.Exitf("Failed to generate %s serial number: %v", c.ComponentName, err)
}
cert.ExtKeyUsage = append(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth)
cert.SerialNumber = serialNumber
cert.NotBefore = time.Now()
cert.NotAfter = pki.UnknownNotAfter
cert.BasicConstraintsValid = true
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
klog.Exitf("Failed to generate %s key: %v", c.ComponentName, err)
}
certBytes, err := x509.CreateCertificate(rand.Reader, &cert, caCert, pub, ca.PrivateKey)
if err != nil {
klog.Exitf("Failed to generate %s certificate: %v", c.ComponentName, err)
}
// And marshal them to disk.
privPKCS, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
klog.Exitf("Failed to marshal %s private key: %v", c.ComponentName, err)
}
err = os.WriteFile(keyPath, pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: privPKCS,
}), 0600)
if err != nil {
klog.Exitf("Failed to write %s private key: %v", c.ComponentName, err)
}
err = os.WriteFile(certPath, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
}), 0644)
if err != nil {
klog.Exitf("Failed to write %s certificate: %v", c.ComponentName, err)
}
return
}
// ensureDevCA ensures that a development CA certificate/key exists and returns
// paths to them. This data is either read from disk if it already exists, or is
// generated when this function is called. If any problem occurs, the code
// panics.
func (c *ComponentConfig) ensureDevCA() (caCertPath string) {
caKeyPath := c.DevCertsPath + "/ca.key"
caCertPath = c.DevCertsPath + "/ca.cert"
if err := os.MkdirAll(c.DevCertsPath, 0700); err != nil {
klog.Exitf("Failed to make developer certificate directory: %v", err)
}
// Check if we already have a key/certificate.
noKey := false
if _, err := os.Stat(caKeyPath); os.IsNotExist(err) {
noKey = true
}
noCert := false
if _, err := os.Stat(caCertPath); os.IsNotExist(err) {
noCert = true
}
if noKey || noCert {
klog.Infof("Generating developer CA certificate...")
} else {
return
}
hostname, err := os.Hostname()
if err != nil {
hostname = "unknown"
}
// No key/certificate, generate them.
ca := pki.CA(fmt.Sprintf("monogon dev certs CA (%s)", hostname))
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 127)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
klog.Exitf("Failed to generate CA serial number: %v", err)
}
ca.SerialNumber = serialNumber
ca.NotBefore = time.Now()
ca.NotAfter = pki.UnknownNotAfter
ca.BasicConstraintsValid = true
caPub, caPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
klog.Exitf("Failed to generate CA key: %v", err)
}
caBytes, err := x509.CreateCertificate(rand.Reader, &ca, &ca, caPub, caPriv)
if err != nil {
klog.Exitf("Failed to generate CA certificate: %v", err)
}
// And marshal them to disk.
caPrivPKCS, err := x509.MarshalPKCS8PrivateKey(caPriv)
if err != nil {
klog.Exitf("Failed to marshal %s private key: %v", c.ComponentName, err)
}
err = os.WriteFile(caKeyPath, pem.EncodeToMemory(&pem.Block{
Type: "PRIVATE KEY",
Bytes: caPrivPKCS,
}), 0600)
if err != nil {
klog.Exitf("Failed to write CA private key: %v", err)
}
err = os.WriteFile(caCertPath, pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
}), 0644)
if err != nil {
klog.Exitf("Failed to write CA certificate: %v", err)
}
return
}