Finish FLEX-WP-0019 layer-model v0.7 conformance
Close the remaining PDP obligations: mechanical layer declaration check, registry-snapshot digest in provenance, explicit allow TTL, per-input-class freshness deadlines, and the published decision-record contract. Document the canonical request digest as the §6.4.2 replay test. Assistant: grok Assistant-Session: 01a06256-fb71-7102-b3a9-27e6734257d0
This commit is contained in:
parent
9689894c15
commit
56940727bf
32 changed files with 1194 additions and 111 deletions
|
|
@ -13,11 +13,14 @@ import (
|
|||
// Adapter maps flex-auth checks and resources to Keycloak Authorization
|
||||
// Services while preserving the flex-auth decision envelope.
|
||||
type Adapter struct {
|
||||
client Client
|
||||
realm string
|
||||
audience string
|
||||
policyPackage string
|
||||
policyVersion string
|
||||
client Client
|
||||
realm string
|
||||
audience string
|
||||
policyPackage string
|
||||
policyVersion string
|
||||
policyPackageDigest string
|
||||
registrySnapshotDigest string
|
||||
allowTTL string
|
||||
}
|
||||
|
||||
func New(client Client, options Options) (*Adapter, error) {
|
||||
|
|
@ -31,11 +34,14 @@ func New(client Client, options Options) (*Adapter, error) {
|
|||
return nil, fmt.Errorf("keycloak audience is required")
|
||||
}
|
||||
return &Adapter{
|
||||
client: client,
|
||||
realm: options.Realm,
|
||||
audience: options.Audience,
|
||||
policyPackage: options.PolicyPackage,
|
||||
policyVersion: options.PolicyVersion,
|
||||
client: client,
|
||||
realm: options.Realm,
|
||||
audience: options.Audience,
|
||||
policyPackage: options.PolicyPackage,
|
||||
policyVersion: options.PolicyVersion,
|
||||
policyPackageDigest: options.PolicyPackageDigest,
|
||||
registrySnapshotDigest: options.RegistrySnapshotDigest,
|
||||
allowTTL: options.AllowTTL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -154,7 +160,8 @@ func (a *Adapter) envelope(request api.CheckRequest, authz AuthorizationRequest,
|
|||
},
|
||||
Caring: caringDecisionMetadata(firstDescriptor(request.CaringContext, result.CaringDescriptor), result.ConformanceFindings),
|
||||
}
|
||||
envelope.ID = decisionID(a.policyPackage, policyVersion, request, effect, reason)
|
||||
envelope = a.finish(request, envelope)
|
||||
envelope.ID = decisionID(a.policyPackage, policyVersion, request, envelope.Effect, envelope.Reason)
|
||||
return envelope
|
||||
}
|
||||
|
||||
|
|
@ -179,10 +186,22 @@ func (a *Adapter) failureEnvelope(request api.CheckRequest, authz AuthorizationR
|
|||
},
|
||||
Caring: caringDecisionMetadata(request.CaringContext, []api.CaringConformanceFinding{failureFinding(kind)}),
|
||||
}
|
||||
envelope = a.finish(request, envelope)
|
||||
envelope.ID = decisionID(a.policyPackage, policyVersion, request, envelope.Effect, envelope.Reason)
|
||||
return envelope
|
||||
}
|
||||
|
||||
func (a *Adapter) finish(request api.CheckRequest, envelope api.DecisionEnvelope) api.DecisionEnvelope {
|
||||
if envelope.Provenance.RegistrySnapshotDigest == "" {
|
||||
envelope.Provenance.RegistrySnapshotDigest = a.registrySnapshotDigest
|
||||
}
|
||||
if envelope.Provenance.PolicyPackageDigest == "" {
|
||||
envelope.Provenance.PolicyPackageDigest = a.policyPackageDigest
|
||||
}
|
||||
api.CompleteDecision(&envelope, request, api.DecisionCompletion{AllowTTL: a.allowTTL})
|
||||
return envelope
|
||||
}
|
||||
|
||||
func caringDecisionMetadata(descriptor *api.CaringAccessDescriptor, findings []api.CaringConformanceFinding) *api.CaringDecisionMetadata {
|
||||
profile := api.CaringProfileCaring040RC2
|
||||
if descriptor != nil && descriptor.Profile != "" {
|
||||
|
|
|
|||
|
|
@ -23,10 +23,13 @@ type Client interface {
|
|||
// Options configures Keycloak mapping without making Keycloak the source of
|
||||
// truth for flex-auth resources or policies.
|
||||
type Options struct {
|
||||
Realm string
|
||||
Audience string
|
||||
PolicyPackage string
|
||||
PolicyVersion string
|
||||
Realm string
|
||||
Audience string
|
||||
PolicyPackage string
|
||||
PolicyVersion string
|
||||
PolicyPackageDigest string
|
||||
RegistrySnapshotDigest string
|
||||
AllowTTL string
|
||||
}
|
||||
|
||||
// AuthorizationRequest is the UMA permission request flex-auth sends to
|
||||
|
|
|
|||
|
|
@ -14,10 +14,13 @@ import (
|
|||
|
||||
// Adapter wraps tuple-oriented PDP results into flex-auth decision envelopes.
|
||||
type Adapter struct {
|
||||
backend Backend
|
||||
backendName string
|
||||
policyPackage string
|
||||
policyVersion string
|
||||
backend Backend
|
||||
backendName string
|
||||
policyPackage string
|
||||
policyVersion string
|
||||
policyPackageDigest string
|
||||
registrySnapshotDigest string
|
||||
allowTTL string
|
||||
}
|
||||
|
||||
// New creates a relationship PDP adapter.
|
||||
|
|
@ -30,10 +33,13 @@ func New(backend Backend, options Options) (*Adapter, error) {
|
|||
backendName = "relationship"
|
||||
}
|
||||
return &Adapter{
|
||||
backend: backend,
|
||||
backendName: backendName,
|
||||
policyPackage: options.PolicyPackage,
|
||||
policyVersion: options.PolicyVersion,
|
||||
backend: backend,
|
||||
backendName: backendName,
|
||||
policyPackage: options.PolicyPackage,
|
||||
policyVersion: options.PolicyVersion,
|
||||
policyPackageDigest: options.PolicyPackageDigest,
|
||||
registrySnapshotDigest: options.RegistrySnapshotDigest,
|
||||
allowTTL: options.AllowTTL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +236,8 @@ func (a *Adapter) envelope(request api.CheckRequest, tupleRequest TupleCheckRequ
|
|||
},
|
||||
Caring: caringDecisionMetadata(request, descriptorForResult(request, result), result.ConformanceFindings),
|
||||
}
|
||||
envelope.ID = decisionID(a.backendName, a.policyPackage, policyVersion, request, effect, reason, result.ConsistencyToken)
|
||||
envelope = a.finish(request, envelope)
|
||||
envelope.ID = decisionID(a.backendName, a.policyPackage, policyVersion, request, envelope.Effect, envelope.Reason, result.ConsistencyToken)
|
||||
return envelope
|
||||
}
|
||||
|
||||
|
|
@ -255,10 +262,22 @@ func (a *Adapter) failureEnvelope(request api.CheckRequest, tupleRequest TupleCh
|
|||
},
|
||||
Caring: caringDecisionMetadata(request, request.CaringContext, []api.CaringConformanceFinding{failureFinding(kind)}),
|
||||
}
|
||||
envelope = a.finish(request, envelope)
|
||||
envelope.ID = decisionID(a.backendName, a.policyPackage, policyVersion, request, envelope.Effect, envelope.Reason, "")
|
||||
return envelope
|
||||
}
|
||||
|
||||
func (a *Adapter) finish(request api.CheckRequest, envelope api.DecisionEnvelope) api.DecisionEnvelope {
|
||||
if envelope.Provenance.RegistrySnapshotDigest == "" {
|
||||
envelope.Provenance.RegistrySnapshotDigest = a.registrySnapshotDigest
|
||||
}
|
||||
if envelope.Provenance.PolicyPackageDigest == "" {
|
||||
envelope.Provenance.PolicyPackageDigest = a.policyPackageDigest
|
||||
}
|
||||
api.CompleteDecision(&envelope, request, api.DecisionCompletion{AllowTTL: a.allowTTL})
|
||||
return envelope
|
||||
}
|
||||
|
||||
func descriptorForResult(request api.CheckRequest, result TupleCheckResult) *api.CaringAccessDescriptor {
|
||||
if request.CaringContext != nil {
|
||||
return request.CaringContext
|
||||
|
|
|
|||
|
|
@ -27,9 +27,12 @@ type Backend interface {
|
|||
// Options configures the relationship adapter without binding callers to a
|
||||
// specific backend protocol.
|
||||
type Options struct {
|
||||
BackendName string
|
||||
PolicyPackage string
|
||||
PolicyVersion string
|
||||
BackendName string
|
||||
PolicyPackage string
|
||||
PolicyVersion string
|
||||
PolicyPackageDigest string
|
||||
RegistrySnapshotDigest string
|
||||
AllowTTL string
|
||||
}
|
||||
|
||||
// Tuple is the canonical relation fact sent to tuple-oriented PDPs.
|
||||
|
|
|
|||
|
|
@ -14,12 +14,15 @@ import (
|
|||
|
||||
// Adapter wraps rule-PDP responses into flex-auth decision envelopes.
|
||||
type Adapter struct {
|
||||
backend Backend
|
||||
backendName string
|
||||
policyPackage string
|
||||
policyVersion string
|
||||
language Language
|
||||
caring api.CaringPolicyMetadata
|
||||
backend Backend
|
||||
backendName string
|
||||
policyPackage string
|
||||
policyVersion string
|
||||
policyPackageDigest string
|
||||
registrySnapshotDigest string
|
||||
allowTTL string
|
||||
language Language
|
||||
caring api.CaringPolicyMetadata
|
||||
}
|
||||
|
||||
// New creates a delegated rule-PDP adapter.
|
||||
|
|
@ -36,12 +39,15 @@ func New(backend Backend, options Options) (*Adapter, error) {
|
|||
language = LanguageRego
|
||||
}
|
||||
return &Adapter{
|
||||
backend: backend,
|
||||
backendName: backendName,
|
||||
policyPackage: options.PolicyPackage,
|
||||
policyVersion: options.PolicyVersion,
|
||||
language: language,
|
||||
caring: options.Caring,
|
||||
backend: backend,
|
||||
backendName: backendName,
|
||||
policyPackage: options.PolicyPackage,
|
||||
policyVersion: options.PolicyVersion,
|
||||
policyPackageDigest: options.PolicyPackageDigest,
|
||||
registrySnapshotDigest: options.RegistrySnapshotDigest,
|
||||
allowTTL: options.AllowTTL,
|
||||
language: language,
|
||||
caring: options.Caring,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +240,8 @@ func (a *Adapter) envelope(request api.CheckRequest, evaluation EvaluationReques
|
|||
},
|
||||
Caring: caringDecisionMetadata(request, firstDescriptor(request.CaringContext, result.CaringDescriptor), a.caring, result.ConformanceFindings),
|
||||
}
|
||||
envelope.ID = decisionID(a.backendName, policyPackage, policyVersion, request, effect, reason)
|
||||
envelope = a.finish(request, envelope)
|
||||
envelope.ID = decisionID(a.backendName, policyPackage, policyVersion, request, envelope.Effect, envelope.Reason)
|
||||
return envelope
|
||||
}
|
||||
|
||||
|
|
@ -259,10 +266,22 @@ func (a *Adapter) failureEnvelope(request api.CheckRequest, evaluation Evaluatio
|
|||
},
|
||||
Caring: caringDecisionMetadata(request, request.CaringContext, a.caring, []api.CaringConformanceFinding{failureFinding(kind)}),
|
||||
}
|
||||
envelope = a.finish(request, envelope)
|
||||
envelope.ID = decisionID(a.backendName, a.policyPackage, policyVersion, request, envelope.Effect, envelope.Reason)
|
||||
return envelope
|
||||
}
|
||||
|
||||
func (a *Adapter) finish(request api.CheckRequest, envelope api.DecisionEnvelope) api.DecisionEnvelope {
|
||||
if envelope.Provenance.RegistrySnapshotDigest == "" {
|
||||
envelope.Provenance.RegistrySnapshotDigest = a.registrySnapshotDigest
|
||||
}
|
||||
if envelope.Provenance.PolicyPackageDigest == "" {
|
||||
envelope.Provenance.PolicyPackageDigest = a.policyPackageDigest
|
||||
}
|
||||
api.CompleteDecision(&envelope, request, api.DecisionCompletion{AllowTTL: a.allowTTL})
|
||||
return envelope
|
||||
}
|
||||
|
||||
func caringDecisionMetadata(
|
||||
request api.CheckRequest,
|
||||
descriptor *api.CaringAccessDescriptor,
|
||||
|
|
|
|||
|
|
@ -33,11 +33,14 @@ type Backend interface {
|
|||
|
||||
// Options configures the rule adapter.
|
||||
type Options struct {
|
||||
BackendName string
|
||||
PolicyPackage string
|
||||
PolicyVersion string
|
||||
Language Language
|
||||
Caring api.CaringPolicyMetadata
|
||||
BackendName string
|
||||
PolicyPackage string
|
||||
PolicyVersion string
|
||||
PolicyPackageDigest string
|
||||
RegistrySnapshotDigest string
|
||||
AllowTTL string
|
||||
Language Language
|
||||
Caring api.CaringPolicyMetadata
|
||||
}
|
||||
|
||||
// EvaluationRequest is the canonical rule-PDP request.
|
||||
|
|
|
|||
|
|
@ -16,9 +16,12 @@ import (
|
|||
// Adapter delegates checks, directory writes, and policy bundle publication to
|
||||
// Topaz while preserving flex-auth request and decision contracts.
|
||||
type Adapter struct {
|
||||
client Client
|
||||
policyPackage string
|
||||
policyVersion string
|
||||
client Client
|
||||
policyPackage string
|
||||
policyVersion string
|
||||
policyPackageDigest string
|
||||
registrySnapshotDigest string
|
||||
allowTTL string
|
||||
}
|
||||
|
||||
// New creates a Topaz adapter.
|
||||
|
|
@ -27,9 +30,12 @@ func New(client Client, options Options) (*Adapter, error) {
|
|||
return nil, fmt.Errorf("topaz client is required")
|
||||
}
|
||||
return &Adapter{
|
||||
client: client,
|
||||
policyPackage: options.PolicyPackage,
|
||||
policyVersion: options.PolicyVersion,
|
||||
client: client,
|
||||
policyPackage: options.PolicyPackage,
|
||||
policyVersion: options.PolicyVersion,
|
||||
policyPackageDigest: options.PolicyPackageDigest,
|
||||
registrySnapshotDigest: options.RegistrySnapshotDigest,
|
||||
allowTTL: options.AllowTTL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -202,6 +208,7 @@ func (a *Adapter) envelope(request api.CheckRequest, topazRequest DirectoryCheck
|
|||
},
|
||||
Caring: caringDecisionMetadata(request, firstDescriptor(request.CaringContext, result.CaringDescriptor), result.ConformanceFindings, result.ExposureEvent),
|
||||
}
|
||||
envelope = a.finish(request, envelope)
|
||||
envelope.ID = decisionID(policyPackage, policyVersion, request, envelope.Effect, envelope.Reason, result.DirectoryETag)
|
||||
if envelope.Caring != nil && envelope.Caring.ExposureEvent != nil && envelope.Caring.ExposureEvent.ID == "" {
|
||||
envelope.Caring.ExposureEvent.ID = envelope.ID + ":exposure"
|
||||
|
|
@ -237,10 +244,22 @@ func (a *Adapter) failureEnvelope(request api.CheckRequest, topazRequest Directo
|
|||
},
|
||||
Caring: caringDecisionMetadata(request, request.CaringContext, findings, nil),
|
||||
}
|
||||
envelope = a.finish(request, envelope)
|
||||
envelope.ID = decisionID(a.policyPackage, policyVersion, request, envelope.Effect, envelope.Reason, "")
|
||||
return envelope
|
||||
}
|
||||
|
||||
func (a *Adapter) finish(request api.CheckRequest, envelope api.DecisionEnvelope) api.DecisionEnvelope {
|
||||
if envelope.Provenance.RegistrySnapshotDigest == "" {
|
||||
envelope.Provenance.RegistrySnapshotDigest = a.registrySnapshotDigest
|
||||
}
|
||||
if envelope.Provenance.PolicyPackageDigest == "" {
|
||||
envelope.Provenance.PolicyPackageDigest = a.policyPackageDigest
|
||||
}
|
||||
api.CompleteDecision(&envelope, request, api.DecisionCompletion{AllowTTL: a.allowTTL})
|
||||
return envelope
|
||||
}
|
||||
|
||||
func addTopazDiagnostics(diagnostics map[string]any, request DirectoryCheckRequest, failure string) {
|
||||
diagnostics["adapter"] = "topaz"
|
||||
diagnostics["mode"] = DelegatedMode
|
||||
|
|
|
|||
|
|
@ -86,6 +86,12 @@ func TestAdapterCheckWrapsTopazAllowInFlexAuthEnvelope(t *testing.T) {
|
|||
if got.Provenance.DirectoryETag != "etag:rel-42" {
|
||||
t.Fatalf("DirectoryETag = %q", got.Provenance.DirectoryETag)
|
||||
}
|
||||
if got.Provenance.RegistrySnapshotDigest != "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" {
|
||||
t.Fatalf("RegistrySnapshotDigest = %q", got.Provenance.RegistrySnapshotDigest)
|
||||
}
|
||||
if got.Lifetime == nil || got.Lifetime.Kind != api.DecisionLifetimeTTL {
|
||||
t.Fatalf("lifetime = %+v; want default ttl", got.Lifetime)
|
||||
}
|
||||
if got.Diagnostics["topaz_object_type"] != "document" || got.Diagnostics["topaz_subject_type"] != "user" {
|
||||
t.Fatalf("diagnostics = %+v; want Topaz check shape", got.Diagnostics)
|
||||
}
|
||||
|
|
@ -201,8 +207,9 @@ func newAdapter(t *testing.T, client *fakeClient) *topaz.Adapter {
|
|||
t.Helper()
|
||||
|
||||
adapter, err := topaz.New(client, topaz.Options{
|
||||
PolicyPackage: "markitect.documents.internal-read",
|
||||
PolicyVersion: "v1",
|
||||
PolicyPackage: "markitect.documents.internal-read",
|
||||
PolicyVersion: "v1",
|
||||
RegistrySnapshotDigest: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New: %v", err)
|
||||
|
|
|
|||
|
|
@ -41,8 +41,11 @@ type BundleSink interface {
|
|||
// Options configures the adapter without leaking Topaz-specific types into the
|
||||
// public flex-auth API.
|
||||
type Options struct {
|
||||
PolicyPackage string
|
||||
PolicyVersion string
|
||||
PolicyPackage string
|
||||
PolicyVersion string
|
||||
PolicyPackageDigest string
|
||||
RegistrySnapshotDigest string
|
||||
AllowTTL string
|
||||
}
|
||||
|
||||
// DirectoryObject is the Topaz directory object shape used by the REST
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue