0daae92c4f
key.go: create-once 0600/0700, corrupt-refusal (never overwrite — may be escrowed identity), clamp for CANONICAL STORED form (x/crypto X25519 clamps derivation internally — discovered during red-proof (c); the stored-clamped test is the real anchor). Fixed vectors generated with real wg pubkey (provenance in test). hub: WireDesiredState.Wireguard + WireguardStatus report stanza + RegisterWG client (typed errors, token-free). S2 golden copied BYTE-IDENTICAL + field-exact decode test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
76 lines
2.3 KiB
Go
76 lines
2.3 KiB
Go
package hub
|
|
|
|
// S3 Group C — RegisterWG client (POST /hosts/{id}/wg, per-host key). Typed-error mapping and
|
|
// the no-token-in-errors invariant, same scaffolding as the desired-state client tests.
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestRegisterWG_PathAuthBodyAndDecode(t *testing.T) {
|
|
var gotPath, gotAuth, gotMethod, gotBody string
|
|
c := testClient(func(r *http.Request) (*http.Response, error) {
|
|
gotPath = r.URL.Path
|
|
gotAuth = r.Header.Get("Authorization")
|
|
gotMethod = r.Method
|
|
b, _ := io.ReadAll(r.Body)
|
|
gotBody = string(b)
|
|
return httpResp(200, `{"pubkey":"PK","assigned_ip":"10.77.0.2/32","existed":false,"generation":3,"sync":"ok"}`), nil
|
|
})
|
|
|
|
resp, err := c.RegisterWG(context.Background(), "PK")
|
|
if err != nil {
|
|
t.Fatalf("RegisterWG: %v", err)
|
|
}
|
|
if gotMethod != http.MethodPost || gotPath != "/api/v1/hosts/demo-host-01/wg" {
|
|
t.Errorf("request = %s %s, want POST /api/v1/hosts/demo-host-01/wg", gotMethod, gotPath)
|
|
}
|
|
if gotAuth != "Bearer super-secret-bearer-key" {
|
|
t.Errorf("auth = %q", gotAuth)
|
|
}
|
|
if gotBody != `{"pubkey":"PK"}` {
|
|
t.Errorf("body = %s", gotBody)
|
|
}
|
|
if resp.AssignedIP != "10.77.0.2/32" || resp.Existed || resp.Generation != 3 || resp.Sync != "ok" {
|
|
t.Errorf("resp = %+v", resp)
|
|
}
|
|
}
|
|
|
|
func TestRegisterWG_TypedErrors(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
status int
|
|
body string
|
|
}{
|
|
{403, "Forbidden: host_id mismatch"},
|
|
{404, "Unknown host_id"},
|
|
{409, "wg endpoint not configured"},
|
|
{409, "pubkey already registered elsewhere"},
|
|
} {
|
|
c := testClient(func(r *http.Request) (*http.Response, error) {
|
|
return httpResp(tc.status, tc.body), nil
|
|
})
|
|
_, err := c.RegisterWG(context.Background(), "PK")
|
|
var he *HTTPError
|
|
if !errors.As(err, &he) || he.StatusCode != tc.status {
|
|
t.Errorf("status %d: err = %v, want HTTPError %d", tc.status, err, tc.status)
|
|
}
|
|
if strings.Contains(err.Error(), "super-secret-bearer-key") {
|
|
t.Fatalf("bearer token leaked into error: %v", err)
|
|
}
|
|
}
|
|
// Transport failure → TransportError, token-free.
|
|
c := testClient(func(r *http.Request) (*http.Response, error) {
|
|
return nil, errors.New("dial tcp: connection refused")
|
|
})
|
|
_, err := c.RegisterWG(context.Background(), "PK")
|
|
var te *TransportError
|
|
if !errors.As(err, &te) {
|
|
t.Errorf("transport err = %v, want TransportError", err)
|
|
}
|
|
}
|