Files
restic-manager/internal/server/http/host_credentials_test.go
T
steve e968abc042 ci: fix race-trip in enrollment fixture + bump golangci-lint to v2.1.6
- host_credentials_test.go's CreateEnrollmentToken fixture passed 1<<20
  as the TTL (third arg, time.Duration) — that's ~1ms in nanoseconds.
  Local non-race runs finished inside the window, but -race overhead
  blew the deadline so the token was already expired by the time
  GetEnrollmentTokenAttachments / ConsumeEnrollmentToken ran. Use
  time.Hour instead, which matches the spirit of a per-test fixture.
- Lint pin v1.61.0 was built against Go 1.23 and refuses to load a
  config targeting newer toolchains. go.mod is on 1.25, so the lint
  step exited 3 ('the Go language version used to build golangci-lint
  is lower than the targeted Go version'). Bumping to v2.1.6, which
  supports Go 1.25.

Both failures showed up only on the Gitea runner because local make
target runs go test without -race and lint hadn't been re-run after
the go.mod toolchain bump.
2026-05-03 11:13:22 +01:00

108 lines
3.4 KiB
Go

package http
import (
"context"
"encoding/json"
"testing"
"time"
)
// TestEnrollmentTransfersRepoCreds verifies the round-trip:
// - operator mints a token with repo_url/username/password
// - encrypted blob lands on the token row, bound to token_hash
// - on consume, the blob is re-encrypted bound to host_id and
// written to host_credentials in the same tx.
func TestEnrollmentTransfersRepoCreds(t *testing.T) {
t.Parallel()
srv, _, st := newTestServerWithHub(t)
ctx := context.Background()
want := repoCredsBlob{
RepoURL: "rest:https://repo.example/host42",
RepoUsername: "host42",
RepoPassword: "hunter2",
}
// Encrypt + create token like the operator endpoint would.
const tokHash = "tok-hash-fixture"
enc, err := srv.encryptRepoCreds(want, []byte("token:"+tokHash))
if err != nil {
t.Fatalf("encrypt: %v", err)
}
if err := st.CreateEnrollmentToken(ctx, tokHash, time.Hour, enc, ""); err != nil {
t.Fatalf("create token: %v", err)
}
// Rebind under host_id, then consume (this is what the agent
// enroll handler does inline).
const hostID = "h-fixture"
_, encForHost, err := srv.rebindTokenAttachments(ctx, tokHash, hostID)
if err != nil {
t.Fatalf("rebind: %v", err)
}
if encForHost == "" {
t.Fatal("rebind returned empty blob; expected re-encrypted ciphertext")
}
if encForHost == enc {
t.Errorf("rebind should change ciphertext (additional-data differs); got identical")
}
// Burn the token, then create the host row, then promote — same
// order the HTTP handler runs.
if err := st.ConsumeEnrollmentToken(ctx, tokHash, hostID); err != nil {
t.Fatalf("consume: %v", err)
}
if _, err := st.DB().Exec(
`INSERT INTO hosts (id, name, os, arch, enrolled_at) VALUES (?,?,?,?,?)`,
hostID, "host42", "linux", "amd64", "2026-01-01T00:00:00Z"); err != nil {
t.Fatalf("insert host: %v", err)
}
if err := st.SetHostCredentials(ctx, hostID, encForHost); err != nil {
t.Fatalf("set host credentials: %v", err)
}
// host_credentials row should now hold the host-bound ciphertext.
got, err := st.GetHostCredentials(ctx, hostID)
if err != nil {
t.Fatalf("get host creds: %v", err)
}
plain, err := srv.deps.AEAD.Decrypt(got, []byte("host:"+hostID))
if err != nil {
t.Fatalf("decrypt: %v", err)
}
var blob repoCredsBlob
if err := json.Unmarshal(plain, &blob); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if blob != want {
t.Errorf("blob mismatch:\n got %+v\nwant %+v", blob, want)
}
// Cross-check: decrypting with a wrong AD must fail (swap
// detection — proves the AAD binding is doing real work).
if _, err := srv.deps.AEAD.Decrypt(got, []byte("host:other-host")); err == nil {
t.Error("decrypt with wrong AD must fail; AAD binding is broken")
}
}
// TestEnrollmentTokenWithoutCreds is the regression that ensures the
// existing ttl/single-use semantics still work when no creds are
// attached (used by the enrollment_test.go fixture path).
func TestEnrollmentTokenWithoutCreds(t *testing.T) {
t.Parallel()
_, _, st := newTestServerWithHub(t)
ctx := context.Background()
const tokHash = "no-creds-token"
if err := st.CreateEnrollmentToken(ctx, tokHash, time.Hour, "", ""); err != nil {
t.Fatalf("create: %v", err)
}
att, err := st.GetEnrollmentTokenAttachments(ctx, tokHash)
if err != nil {
t.Fatalf("get token attachments: %v", err)
}
if att.EncRepoCreds != "" {
t.Errorf("token without creds should return empty blob; got %q", att.EncRepoCreds)
}
}