Files
DDI/server/cmd/ddi-server/license_test.go
T
sir_melonchenandClaude Opus 5.5 df25705b6e feat(server): add license set-key command reading the key from stdin
Validates and activates the key like PUT /api/v1/license (same service
call, audited as the system actor) and prints the license state without
the key. Exit codes: 2 bad input, 3 key rejected, 4 license server
unreachable or untrusted (nothing stored).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:28:33 +02:00

191 lines
6.8 KiB
Go

// SPDX-License-Identifier: LicenseRef-DDI-Proprietary
package main
import (
"bytes"
"context"
"crypto/ed25519"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"time"
"github.com/jackc/pgx/v5"
)
const testKeygenAccount = "9de54794-8e94-4250-83f6-87e3d5686a0b"
// fakeKeygen answers the requests of license activation with signed
// responses: "GOOD-KEY" is valid (no machine activation needed), every
// other key does not exist.
func fakeKeygen(t *testing.T) (url string, pub ed25519.PublicKey) {
t.Helper()
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatal(err)
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/v1/accounts/"+testKeygenAccount)
body, _ := io.ReadAll(r.Body)
var resp any
switch {
case r.Method == http.MethodPost && path == "/licenses/actions/validate-key":
var req struct {
Meta struct {
Key string `json:"key"`
Nonce int64 `json:"nonce"`
} `json:"meta"`
}
_ = json.Unmarshal(body, &req)
meta := map[string]any{"valid": false, "code": "NOT_FOUND", "detail": "does not exist", "nonce": req.Meta.Nonce}
var data any
if req.Meta.Key == "GOOD-KEY" {
meta["valid"], meta["code"], meta["detail"] = true, "VALID", "valid"
data = map[string]any{"id": "lic-1", "type": "licenses", "attributes": map[string]any{
"name": "CLI Test", "expiry": "2030-01-01T00:00:00Z", "status": "ACTIVE",
"metadata": map[string]any{"maxAgents": 5},
}}
}
resp = map[string]any{"meta": meta, "data": data}
case r.Method == http.MethodGet && path == "/licenses/lic-1/entitlements":
resp = map[string]any{"data": []any{map[string]any{"attributes": map[string]any{"code": "HA"}}}}
default:
w.WriteHeader(http.StatusNotFound)
return
}
out, _ := json.Marshal(resp)
sum := sha256.Sum256(out)
digest := "sha-256=" + base64.StdEncoding.EncodeToString(sum[:])
date := time.Now().UTC().Format(http.TimeFormat)
signing := "(request-target): " + strings.ToLower(r.Method) + " " + r.URL.RequestURI() + "\nhost: " + r.Host +
"\ndate: " + date + "\ndigest: " + digest
sig := ed25519.Sign(priv, []byte(signing))
w.Header().Set("Date", date)
w.Header().Set("Digest", digest)
w.Header().Set("Keygen-Signature", `keyid="`+testKeygenAccount+`", algorithm="ed25519", signature="`+
base64.StdEncoding.EncodeToString(sig)+`", headers="(request-target) host date digest"`)
w.Header().Set("Content-Type", "application/vnd.api+json")
_, _ = w.Write(out)
}))
t.Cleanup(srv.Close)
return srv.URL, pub
}
func TestReadKey(t *testing.T) {
if k, err := readKey(strings.NewReader(" ABCD-1234 \n")); err != nil || k != "ABCD-1234" {
t.Errorf("trimmed key: %q %v", k, err)
}
for name, in := range map[string]string{
"empty": "",
"blank": " \n\t\n",
"too long": strings.Repeat("A", maxKeyLength+1),
"oversized": strings.Repeat("A", maxKeyInput+1),
"two lines": "KEY-1\nKEY-2\n",
"blank line": "KEY-1\r\nKEY-2",
} {
_, err := readKey(strings.NewReader(in))
ee, ok := err.(*exitError) //nolint:errorlint // readKey returns it unwrapped
if !ok || ee.code != exitUsage {
t.Errorf("%s: %v", name, err)
}
}
}
func TestLicenseCommandUsage(t *testing.T) {
var out, errOut bytes.Buffer
if code := run(context.Background(), []string{"license"}, lookupFrom(nil), nil, &out, &errOut); code != exitUsage {
t.Errorf("without subcommand: %d", code)
}
if code := run(context.Background(), []string{"license", "frob"}, lookupFrom(nil), nil, &out, &errOut); code != exitUsage {
t.Errorf("unknown subcommand: %d", code)
}
if code := run(context.Background(), []string{"license", "set-key", "KEY"}, lookupFrom(nil), nil, &out, &errOut); code != exitUsage ||
!strings.Contains(errOut.String(), "stdin") {
t.Errorf("key as argument: %d %q", code, errOut.String())
}
out.Reset()
if code := run(context.Background(), []string{"license", "help"}, lookupFrom(nil), nil, &out, &errOut); code != 0 ||
!strings.Contains(out.String(), "set-key") {
t.Errorf("help: %d %q", code, out.String())
}
errOut.Reset()
if code := run(context.Background(), []string{"license", "set-key"}, lookupFrom(nil), strings.NewReader("\n"), &out, &errOut); code != exitUsage ||
!strings.Contains(errOut.String(), "no license key") {
t.Errorf("empty stdin: %d %q", code, errOut.String())
}
}
func TestLicenseSetKey(t *testing.T) {
dbURL := testDatabase(t)
dir := t.TempDir()
keyFile := filepath.Join(dir, "master.key")
if code := run(context.Background(), []string{"gen-master-key", "-out", keyFile}, lookupFrom(nil), nil, io.Discard, io.Discard); code != 0 {
t.Fatal("gen-master-key failed")
}
keygenURL, pub := fakeKeygen(t)
env := map[string]string{
"DDI_DATABASE_URL": dbURL, "DDI_MASTER_KEY_FILE": keyFile, "DDI_STATE_DIR": dir, "DDI_LOG_LEVEL": "error",
"KEYGEN_URL": keygenURL, "KEYGEN_ACCOUNT_ID": testKeygenAccount, "KEYGEN_PUBLIC_KEY": hex.EncodeToString(pub),
}
setKey := func(key string, env map[string]string) (int, string, string) {
var out, errOut bytes.Buffer
code := run(context.Background(), []string{"license", "set-key"}, lookupFrom(env), strings.NewReader(key), &out, &errOut)
return code, out.String(), errOut.String()
}
code, out, errOut := setKey("UNKNOWN-KEY\n", env)
if code != exitKeyRejected || !strings.Contains(errOut, "license_invalid") || !strings.Contains(errOut, "NOT_FOUND") {
t.Errorf("unknown key: %d %q", code, errOut)
}
if strings.Contains(out+errOut, "UNKNOWN-KEY") {
t.Error("the key must not be printed")
}
unreachable := map[string]string{}
for k, v := range env {
unreachable[k] = v
}
unreachable["KEYGEN_URL"] = "http://127.0.0.1:1"
if code, _, errOut := setKey("GOOD-KEY", unreachable); code != exitLicenseSvc || !strings.Contains(errOut, "license_server_unreachable") {
t.Errorf("unreachable license server: %d %q", code, errOut)
}
code, out, errOut = setKey(" GOOD-KEY\n", env)
if code != 0 {
t.Fatalf("set-key: %d %s", code, errOut)
}
for _, want := range []string{"status: valid", "CLI Test (lic-1)", "expires: 2030-01-01T00:00:00Z",
"max agents: 5", "max addresses: unlimited", "features: HA"} {
if !strings.Contains(out, want) {
t.Errorf("output lacks %q:\n%s", want, out)
}
}
if strings.Contains(out+errOut, "GOOD-KEY") {
t.Error("the key must not be printed")
}
conn, err := pgx.Connect(context.Background(), dbURL)
if err != nil {
t.Fatal(err)
}
defer conn.Close(context.Background()) //nolint:errcheck // test
var actor string
var n int
if err := conn.QueryRow(context.Background(),
`SELECT min(actor_name), count(*) FROM audit_log WHERE action = 'license.set_key'`).Scan(&actor, &n); err != nil {
t.Fatal(err)
}
if n != 1 || actor != "system" {
t.Errorf("audit: %d entries by %q", n, actor)
}
}