package main import ( "archive/tar" "bytes" "crypto" "encoding/json" "fmt" "io" "log" "net/http" "os" yaml "gopkg.in/yaml.v2" "novit.tech/direktil/pkg/cpiocat" ) func renderBootstrapConfig(w http.ResponseWriter, r *http.Request, ctx *renderContext, asJson bool) (err error) { log.Printf("sending bootstrap config for %q", ctx.Host.Name) _, cfg, err := ctx.BootstrapConfig() if err != nil { return err } if asJson { err = json.NewEncoder(w).Encode(cfg) } else { err = yaml.NewEncoder(w).Encode(cfg) } return nil } func buildInitrd(out io.Writer, ctx *renderContext) (err error) { _, cfg, err := ctx.Config() if err != nil { return } cat := cpiocat.New(out) // initrd initrdPath, err := ctx.distFetch("initrd", ctx.Host.Initrd) if err != nil { return } cat.AppendArchFile(initrdPath) // embedded layers (modules) for _, layer := range cfg.Layers { switch layer { case "modules": layerVersion := ctx.Host.Versions[layer] modulesPath, err := ctx.distFetch("layers", layer, layerVersion) if err != nil { return err } cat.AppendFile(modulesPath, "modules.sqfs") } } // config cfgBytes, _, err := ctx.BootstrapConfig() if err != nil { return } cat.AppendBytes(cfgBytes, "config.yaml", 0o600) // ssh keys cat.AppendDir("/etc", 0o755) cat.AppendDir("/etc/ssh", 0o700) // XXX do we want bootstrap-stage keys instead of the real host key? for _, format := range []string{"rsa", "dsa", "ecdsa", "ed25519"} { keyPath := "/etc/ssh/ssh_host_" + format + "_key" cat.AppendBytes(cfg.FileContent(keyPath), keyPath, 0o600) } // ssh user CA userCA, err := sshCAPubKey(ctx.Host.ClusterName) if err != nil { return fmt.Errorf("failed to get SSH user CA: %w", err) } cat.AppendBytes(userCA, "user_ca.pub", 0600) return cat.Close() } func buildBootstrap(out io.Writer, ctx *renderContext) (err error) { arch := tar.NewWriter(out) defer arch.Close() ca, err := getUsableClusterCA(ctx.Host.ClusterName, "boot-signer") if err != nil { return } signer, err := ca.ParseKey() if err != nil { return } hash := crypto.SHA512 sign := func(name string, digest []byte) (err error) { sigBytes, err := signer.Sign(nil, digest, hash) if err != nil { err = fmt.Errorf("signing to %s failed: %w", name, err) return err } if err = arch.WriteHeader(&tar.Header{ Name: name, Size: int64(len(sigBytes)), Mode: 0o644, }); err != nil { return } _, err = io.Copy(arch, bytes.NewReader(sigBytes)) return } // config cfgBytes, cfg, err := ctx.Config() if err != nil { return err } err = arch.WriteHeader(&tar.Header{ Name: "config.yaml", Size: int64(len(cfgBytes)), Mode: 0o600, }) if err != nil { return } _, err = arch.Write(cfgBytes) if err != nil { return } { h := hash.New() h.Write(cfgBytes) err = sign("config.yaml.sig", h.Sum(nil)) if err != nil { return } } // layers for _, layer := range cfg.Layers { if layer == "modules" { continue // modules are in the initrd with boot v2 } layerVersion := ctx.Host.Versions[layer] if layerVersion == "" { return fmt.Errorf("layer %q not mapped to a version", layer) } outPath, err := ctx.distFetch("layers", layer, layerVersion) if err != nil { return err } f, err := os.Open(outPath) if err != nil { return err } defer f.Close() stat, err := f.Stat() if err != nil { return err } h := hash.New() reader := io.TeeReader(f, h) if err = arch.WriteHeader(&tar.Header{ Name: layer + ".fs", Size: stat.Size(), Mode: 0o600, }); err != nil { return err } _, err = io.Copy(arch, reader) if err != nil { return err } digest := h.Sum(nil) err = sign(layer+".fs.sig", digest) if err != nil { return err } } return nil }