Skip to content

Commit 5873a31

Browse files
authored
feat: Add debug logging for RBAC access control decisions (#4255)
Refactor is_action_allowed() to track decision outcome, matched rule index, and reason. Add structured debug log output for troubleshooting access control. Signed-off-by: Derek Higgins <derekh@redhat.com>
1 parent fcd6370 commit 5873a31

1 file changed

Lines changed: 58 additions & 27 deletions

File tree

src/llama_stack/core/access_control/access_control.py

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from typing import Any
88

99
from llama_stack.core.datatypes import User
10+
from llama_stack.log import get_logger
1011

1112
from .conditions import (
1213
Condition,
@@ -19,6 +20,8 @@
1920
Scope,
2021
)
2122

23+
logger = get_logger(name=__name__, category="core::auth")
24+
2225

2326
def matches_resource(resource_scope: str, actual_resource: str) -> bool:
2427
if resource_scope == actual_resource:
@@ -74,35 +77,63 @@ def is_action_allowed(
7477
resource: ProtectedResource,
7578
user: User | None,
7679
) -> bool:
80+
qualified_resource_id = f"{resource.type}::{resource.identifier}"
81+
decision = False
82+
reason = ""
83+
index = -1
84+
7785
# If user is not set, assume authentication is not enabled
7886
if not user:
79-
return True
80-
81-
if not len(policy):
82-
policy = default_policy()
83-
84-
qualified_resource_id = f"{resource.type}::{resource.identifier}"
85-
for rule in policy:
86-
if rule.forbid and matches_scope(rule.forbid, action, qualified_resource_id, user.principal):
87-
if rule.when:
88-
if matches_conditions(parse_conditions(as_list(rule.when)), resource, user):
89-
return False
90-
elif rule.unless:
91-
if not matches_conditions(parse_conditions(as_list(rule.unless)), resource, user):
92-
return False
93-
else:
94-
return False
95-
elif rule.permit and matches_scope(rule.permit, action, qualified_resource_id, user.principal):
96-
if rule.when:
97-
if matches_conditions(parse_conditions(as_list(rule.when)), resource, user):
98-
return True
99-
elif rule.unless:
100-
if not matches_conditions(parse_conditions(as_list(rule.unless)), resource, user):
101-
return True
102-
else:
103-
return True
104-
# assume access is denied unless we find a rule that permits access
105-
return False
87+
decision = True
88+
reason = "no auth"
89+
else:
90+
if not len(policy):
91+
policy = default_policy()
92+
93+
for index, rule in enumerate(policy): # noqa: B007
94+
if rule.forbid and matches_scope(rule.forbid, action, qualified_resource_id, user.principal):
95+
if rule.when:
96+
if matches_conditions(parse_conditions(as_list(rule.when)), resource, user):
97+
decision = False
98+
reason = rule.description or ""
99+
break
100+
elif rule.unless:
101+
if not matches_conditions(parse_conditions(as_list(rule.unless)), resource, user):
102+
decision = False
103+
reason = rule.description or ""
104+
break
105+
else:
106+
decision = False
107+
reason = rule.description or ""
108+
break
109+
elif rule.permit and matches_scope(rule.permit, action, qualified_resource_id, user.principal):
110+
if rule.when:
111+
if matches_conditions(parse_conditions(as_list(rule.when)), resource, user):
112+
decision = True
113+
reason = rule.description or ""
114+
break
115+
elif rule.unless:
116+
if not matches_conditions(parse_conditions(as_list(rule.unless)), resource, user):
117+
decision = True
118+
reason = rule.description or ""
119+
break
120+
else:
121+
decision = True
122+
reason = rule.description or ""
123+
break
124+
else:
125+
reason = "no matching rule"
126+
index = -1
127+
128+
# print apprived or denied
129+
decision_str = "APPROVED" if decision else "DENIED"
130+
user_str = user.principal if user else "none"
131+
logger.debug(
132+
f"AUTHZ,decision={decision_str},user={user_str},"
133+
f"resource_id={qualified_resource_id},action={action},"
134+
f"rule_index={index},reason={reason!r}"
135+
)
136+
return decision
106137

107138

108139
class AccessDeniedError(RuntimeError):

0 commit comments

Comments
 (0)