136 lines
5.5 KiB
Go
136 lines
5.5 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package tentacle
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/tentaclewire"
|
|
)
|
|
|
|
func TestRemoteCloseBeforeRetirementCallback(t *testing.T) {
|
|
for _, state := range []string{"expired", "cancelled"} {
|
|
t.Run(state, func(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
access := &remoteAccess{lease: strings.Repeat("a", 32), epoch: 1,
|
|
ctx: ctx, cancel: cancel, deadline: time.Now().Add(time.Minute)}
|
|
if state == "expired" {
|
|
access.deadline = time.Now().Add(-time.Second)
|
|
} else {
|
|
cancel()
|
|
}
|
|
manager := &Manager{}
|
|
session := &Session{manager: manager, remote: access, remoteEpoch: 1,
|
|
outbound: make(chan outgoing, 4)}
|
|
manager.remoteOwner = session
|
|
operation := tentaclewire.RemoteOperation{Lease: access.lease, Epoch: 1, Sequence: 1}
|
|
data, _ := json.Marshal(operation)
|
|
if _, failure := session.remote_call_locked("remote.heartbeat", data); failure == nil {
|
|
t.Fatal("heartbeat revived retired authority")
|
|
}
|
|
wrong := operation
|
|
wrong.Lease = strings.Repeat("b", 32)
|
|
invalid, _ := json.Marshal(wrong)
|
|
if _, failure := session.remote_call_locked("remote.close", invalid); failure == nil {
|
|
t.Fatal("foreign token closed pending retirement")
|
|
}
|
|
if _, failure := session.remote_call_locked("remote.close", data); failure != nil {
|
|
t.Fatal("cleanup rejected before retirement callback", failure)
|
|
}
|
|
if session.remote != nil || manager.remoteOwner != nil {
|
|
t.Fatal("close retained remote ownership")
|
|
}
|
|
if _, failure := session.remote_call_locked("remote.close", data); failure != nil {
|
|
t.Fatal("repeated cleanup failed", failure)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRemotePolicyRevokesPendingAndLiveSDKAccess(t *testing.T) {
|
|
for _, language := range []string{"go", "python"} {
|
|
t.Run(language, func(t *testing.T) {
|
|
manager := test_manager(t, DefaultLimits(), nil)
|
|
policy := NewRemotePolicy()
|
|
if err := manager.UseRemotePolicy(policy); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
session := startSessionSDK(t, manager, language, "policy-probe")
|
|
if _, err := session.AuthorizeRemote("controlled", []string{"output-observe"}); !errors.Is(err, ErrPermission) {
|
|
t.Fatal("fixture authorizer bypassed policy", err)
|
|
}
|
|
stale, _ := session.BeginRemoteApproval("controlled", []string{"output-observe"})
|
|
policy.SetAvailable(false)
|
|
if _, err := session.BeginRemoteApproval("controlled", []string{"output-observe"}); !errors.Is(err, ErrPermission) {
|
|
t.Fatal("unavailable policy issued ticket", err)
|
|
}
|
|
policy.SetAvailable(true)
|
|
if _, err := session.AuthorizeRemoteWithApproval(stale, "controlled", []string{"output-observe"}); !errors.Is(err, ErrPermission) {
|
|
t.Fatal("lock round trip revived ticket", err)
|
|
}
|
|
foreign := &RemoteApproval{policy: NewRemotePolicy()}
|
|
if _, err := session.AuthorizeRemoteWithApproval(foreign, "controlled", []string{"output-observe"}); !errors.Is(err, ErrPermission) {
|
|
t.Fatal("foreign ticket accepted", err)
|
|
}
|
|
for _, opened := range []bool{false, true} {
|
|
approval, _ := session.BeginRemoteApproval("controlled", []string{"output-observe"})
|
|
action, err := session.AuthorizeRemoteWithApproval(approval, "controlled", []string{"output-observe"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := session.AuthorizeRemoteWithApproval(approval, "controlled", []string{"output-observe"}); !errors.Is(err, ErrPermission) {
|
|
t.Fatal("replayed approval", err)
|
|
}
|
|
invokeSessionSDK(t, session, "event", nil)
|
|
var lease tentaclewire.RemoteLease
|
|
request := tentaclewire.RemoteOpen{Action: action.Action, Epoch: action.Epoch, Role: action.Role, Grants: action.Grants}
|
|
if opened {
|
|
value, code := remoteCall(t, session, "remote.open", request)
|
|
if code != 0 || json.Unmarshal(value, &lease) != nil {
|
|
t.Fatal("open failed", code)
|
|
}
|
|
}
|
|
// Hold the session lock so asynchronous event cleanup cannot run.
|
|
// Policy cancellation must already invalidate every delivery context.
|
|
session.mutex.Lock()
|
|
access := session.remote
|
|
policy.SetAvailable(false)
|
|
cancelled := access.ctx.Err() != nil
|
|
session.mutex.Unlock()
|
|
if !cancelled {
|
|
t.Fatal("revocation waited for session worker")
|
|
}
|
|
policy.SetAvailable(true)
|
|
if _, code := remoteCall(t, session, "remote.open", request); code != -32001 {
|
|
t.Fatal("stale open revived approval", code)
|
|
}
|
|
if opened {
|
|
if _, err := session.RemoteContext(lease, "output-observe"); !errors.Is(err, ErrPermission) {
|
|
t.Fatal("delivery survived policy", err)
|
|
}
|
|
if _, code := remoteCall(t, session, "remote.heartbeat", tentaclewire.RemoteOperation{Lease: lease.Lease, Epoch: lease.Epoch, Sequence: 1}); code != -32001 {
|
|
t.Fatal("heartbeat revived lease", code)
|
|
}
|
|
}
|
|
var event tentaclewire.SessionEvent
|
|
if json.Unmarshal(invokeSessionSDK(t, session, "event", nil), &event) != nil || event.Kind != "remote.revoked" || event.Epoch != action.Epoch || event.Lease != lease.Lease || (!opened && event.Action != action.Action) {
|
|
t.Fatal("revocation event", event)
|
|
}
|
|
}
|
|
// A fresh approval can replace the cancelled epoch even if its
|
|
// asynchronous policy callback has not acquired the session lock yet.
|
|
fresh, _ := session.BeginRemoteApproval("controlled", []string{"output-observe"})
|
|
if _, err := session.AuthorizeRemoteWithApproval(fresh, "controlled", []string{"output-observe"}); err != nil {
|
|
t.Fatal("fresh approval failed", err)
|
|
}
|
|
})
|
|
}
|
|
}
|