feat: update plan 16 sec14-26
This commit is contained in:
@@ -37,7 +37,12 @@ CLAIM_ROLE_MAP = {
|
||||
}
|
||||
|
||||
|
||||
def decide(role, resource, action, role_project, target_project, sensitive):
|
||||
def decide(role, resource, action, role_project, target_project, sensitive,
|
||||
role_tenant="", target_tenant=""):
|
||||
# SEC-23 (MT-01): tenant isolation is enforced at the DATA layer BEFORE any role
|
||||
# grant — even an org-admin of tenant A may not act on tenant B's resources.
|
||||
if (role_tenant or target_tenant) and role_tenant != target_tenant:
|
||||
return False, f"CROSS_TENANT_DENY tenant={role_tenant or 'none'}!={target_tenant or 'none'}"
|
||||
perm = PERMISSIONS.get(role)
|
||||
if perm is None:
|
||||
return False, f"UNKNOWN_ROLE {role}"
|
||||
@@ -66,6 +71,37 @@ def decide(role, resource, action, role_project, target_project, sensitive):
|
||||
return True, f"ALLOW {role} {action_key}"
|
||||
|
||||
|
||||
def _audit_decision(args, verdict, reason):
|
||||
"""Plan-14: write each RBAC decision to an H5-style oversight log (opt-in via
|
||||
CASAN_RBAC_AUDIT_LOG). Append-only; feeds the RAI/Control-Plane oversight view.
|
||||
Off by default so existing flows are unchanged."""
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
path = os.environ.get("CASAN_RBAC_AUDIT_LOG")
|
||||
if not path:
|
||||
return
|
||||
rec = {
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"harness": "H5-rbac",
|
||||
"role": args.role,
|
||||
"resource": args.resource,
|
||||
"action": f"{args.resource}:{args.action}",
|
||||
"role_tenant": args.role_tenant or None,
|
||||
"target_tenant": args.target_tenant or None,
|
||||
"verdict": verdict,
|
||||
"reason": reason,
|
||||
}
|
||||
try:
|
||||
d = os.path.dirname(path)
|
||||
if d:
|
||||
os.makedirs(d, exist_ok=True)
|
||||
with open(path, "a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
sub = ap.add_subparsers(dest="cmd", required=True)
|
||||
@@ -76,6 +112,8 @@ def main() -> int:
|
||||
c.add_argument("--action", required=True)
|
||||
c.add_argument("--role-project", default="")
|
||||
c.add_argument("--target-project", default="")
|
||||
c.add_argument("--role-tenant", default="")
|
||||
c.add_argument("--target-tenant", default="")
|
||||
c.add_argument("--sensitive", action="store_true")
|
||||
|
||||
s = sub.add_parser("check-sod")
|
||||
@@ -110,8 +148,10 @@ def main() -> int:
|
||||
return 0
|
||||
|
||||
allowed, reason = decide(
|
||||
args.role, args.resource, args.action, args.role_project, args.target_project, args.sensitive
|
||||
args.role, args.resource, args.action, args.role_project, args.target_project,
|
||||
args.sensitive, args.role_tenant, args.target_tenant,
|
||||
)
|
||||
_audit_decision(args, "ALLOW" if allowed else "DENY", reason)
|
||||
if allowed:
|
||||
print(f"RBAC_ALLOW {reason}")
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user