Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
146 lines
5.8 KiB
Go
146 lines
5.8 KiB
Go
package taskexecution
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"github.com/netkingdom/access-engine/internal/callerauth"
|
|
"github.com/netkingdom/access-engine/pkg/api"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
var ErrServiceInput = errors.New("service task execution input refused")
|
|
|
|
const ServicePilotPackageID = "secrets-engine.task-execution.service-pilot"
|
|
const ServicePilotPackageVersion = "v1"
|
|
|
|
const ServicePilotMappingVersion = "proposed-service-task-caller/v1"
|
|
const ServicePilotPrincipal = "system:serviceaccount:secrets-engine:secrets-engine"
|
|
|
|
// ServicePilotInputs installs the exact B1 service input mapping for signed DENY.
|
|
// It does not validate Approval facts or establish executable eligibility.
|
|
// It installs the explicit
|
|
// proposed fixed subject-to-transport mapping, never a request-selected mapping.
|
|
// No constructor parameter can select warn/disabled mode or another subject.
|
|
type ServicePilotInputs struct{ auth *callerauth.Authenticator }
|
|
|
|
func NewServicePilotInputs(reviewer callerauth.TokenReviewer) (*ServicePilotInputs, error) {
|
|
a, e := callerauth.New(callerauth.ModeEnforce, reviewer, "flex-auth", map[string]string{"secrets-engine": ServicePilotPrincipal}, nil)
|
|
if e != nil {
|
|
return nil, ErrServiceInput
|
|
}
|
|
return &ServicePilotInputs{auth: a}, nil
|
|
}
|
|
|
|
// ServiceInput cannot be fabricated through exported fields. It proves only
|
|
// source input preparation through TokenReview, not approval provenance/consume
|
|
// or execution authority. Default zero value is not an authenticated input.
|
|
type ServiceInput struct {
|
|
serialized []byte
|
|
caller callerauth.Record
|
|
}
|
|
|
|
func (s ServiceInput) Request() (api.CheckRequest, error) {
|
|
if s.caller.Mode != callerauth.ModeEnforce || s.caller.Principal != ServicePilotPrincipal || len(s.serialized) == 0 {
|
|
return api.CheckRequest{}, ErrServiceInput
|
|
}
|
|
var r api.CheckRequest
|
|
if json.Unmarshal(s.serialized, &r) != nil {
|
|
return r, ErrServiceInput
|
|
}
|
|
return r, nil
|
|
}
|
|
func (s ServiceInput) Caller() callerauth.Record { return s.caller }
|
|
|
|
// RequestAt rechecks transport and operation lifetime at the runtime use site.
|
|
// Request() remains a detached source snapshot, never an execution capability.
|
|
func (s ServiceInput) RequestAt(now time.Time) (api.CheckRequest, error) {
|
|
r, err := s.Request()
|
|
if err != nil || now.IsZero() || s.caller.Audience != "flex-auth" || !s.caller.NotAfter.After(now) {
|
|
return api.CheckRequest{}, ErrServiceInput
|
|
}
|
|
contract, ok := r.Context["execution_contract"].(map[string]any)
|
|
if !ok {
|
|
return api.CheckRequest{}, ErrServiceInput
|
|
}
|
|
value, ok := contract["operation_deadline"].(string)
|
|
if !ok {
|
|
return api.CheckRequest{}, ErrServiceInput
|
|
}
|
|
deadline, err := time.Parse(time.RFC3339Nano, value)
|
|
if err != nil || !deadline.After(now) || deadline.After(s.caller.NotAfter) {
|
|
return api.CheckRequest{}, ErrServiceInput
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// DecodeServiceRequest reuses the owner strict JSON scanner for this bounded
|
|
// task-only route. It never changes producer canonicalization or legacy input.
|
|
func DecodeServiceRequest(reader io.Reader) (api.CheckRequest, error) {
|
|
raw, err := io.ReadAll(io.LimitReader(reader, 524289))
|
|
var request api.CheckRequest
|
|
if err != nil || len(raw) > 524288 || strictObject(raw, &request) != nil {
|
|
return request, ErrServiceInput
|
|
}
|
|
decoder := json.NewDecoder(bytes.NewReader(raw))
|
|
decoder.DisallowUnknownFields()
|
|
if decoder.Decode(&request) != nil {
|
|
return api.CheckRequest{}, ErrServiceInput
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
// DeclaredServiceBinding is public immutable metadata to bind at approval issue.
|
|
// It is not authentication evidence; Prepare independently verifies its principal.
|
|
func DeclaredServiceBinding() map[string]any {
|
|
return map[string]any{"version": ServicePilotMappingVersion, "transport_principal": ServicePilotPrincipal, "subject_id": "secrets-engine", "subject_type": "service", "tenant": "tenant:platform"}
|
|
}
|
|
func (s *ServicePilotInputs) Prepare(ctx context.Context, authorization string, r api.CheckRequest, now time.Time) (ServiceInput, error) {
|
|
refuse := func() (ServiceInput, error) { return ServiceInput{}, ErrServiceInput }
|
|
if s == nil || s.auth == nil || now.IsZero() || r.Action != "execute-ccr" || r.Tenant != "tenant:platform" || r.Subject.ID != "secrets-engine" || r.Subject.Type != "service" || r.Resource.System != "secrets-engine" || r.Resource.Type != "secret-catalog-lane" || r.Resource.Attributes["stage"] != "prod" || r.Context["human_control"] != true {
|
|
return refuse()
|
|
}
|
|
for _, key := range []string{"task_execution_projection", "initiator", "represented_principal", "delegation", "verified", "owner_admission"} {
|
|
if _, found := r.Context[key]; found {
|
|
return refuse()
|
|
}
|
|
}
|
|
if _, found := r.Subject.Attributes["principal"]; found {
|
|
return refuse()
|
|
}
|
|
supplied, err := json.Marshal(r.Context["service_actor_binding"])
|
|
if err != nil {
|
|
return refuse()
|
|
}
|
|
wanted, _ := json.Marshal(DeclaredServiceBinding())
|
|
if string(supplied) != string(wanted) {
|
|
return refuse()
|
|
}
|
|
identity, err := s.auth.Authorize(ctx, authorization, []string{r.Resource.System})
|
|
if err != nil {
|
|
return refuse()
|
|
}
|
|
record := s.auth.Record(identity)
|
|
if record.Mode != callerauth.ModeEnforce || record.Principal != ServicePilotPrincipal || record.Audience != "flex-auth" || record.NotAfter.IsZero() || !record.NotAfter.After(now) {
|
|
return refuse()
|
|
}
|
|
c, ok := r.Context["execution_contract"].(map[string]any)
|
|
if !ok || c["contract_version"] != "SECRETS-EXEC-20261011-v1" || c["operation"] != "metadata-apply-ccr" || c["execution_actor"] != "service:secrets-engine" {
|
|
return refuse()
|
|
}
|
|
deadline, ok := c["operation_deadline"].(string)
|
|
if !ok {
|
|
return refuse()
|
|
}
|
|
end, err := time.Parse(time.RFC3339Nano, deadline)
|
|
if err != nil || !end.After(now) || end.After(record.NotAfter) {
|
|
return refuse()
|
|
}
|
|
raw, err := json.Marshal(r)
|
|
if err != nil {
|
|
return refuse()
|
|
}
|
|
return ServiceInput{serialized: raw, caller: record}, nil
|
|
}
|