fix(check-safety): recognize methodRolesSeed RBAC table; resolve backend scan-root
extractRBACMethodRoles matched only a map literal named MethodRoles. The CMS now keeps its RBAC table behind an atomic-pointer copy-on-write live table, with the static literal renamed to methodRolesSeed -- so the validator read an empty table and reported 641 phantom "missing RBAC entry" methods, blinding the gate to any genuine unregistered RPC. Match methodRolesSeed as well as MethodRoles, and add a regression test (TestExtractRBACMethodRolesParsesSeedTable). resolveScanRoots: also resolve a `backend` directory containing go.mod whose parent holds buf.yaml or proto/, so the checker targets that layout correctly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c048766075
commit
73259c6b30
65
main_test.go
65
main_test.go
@ -81,6 +81,35 @@ func TestCollectTargetsSkipsCoreWhenIncludeCoreTargetsFalse(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveScanRootsDetectsConsolidatedBackendDir(t *testing.T) {
|
||||||
|
repoRoot := t.TempDir()
|
||||||
|
backendDir := filepath.Join(repoRoot, "backend")
|
||||||
|
protoDir := filepath.Join(repoRoot, "proto")
|
||||||
|
|
||||||
|
for _, dir := range []string{backendDir, protoDir} {
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
t.Fatalf("mkdir %s: %v", dir, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(backendDir, "go.mod"), []byte("module example.com/cms/backend\n"), 0644); err != nil {
|
||||||
|
t.Fatalf("write go.mod: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(repoRoot, "buf.yaml"), []byte("version: v2\n"), 0644); err != nil {
|
||||||
|
t.Fatalf("write buf.yaml: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotBackend, gotRepo, err := resolveScanRoots(backendDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("resolveScanRoots() error = %v", err)
|
||||||
|
}
|
||||||
|
if gotBackend != backendDir {
|
||||||
|
t.Fatalf("backendDir = %q, want %q", gotBackend, backendDir)
|
||||||
|
}
|
||||||
|
if gotRepo != repoRoot {
|
||||||
|
t.Fatalf("repoRoot = %q, want %q", gotRepo, repoRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDiscoverOrchestratorTestTargetsRequiresCoreScan(t *testing.T) {
|
func TestDiscoverOrchestratorTestTargetsRequiresCoreScan(t *testing.T) {
|
||||||
repoRoot := blockNinjaRepoRoot()
|
repoRoot := blockNinjaRepoRoot()
|
||||||
backendDir := filepath.Join(repoRoot, "backend")
|
backendDir := filepath.Join(repoRoot, "backend")
|
||||||
@ -189,3 +218,39 @@ var MethodRoles = map[string]Role{
|
|||||||
t.Fatalf("admin role = %q, want admin", methods["/example.v1.Service/Admin"])
|
t.Fatalf("admin role = %q, want admin", methods["/example.v1.Service/Admin"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExtractRBACMethodRolesParsesSeedTable(t *testing.T) {
|
||||||
|
// The CMS renamed the static RBAC table to methodRolesSeed, kept behind an
|
||||||
|
// atomic-pointer copy-on-write live table for thread safety. The extractor must
|
||||||
|
// recognise methodRolesSeed as well as MethodRoles, or every CMS RPC reads as
|
||||||
|
// "missing RBAC entry".
|
||||||
|
dir := t.TempDir()
|
||||||
|
file := filepath.Join(dir, "interceptor.go")
|
||||||
|
if err := os.WriteFile(file, []byte(`package rbac
|
||||||
|
|
||||||
|
type Role string
|
||||||
|
|
||||||
|
const (
|
||||||
|
RoleAdmin Role = "admin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var methodRolesSeed = map[string]Role{
|
||||||
|
"/example.v1.Service/Public": "",
|
||||||
|
"/example.v1.Service/Admin": RoleAdmin,
|
||||||
|
}
|
||||||
|
`), 0644); err != nil {
|
||||||
|
t.Fatalf("write interceptor.go: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
methods, err := extractRBACMethodRoles(file)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("extractRBACMethodRoles() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if methods["/example.v1.Service/Public"] != "public" {
|
||||||
|
t.Fatalf("public role = %q, want public", methods["/example.v1.Service/Public"])
|
||||||
|
}
|
||||||
|
if methods["/example.v1.Service/Admin"] != "admin" {
|
||||||
|
t.Fatalf("admin role = %q, want admin", methods["/example.v1.Service/Admin"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -630,7 +630,9 @@ func extractConnectProceduresRecursive(root string, allowedPackagePrefixes ...st
|
|||||||
return procedures, nil
|
return procedures, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractRBACMethodRoles parses interceptor.go and extracts all keys and role values from the MethodRoles map.
|
// extractRBACMethodRoles parses interceptor.go and extracts all keys and role values from the
|
||||||
|
// MethodRoles map (orchestrator/helpdesk) or the methodRolesSeed map (CMS — the static seed
|
||||||
|
// table behind the atomic-pointer copy-on-write live table; see interceptor.go).
|
||||||
func extractRBACMethodRoles(file string) (map[string]string, error) {
|
func extractRBACMethodRoles(file string) (map[string]string, error) {
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
f, err := parser.ParseFile(fset, file, nil, 0)
|
f, err := parser.ParseFile(fset, file, nil, 0)
|
||||||
@ -641,9 +643,13 @@ func extractRBACMethodRoles(file string) (map[string]string, error) {
|
|||||||
methods := make(map[string]string)
|
methods := make(map[string]string)
|
||||||
|
|
||||||
ast.Inspect(f, func(n ast.Node) bool {
|
ast.Inspect(f, func(n ast.Node) bool {
|
||||||
// Find: var MethodRoles = map[string]Role{ ... }
|
// Find: var MethodRoles = map[string]Role{ ... } (orchestrator/helpdesk)
|
||||||
|
// or: var methodRolesSeed = map[string]Role{ ... } (CMS copy-on-write seed)
|
||||||
vs, ok := n.(*ast.ValueSpec)
|
vs, ok := n.(*ast.ValueSpec)
|
||||||
if !ok || len(vs.Names) == 0 || vs.Names[0].Name != "MethodRoles" {
|
if !ok || len(vs.Names) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if name := vs.Names[0].Name; name != "MethodRoles" && name != "methodRolesSeed" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if len(vs.Values) == 0 {
|
if len(vs.Values) == 0 {
|
||||||
|
|||||||
@ -62,6 +62,13 @@ func resolveScanRoots(target string) (backendDir string, repoRoot string, err er
|
|||||||
return filepath.Join(absTarget, "backend"), absTarget, nil
|
return filepath.Join(absTarget, "backend"), absTarget, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filepath.Base(absTarget) == "backend" && fileExists(filepath.Join(absTarget, "go.mod")) {
|
||||||
|
parent := filepath.Clean(filepath.Join(absTarget, ".."))
|
||||||
|
if fileExists(filepath.Join(parent, "buf.yaml")) || dirExists(filepath.Join(parent, "proto")) {
|
||||||
|
return absTarget, parent, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if dirExists(filepath.Join(absTarget, "cmd", "check-safety")) {
|
if dirExists(filepath.Join(absTarget, "cmd", "check-safety")) {
|
||||||
parent := filepath.Clean(filepath.Join(absTarget, ".."))
|
parent := filepath.Clean(filepath.Join(absTarget, ".."))
|
||||||
if fileExists(filepath.Join(parent, "buf.yaml")) {
|
if fileExists(filepath.Join(parent, "buf.yaml")) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user