Skip to content
quarry Artifact [607eb904ff]
Login

Artifact [607eb904ff]

Artifact 607eb904ff75f3d24811f7e7bc3cffd4494d49aec22e7615e0b060f7119a4687:


package oltp

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

// The binding round-trips with its secret, the table never holds the
// plaintext, the key file is made 0600 beside the database on first
// use and a reopen reads the same key; a probe stamps the row and an
// unbind removes it.
func TestBucketBindingSealsTheSecret(t *testing.T) {
	dir := t.TempDir()
	path := filepath.Join(dir, "oltp.db")
	d, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := os.Stat(filepath.Join(dir, sealKeyFile)); !os.IsNotExist(err) {
		t.Fatalf("FAIL: opening wrote %s before anything was sealed", sealKeyFile)
	}
	if _, ok, err := d.Bucket("w"); err != nil || ok {
		t.Fatalf("FAIL: unbound repo answered %v %v", ok, err)
	}
	in := BucketBinding{Endpoint: "https://acct.r2.cloudflarestorage.com", Bucket: "files", Region: "auto",
		AccessKey: "AK", Secret: "s3cret-key-material", PublicBase: "https://files.example"}
	if err := d.BindBucket("w", in); err != nil {
		t.Fatal(err)
	}
	st, err := os.Stat(filepath.Join(dir, sealKeyFile))
	if err != nil {
		t.Fatalf("FAIL: no key file: %v", err)
	}
	if st.Mode().Perm() != 0o600 {
		t.Errorf("FAIL: key file mode %o, want 600", st.Mode().Perm())
	}
	var stored string
	if err := d.db.QueryRow(`SELECT secret_enc FROM bucket WHERE repo = 'w'`).Scan(&stored); err != nil {
		t.Fatal(err)
	}
	if strings.Contains(stored, in.Secret) || stored == "" {
		t.Fatalf("FAIL: secret_enc holds the plaintext: %q", stored)
	}
	got, ok, err := d.Bucket("w")
	if err != nil || !ok {
		t.Fatalf("FAIL: Bucket: %v %v", ok, err)
	}
	if got.Secret != in.Secret || got.Endpoint != in.Endpoint || got.LimitBytes != DefaultBucketLimit || got.PublicBase != in.PublicBase {
		t.Fatalf("FAIL: round trip %+v", got)
	}
	at := time.Date(2026, 9, 11, 10, 0, 0, 0, time.UTC)
	if err := d.SetBucketProbe("w", at, "PUT returned 403 AccessDenied", true); err != nil {
		t.Fatal(err)
	}
	if got, _, _ = d.Bucket("w"); got.ProbedAt != "2026-09-11T10:00:00Z" || got.ProbeErr == "" || !got.CorsOK {
		t.Fatalf("FAIL: probe not recorded: %+v", got)
	}
	d.Close()

	// A reopen reads the same key file and opens what the first
	// process sealed.
	d2, err := Open(path)
	if err != nil {
		t.Fatal(err)
	}
	defer d2.Close()
	if got, _, err = d2.Bucket("w"); err != nil || got.Secret != in.Secret {
		t.Fatalf("FAIL: after reopen: %v %+v", err, got)
	}
	if err := d2.UnbindBucket("w"); err != nil {
		t.Fatal(err)
	}
	if _, ok, _ := d2.Bucket("w"); ok {
		t.Fatal("FAIL: still bound after unbind")
	}
	if err := d2.BindBucket("w", BucketBinding{Endpoint: "https://x", Bucket: "b", AccessKey: "k"}); err == nil {
		t.Fatal("FAIL: a binding without a secret was accepted")
	}
	t.Log("ok: sealed at rest, unsealed on read, key file 0600 and stable across reopen")
}

// A replaced key file makes the stored secret unreadable, and the
// error says so rather than answering garbage.
func TestBucketSecretNamesALostKey(t *testing.T) {
	dir := t.TempDir()
	d, err := Open(filepath.Join(dir, "oltp.db"))
	if err != nil {
		t.Fatal(err)
	}
	if err := d.BindBucket("w", BucketBinding{Endpoint: "https://x", Bucket: "b", AccessKey: "k", Secret: "s"}); err != nil {
		t.Fatal(err)
	}
	d.Close()
	if err := os.Remove(filepath.Join(dir, sealKeyFile)); err != nil {
		t.Fatal(err)
	}
	d, err = Open(filepath.Join(dir, "oltp.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer d.Close()
	_, _, err = d.Bucket("w")
	if err == nil || !strings.Contains(err.Error(), sealKeyFile) {
		t.Fatalf("FAIL: want an error naming the key file, got %v", err)
	}
	t.Logf("ok: %v", err)
}