1use {
17 krabka_metadata::{AclOperation, PatternType, PermissionType, ResourceType},
18 krabka_security::Principal,
19 krabka_verified::{
20 AclDecision, acl_decision,
21 authz::{AclDefault, AclFacts},
22 },
23 std::{collections::HashSet, net::SocketAddr},
24 tracing::field::Empty,
25};
26
27mod coverage;
28mod matching;
29#[cfg(test)]
30mod test_support;
31#[cfg(test)]
32mod tests;
33
34use {
35 self::{
36 coverage::DeniedNames,
37 matching::{matches_host, matches_operation, matches_principal, matches_resource},
38 },
39 crate::{AclSource, AuthorizationRequest, AuthorizationResult, Authorizer, jdk_host_address},
40};
41
42const HARDCODED_RESOURCE_NAME: &str = "hardcode";
45
46#[derive(Debug)]
54pub struct SimpleAclAuthorizer {
55 super_users: HashSet<String>,
56 allow_everyone_if_no_acl_found: bool,
57}
58
59impl SimpleAclAuthorizer {
60 #[must_use]
61 pub fn new(super_users: HashSet<String>) -> Self {
62 Self {
63 super_users,
64 allow_everyone_if_no_acl_found: false,
65 }
66 }
67
68 #[must_use]
71 pub fn with_allow_everyone_if_no_acl_found(mut self, allow: bool) -> Self {
72 self.allow_everyone_if_no_acl_found = allow;
73 self
74 }
75}
76
77impl Authorizer for SimpleAclAuthorizer {
78 #[tracing::instrument(
84 level = "debug",
85 skip_all,
86 fields(
87 principal = %req.principal.name,
88 resource_type = ?req.resource_type,
89 resource = %req.resource_name,
90 operation = ?req.operation,
91 host = %jdk_host_address(req.host.ip()),
92 decision = Empty,
93 )
94 )]
95 fn authorize(
96 &self,
97 source: &dyn AclSource,
98 req: &AuthorizationRequest<'_>,
99 ) -> AuthorizationResult {
100 let span = tracing::Span::current();
101 let super_user = self.super_users.contains(&req.principal.name);
102 let mut saw_allow = false;
103 let mut saw_deny = false;
104 let mut has_resource_acls = false;
105 if !super_user {
106 let cidr_hosts_supported = source.cidr_hosts_supported();
107 for entry in source.matching_acls(req.resource_type, req.resource_name) {
108 if !matches_resource(entry, req.resource_type, req.resource_name) {
109 continue;
110 }
111 has_resource_acls = true;
112 if !matches_principal(entry, &req.principal.name)
113 || !matches_host(entry, req.host.ip(), cidr_hosts_supported)
114 || !matches_operation(entry.operation, req.operation, entry.permission_type)
115 {
116 continue;
117 }
118 match entry.permission_type {
119 PermissionType::Allow => saw_allow = true,
120 PermissionType::Deny => {
121 saw_deny = true;
122 break;
123 }
124 }
125 }
126 }
127 let default_decision = if self.allow_everyone_if_no_acl_found && !has_resource_acls {
128 AclDefault::Allow
129 } else {
130 AclDefault::Deny
131 };
132 let decision = acl_decision(AclFacts {
133 super_user,
134 saw_allow,
135 saw_deny,
136 default_decision,
137 });
138
139 let (label, result) = match decision {
140 AclDecision::AllowSuperuser => ("allow-superuser", AuthorizationResult::Allow),
141 AclDecision::AllowAcl => ("allow-acl", AuthorizationResult::Allow),
142 AclDecision::AllowNoAcl => ("allow-no-acl", AuthorizationResult::Allow),
143 AclDecision::DenyExplicit => ("deny-explicit", AuthorizationResult::Deny),
144 AclDecision::DenyDefault => ("deny-default", AuthorizationResult::Deny),
145 };
146 span.record("decision", label);
147 result
148 }
149
150 #[tracing::instrument(
161 level = "debug",
162 skip_all,
163 fields(
164 principal = %principal.name,
165 resource_type = ?resource_type,
166 operation = ?operation,
167 host = %jdk_host_address(host.ip()),
168 decision = Empty,
169 )
170 )]
171 fn authorize_by_resource_type(
172 &self,
173 source: &dyn AclSource,
174 principal: &Principal,
175 host: &SocketAddr,
176 resource_type: ResourceType,
177 operation: AclOperation,
178 ) -> AuthorizationResult {
179 let span = tracing::Span::current();
180 if self.super_users.contains(&principal.name) {
181 span.record("decision", "allow-superuser");
182 return AuthorizationResult::Allow;
183 }
184 let hardcode = AuthorizationRequest {
191 principal,
192 host,
193 resource_type,
194 resource_name: HARDCODED_RESOURCE_NAME,
195 operation,
196 };
197 if self.authorize(source, &hardcode) == AuthorizationResult::Allow {
198 span.record("decision", "allow-hardcode");
199 return AuthorizationResult::Allow;
200 }
201 let cidr_hosts_supported = source.cidr_hosts_supported();
202 let applicable: Vec<_> = source
203 .acls_of_type(resource_type)
204 .filter(|entry| {
205 matches_principal(entry, &principal.name)
206 && matches_host(entry, host.ip(), cidr_hosts_supported)
207 && matches_operation(entry.operation, operation, entry.permission_type)
208 })
209 .collect();
210 let denied = DeniedNames::new(
211 applicable
212 .iter()
213 .copied()
214 .filter(|entry| entry.permission_type == PermissionType::Deny),
215 );
216 let uncovered = applicable.iter().any(|entry| {
217 entry.permission_type == PermissionType::Allow
218 && match entry.pattern_type {
219 PatternType::Literal if entry.resource_name == "*" => denied.allows_wildcard(),
220 PatternType::Literal => denied.allows_literal(&entry.resource_name),
221 PatternType::Prefixed => denied.allows_prefix(&entry.resource_name),
222 }
223 });
224 if uncovered {
225 span.record("decision", "allow-acl");
226 return AuthorizationResult::Allow;
227 }
228 span.record("decision", "deny-default");
229 AuthorizationResult::Deny
230 }
231}