Verify normalized task approval facts and preserve signed source types
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
This commit is contained in:
parent
5a2099f400
commit
4dd004a225
18 changed files with 756 additions and 60 deletions
|
|
@ -1,7 +1,7 @@
|
|||
# Access Engine decision verifier
|
||||
|
||||
The API types and `internal/sign/sign.go` are unchanged producer source, pinned
|
||||
by revision and SHA-256 in `producer-source.json`. License attribution and the
|
||||
by source revision/proposal and SHA-256 in `producer-source.json`. License attribution and the
|
||||
original license text are in `LICENSE.access-engine`. This is source vendoring
|
||||
necessitated by the producer's Go `internal` package boundary, not a separately
|
||||
maintained crypto implementation. There are no external Go dependencies.
|
||||
|
|
@ -33,3 +33,23 @@ For the complete reproducible CLI build and offline fresh-prefix installation,
|
|||
use [the release bundle contract](../docs/cli-release-bundle.md). This replaces
|
||||
manual helper copying for packaged deployments; package and key admission remain
|
||||
separate owner decisions.
|
||||
|
||||
The trusted-service successor snapshot follows the frozen Access B2/B3 proposal lineage,
|
||||
identified by its patch SHA-256 in `producer-source.json`; this is source review,
|
||||
not a claim of published producer revision or runtime/key admission. The wrapper
|
||||
uses producer-owned `sign.DecodeEnvelope` to preserve JSON numeric lexemes in
|
||||
signed maps. It retains exact authenticated producer JSON through strict
|
||||
preparation and CAS reauthentication; Python does not recreate signed bytes.
|
||||
Approval's fourteen native binding vectors are signature interoperability cases,
|
||||
not an expansion of existing request numeric admission. Signed
|
||||
`approval_observation` supplements the original immutable carried claim;
|
||||
strict handoff checks its native digest and observation/deadline bounds. It never
|
||||
replaces original context or freshness.
|
||||
|
||||
Successor R2 execution builder correspondence emits the producer's fixed public
|
||||
DeclaredServiceBinding only for the typed prod/platform secrets-engine service
|
||||
lane before approval issuance. Both reference tenants are explicit to match
|
||||
existing producer normalization. It is not caller authentication; independently
|
||||
signed enforced caller, exact original/native/descriptor joins and all existing
|
||||
approval, expiry and CAS gates still apply. Actual production source-server
|
||||
interoperability receipts are separate from native runtime admission.
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
package sign
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/netkingdom/access-engine/pkg/api"
|
||||
)
|
||||
|
|
@ -74,3 +76,20 @@ func Verify(envelope api.DecisionEnvelope, keys map[string]ed25519.PublicKey) er
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeEnvelope preserves JSON numeric kinds/lexemes emitted by task inputs.
|
||||
// Legacy producer envelopes remain byte-compatible. It does not invent another
|
||||
// signature canonicalization: CanonicalBytes still calls typed json.Marshal.
|
||||
func DecodeEnvelope(raw []byte) (api.DecisionEnvelope, error) {
|
||||
var envelope api.DecisionEnvelope
|
||||
decoder := json.NewDecoder(bytes.NewReader(raw))
|
||||
decoder.UseNumber()
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&envelope); err != nil {
|
||||
return envelope, err
|
||||
}
|
||||
if _, err := decoder.Token(); err != io.EOF {
|
||||
return api.DecisionEnvelope{}, fmt.Errorf("trailing envelope data")
|
||||
}
|
||||
return envelope, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/netkingdom/access-engine/internal/sign"
|
||||
"github.com/netkingdom/access-engine/pkg/api"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
|
@ -23,10 +21,8 @@ func run() error {
|
|||
if err := json.NewDecoder(io.LimitReader(os.Stdin, 1048577)).Decode(&in); err != nil {
|
||||
return err
|
||||
}
|
||||
var envelope api.DecisionEnvelope
|
||||
decoder := json.NewDecoder(bytes.NewReader(in.Envelope))
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(&envelope); err != nil {
|
||||
envelope, err := sign.DecodeEnvelope(in.Envelope)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys := map[string]ed25519.PublicKey{}
|
||||
|
|
|
|||
|
|
@ -635,16 +635,30 @@ type Obligation struct {
|
|||
|
||||
// DecisionProvenance captures evaluator and policy provenance.
|
||||
type DecisionProvenance struct {
|
||||
Evaluator string `json:"evaluator" yaml:"evaluator"`
|
||||
Mode string `json:"mode" yaml:"mode"`
|
||||
PolicyPackage string `json:"policy_package,omitempty" yaml:"policy_package,omitempty"`
|
||||
PolicyVersion string `json:"policy_version,omitempty" yaml:"policy_version,omitempty"`
|
||||
PolicyPackageDigest string `json:"policy_package_digest,omitempty" yaml:"policy_package_digest,omitempty"`
|
||||
RegistrySnapshotDigest string `json:"registry_snapshot_digest,omitempty" yaml:"registry_snapshot_digest,omitempty"`
|
||||
DirectoryETag string `json:"directory_etag,omitempty" yaml:"directory_etag,omitempty"`
|
||||
InputClaimDigests map[string]string `json:"input_claim_digests,omitempty" yaml:"input_claim_digests,omitempty"`
|
||||
DecisionTime string `json:"decision_time,omitempty" yaml:"decision_time,omitempty"`
|
||||
Caller *CallerProvenance `json:"caller,omitempty" yaml:"caller,omitempty"`
|
||||
Evaluator string `json:"evaluator" yaml:"evaluator"`
|
||||
Mode string `json:"mode" yaml:"mode"`
|
||||
PolicyPackage string `json:"policy_package,omitempty" yaml:"policy_package,omitempty"`
|
||||
PolicyVersion string `json:"policy_version,omitempty" yaml:"policy_version,omitempty"`
|
||||
PolicyPackageDigest string `json:"policy_package_digest,omitempty" yaml:"policy_package_digest,omitempty"`
|
||||
RegistrySnapshotDigest string `json:"registry_snapshot_digest,omitempty" yaml:"registry_snapshot_digest,omitempty"`
|
||||
DirectoryETag string `json:"directory_etag,omitempty" yaml:"directory_etag,omitempty"`
|
||||
InputClaimDigests map[string]string `json:"input_claim_digests,omitempty" yaml:"input_claim_digests,omitempty"`
|
||||
DecisionTime string `json:"decision_time,omitempty" yaml:"decision_time,omitempty"`
|
||||
ApprovalObservation *ApprovalObservationProvenance `json:"approval_observation,omitempty" yaml:"approval_observation,omitempty"`
|
||||
Caller *CallerProvenance `json:"caller,omitempty" yaml:"caller,omitempty"`
|
||||
}
|
||||
|
||||
// ApprovalObservationProvenance binds fresh server-owned fact evidence without
|
||||
// rewriting the original carried claim/context or either PDP request digest.
|
||||
type ApprovalObservationProvenance struct {
|
||||
SourceID string `json:"source_id"`
|
||||
SourceRevision string `json:"source_revision"`
|
||||
ConfigurationDigest string `json:"configuration_digest"`
|
||||
ClaimDigest string `json:"claim_digest"`
|
||||
NativeBindingDigest string `json:"native_binding_digest"`
|
||||
ObservedAt string `json:"observed_at"`
|
||||
NotAfter string `json:"not_after"`
|
||||
ValidatedAt string `json:"validated_at"`
|
||||
}
|
||||
|
||||
// CallerProvenance records how the request was authenticated to flex-auth.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
{
|
||||
"upstream": "https://github.com/netkingdom/access-engine",
|
||||
"revision": "62ac2b4e99dec2005c7c407fe633612578a7907d",
|
||||
"revision": "B2/B3 R3 retained-registry source proposal over B1 R2; canonical native-prep base 919ce25",
|
||||
"files": {
|
||||
"pkg/api/canonical.go": "21e754617c2b82d673cef31b7075f8d177e4367782b63b20b37125587bf9e300",
|
||||
"pkg/api/canonical.go": "a122a2e8cd524333b13c5939c7a3685e41efbe47497a64e0c7297c92b7f10d19",
|
||||
"pkg/api/caring.go": "1907156f0f889e7b9e9c14970cf7a86a20d00478a496efaf92c7231f27fec5e3",
|
||||
"pkg/api/resource_manifest.go": "f0fa4080287d389ed48c2b1d6af96cc62e57ef72c902333f677f964420be133f",
|
||||
"pkg/api/doc.go": "0638f06ae47369899dd1e2dd5dd23a7200e7dbb3b505b04f4d3c78c0a074def9",
|
||||
"internal/sign/sign.go": "dad2d0c9f6d1fb2fd68e774949ccdf954382acd075191b5ddfea2c3cc2f4cb62"
|
||||
"internal/sign/sign.go": "0df9515922e90775ebbfac798361dcafb20da8a4213796b44e246b8a92046cde"
|
||||
},
|
||||
"fixtures": {
|
||||
"decision_rotate_signed.json": "a7bc499f3583f53551cf163109a3c108188345100176092adc31eccefc222fd8",
|
||||
"decision_rotate_signed_tampered.json": "7f2d2e9372a9617bd4b197033f09a5e33a08b202046de8109582da1dc90cb527"
|
||||
},
|
||||
"source_proposal": {
|
||||
"patch_sha256": "46bfeb706d3a88bf6054309cc9303eb360a91e57dda3b0fcbb7b80d551a75db6",
|
||||
"predecessor_patch_sha256": "ed85a55b54f8380a657c0688d6f216699a0873b1b94b4c0c2e09332620c8ebf4",
|
||||
"api_sign_bytes_unchanged": true,
|
||||
"status": "source proposal; no runtime or key admission"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"crypto/ed25519"
|
||||
"encoding/json"
|
||||
"github.com/netkingdom/access-engine/internal/sign"
|
||||
"github.com/netkingdom/access-engine/pkg/api"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
|
|
@ -17,8 +17,12 @@ func main() {
|
|||
seed = 0x43
|
||||
kid = "next-admitted"
|
||||
}
|
||||
var env api.DecisionEnvelope
|
||||
if json.NewDecoder(os.Stdin).Decode(&env) != nil {
|
||||
raw, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
env, err := sign.DecodeEnvelope(raw)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
if sign.Sign(&env, ed25519.NewKeyFromSeed(bytes.Repeat([]byte{seed}, 32)), kid) != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue