242 lines
5.8 KiB
Go
242 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"forge-mesh/internal/db"
|
|
"forge-mesh/internal/forge"
|
|
)
|
|
|
|
func TestForgeBuildCrossCompileAndSign(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfgPath := writeForgeConfig(t, dir)
|
|
bin := buildForgeBinary(t)
|
|
|
|
out, err := exec.Command(bin, "build",
|
|
"--config", cfgPath,
|
|
"--version", "test-1.0",
|
|
"--public",
|
|
).CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("forge build: %v\n%s", err, out)
|
|
}
|
|
|
|
output := string(out)
|
|
if !strings.Contains(output, "linux/amd64") || !strings.Contains(output, "linux/arm64") {
|
|
t.Fatalf("expected amd64 and arm64 builds in output:\n%s", output)
|
|
}
|
|
if !strings.Contains(output, "checksum=") {
|
|
t.Fatalf("expected checksum in build output:\n%s", output)
|
|
}
|
|
if !strings.Contains(output, "public key:") {
|
|
t.Fatalf("expected public key in build output:\n%s", output)
|
|
}
|
|
|
|
artifactsDir := filepath.Join(dir, "artifacts")
|
|
for _, arch := range []string{"amd64", "arm64"} {
|
|
path := filepath.Join(artifactsDir, "forge-mesh-agent-linux-"+arch)
|
|
assertSignedArtifact(t, path, cfgPath)
|
|
}
|
|
}
|
|
|
|
func TestForgeBuildPublicFlag(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfgPath := writeForgeConfig(t, dir)
|
|
bin := buildForgeBinary(t)
|
|
|
|
if out, err := exec.Command(bin, "build", "--config", cfgPath, "--public=false").CombinedOutput(); err != nil {
|
|
t.Fatalf("forge build private: %v\n%s", err, out)
|
|
}
|
|
|
|
conn := openDB(t, filepath.Join(dir, "forge-mesh.db"))
|
|
defer conn.Close()
|
|
|
|
var publicCount int
|
|
if err := conn.QueryRow(`SELECT COUNT(*) FROM builds WHERE public = 1`).Scan(&publicCount); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if publicCount != 0 {
|
|
t.Fatalf("expected no public builds with --public=false, got %d", publicCount)
|
|
}
|
|
|
|
if out, err := exec.Command(bin, "build", "--config", cfgPath, "--public").CombinedOutput(); err != nil {
|
|
t.Fatalf("forge build public: %v\n%s", err, out)
|
|
}
|
|
if err := conn.QueryRow(`SELECT COUNT(*) FROM builds WHERE public = 1`).Scan(&publicCount); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if publicCount < 2 {
|
|
t.Fatalf("expected public builds after --public, got %d", publicCount)
|
|
}
|
|
}
|
|
|
|
func TestForgePubkey(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cfgPath := writeForgeConfig(t, dir)
|
|
bin := buildForgeBinary(t)
|
|
|
|
out, err := exec.Command(bin, "pubkey", "--config", cfgPath).CombinedOutput()
|
|
if err != nil {
|
|
t.Fatalf("forge pubkey: %v\n%s", err, out)
|
|
}
|
|
|
|
hexKey := strings.TrimSpace(string(out))
|
|
if len(hexKey) != 64 {
|
|
t.Fatalf("expected 64-char ed25519 pubkey hex, got %q", hexKey)
|
|
}
|
|
|
|
cfg := readForgeConfigFile(t, cfgPath)
|
|
kp, err := forge.LoadOrCreateKey(cfg.Forge.SigningKeyPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if kp.PublicKeyHex() != hexKey {
|
|
t.Fatalf("pubkey mismatch: CLI %s key file %s", hexKey, kp.PublicKeyHex())
|
|
}
|
|
}
|
|
|
|
func TestForgeUsageExitsNonZero(t *testing.T) {
|
|
bin := buildForgeBinary(t)
|
|
cmd := exec.Command(bin)
|
|
err := cmd.Run()
|
|
if err == nil {
|
|
t.Fatal("expected non-zero exit without subcommand")
|
|
}
|
|
}
|
|
|
|
func assertSignedArtifact(t *testing.T, path, cfgPath string) {
|
|
t.Helper()
|
|
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
t.Fatalf("artifact %s: %v", path, err)
|
|
}
|
|
if info.Size() == 0 {
|
|
t.Fatalf("empty artifact %s", path)
|
|
}
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sum := sha256.Sum256(data)
|
|
checksum := hex.EncodeToString(sum[:])
|
|
|
|
cfg := readForgeConfigFile(t, cfgPath)
|
|
kp, err := forge.LoadOrCreateKey(cfg.Forge.SigningKeyPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
sig := kp.Sign(data)
|
|
if !forge.Verify(kp.Public, data, sig) {
|
|
t.Fatalf("artifact %s failed ed25519 verification", path)
|
|
}
|
|
|
|
conn := openDB(t, cfg.DatabasePath)
|
|
defer conn.Close()
|
|
|
|
var dbChecksum, dbSig string
|
|
err = conn.QueryRow(`
|
|
SELECT checksum, signature FROM builds
|
|
WHERE path = ? ORDER BY created_at DESC LIMIT 1
|
|
`, path).Scan(&dbChecksum, &dbSig)
|
|
if err != nil {
|
|
t.Fatalf("build row for %s: %v", path, err)
|
|
}
|
|
if dbChecksum != checksum {
|
|
t.Fatalf("checksum mismatch for %s: db %s file %s", path, dbChecksum, checksum)
|
|
}
|
|
if !forge.Verify(kp.Public, data, dbSig) {
|
|
t.Fatalf("db signature invalid for %s", path)
|
|
}
|
|
}
|
|
|
|
func buildForgeBinary(t *testing.T) string {
|
|
t.Helper()
|
|
out := filepath.Join(t.TempDir(), "forge-mesh-forge")
|
|
cmd := exec.Command("go", "build", "-o", out, "./cmd/forge")
|
|
cmd.Dir = repoRoot(t)
|
|
if outBytes, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("build forge: %v\n%s", err, outBytes)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func writeForgeConfig(t *testing.T, dir string) string {
|
|
t.Helper()
|
|
cfgPath := filepath.Join(dir, "config.json")
|
|
content := fmt.Sprintf(`{
|
|
"listen_addr": ":0",
|
|
"data_dir": %q,
|
|
"database_path": %q,
|
|
"auth": {
|
|
"fleet_secret": "forge-test-secret"
|
|
},
|
|
"forge": {
|
|
"signing_key_path": %q,
|
|
"artifacts_dir": %q
|
|
}
|
|
}`, dir, filepath.Join(dir, "forge-mesh.db"),
|
|
filepath.Join(dir, "signing.key"), filepath.Join(dir, "artifacts"))
|
|
if err := os.WriteFile(cfgPath, []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return cfgPath
|
|
}
|
|
|
|
type forgeConfigSnippet struct {
|
|
DatabasePath string `json:"database_path"`
|
|
Forge struct {
|
|
SigningKeyPath string `json:"signing_key_path"`
|
|
} `json:"forge"`
|
|
}
|
|
|
|
func readForgeConfigFile(t *testing.T, path string) forgeConfigSnippet {
|
|
t.Helper()
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var cfg forgeConfigSnippet
|
|
if err := json.Unmarshal(data, &cfg); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func openDB(t *testing.T, path string) *sql.DB {
|
|
t.Helper()
|
|
conn, err := db.Open(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return conn
|
|
}
|
|
|
|
func repoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
wd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for {
|
|
if _, err := os.Stat(filepath.Join(wd, "go.mod")); err == nil {
|
|
return wd
|
|
}
|
|
parent := filepath.Dir(wd)
|
|
if parent == wd {
|
|
t.Fatal("go.mod not found")
|
|
}
|
|
wd = parent
|
|
}
|
|
}
|