1use super::{Effect, Error as IamError, ID, Statement, action::Action, statement::BPStatement};
16use crate::error::{Error, Result};
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19use std::collections::{HashMap, HashSet};
20
21pub const DEFAULT_VERSION: &str = "2012-10-17";
24
25pub trait Validator {
27 type Error;
28 fn is_valid(&self) -> Result<()> {
29 Ok(())
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct Args<'a> {
35 pub account: &'a str,
36 pub groups: &'a Option<Vec<String>>,
37 pub action: Action,
38 pub bucket: &'a str,
39 pub conditions: &'a HashMap<String, Vec<String>>,
40 pub is_owner: bool,
41 pub object: &'a str,
42 pub claims: &'a HashMap<String, Value>,
43 pub deny_only: bool,
44}
45
46impl Args<'_> {
47 pub fn get_role_arn(&self) -> Option<&str> {
48 self.claims.get("roleArn").and_then(|x| x.as_str())
49 }
50 pub fn get_policies(&self, policy_claim_name: &str) -> (HashSet<String>, bool) {
51 get_policies_from_claims(self.claims, policy_claim_name)
52 }
53}
54
55#[derive(Serialize, Deserialize, Clone, Default, Debug)]
56pub struct Policy {
57 #[serde(default, rename = "ID")]
58 pub id: ID,
59 #[serde(rename = "Version")]
60 pub version: String,
61 #[serde(rename = "Statement")]
62 pub statements: Vec<Statement>,
63}
64
65impl Policy {
66 pub fn is_allowed(&self, args: &Args) -> bool {
67 for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Deny)) {
68 if !statement.is_allowed(args) {
69 return false;
70 }
71 }
72
73 if args.deny_only || args.is_owner {
74 return true;
75 }
76
77 for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Allow)) {
78 if statement.is_allowed(args) {
79 return true;
80 }
81 }
82
83 false
84 }
85
86 pub fn match_resource(&self, resource: &str) -> bool {
87 for statement in self.statements.iter() {
88 if statement.resources.match_resource(resource) {
89 return true;
90 }
91 }
92 false
93 }
94
95 fn drop_duplicate_statements(&mut self) {
96 let mut dups = HashSet::new();
97 for i in 0..self.statements.len() {
98 if dups.contains(&i) {
99 continue;
102 }
103 for j in (i + 1)..self.statements.len() {
104 if !self.statements[i].eq(&self.statements[j]) {
105 continue;
106 }
107
108 dups.insert(j);
110 }
111 }
112
113 let mut c = 0;
115 for i in 0..self.statements.len() {
116 if dups.contains(&i) {
117 continue;
118 }
119 self.statements[c] = self.statements[i].clone();
120 c += 1;
121 }
122 self.statements.truncate(c);
123 }
124 pub fn merge_policies(inputs: Vec<Policy>) -> Policy {
125 let mut merged = Policy::default();
126
127 for p in inputs {
128 if merged.version.is_empty() {
129 merged.version = p.version.clone();
130 }
131 for st in p.statements {
132 merged.statements.push(st.clone());
133 }
134 }
135 merged.drop_duplicate_statements();
136 merged
137 }
138
139 pub fn is_empty(&self) -> bool {
140 self.statements.is_empty()
141 }
142
143 pub fn validate(&self) -> Result<()> {
144 self.is_valid()
145 }
146
147 pub fn parse_config(data: &[u8]) -> Result<Policy> {
148 let policy: Policy = serde_json::from_slice(data)?;
149 policy.validate()?;
150 Ok(policy)
151 }
152}
153
154impl Validator for Policy {
155 type Error = Error;
156
157 fn is_valid(&self) -> Result<()> {
158 if !self.id.is_empty() && !self.id.eq(DEFAULT_VERSION) {
159 return Err(IamError::InvalidVersion(self.id.0.clone()).into());
160 }
161
162 for statement in self.statements.iter() {
163 statement.is_valid()?;
164 }
165
166 Ok(())
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct BucketPolicyArgs<'a> {
172 pub account: &'a str,
173 pub groups: &'a Option<Vec<String>>,
174 pub action: Action,
175 pub bucket: &'a str,
176 pub conditions: &'a HashMap<String, Vec<String>>,
177 pub is_owner: bool,
178 pub object: &'a str,
179}
180
181#[derive(Serialize, Deserialize, Clone, Default, Debug)]
182pub struct BucketPolicy {
183 #[serde(default, rename = "ID")]
184 pub id: ID,
185 #[serde(rename = "Version")]
186 pub version: String,
187 #[serde(rename = "Statement")]
188 pub statements: Vec<BPStatement>,
189}
190
191impl BucketPolicy {
192 pub fn is_allowed(&self, args: &BucketPolicyArgs) -> bool {
193 for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Deny)) {
194 if !statement.is_allowed(args) {
195 return false;
196 }
197 }
198
199 if args.is_owner {
200 return true;
201 }
202
203 for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Allow)) {
204 if statement.is_allowed(args) {
205 return true;
206 }
207 }
208
209 false
210 }
211}
212
213impl Validator for BucketPolicy {
214 type Error = Error;
215
216 fn is_valid(&self) -> Result<()> {
217 if !self.id.is_empty() && !self.id.eq(DEFAULT_VERSION) {
218 return Err(IamError::InvalidVersion(self.id.0.clone()).into());
219 }
220
221 for statement in self.statements.iter() {
222 statement.is_valid()?;
223 }
224
225 Ok(())
226 }
227}
228
229fn get_values_from_claims(claims: &HashMap<String, Value>, claim_name: &str) -> (HashSet<String>, bool) {
230 let mut s = HashSet::new();
231 if let Some(pname) = claims.get(claim_name) {
232 if let Some(pnames) = pname.as_array() {
233 for pname in pnames {
234 if let Some(pname_str) = pname.as_str() {
235 for pname in pname_str.split(',') {
236 let pname = pname.trim();
237 if !pname.is_empty() {
238 s.insert(pname.to_string());
239 }
240 }
241 }
242 }
243 return (s, true);
244 } else if let Some(pname_str) = pname.as_str() {
245 for pname in pname_str.split(',') {
246 let pname = pname.trim();
247 if !pname.is_empty() {
248 s.insert(pname.to_string());
249 }
250 }
251 return (s, true);
252 }
253 }
254 (s, false)
255}
256
257pub fn get_policies_from_claims(claims: &HashMap<String, Value>, policy_claim_name: &str) -> (HashSet<String>, bool) {
258 get_values_from_claims(claims, policy_claim_name)
259}
260
261pub fn iam_policy_claim_name_sa() -> String {
262 "sa-policy".to_string()
263}
264
265pub mod default {
266 use std::{collections::HashSet, sync::LazyLock};
267
268 use crate::policy::{
269 ActionSet, DEFAULT_VERSION, Effect, Functions, ResourceSet, Statement,
270 action::{Action, AdminAction, KmsAction, S3Action},
271 resource::Resource,
272 };
273
274 use super::Policy;
275
276 #[allow(clippy::incompatible_msrv)]
277 pub static DEFAULT_POLICIES: LazyLock<[(&'static str, Policy); 6]> = LazyLock::new(|| {
278 [
279 (
280 "readwrite",
281 Policy {
282 id: "".into(),
283 version: DEFAULT_VERSION.into(),
284 statements: vec![Statement {
285 sid: "".into(),
286 effect: Effect::Allow,
287 actions: ActionSet({
288 let mut hash_set = HashSet::new();
289 hash_set.insert(Action::S3Action(S3Action::AllActions));
290 hash_set
291 }),
292 not_actions: ActionSet(Default::default()),
293 resources: ResourceSet({
294 let mut hash_set = HashSet::new();
295 hash_set.insert(Resource::S3("*".into()));
296 hash_set
297 }),
298 conditions: Functions::default(),
299 ..Default::default()
300 }],
301 },
302 ),
303 (
304 "readonly",
305 Policy {
306 id: "".into(),
307 version: DEFAULT_VERSION.into(),
308 statements: vec![Statement {
309 sid: "".into(),
310 effect: Effect::Allow,
311 actions: ActionSet({
312 let mut hash_set = HashSet::new();
313 hash_set.insert(Action::S3Action(S3Action::GetBucketLocationAction));
314 hash_set.insert(Action::S3Action(S3Action::GetObjectAction));
315 hash_set
316 }),
317 not_actions: ActionSet(Default::default()),
318 resources: ResourceSet({
319 let mut hash_set = HashSet::new();
320 hash_set.insert(Resource::S3("*".into()));
321 hash_set
322 }),
323 conditions: Functions::default(),
324 ..Default::default()
325 }],
326 },
327 ),
328 (
329 "writeonly",
330 Policy {
331 id: "".into(),
332 version: DEFAULT_VERSION.into(),
333 statements: vec![Statement {
334 sid: "".into(),
335 effect: Effect::Allow,
336 actions: ActionSet({
337 let mut hash_set = HashSet::new();
338 hash_set.insert(Action::S3Action(S3Action::PutObjectAction));
339 hash_set
340 }),
341 not_actions: ActionSet(Default::default()),
342 resources: ResourceSet({
343 let mut hash_set = HashSet::new();
344 hash_set.insert(Resource::S3("*".into()));
345 hash_set
346 }),
347 conditions: Functions::default(),
348 ..Default::default()
349 }],
350 },
351 ),
352 (
353 "writeonly",
354 Policy {
355 id: "".into(),
356 version: DEFAULT_VERSION.into(),
357 statements: vec![Statement {
358 sid: "".into(),
359 effect: Effect::Allow,
360 actions: ActionSet({
361 let mut hash_set = HashSet::new();
362 hash_set.insert(Action::S3Action(S3Action::PutObjectAction));
363 hash_set
364 }),
365 not_actions: ActionSet(Default::default()),
366 resources: ResourceSet({
367 let mut hash_set = HashSet::new();
368 hash_set.insert(Resource::S3("*".into()));
369 hash_set
370 }),
371 conditions: Functions::default(),
372 ..Default::default()
373 }],
374 },
375 ),
376 (
377 "diagnostics",
378 Policy {
379 id: "".into(),
380 version: DEFAULT_VERSION.into(),
381 statements: vec![Statement {
382 sid: "".into(),
383 effect: Effect::Allow,
384 actions: ActionSet({
385 let mut hash_set = HashSet::new();
386 hash_set.insert(Action::AdminAction(AdminAction::ProfilingAdminAction));
387 hash_set.insert(Action::AdminAction(AdminAction::TraceAdminAction));
388 hash_set.insert(Action::AdminAction(AdminAction::ConsoleLogAdminAction));
389 hash_set.insert(Action::AdminAction(AdminAction::ServerInfoAdminAction));
390 hash_set.insert(Action::AdminAction(AdminAction::TopLocksAdminAction));
391 hash_set.insert(Action::AdminAction(AdminAction::HealthInfoAdminAction));
392 hash_set.insert(Action::AdminAction(AdminAction::PrometheusAdminAction));
393 hash_set.insert(Action::AdminAction(AdminAction::BandwidthMonitorAction));
394 hash_set
395 }),
396 not_actions: ActionSet(Default::default()),
397 resources: ResourceSet({
398 let mut hash_set = HashSet::new();
399 hash_set.insert(Resource::S3("*".into()));
400 hash_set
401 }),
402 conditions: Functions::default(),
403 ..Default::default()
404 }],
405 },
406 ),
407 (
408 "consoleAdmin",
409 Policy {
410 id: "".into(),
411 version: DEFAULT_VERSION.into(),
412 statements: vec![
413 Statement {
414 sid: "".into(),
415 effect: Effect::Allow,
416 actions: ActionSet({
417 let mut hash_set = HashSet::new();
418 hash_set.insert(Action::AdminAction(AdminAction::AllAdminActions));
419 hash_set
420 }),
421 not_actions: ActionSet(Default::default()),
422 resources: ResourceSet(HashSet::new()),
423 conditions: Functions::default(),
424 ..Default::default()
425 },
426 Statement {
427 sid: "".into(),
428 effect: Effect::Allow,
429 actions: ActionSet({
430 let mut hash_set = HashSet::new();
431 hash_set.insert(Action::KmsAction(KmsAction::AllActions));
432 hash_set
433 }),
434 not_actions: ActionSet(Default::default()),
435 resources: ResourceSet(HashSet::new()),
436 conditions: Functions::default(),
437 ..Default::default()
438 },
439 Statement {
440 sid: "".into(),
441 effect: Effect::Allow,
442 actions: ActionSet({
443 let mut hash_set = HashSet::new();
444 hash_set.insert(Action::S3Action(S3Action::AllActions));
445 hash_set
446 }),
447 not_actions: ActionSet(Default::default()),
448 resources: ResourceSet({
449 let mut hash_set = HashSet::new();
450 hash_set.insert(Resource::S3("*".into()));
451 hash_set
452 }),
453 conditions: Functions::default(),
454 ..Default::default()
455 },
456 ],
457 },
458 ),
459 ]
460 });
461}
462
463#[cfg(test)]
464mod test {
465 use super::*;
466 use crate::error::Result;
467
468 #[tokio::test]
469 async fn test_parse_policy() -> Result<()> {
470 let data = r#"
471{
472 "Version": "2012-10-17",
473 "Statement": [
474 {
475 "Effect": "Allow",
476 "Action": ["s3:GetObject"],
477 "Resource": ["arn:aws:s3:::dada/*"],
478 "Condition": {
479 "StringEquals": {
480 "s3:ExistingObjectTag/security": "public"
481 }
482 }
483 },
484 {
485 "Effect": "Allow",
486 "Action": ["s3:DeleteObjectTagging"],
487 "Resource": ["arn:aws:s3:::dada/*"],
488 "Condition": {
489 "StringEquals": {
490 "s3:ExistingObjectTag/security": "public"
491 }
492 }
493 },
494 {
495 "Effect": "Allow",
496 "Action": ["s3:DeleteObject"],
497 "Resource": ["arn:aws:s3:::dada/*"]
498 },
499 {
500 "Effect": "Allow",
501 "Action": [
502 "s3:PutObject"
503 ],
504 "Resource": [
505 "arn:aws:s3:::dada/*"
506 ],
507 "Condition": {
508 "ForAllValues:StringLike": {
509 "s3:RequestObjectTagKeys": [
510 "security",
511 "virus"
512 ]
513 }
514 }
515 }
516 ]
517}
518"#;
519
520 let p = Policy::parse_config(data.as_bytes())?;
521
522 let str = serde_json::to_string(&p)?;
525
526 let _p2 = Policy::parse_config(str.as_bytes())?;
529 Ok(())
533 }
534}