189 lines
5.9 KiB
Go
189 lines
5.9 KiB
Go
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)
|
||
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)
|
||
}
|
||
}
|