141 lines
6.7 KiB
Go
141 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.cin.su/m/Assistant/contracts/authority"
|
|
"git.cin.su/m/Assistant/contracts/capability"
|
|
"git.cin.su/m/Assistant/contracts/identity"
|
|
"git.cin.su/m/Assistant/platform/ids"
|
|
)
|
|
|
|
type delegatedAuthorityFake struct {
|
|
grant authority.DelegatedControlPlaneGrant
|
|
audits []authority.DelegatedControlPlaneAudit
|
|
revoked bool
|
|
}
|
|
|
|
func TestCandidateBindingResolutionUsesServiceCredential(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/v1/internal/capability-bindings/candidate/resolve" || r.Header.Get("Authorization") != "Bearer service-token" {
|
|
t.Fatalf("unexpected candidate resolution request: %s %q", r.URL.Path, r.Header.Get("Authorization"))
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"reason":"EXACT_CONTEXT"}`))
|
|
}))
|
|
defer server.Close()
|
|
client, err := newCreatorCoreClient(server.URL, server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
client.serviceCredential = func() (string, error) { return "service-token", nil }
|
|
if _, err := client.ResolveCandidateCapabilityBinding(context.Background(), capability.CandidateReleaseContext{}); err != nil {
|
|
t.Fatalf("resolve candidate binding: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestDelegatedAuthorityCredentialRotationRejectsOldToken(t *testing.T) {
|
|
root := t.TempDir()
|
|
tokenPath := filepath.Join(root, "control_plane_service_token")
|
|
if err := os.WriteFile(tokenPath, []byte("old-token\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Setenv("CONTROL_PLANE_SERVICE_CREDENTIAL_REF", "credential://domovoy/control-plane/service-token/v1")
|
|
t.Setenv("CONTROL_PLANE_SERVICE_SECRET_ROOT", root)
|
|
h := delegatedControlPlaneHandler{credential: delegatedControlPlaneCredential}
|
|
oldRequest := httptest.NewRequest(http.MethodPost, "/v1/internal/control-plane/authorize", nil)
|
|
oldRequest.Header.Set("Authorization", "Bearer old-token")
|
|
if !h.serviceAuthenticated(oldRequest) {
|
|
t.Fatal("active credential was rejected before rotation")
|
|
}
|
|
if err := os.WriteFile(tokenPath, []byte("new-token\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h.serviceAuthenticated(oldRequest) {
|
|
t.Fatal("old credential remained valid after rotation")
|
|
}
|
|
newRequest := httptest.NewRequest(http.MethodPost, "/v1/internal/control-plane/authorize", nil)
|
|
newRequest.Header.Set("Authorization", "Bearer new-token")
|
|
if !h.serviceAuthenticated(newRequest) {
|
|
t.Fatal("new credential was rejected after rotation")
|
|
}
|
|
}
|
|
|
|
func (f *delegatedAuthorityFake) LoadDelegatedControlPlaneGrant(context.Context, authority.GrantID) (authority.DelegatedControlPlaneGrant, error) {
|
|
if f.grant.ID == "" {
|
|
return authority.DelegatedControlPlaneGrant{}, errors.New("missing")
|
|
}
|
|
return f.grant, nil
|
|
}
|
|
func (f *delegatedAuthorityFake) RecordDelegatedControlPlaneAudit(_ context.Context, audit authority.DelegatedControlPlaneAudit) error {
|
|
f.audits = append(f.audits, audit)
|
|
return nil
|
|
}
|
|
func (f *delegatedAuthorityFake) RevokeDelegatedControlPlaneGrant(context.Context, authority.GrantID, string) (bool, error) {
|
|
f.revoked = true
|
|
f.grant.Status = authority.DelegatedGrantRevoked
|
|
return true, nil
|
|
}
|
|
|
|
type delegatedCoreFake struct{}
|
|
|
|
func (delegatedCoreFake) Binding(context.Context, identity.ActorID, identity.PrincipalID) (identity.PrincipalBinding, error) {
|
|
return identity.PrincipalBinding{ActorID: delegatedControlPlaneActor, PrincipalID: delegatedControlPlanePrincipal, Active: true}, nil
|
|
}
|
|
func (delegatedCoreFake) Session(context.Context, string) (identity.CreatorSession, error) {
|
|
return identity.CreatorSession{PrincipalID: "owner"}, nil
|
|
}
|
|
|
|
func TestDelegatedAuthorityRejectsWrongResourceAndSurvivesRestart(t *testing.T) {
|
|
resource := authority.CognitiveBindingResource{BindingID: "binding", ProviderInstanceID: "provider", ModelDescriptorID: "model"}
|
|
fake := &delegatedAuthorityFake{grant: authority.DelegatedControlPlaneGrant{ID: "grant", ServiceActorID: delegatedControlPlaneActor, ServicePrincipalID: delegatedControlPlanePrincipal, IssuerPrincipalID: "owner", TaskID: "GEN2-COGNITIVE-003", TaskRevision: 4, Operations: []authority.DelegatedControlPlaneOperation{authority.DelegatedOperationCanaryCognitiveBinding}, Resource: resource, IssuedAtUnixMillis: 1, ExpiresAtUnixMillis: 100, ArchitectureRevision: "ADR-0021", Nonce: "nonce", Revision: 1, Status: authority.DelegatedGrantActive}}
|
|
h := delegatedControlPlaneHandler{authority: fake, core: delegatedCoreFake{}, credential: func() (string, error) { return "service-token", nil }, now: func() time.Time { return time.UnixMilli(10) }, newID: func() (ids.ID, error) { return "audit", nil }}
|
|
mux := http.NewServeMux()
|
|
registerDelegatedControlPlaneRoutes(mux, h)
|
|
valid := `{"GrantID":"grant","TaskID":"GEN2-COGNITIVE-003","TaskRevision":4,"Operation":"CANARY_COGNITIVE_BINDING","Resource":{"BindingID":"binding","ProviderInstanceID":"provider","ModelDescriptorID":"model"}}`
|
|
request := httptest.NewRequest(http.MethodPost, "/v1/internal/control-plane/authorize", strings.NewReader(valid))
|
|
request.Header.Set("Authorization", "Bearer service-token")
|
|
response := httptest.NewRecorder()
|
|
mux.ServeHTTP(response, request)
|
|
if response.Code != http.StatusNoContent {
|
|
t.Fatalf("valid delegation status=%d", response.Code)
|
|
}
|
|
wrong := strings.Replace(valid, `"binding"`, `"other"`, 1)
|
|
request = httptest.NewRequest(http.MethodPost, "/v1/internal/control-plane/authorize", strings.NewReader(wrong))
|
|
request.Header.Set("Authorization", "Bearer service-token")
|
|
response = httptest.NewRecorder()
|
|
mux.ServeHTTP(response, request)
|
|
if response.Code != http.StatusForbidden {
|
|
t.Fatalf("wrong resource status=%d", response.Code)
|
|
}
|
|
// A fresh handler reads the same durable grant source; no process-local allow survives restart.
|
|
request = httptest.NewRequest(http.MethodPost, "/v1/creator/control-plane/grants/grant/revoke", nil)
|
|
request.Header.Set("Authorization", "Bearer creator-session")
|
|
response = httptest.NewRecorder()
|
|
mux.ServeHTTP(response, request)
|
|
if response.Code != http.StatusNoContent || !fake.revoked {
|
|
t.Fatalf("revoke status=%d revoked=%v", response.Code, fake.revoked)
|
|
}
|
|
restarted := delegatedControlPlaneHandler{authority: fake, core: delegatedCoreFake{}, credential: h.credential, now: h.now, newID: h.newID}
|
|
mux = http.NewServeMux()
|
|
registerDelegatedControlPlaneRoutes(mux, restarted)
|
|
request = httptest.NewRequest(http.MethodPost, "/v1/internal/control-plane/authorize", strings.NewReader(valid))
|
|
request.Header.Set("Authorization", "Bearer service-token")
|
|
response = httptest.NewRecorder()
|
|
mux.ServeHTTP(response, request)
|
|
if response.Code != http.StatusForbidden {
|
|
t.Fatalf("revoked grant accepted after restart: %d", response.Code)
|
|
}
|
|
if len(fake.audits) < 3 {
|
|
t.Fatalf("audit trail missing: %#v", fake.audits)
|
|
}
|
|
}
|