117 lines
3.3 KiB
Go
117 lines
3.3 KiB
Go
|
|
package secrets
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func testStore(t *testing.T, h http.Handler) *Store {
|
||
|
|
t.Helper()
|
||
|
|
srv := httptest.NewServer(h)
|
||
|
|
t.Cleanup(srv.Close)
|
||
|
|
t.Setenv("BAO_ADDR", srv.URL)
|
||
|
|
t.Setenv("BAO_TOKEN", "test-token")
|
||
|
|
s, err := NewFromEnv("hall-of-helix")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
return s
|
||
|
|
}
|
||
|
|
|
||
|
|
// The salt rule. Rotating it silently invalidates every longitudinal comparison
|
||
|
|
// the interface has made, with no visible failure -- so the tool must have no
|
||
|
|
// path that replaces an existing one.
|
||
|
|
func TestCreateIfAbsentNeverOverwrites(t *testing.T) {
|
||
|
|
var writes int
|
||
|
|
existing := map[string]string{"salt": "the-original"}
|
||
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
if r.Method == http.MethodPost {
|
||
|
|
writes++
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"data": existing}})
|
||
|
|
}))
|
||
|
|
|
||
|
|
created, err := s.CreateIfAbsent(context.Background(), KeyRedactionSalt,
|
||
|
|
map[string]string{"salt": "a-replacement"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if created {
|
||
|
|
t.Error("reported creating a salt that already existed")
|
||
|
|
}
|
||
|
|
if writes != 0 {
|
||
|
|
t.Errorf("wrote over an existing salt (%d writes)", writes)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestCreateIfAbsentWritesWhenMissing(t *testing.T) {
|
||
|
|
var got map[string]any
|
||
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
if r.Method == http.MethodPost {
|
||
|
|
json.NewDecoder(r.Body).Decode(&got)
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
w.WriteHeader(http.StatusNotFound)
|
||
|
|
}))
|
||
|
|
|
||
|
|
created, err := s.CreateIfAbsent(context.Background(), KeyRedactionSalt,
|
||
|
|
map[string]string{"salt": "fresh"})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if !created {
|
||
|
|
t.Fatal("did not create a salt when none existed")
|
||
|
|
}
|
||
|
|
if got["data"].(map[string]any)["salt"] != "fresh" {
|
||
|
|
t.Errorf("wrote %v", got)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// A missing path is an ordinary first-run state, not an error.
|
||
|
|
func TestGetMissingIsNotAnError(t *testing.T) {
|
||
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.WriteHeader(http.StatusNotFound)
|
||
|
|
}))
|
||
|
|
_, found, err := s.Get(context.Background(), KeyBotToken)
|
||
|
|
if err != nil || found {
|
||
|
|
t.Fatalf("found=%v err=%v", found, err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ref is printed in plans and errors, so it must name a location and never
|
||
|
|
// carry a value.
|
||
|
|
func TestRefIsSafeToPrint(t *testing.T) {
|
||
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
json.NewEncoder(w).Encode(map[string]any{
|
||
|
|
"data": map[string]any{"data": map[string]string{"token": "123:SECRET"}}})
|
||
|
|
}))
|
||
|
|
ref := s.Ref(KeyBotToken)
|
||
|
|
if strings.Contains(ref, "SECRET") || strings.Contains(ref, "test-token") {
|
||
|
|
t.Fatalf("Ref leaked a secret: %q", ref)
|
||
|
|
}
|
||
|
|
if !strings.Contains(ref, "hall-of-helix") || !strings.Contains(ref, KeyBotToken) {
|
||
|
|
t.Errorf("Ref should locate the secret: %q", ref)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Errors name the path that failed, never what was at it.
|
||
|
|
func TestErrorsDoNotCarryValues(t *testing.T) {
|
||
|
|
s := testStore(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.WriteHeader(http.StatusInternalServerError)
|
||
|
|
}))
|
||
|
|
_, _, err := s.Get(context.Background(), KeyOperatorSession)
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected an error")
|
||
|
|
}
|
||
|
|
if strings.Contains(err.Error(), "test-token") {
|
||
|
|
t.Fatalf("error leaked the bao token: %v", err)
|
||
|
|
}
|
||
|
|
}
|