feat(authority): add delegated control-plane grants
CI / test (push) Failing after 37s

This commit is contained in:
m
2026-08-31 11:33:48 +03:00
parent cb50b7357b
commit 1c05619899
21 changed files with 865 additions and 10 deletions
@@ -0,0 +1,169 @@
package main
import (
"context"
"crypto/subtle"
"encoding/json"
"errors"
"io"
"net/http"
"os"
"strings"
"time"
"git.cin.su/m/Assistant/contracts/authority"
"git.cin.su/m/Assistant/contracts/identity"
secretcontracts "git.cin.su/m/Assistant/contracts/secrets"
authoritydomain "git.cin.su/m/Assistant/modules/authority/domain"
"git.cin.su/m/Assistant/platform/ids"
platformsecrets "git.cin.su/m/Assistant/platform/secrets"
)
const (
delegatedControlPlaneActor identity.ActorID = "domovoy-control-plane-actor"
delegatedControlPlanePrincipal identity.PrincipalID = "domovoy-control-plane-principal"
delegatedControlPlaneTokenPath = "/run/secrets/control_plane_service_token"
)
type delegatedControlPlaneAuthoritySource interface {
LoadDelegatedControlPlaneGrant(context.Context, authority.GrantID) (authority.DelegatedControlPlaneGrant, error)
RecordDelegatedControlPlaneAudit(context.Context, authority.DelegatedControlPlaneAudit) error
RevokeDelegatedControlPlaneGrant(context.Context, authority.GrantID, string) (bool, error)
}
type delegatedControlPlaneCore interface {
Session(context.Context, string) (identity.CreatorSession, error)
Binding(context.Context, identity.ActorID, identity.PrincipalID) (identity.PrincipalBinding, error)
}
type delegatedControlPlaneHandler struct {
authority delegatedControlPlaneAuthoritySource
core delegatedControlPlaneCore
credential func() (string, error)
now func() time.Time
newID func() (ids.ID, error)
}
func registerDelegatedControlPlaneRoutes(mux *http.ServeMux, handler delegatedControlPlaneHandler) {
mux.HandleFunc("POST /v1/internal/control-plane/authorize", handler.authorize)
mux.HandleFunc("POST /v1/internal/control-plane/completions", handler.complete)
mux.HandleFunc("POST /v1/creator/control-plane/grants/{grant}/revoke", handler.revoke)
}
func (h delegatedControlPlaneHandler) authorize(w http.ResponseWriter, r *http.Request) {
if !h.serviceAuthenticated(r) {
http.Error(w, "service principal denied", http.StatusUnauthorized)
return
}
var request authority.DelegatedControlPlaneRequest
decoder := json.NewDecoder(io.LimitReader(r.Body, 8<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&request); err != nil {
http.Error(w, "invalid delegated control-plane request", http.StatusBadRequest)
return
}
grant, err := h.authority.LoadDelegatedControlPlaneGrant(r.Context(), request.GrantID)
if err != nil {
http.Error(w, "delegated authority denied", http.StatusForbidden)
return
}
binding, err := h.core.Binding(r.Context(), delegatedControlPlaneActor, delegatedControlPlanePrincipal)
if err != nil || authoritydomain.ValidateDelegatedControlPlaneGrant(h.now().UnixMilli(), binding, grant, request) != nil {
_ = h.audit(r.Context(), grant, request, "DENY", "FAILED")
http.Error(w, "delegated authority denied", http.StatusForbidden)
return
}
if err := h.audit(r.Context(), grant, request, "ALLOW", "PENDING"); err != nil {
http.Error(w, "delegated audit unavailable", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h delegatedControlPlaneHandler) complete(w http.ResponseWriter, r *http.Request) {
if !h.serviceAuthenticated(r) {
http.Error(w, "service principal denied", http.StatusUnauthorized)
return
}
var value authority.DelegatedControlPlaneCompletion
decoder := json.NewDecoder(io.LimitReader(r.Body, 8<<10))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil || (value.Outcome != "COMPLETED" && value.Outcome != "FAILED") {
http.Error(w, "invalid delegated completion", http.StatusBadRequest)
return
}
grant, err := h.authority.LoadDelegatedControlPlaneGrant(r.Context(), value.Request.GrantID)
if err != nil {
http.Error(w, "delegated authority unavailable", http.StatusServiceUnavailable)
return
}
if !authoritydomain.RequestMatchesDelegatedControlPlaneGrant(grant, value.Request) {
http.Error(w, "delegated completion denied", http.StatusForbidden)
return
}
if err := h.audit(r.Context(), grant, value.Request, "ALLOW", value.Outcome); err != nil {
http.Error(w, "delegated audit unavailable", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h delegatedControlPlaneHandler) revoke(w http.ResponseWriter, r *http.Request) {
session, err := h.core.Session(r.Context(), r.Header.Get("Authorization"))
if err != nil {
http.Error(w, "creator session denied", http.StatusUnauthorized)
return
}
grant, err := h.authority.LoadDelegatedControlPlaneGrant(r.Context(), authority.GrantID(r.PathValue("grant")))
if err != nil || !authoritydomain.CanIssuerRevokeDelegatedGrant(session.PrincipalID, grant) {
http.Error(w, "delegated grant revoke denied", http.StatusForbidden)
return
}
ok, err := h.authority.RevokeDelegatedControlPlaneGrant(r.Context(), grant.ID, string(session.PrincipalID))
if err != nil || !ok {
http.Error(w, "delegated grant revoke denied", http.StatusConflict)
return
}
request := authority.DelegatedControlPlaneRequest{GrantID: grant.ID, TaskID: grant.TaskID, TaskRevision: grant.TaskRevision, Operation: authority.DelegatedOperationRevokeControlPlaneGrant, Resource: grant.Resource}
if err := h.audit(r.Context(), grant, request, "REVOKE", "REVOKED"); err != nil {
http.Error(w, "delegated audit unavailable", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusNoContent)
}
func (h delegatedControlPlaneHandler) serviceAuthenticated(r *http.Request) bool {
if h.credential == nil {
return false
}
expected, err := h.credential()
presented := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
return err == nil && expected != "" && presented != "" && subtle.ConstantTimeCompare([]byte(expected), []byte(presented)) == 1
}
func (h delegatedControlPlaneHandler) audit(ctx context.Context, grant authority.DelegatedControlPlaneGrant, request authority.DelegatedControlPlaneRequest, decision, outcome string) error {
if h.newID == nil {
return errors.New("delegated audit id unavailable")
}
id, err := h.newID()
if err != nil {
return err
}
return h.authority.RecordDelegatedControlPlaneAudit(ctx, authority.DelegatedControlPlaneAudit{AuditID: id, GrantID: grant.ID, ServiceActorID: delegatedControlPlaneActor, ServicePrincipalID: delegatedControlPlanePrincipal, TaskID: request.TaskID, TaskRevision: request.TaskRevision, Operation: request.Operation, Resource: request.Resource, Decision: decision, Outcome: outcome, RecordedAtUnixMillis: h.now().UnixMilli()})
}
func delegatedControlPlaneCredential() (string, error) {
ref := os.Getenv("CONTROL_PLANE_SERVICE_CREDENTIAL_REF")
if ref == "" {
return "", errors.New("control-plane service credential ref is required")
}
root := os.Getenv("SECRET_BACKEND_ROOT")
if root == "" {
root = "/run/secrets"
}
resolver, err := platformsecrets.NewFileResolver(root)
if err != nil {
return "", err
}
return resolver.ResolveFile(secretcontracts.SecretFileRef{CredentialRef: secretcontracts.CredentialRef(ref), Purpose: secretcontracts.SecretPurposeServiceAuthentication, Consumer: secretcontracts.RuntimeConsumerCommit, Path: delegatedControlPlaneTokenPath})
}
@@ -0,0 +1,91 @@
package main
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"git.cin.su/m/Assistant/contracts/authority"
"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 (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)
}
}
+3
View File
@@ -100,6 +100,9 @@ func main() {
core: creatorCore, continuity: creatorContinuity, authority: authorityRepository, repository: repository,
executor: dispatcher, newID: ids.New, now: time.Now,
}, executorToken)
registerDelegatedControlPlaneRoutes(mux, delegatedControlPlaneHandler{
authority: authorityRepository, core: creatorCore, credential: delegatedControlPlaneCredential, now: time.Now, newID: ids.New,
})
mux.HandleFunc("POST /v1/authorized-actions", func(w http.ResponseWriter, r *http.Request) {
var request authorizationRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {