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
147
internal/layer/conformance.go
Normal file
147
internal/layer/conformance.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
// Package layer asserts the NetKingdom security-layer-model §11 declaration.
|
||||
package layer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Layer vocabulary from security-layer-model_v0.7 §3.
|
||||
var validLayers = map[string]bool{
|
||||
"Staff": true,
|
||||
"Engine": true,
|
||||
"Tooling": true,
|
||||
}
|
||||
|
||||
// Engine roles from §3.3. An Engine declaration must state one.
|
||||
var validEngineRoles = map[string]bool{
|
||||
"PDP": true,
|
||||
"PIP": true,
|
||||
}
|
||||
|
||||
// Tooling clients are invocations, not mentions. These match import paths and
|
||||
// argv construction that would actually contact OpenBao/Vault.
|
||||
var toolingPatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`github\.com/hashicorp/vault`),
|
||||
regexp.MustCompile(`github\.com/openbao/`),
|
||||
regexp.MustCompile(`exec\.Command\([^)]*["'](?:bao|vault)["']`),
|
||||
}
|
||||
|
||||
// Declaration is the machine-readable §11 form carried in INTENT.md frontmatter.
|
||||
type Declaration struct {
|
||||
Layer string `yaml:"layer"`
|
||||
Role string `yaml:"role"`
|
||||
Framework string `yaml:"framework"`
|
||||
StandardVersion string `yaml:"standard_version"`
|
||||
DeclaredBy string `yaml:"declared_by"`
|
||||
DeclaredAt string `yaml:"declared_at"`
|
||||
PepStance any `yaml:"pep_stance"`
|
||||
ToolingContacts []any `yaml:"tooling_contacts"`
|
||||
}
|
||||
|
||||
// Check parses INTENT.md, asserts the Engine/PDP declaration, and scans
|
||||
// production Go sources for undeclared Tooling clients.
|
||||
func Check(root string) error {
|
||||
decl, err := LoadDeclaration(filepath.Join(root, "INTENT.md"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ValidateDeclaration(decl); err != nil {
|
||||
return err
|
||||
}
|
||||
hits, err := ScanToolingClients(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(hits) > 0 {
|
||||
return fmt.Errorf("undeclared Tooling client(s) under §11: %s", strings.Join(hits, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadDeclaration reads YAML frontmatter from INTENT.md.
|
||||
func LoadDeclaration(path string) (Declaration, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return Declaration{}, fmt.Errorf("read layer declaration: %w", err)
|
||||
}
|
||||
frontmatter, err := splitFrontmatter(string(data))
|
||||
if err != nil {
|
||||
return Declaration{}, err
|
||||
}
|
||||
var decl Declaration
|
||||
if err := yaml.Unmarshal([]byte(frontmatter), &decl); err != nil {
|
||||
return Declaration{}, fmt.Errorf("parse layer declaration: %w", err)
|
||||
}
|
||||
return decl, nil
|
||||
}
|
||||
|
||||
// ValidateDeclaration asserts §3 vocabulary and Engine-role presence.
|
||||
func ValidateDeclaration(decl Declaration) error {
|
||||
if !validLayers[decl.Layer] {
|
||||
return fmt.Errorf("layer %q is not in the §3 vocabulary (Staff, Engine, Tooling)", decl.Layer)
|
||||
}
|
||||
if decl.Layer == "Engine" && !validEngineRoles[decl.Role] {
|
||||
return fmt.Errorf("Engine declaration must state role PDP or PIP; got %q", decl.Role)
|
||||
}
|
||||
if decl.Layer != "Engine" && strings.TrimSpace(decl.Role) != "" {
|
||||
return fmt.Errorf("layer %q must not state an Engine role", decl.Layer)
|
||||
}
|
||||
if len(decl.ToolingContacts) > 0 {
|
||||
return fmt.Errorf("Engine/PDP holds no Tooling client; tooling_contacts must be empty")
|
||||
}
|
||||
if decl.PepStance != nil {
|
||||
return fmt.Errorf("flex-auth is not PEP-shaped; pep_stance must be null")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ScanToolingClients returns production Go files that invoke OpenBao/Vault.
|
||||
func ScanToolingClients(root string) ([]string, error) {
|
||||
var hits []string
|
||||
for _, dir := range []string{"cmd", "internal", "pkg"} {
|
||||
err := filepath.WalkDir(filepath.Join(root, dir), func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
||||
return nil
|
||||
}
|
||||
body, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, pattern := range toolingPatterns {
|
||||
if pattern.Find(body) != nil {
|
||||
rel, _ := filepath.Rel(root, path)
|
||||
hits = append(hits, rel)
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return hits, nil
|
||||
}
|
||||
|
||||
func splitFrontmatter(document string) (string, error) {
|
||||
document = strings.TrimPrefix(document, "\ufeff")
|
||||
lines := strings.SplitAfter(document, "\n")
|
||||
if len(lines) == 0 || strings.TrimSpace(lines[0]) != "---" {
|
||||
return "", fmt.Errorf("INTENT.md must start with YAML frontmatter")
|
||||
}
|
||||
for i := 1; i < len(lines); i++ {
|
||||
if strings.TrimSpace(lines[i]) == "---" {
|
||||
return strings.Join(lines[1:i], ""), nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("INTENT.md frontmatter is not closed")
|
||||
}
|
||||
56
internal/layer/conformance_test.go
Normal file
56
internal/layer/conformance_test.go
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package layer_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/netkingdom/flex-auth/internal/layer"
|
||||
)
|
||||
|
||||
func TestLayerDeclarationConforms(t *testing.T) {
|
||||
root := repoRoot(t)
|
||||
if err := layer.Check(root); err != nil {
|
||||
t.Fatalf("layer conformance: %v", err)
|
||||
}
|
||||
|
||||
decl, err := layer.LoadDeclaration(filepath.Join(root, "INTENT.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("LoadDeclaration: %v", err)
|
||||
}
|
||||
if decl.Layer != "Engine" {
|
||||
t.Fatalf("layer = %q; want Engine", decl.Layer)
|
||||
}
|
||||
if decl.Role != "PDP" {
|
||||
t.Fatalf("role = %q; want PDP", decl.Role)
|
||||
}
|
||||
if decl.Framework != "netkingdom-security-layer-model" {
|
||||
t.Fatalf("framework = %q", decl.Framework)
|
||||
}
|
||||
if decl.StandardVersion != "0.7" {
|
||||
t.Fatalf("standard_version = %q; want 0.7", decl.StandardVersion)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngineWithoutRoleIsRejected(t *testing.T) {
|
||||
err := layer.ValidateDeclaration(layer.Declaration{Layer: "Engine"})
|
||||
if err == nil {
|
||||
t.Fatal("Engine without role was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnknownLayerIsRejected(t *testing.T) {
|
||||
err := layer.ValidateDeclaration(layer.Declaration{Layer: "ControlPlane", Role: "PDP"})
|
||||
if err == nil {
|
||||
t.Fatal("unknown layer was accepted")
|
||||
}
|
||||
}
|
||||
|
||||
func repoRoot(t *testing.T) string {
|
||||
t.Helper()
|
||||
_, file, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
t.Fatal("runtime.Caller failed")
|
||||
}
|
||||
return filepath.Clean(filepath.Join(filepath.Dir(file), "..", ".."))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue