Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package kvm

import (
"context"
"encoding/pem"
"fmt"
"net/http"
"os"
Expand All @@ -16,9 +17,29 @@ import (
"github.com/rs/zerolog"
)

// caCertBundlePath is the path where the embedded CA certificate bundle is
// written at startup so that child processes (e.g. tailscale) can validate TLS
// certificates even though the device rootfs ships no system CA store.
const caCertBundlePath = "/tmp/jetkvm-cacerts.pem"

var appCtx context.Context
var procPrefix string = "jetkvm: [app]"

// writeCABundleFile converts the embedded rootcerts DER certificates to PEM
// and writes them to caCertBundlePath. This allows child processes to use the
// bundle via the SSL_CERT_FILE environment variable.
func writeCABundleFile() error {
var bundle []byte
for _, c := range rootcerts.CertsByTrust(rootcerts.ServerTrustedDelegator) {
block := &pem.Block{
Type: "CERTIFICATE",
Bytes: c.DER,
}
bundle = append(bundle, pem.EncodeToMemory(block)...)
}
return os.WriteFile(caCertBundlePath, bundle, 0644) //nolint:gosec
}

func setProcTitle(status string) {
if status != "" {
status = " " + status
Expand Down Expand Up @@ -81,6 +102,12 @@ func Main() {
Int("ca_certs_loaded", len(rootcerts.Certs())).
Msg("loaded Root CA certificates")

// Write the embedded CA bundle to disk so child processes (tailscale, etc.)
// can validate TLS certificates via SSL_CERT_FILE.
if werr := writeCABundleFile(); werr != nil {
logger.Warn().Err(werr).Msg("failed to write CA certificate bundle to disk")
}

initOta()

http.DefaultClient.Timeout = 1 * time.Minute
Expand Down
12 changes: 11 additions & 1 deletion tailscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"time"
Expand Down Expand Up @@ -54,11 +55,20 @@ func isTailscaleInstalled() bool {

// execTailscaleStatus runs `tailscale status --json` and returns the raw output.
// This is a package-level var to allow test substitution.
// newTailscaleCommand creates an exec.Cmd for a tailscale subcommand with the
// SSL_CERT_FILE environment variable set so that the tailscale binary can
// validate TLS certificates using the embedded CA bundle written at startup.
func newTailscaleCommand(ctx context.Context, args ...string) *exec.Cmd {
cmd := exec.CommandContext(ctx, "tailscale", args...)
cmd.Env = append(os.Environ(), "SSL_CERT_FILE="+caCertBundlePath)
return cmd
}

var execTailscaleStatus = func() ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), tailscaleCommandTimeout)
defer cancel()

output, err := exec.CommandContext(ctx, "tailscale", "status", "--json").CombinedOutput()
output, err := newTailscaleCommand(ctx, "status", "--json").CombinedOutput()
if err != nil {
return nil, fmt.Errorf("tailscale status: %w: %s", err, strings.TrimSpace(string(output)))
}
Expand Down
Loading