Skip to content
quarry Artifact [9133af8959]
Login

Artifact [9133af8959]

Artifact 9133af89599ef1ceb688850eeb91cd94847b5959c80daddd16a272cd448ee2e7:


package api

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/cisecret"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
)

func testBox(t *testing.T) cisecret.Box {
	t.Helper()
	box, err := cisecret.New(base64.StdEncoding.EncodeToString([]byte("0123456789abcdef0123456789abcdef")))
	if err != nil {
		t.Fatal(err)
	}
	return box
}

// An approved request's secret is sealed at rest, handed to the asking
// agent once, and, uncollected, cleared after its limit with the token
// it held revoked and the lapse logged; a second sweep does nothing.
func TestASignOffSecretIsSealedCollectedOnceAndLapses(t *testing.T) {
	db, err := oltp.Open(filepath.Join(t.TempDir(), "o.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.Close() })
	for _, who := range []string{"jva"} {
		if err := db.AddAccount(who, "a-long-enough-password", false); err != nil {
			t.Fatal(err)
		}
	}
	if err := db.AddAccount("jva-bot", "a-long-enough-password", false); err != nil {
		t.Fatal(err)
	}
	if err := db.SetOperator("jva-bot", "jva"); err != nil {
		t.Fatal(err)
	}
	eng := &workerEngine{}
	rec := &settingRecorder{}
	holds := func(string, string) CapSet { return oltp.CapSet("a") }
	deps := Deps{Version: "v", WorkerTokens: eng, SettingLog: rec, SecretBox: testBox(t),
		Repos:    NewRepos(map[string]*Repo{"test": {Index: wikiIndex(t), Code: "code-test"}}),
		Accounts: adminAdapter{acctAdapter{db}}, SignOffs: testSignOffs{db}, CapsFor: holds}
	r := ops.NewRegistry()
	if err := RegisterAll(r, deps); err != nil {
		t.Fatal(err)
	}
	h, err := RouterWithCaps(r, "v", holds, nil)
	if err != nil {
		t.Fatal(err)
	}
	ctx := ops.WithToken(ops.WithUser(context.Background(), "jva-bot"), ops.TokenAuth{ID: "T", Caps: func(string) ops.Caps { return oltp.CapSet("a") }})
	ask := func(label string) string {
		req := httptest.NewRequest("POST", "/api/v1/r/test/ci/worker-tokens", strings.NewReader(`{"label":"`+label+`"}`)).WithContext(ctx)
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Quarry-Ask", "a runner")
		w := httptest.NewRecorder()
		h.ServeHTTP(w, req)
		var asked struct {
			SignOff SignOffRequest `json:"signoff"`
		}
		if err := json.Unmarshal(w.Body.Bytes(), &asked); err != nil || w.Code != 202 {
			t.Fatalf("FAIL: asking: %d %s", w.Code, w.Body)
		}
		if _, err := r.Invoke(asUser("jva"), "signoff.decide", signOffDecideIn{ID: asked.SignOff.ID, Approve: true}); err != nil {
			t.Fatal(err)
		}
		return asked.SignOff.ID
	}

	// Collected: sealed at rest, the value once.
	first := ask("one")
	row, _, _ := db.SignOffByID(first)
	if !strings.HasPrefix(row.Secret, sealedPrefix) || strings.Contains(row.Secret, "s3cret-bearer-value") {
		t.Fatalf("FAIL: the stored column is not sealed: %.60s", row.Secret)
	}
	got, err := r.Invoke(asUser("jva-bot"), "signoff.get", signOffGetIn{ID: first})
	if err != nil || !strings.Contains(string(got.(signOffGetOut).Secret), "s3cret-bearer-value") {
		t.Fatalf("FAIL: the agent did not get the value: %v %s", err, got.(signOffGetOut).Secret)
	}
	again, _ := r.Invoke(asUser("jva-bot"), "signoff.get", signOffGetIn{ID: first})
	if len(again.(signOffGetOut).Secret) != 0 {
		t.Error("FAIL: the value was readable twice")
	}

	// Uncollected: cleared after the limit, the token revoked, logged once.
	second := ask("two")
	before := len(eng.revoked)
	if n, err := deps.LapseSignOffSecrets(time.Now()); err != nil || n != 0 {
		t.Fatalf("FAIL: a fresh secret lapsed: %d %v", n, err)
	}
	if n, err := deps.LapseSignOffSecrets(time.Now().Add(25 * time.Hour)); err != nil || n != 1 {
		t.Fatalf("FAIL: the sweep cleared %d (%v), want 1", n, err)
	}
	if len(eng.revoked) != before+1 || eng.revoked[len(eng.revoked)-1] != "new-2" {
		t.Errorf("FAIL: revoked %v, want new-2 added", eng.revoked)
	}
	var lapsedRows int
	for _, row := range rec.rows {
		if strings.Contains(row.now, "lapsed uncollected") && strings.Contains(row.now, second) && strings.Contains(row.now, "jva-bot") {
			lapsedRows++
		}
	}
	if lapsedRows != 1 {
		t.Errorf("FAIL: %d lapse rows in %+v", lapsedRows, rec.rows)
	}
	row, _, _ = db.SignOffByID(second)
	if row.Secret != oltp.SignOffSecretLapsed {
		t.Errorf("FAIL: the secret column holds %q after the sweep", row.Secret)
	}
	if n, _ := deps.LapseSignOffSecrets(time.Now().Add(26 * time.Hour)); n != 0 || len(eng.revoked) != before+1 {
		t.Errorf("FAIL: a second sweep did something: %d, revoked %v", n, eng.revoked)
	}
	late, err := r.Invoke(asUser("jva-bot"), "signoff.get", signOffGetIn{ID: second})
	if err != nil || late.(signOffGetOut).Note == "" || len(late.(signOffGetOut).Secret) != 0 {
		t.Errorf("FAIL: the agent reading after the limit: %v %+v", err, late)
	}

	// A row written before secrets were sealed is still handed over once.
	third := ask("three")
	if err := db.FinishSignOff(third, `{}`, `{"value":"legacy-plain"}`); err != nil {
		t.Fatal(err)
	}
	old, _ := r.Invoke(asUser("jva-bot"), "signoff.get", signOffGetIn{ID: third})
	if !strings.Contains(string(old.(signOffGetOut).Secret), "legacy-plain") {
		t.Errorf("FAIL: an unsealed row was not readable: %s", old.(signOffGetOut).Secret)
	}
	t.Log("ok")
}