a73x

internal/smoke/userca.go

Ref:   Size: 2.3 KiB   History

package smoke

import (
	"crypto/ed25519"
	"crypto/rand"
	"encoding/pem"
	"errors"
	"fmt"
	"io/fs"
	"os"
	"path/filepath"

	"golang.org/x/crypto/ssh"
)

// smokeUserCA returns the CA the smoke signs with: the persistent one at path,
// or a throwaway held in memory when no path is configured. The gate check
// needs the persistent one (its public half is registered once and reused);
// the delegation leg registers whichever it gets, so it works either way.
func smokeUserCA(path string) (ssh.Signer, error) {
	if path == "" {
		return generateUserCA()
	}
	return loadOrCreateUserCA(path)
}

// generateUserCA returns a fresh ed25519 CA that is never written down.
func generateUserCA() (ssh.Signer, error) {
	_, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("generate user CA key: %w", err)
	}
	signer, err := ssh.NewSignerFromSigner(priv)
	if err != nil {
		return nil, fmt.Errorf("build user CA signer: %w", err)
	}
	return signer, nil
}

// loadOrCreateUserCA loads the smoke's persistent user CA key from path,
// generating and persisting a fresh ed25519 key on first run so subsequent
// smoke runs reuse the same CA identity (the gate check registers this key's
// public half with the tenant once; a fresh key every run would mean an
// ever-growing set of trusted, never-reused CAs).
func loadOrCreateUserCA(path string) (ssh.Signer, error) {
	data, err := os.ReadFile(path)
	if err == nil {
		signer, err := ssh.ParsePrivateKey(data)
		if err != nil {
			return nil, fmt.Errorf("parse user CA key %q: %w", path, err)
		}
		return signer, nil
	}
	if !errors.Is(err, fs.ErrNotExist) {
		return nil, fmt.Errorf("read user CA key %q: %w", path, err)
	}

	_, priv, err := ed25519.GenerateKey(rand.Reader)
	if err != nil {
		return nil, fmt.Errorf("generate user CA key: %w", err)
	}
	block, err := ssh.MarshalPrivateKey(priv, "")
	if err != nil {
		return nil, fmt.Errorf("marshal user CA key: %w", err)
	}
	pemBytes := pem.EncodeToMemory(block)

	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		return nil, fmt.Errorf("mkdir user CA key dir: %w", err)
	}
	if err := os.WriteFile(path, pemBytes, 0o600); err != nil {
		return nil, fmt.Errorf("write user CA key %q: %w", path, err)
	}

	signer, err := ssh.NewSignerFromSigner(priv)
	if err != nil {
		return nil, fmt.Errorf("build user CA signer: %w", err)
	}
	return signer, nil
}