252 lines
6.6 KiB
Go
252 lines
6.6 KiB
Go
package license
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"aijianzhan/platform/internal/config"
|
|
)
|
|
|
|
func testCfg(dir, seed string) config.LicenseConf {
|
|
return config.LicenseConf{
|
|
Enabled: true,
|
|
LeaseDir: dir,
|
|
SeedNotAfter: seed,
|
|
Customer: "t",
|
|
ControlSecret: "test-license-secret",
|
|
SignSecret: "test-license-secret",
|
|
}
|
|
}
|
|
|
|
func countLeaseFiles(dir string) int {
|
|
ents, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
n := 0
|
|
for _, e := range ents {
|
|
if !e.IsDir() && strings.HasPrefix(e.Name(), "lease-") && strings.HasSuffix(e.Name(), ".json") {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func TestEachOpWritesNewFile(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 10).Format("2006-01-02")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n := countLeaseFiles(dir); n != 1 {
|
|
t.Fatalf("bootstrap files=%d", n)
|
|
}
|
|
if _, err := m.Extend(7, "support", "e1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.Renew(time.Now().UTC().AddDate(1, 0, 0), "billing", "y1"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n := countLeaseFiles(dir); n != 3 {
|
|
t.Fatalf("want 3 lease files, got %d", n)
|
|
}
|
|
st := m.Status(time.Now())
|
|
if st.LeaseCount != 3 || st.ActiveFile == "" || st.Expired {
|
|
t.Fatalf("%+v", st)
|
|
}
|
|
}
|
|
|
|
func TestExtendLimits(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 10).Format("2006-01-02")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := 0; i < DefaultMaxExtensions; i++ {
|
|
if _, err := m.Extend(DefaultExtensionMaxDays, "test", ""); err != nil {
|
|
t.Fatalf("extend %d: %v", i+1, err)
|
|
}
|
|
}
|
|
if _, err := m.Extend(1, "test", ""); err == nil {
|
|
t.Fatal("expected max extensions exceeded")
|
|
}
|
|
}
|
|
|
|
func TestTamperDoesNotOverrideValidHistory(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 30).Format("2006-01-02")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
good := m.Status(time.Now()).ActiveFile
|
|
// 写入一份篡改文件
|
|
bad := `{
|
|
"id": "deadbeef",
|
|
"customer": "hack",
|
|
"not_after": "2099-12-31T00:00:00Z",
|
|
"extensions_used": 0,
|
|
"extensions_max": 5,
|
|
"extension_max_days": 30,
|
|
"updated_at": "2099-01-01T00:00:00Z",
|
|
"updated_by": "hacker",
|
|
"note": "",
|
|
"signature": "00"
|
|
}`
|
|
if err := os.WriteFile(filepath.Join(dir, "lease-20990101T000000Z-hacker-deadbeef.json"), []byte(bad), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
st := m.Status(time.Now())
|
|
if st.Expired || !st.SignatureOK || st.ActiveFile != good {
|
|
t.Fatalf("tampered file must be ignored: %+v want active=%s", st, good)
|
|
}
|
|
}
|
|
|
|
func TestTamperActiveRejectedWhenAlone(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 30).Format("2006-01-02")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
active := m.Status(time.Now()).ActiveFile
|
|
b, _ := os.ReadFile(filepath.Join(dir, active))
|
|
var raw map[string]any
|
|
_ = json.Unmarshal(b, &raw)
|
|
raw["not_after"] = "2099-12-31T00:00:00Z"
|
|
out, _ := json.MarshalIndent(raw, "", " ")
|
|
_ = os.WriteFile(filepath.Join(dir, active), out, 0o644)
|
|
st := m.Status(time.Now())
|
|
if !st.Expired || st.SignatureOK {
|
|
t.Fatalf("tampered sole lease must fail: %+v", st)
|
|
}
|
|
}
|
|
|
|
func TestWatchdogViaAPI(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := NewManager(testCfg(dir, "2020-01-01"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before := countLeaseFiles(dir)
|
|
lease, err := m.PutLease(&Lease{
|
|
Customer: "A",
|
|
NotAfter: time.Date(2099, 12, 31, 0, 0, 0, 0, time.UTC),
|
|
}, "watchdog")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if lease.FileName == "" || lease.ID == "" {
|
|
t.Fatalf("%+v", lease)
|
|
}
|
|
if countLeaseFiles(dir) != before+1 {
|
|
t.Fatal("put should add a new file")
|
|
}
|
|
}
|
|
|
|
func TestOfflineSameFileOnce(t *testing.T) {
|
|
dir := t.TempDir()
|
|
m, err := NewManager(testCfg(dir, time.Now().UTC().AddDate(0, 0, 5).Format("2006-01-02")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = m.Status(time.Now()) // 初始化消费账本
|
|
cur := m.Status(time.Now())
|
|
pkg, err := m.MintSignedJSON(&Lease{
|
|
Customer: "t",
|
|
NotAfter: time.Now().UTC().AddDate(0, 0, 35),
|
|
ExtensionsUsed: 1,
|
|
ExtensionsMax: 1,
|
|
ExtensionMaxDays: 30,
|
|
UpdatedAt: time.Now().UTC(),
|
|
UpdatedBy: "offline-pack",
|
|
Note: "usb",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.ImportSigned(pkg, "usb"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if m.Status(time.Now()).Expired {
|
|
t.Fatal("after import should work")
|
|
}
|
|
// 同一份再导入 → 拒绝
|
|
if _, err := m.ImportSigned(pkg, "usb"); err == nil {
|
|
t.Fatal("same file must not import twice")
|
|
}
|
|
// 正式续费后,再丢回这份旧包仍应拒绝
|
|
if _, err := m.Renew(time.Now().UTC().AddDate(1, 0, 0), "billing", ""); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.ImportSigned(pkg, "usb"); err == nil {
|
|
t.Fatal("consumed id must stay rejected after renew")
|
|
}
|
|
_ = cur
|
|
}
|
|
|
|
func TestClearConsumedFailsClosed(t *testing.T) {
|
|
dir := t.TempDir()
|
|
leaseDir := filepath.Join(dir, "leases")
|
|
stateDir := filepath.Join(dir, "state")
|
|
cfg := testCfg(leaseDir, time.Now().UTC().AddDate(0, 0, 20).Format("2006-01-02"))
|
|
cfg.StateDir = stateDir
|
|
m, err := NewManager(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = m.Status(time.Now())
|
|
if err := os.WriteFile(filepath.Join(stateDir, "_consumed.json"), []byte(`{"active_id":"x","ids":[],"updated_at":"2020-01-01T00:00:00Z","signature":"00"}`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
st := m.Status(time.Now())
|
|
if !st.Expired {
|
|
t.Fatalf("tampered consumed ledger must fail closed: %+v", st)
|
|
}
|
|
}
|
|
|
|
func TestWipeLeasesKeepsState(t *testing.T) {
|
|
dir := t.TempDir()
|
|
leaseDir := filepath.Join(dir, "leases")
|
|
stateDir := filepath.Join(dir, "state")
|
|
cfg := testCfg(leaseDir, time.Now().UTC().AddDate(0, 0, 5).Format("2006-01-02"))
|
|
cfg.StateDir = stateDir
|
|
m, err := NewManager(cfg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = m.Status(time.Now())
|
|
pkg, err := m.MintSignedJSON(&Lease{
|
|
Customer: "t", NotAfter: time.Now().UTC().AddDate(0, 0, 40),
|
|
ExtensionsUsed: 1, ExtensionsMax: 1, ExtensionMaxDays: 30,
|
|
UpdatedAt: time.Now().UTC(), UpdatedBy: "pack",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := m.ImportSigned(pkg, "usb"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// 删光租约文件,保留 state
|
|
ents, _ := os.ReadDir(leaseDir)
|
|
for _, e := range ents {
|
|
_ = os.Remove(filepath.Join(leaseDir, e.Name()))
|
|
}
|
|
if _, err := m.ImportSigned(pkg, "usb"); err == nil {
|
|
t.Fatal("after wiping leases, same pack must still be rejected via state")
|
|
}
|
|
}
|
|
|
|
func TestDisabled(t *testing.T) {
|
|
m, err := NewManager(config.LicenseConf{Enabled: false})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
st := m.Status(time.Now())
|
|
if st.Expired || st.Enabled {
|
|
t.Fatalf("%+v", st)
|
|
}
|
|
}
|