mesh/delivery-group group feat/774-l3-reland rejected: composed together: the checker's verdict at the members' heads (b48c90fea31f): integration: mesh-self-ch…
mesh/delivery closed: a newer head of the same pull request
mesh/check failed: integration: mesh-self-check/cmd/mesh-self-check.TestTheSelfCheckWholeRunsOnTheBus: the run answered {Run:doctor-1793351737-1 At:20…
One commit with exactly the tree of the reviewed head35b4509c0a, on main12c2574c9d, so two compiled programs added and removed on the branch leave no trace in history (issue 1577).
175 lines
6.2 KiB
Go
175 lines
6.2 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/pem"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
// The bus's own certificate, made by the mesh rather than borrowed from an image. Moved here from mesh-controller's
|
|
// `broker certificate` with the same output (design 47's L3 s11): the command belongs to the bus module, and the
|
|
// controller's binary is retired after its lease is handed away (ADR 0547 §11). Nothing changes at the lease write: the
|
|
// certificate in /var/lib/mesh-broker-tls stays, and every pin stays valid.
|
|
//
|
|
// **The foundation asked a third-party image for a tool it never said must be there** (novox/hq
|
|
// 04-ISSUES/146). The bootstrap made this certificate by running `openssl` inside the broker's
|
|
// image, which worked while the broker was one that happened to carry it and stopped the day the
|
|
// bus changed: the new one has a shell and no openssl, so the step exited 127 and no mesh could be
|
|
// raised. Substituting another image the bundle names does not help — none of them carry it
|
|
// either.
|
|
//
|
|
// So the program that needs a certificate makes one. It is the mesh's own binary, already on the
|
|
// machine at this point in the bootstrap (the schema step ran it), and it needs nothing from the
|
|
// image it writes into but a mounted directory.
|
|
//
|
|
// **Self-signed, and that is the design** — a host pins this server's exact certificate and
|
|
// authenticates with a password (novox/hq ADR 0004). There is no authority above it to ask, and at
|
|
// this moment in a bootstrap there is no mesh to ask one of.
|
|
//
|
|
// Idempotent, because the step is applied again on every reconcile and a second certificate would
|
|
// be one the hosts that pinned the first no longer believe.
|
|
|
|
// busCertificateNames is what the bus is reached by: the container name on a mesh network, and the
|
|
// loopback address the machine's own foundation dials.
|
|
var busCertificateNames = []string{"mesh-broker"}
|
|
|
|
const busCertificateLife = 10 * 365 * 24 * time.Hour
|
|
|
|
// busCertificate makes the bus's certificate in a directory, or says whether one is there.
|
|
//
|
|
// mesh-nats-snapshot certificate --into /tls make it if it is not there
|
|
// mesh-nats-snapshot certificate --check --into /tls exit non-zero unless a usable pair is
|
|
func busCertificate(args []string) error {
|
|
into, check := "", false
|
|
for i := 0; i < len(args); i++ {
|
|
switch args[i] {
|
|
case "--check":
|
|
check = true
|
|
case "--into":
|
|
if i+1 >= len(args) {
|
|
return errors.New("--into needs a directory")
|
|
}
|
|
into = args[i+1]
|
|
i++
|
|
default:
|
|
return fmt.Errorf("certificate [--check] --into <directory>: %q", args[i])
|
|
}
|
|
}
|
|
if into == "" {
|
|
return errors.New("certificate [--check] --into <directory>")
|
|
}
|
|
crt, key := filepath.Join(into, "tls.crt"), filepath.Join(into, "tls.key")
|
|
|
|
if usable, err := busCertificateUsable(crt, key); err != nil {
|
|
return err
|
|
} else if usable {
|
|
fmt.Printf("the bus already has a certificate at %s, and it was left alone\n", crt)
|
|
return nil
|
|
}
|
|
if check {
|
|
// Said as a failure, because that is what the caller asked: a bootstrap's verify runs
|
|
// this and a false answer is what makes the step run.
|
|
return fmt.Errorf("no usable certificate and key at %s", into)
|
|
}
|
|
return writeBusCertificate(crt, key)
|
|
}
|
|
|
|
// busCertificateUsable says whether a certificate and its key are both there and parse.
|
|
//
|
|
// Both, and parsed rather than stat'ed: a half-written pair is the state a bootstrap interrupted
|
|
// between the two files leaves behind, and a step that treated it as done would hand the server a
|
|
// certificate with no key and report success.
|
|
func busCertificateUsable(crt, key string) (bool, error) {
|
|
certPEM, err := os.ReadFile(crt)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
keyPEM, err := os.ReadFile(key)
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return false, nil
|
|
}
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if _, err := tlsPairParses(certPEM, keyPEM); err != nil {
|
|
return false, nil
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func tlsPairParses(certPEM, keyPEM []byte) (*x509.Certificate, error) {
|
|
block, _ := pem.Decode(certPEM)
|
|
if block == nil || block.Type != "CERTIFICATE" {
|
|
return nil, errors.New("not a certificate")
|
|
}
|
|
certificate, err := x509.ParseCertificate(block.Bytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyBlock, _ := pem.Decode(keyPEM)
|
|
if keyBlock == nil {
|
|
return nil, errors.New("not a key")
|
|
}
|
|
if _, err := x509.ParsePKCS8PrivateKey(keyBlock.Bytes); err != nil {
|
|
if _, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return certificate, nil
|
|
}
|
|
|
|
func writeBusCertificate(crt, key string) error {
|
|
private, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
template := &x509.Certificate{
|
|
SerialNumber: serial,
|
|
Subject: pkix.Name{CommonName: busCertificateNames[0]},
|
|
DNSNames: busCertificateNames,
|
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
|
NotBefore: time.Now().Add(-time.Hour),
|
|
NotAfter: time.Now().Add(busCertificateLife),
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
BasicConstraintsValid: true,
|
|
}
|
|
der, err := x509.CreateCertificate(rand.Reader, template, template, &private.PublicKey, private)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pkcs8, err := x509.MarshalPKCS8PrivateKey(private)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// **The key first, and only then the certificate**, so the pair a reader finds is never a
|
|
// certificate whose key has not been written yet — the one order in which an interruption
|
|
// leaves something that looks finished (novox/hq 04-ISSUES/014, a key present and unusable).
|
|
if err := os.WriteFile(key, pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: pkcs8}), 0o600); err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(crt, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der}), 0o644); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("made the bus a certificate for %v, valid until %s\n %s\n %s\n",
|
|
busCertificateNames, template.NotAfter.Format(time.RFC3339), crt, key)
|
|
return nil
|
|
}
|