Files
br ca6cdcca63 software steht so weit.
jetzt build + docker build und testen
2026-10-02 13:25:34 +02:00

189 lines
5.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package store
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"sync"
"testing"
"time"
)
func TestEnroll(t *testing.T) {
s, clock := openTest(t)
mustTenant(t, s, "t1")
if err := s.AddEnrollmentToken(ctx, "sltn_geheim", "t1", clock.now().Add(time.Hour)); err != nil {
t.Fatalf("AddEnrollmentToken: %v", err)
}
n := sampleNode("n1", "key1")
n.TenantID = "manipuliert" // muss durch den Mandanten des Tokens ersetzt werden
node, err := s.Enroll(ctx, "sltn_geheim", n)
if err != nil {
t.Fatalf("Enroll: %v", err)
}
if node.TenantID != "t1" {
t.Errorf("TenantID = %q, erwartet t1 (aus dem Token)", node.TenantID)
}
if got, err := s.GetNodeByPublicKey(ctx, "key1"); err != nil || got.ID != "n1" {
t.Errorf("Knoten nicht angelegt: %+v, %v", got, err)
}
}
func TestAddEnrollmentToken_StoresOnlyHash(t *testing.T) {
s, clock := openTest(t)
mustTenant(t, s, "t1")
if err := s.AddEnrollmentToken(ctx, "sltn_geheim", "t1", clock.now().Add(time.Hour)); err != nil {
t.Fatal(err)
}
var stored string
if err := s.db.QueryRowContext(ctx, "SELECT token_hash FROM enrollment_tokens").Scan(&stored); err != nil {
t.Fatal(err)
}
sum := sha256.Sum256([]byte("sltn_geheim"))
if stored != hex.EncodeToString(sum[:]) {
t.Errorf("gespeichert %q, erwartet SHA-256-Hex des Tokens", stored)
}
}
func TestAddEnrollmentToken_Errors(t *testing.T) {
s, clock := openTest(t)
mustTenant(t, s, "t1")
later := clock.now().Add(time.Hour)
if err := s.AddEnrollmentToken(ctx, "tok", "fehlt", later); !errors.Is(err, ErrNotFound) {
t.Errorf("unbekannter Mandant: erwartet ErrNotFound, bekommen %v", err)
}
if err := s.AddEnrollmentToken(ctx, "", "t1", later); !errors.Is(err, ErrInvalid) {
t.Errorf("leerer Token: erwartet ErrInvalid, bekommen %v", err)
}
if err := s.AddEnrollmentToken(ctx, "tok", "t1", clock.now()); !errors.Is(err, ErrInvalid) {
t.Errorf("Ablauf nicht in der Zukunft: erwartet ErrInvalid, bekommen %v", err)
}
if err := s.AddEnrollmentToken(ctx, "tok", "t1", later); err != nil {
t.Fatal(err)
}
if err := s.AddEnrollmentToken(ctx, "tok", "t1", later); !errors.Is(err, ErrExists) {
t.Errorf("doppelter Token: erwartet ErrExists, bekommen %v", err)
}
}
func TestEnroll_TokenOnlyOnce(t *testing.T) {
s, clock := openTest(t)
mustTenant(t, s, "t1")
s.AddEnrollmentToken(ctx, "tok", "t1", clock.now().Add(time.Hour))
if _, err := s.Enroll(ctx, "tok", sampleNode("n1", "k1")); err != nil {
t.Fatal(err)
}
if _, err := s.Enroll(ctx, "tok", sampleNode("n2", "k2")); !errors.Is(err, ErrTokenUsed) {
t.Errorf("erwartet ErrTokenUsed, bekommen %v", err)
}
if _, err := s.GetNode(ctx, "n2"); !errors.Is(err, ErrNotFound) {
t.Errorf("zweiter Knoten wurde trotzdem angelegt")
}
}
func TestEnroll_Expired(t *testing.T) {
s, clock := openTest(t)
mustTenant(t, s, "t1")
s.AddEnrollmentToken(ctx, "tok", "t1", clock.now().Add(time.Hour))
clock.advance(time.Hour) // Ablaufzeitpunkt selbst gilt schon als abgelaufen
if _, err := s.Enroll(ctx, "tok", sampleNode("n1", "k1")); !errors.Is(err, ErrTokenExpired) {
t.Errorf("erwartet ErrTokenExpired, bekommen %v", err)
}
if _, err := s.GetNode(ctx, "n1"); !errors.Is(err, ErrNotFound) {
t.Errorf("Knoten trotz abgelaufenem Token angelegt")
}
}
func TestEnroll_UnknownToken(t *testing.T) {
s, _ := openTest(t)
if _, err := s.Enroll(ctx, "gibts-nicht", sampleNode("n1", "k1")); !errors.Is(err, ErrNotFound) {
t.Errorf("erwartet ErrNotFound, bekommen %v", err)
}
}
// Schlägt das Anlegen des Knotens fehl, darf der Token nicht verbraucht sein.
func TestEnroll_RollbackKeepsToken(t *testing.T) {
s, clock := openTest(t)
mustTenant(t, s, "t1")
mustNode(t, s, "vorhanden", "t1", "k-belegt")
s.AddEnrollmentToken(ctx, "tok", "t1", clock.now().Add(time.Hour))
if _, err := s.Enroll(ctx, "tok", sampleNode("n1", "k-belegt")); !errors.Is(err, ErrExists) {
t.Fatalf("doppelter Key: erwartet ErrExists, bekommen %v", err)
}
invalid := sampleNode("n1", "k-neu")
invalid.Hostname = ""
if _, err := s.Enroll(ctx, "tok", invalid); !errors.Is(err, ErrInvalid) {
t.Fatalf("ungültiger Knoten: erwartet ErrInvalid, bekommen %v", err)
}
if _, err := s.Enroll(ctx, "tok", sampleNode("n1", "k-neu")); err != nil {
t.Errorf("Token nach fehlgeschlagenem Enroll nicht mehr nutzbar: %v", err)
}
}
func TestEnroll_ConcurrentSingleUse(t *testing.T) {
s, clock := openTest(t)
mustTenant(t, s, "t1")
s.AddEnrollmentToken(ctx, "tok", "t1", clock.now().Add(time.Hour))
const workers = 10
errs := make([]error, workers)
var wg sync.WaitGroup
for i := range workers {
wg.Go(func() {
_, errs[i] = s.Enroll(ctx, "tok", sampleNode(fmt.Sprintf("n%d", i), fmt.Sprintf("k%d", i)))
})
}
wg.Wait()
ok, used := 0, 0
for _, err := range errs {
switch {
case err == nil:
ok++
case errors.Is(err, ErrTokenUsed):
used++
default:
t.Errorf("unerwarteter Fehler: %v", err)
}
}
if ok != 1 || used != workers-1 {
t.Errorf("%d erfolgreich, %d ErrTokenUsed – erwartet genau 1 Erfolg", ok, used)
}
nodes, _ := s.ListNodes(ctx, "t1")
if len(nodes) != 1 {
t.Errorf("%d Knoten angelegt, erwartet 1", len(nodes))
}
}
func TestPurgeEnrollmentTokens(t *testing.T) {
s, clock := openTest(t)
mustTenant(t, s, "t1")
s.AddEnrollmentToken(ctx, "benutzt", "t1", clock.now().Add(time.Hour))
s.AddEnrollmentToken(ctx, "läuft-ab", "t1", clock.now().Add(time.Minute))
s.AddEnrollmentToken(ctx, "gültig", "t1", clock.now().Add(2*time.Hour))
if _, err := s.Enroll(ctx, "benutzt", sampleNode("n1", "k1")); err != nil {
t.Fatal(err)
}
clock.advance(time.Minute)
n, err := s.PurgeEnrollmentTokens(ctx, 0)
if err != nil {
t.Fatal(err)
}
if n != 2 {
t.Errorf("%d Tokens gelöscht, erwartet 2", n)
}
if _, err := s.Enroll(ctx, "gültig", sampleNode("n2", "k2")); err != nil {
t.Errorf("gültiger Token wurde gelöscht: %v", err)
}
if _, err := s.Enroll(ctx, "läuft-ab", sampleNode("n3", "k3")); !errors.Is(err, ErrNotFound) {
t.Errorf("abgelaufener Token noch vorhanden: %v", err)
}
}