add bucketpolicy

This commit is contained in:
weisd
2025-03-26 11:25:12 +08:00
parent c8e13b8ab5
commit d91429c5cf
5 changed files with 257 additions and 82 deletions

View File

@@ -17,7 +17,9 @@ use policy::{
arn::ARN,
auth::{self, get_claims_from_token_with_secret, is_secret_key_valid, jwt_sign, Credentials, UserIdentity},
format::Format,
policy::{iam_policy_claim_name_sa, Policy, PolicyDoc, DEFAULT_POLICIES, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE},
policy::{
default::DEFAULT_POLICIES, iam_policy_claim_name_sa, Policy, PolicyDoc, EMBEDDED_POLICY_TYPE, INHERITED_POLICY_TYPE,
},
};
use serde::{Deserialize, Serialize};
use serde_json::Value;

View File

@@ -5,27 +5,22 @@ mod function;
mod id;
#[allow(clippy::module_inception)]
mod policy;
mod principal;
pub mod resource;
pub mod statement;
pub(crate) mod utils;
use std::collections::{HashMap, HashSet};
use action::Action;
pub use action::ActionSet;
pub use doc::PolicyDoc;
pub use effect::Effect;
pub use function::Functions;
pub use id::ID;
pub use policy::{default::DEFAULT_POLICIES, Policy};
pub use policy::*;
pub use principal::Principal;
pub use resource::ResourceSet;
use serde_json::Value;
pub use statement::Statement;
use common::error::Result;
pub const EMBEDDED_POLICY_TYPE: &str = "embedded-policy";
pub const INHERITED_POLICY_TYPE: &str = "inherited-policy";
@@ -56,73 +51,3 @@ pub enum Error {
#[error("invalid resource, type: '{0}', pattern: '{1}'")]
InvalidResource(String, String),
}
/// DEFAULT_VERSION is the default version.
/// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
pub const DEFAULT_VERSION: &str = "2012-10-17";
/// check the data is Validator
pub trait Validator {
type Error;
fn is_valid(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Args<'a> {
pub account: &'a str,
pub groups: &'a Option<Vec<String>>,
pub action: Action,
pub bucket: &'a str,
pub conditions: &'a HashMap<String, Vec<String>>,
pub is_owner: bool,
pub object: &'a str,
pub claims: &'a HashMap<String, Value>,
pub deny_only: bool,
}
impl Args<'_> {
pub fn get_role_arn(&self) -> Option<&str> {
self.claims.get("roleArn").and_then(|x| x.as_str())
}
pub fn get_policies(&self, policy_claim_name: &str) -> (HashSet<String>, bool) {
get_policies_from_claims(self.claims, policy_claim_name)
}
}
fn get_values_from_claims(claims: &HashMap<String, Value>, claim_name: &str) -> (HashSet<String>, bool) {
let mut s = HashSet::new();
if let Some(pname) = claims.get(claim_name) {
if let Some(pnames) = pname.as_array() {
for pname in pnames {
if let Some(pname_str) = pname.as_str() {
for pname in pname_str.split(',') {
let pname = pname.trim();
if !pname.is_empty() {
s.insert(pname.to_string());
}
}
}
}
return (s, true);
} else if let Some(pname_str) = pname.as_str() {
for pname in pname_str.split(',') {
let pname = pname.trim();
if !pname.is_empty() {
s.insert(pname.to_string());
}
}
return (s, true);
}
}
(s, false)
}
fn get_policies_from_claims(claims: &HashMap<String, Value>, policy_claim_name: &str) -> (HashSet<String>, bool) {
get_values_from_claims(claims, policy_claim_name)
}
pub fn iam_policy_claim_name_sa() -> String {
"sa-policy".to_string()
}

View File

@@ -1,7 +1,42 @@
use super::{Args, Effect, Error as IamError, Statement, Validator, DEFAULT_VERSION, ID};
use super::{action::Action, statement::BPStatement, Effect, Error as IamError, Statement, ID};
use common::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use serde_json::Value;
use std::collections::{HashMap, HashSet};
/// DEFAULT_VERSION is the default version.
/// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
pub const DEFAULT_VERSION: &str = "2012-10-17";
/// check the data is Validator
pub trait Validator {
type Error;
fn is_valid(&self) -> Result<()> {
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Args<'a> {
pub account: &'a str,
pub groups: &'a Option<Vec<String>>,
pub action: Action,
pub bucket: &'a str,
pub conditions: &'a HashMap<String, Vec<String>>,
pub is_owner: bool,
pub object: &'a str,
pub claims: &'a HashMap<String, Value>,
pub deny_only: bool,
}
impl Args<'_> {
pub fn get_role_arn(&self) -> Option<&str> {
self.claims.get("roleArn").and_then(|x| x.as_str())
}
pub fn get_policies(&self, policy_claim_name: &str) -> (HashSet<String>, bool) {
get_policies_from_claims(self.claims, policy_claim_name)
}
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct Policy {
@@ -118,6 +153,101 @@ impl Validator for Policy {
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BucketPolicyArgs<'a> {
pub account: &'a str,
pub groups: &'a Option<Vec<String>>,
pub action: Action,
pub bucket: &'a str,
pub conditions: &'a HashMap<String, Vec<String>>,
pub is_owner: bool,
pub object: &'a str,
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct BucketPolicy {
#[serde(default, rename = "ID")]
pub id: ID,
#[serde(rename = "Version")]
pub version: String,
#[serde(rename = "Statement")]
pub statements: Vec<BPStatement>,
}
impl BucketPolicy {
pub fn is_allowed(&self, args: &BucketPolicyArgs) -> bool {
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Deny)) {
if !statement.is_allowed(args) {
return false;
}
}
if args.is_owner {
return true;
}
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Allow)) {
if statement.is_allowed(args) {
return true;
}
}
false
}
}
impl Validator for BucketPolicy {
type Error = Error;
fn is_valid(&self) -> Result<()> {
if !self.id.is_empty() && !self.id.eq(DEFAULT_VERSION) {
return Err(IamError::InvalidVersion(self.id.0.clone()).into());
}
for statement in self.statements.iter() {
statement.is_valid()?;
}
Ok(())
}
}
fn get_values_from_claims(claims: &HashMap<String, Value>, claim_name: &str) -> (HashSet<String>, bool) {
let mut s = HashSet::new();
if let Some(pname) = claims.get(claim_name) {
if let Some(pnames) = pname.as_array() {
for pname in pnames {
if let Some(pname_str) = pname.as_str() {
for pname in pname_str.split(',') {
let pname = pname.trim();
if !pname.is_empty() {
s.insert(pname.to_string());
}
}
}
}
return (s, true);
} else if let Some(pname_str) = pname.as_str() {
for pname in pname_str.split(',') {
let pname = pname.trim();
if !pname.is_empty() {
s.insert(pname.to_string());
}
}
return (s, true);
}
}
(s, false)
}
fn get_policies_from_claims(claims: &HashMap<String, Value>, policy_claim_name: &str) -> (HashSet<String>, bool) {
get_values_from_claims(claims, policy_claim_name)
}
pub fn iam_policy_claim_name_sa() -> String {
"sa-policy".to_string()
}
pub mod default {
use std::{collections::HashSet, sync::LazyLock};

View File

@@ -0,0 +1,32 @@
use super::{utils::wildcard, Validator};
use common::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
#[serde(rename_all = "PascalCase", default)]
pub struct Principal {
#[serde(rename = "AWS")]
aws: HashSet<String>,
}
impl Principal {
pub fn is_match(&self, parincipal: &str) -> bool {
for pattern in self.aws.iter() {
if wildcard::is_simple_match(pattern, parincipal) {
return true;
}
}
false
}
}
impl Validator for Principal {
type Error = Error;
fn is_valid(&self) -> Result<()> {
if self.aws.is_empty() {
return Err(Error::msg("Principal is empty"));
}
Ok(())
}
}

View File

@@ -1,4 +1,7 @@
use super::{action::Action, ActionSet, Args, Effect, Error as IamError, Functions, ResourceSet, Validator, ID};
use super::{
action::Action, ActionSet, Args, BucketPolicyArgs, Effect, Error as IamError, Functions, Principal, ResourceSet, Validator,
ID,
};
use common::error::{Error, Result};
use serde::{Deserialize, Serialize};
@@ -115,3 +118,86 @@ impl PartialEq for Statement {
&& self.conditions == other.conditions
}
}
#[derive(Debug, Deserialize, Serialize, Default, Clone)]
#[serde(rename_all = "PascalCase", default)]
pub struct BPStatement {
#[serde(rename = "Sid", default)]
pub sid: ID,
#[serde(rename = "Effect")]
pub effect: Effect,
#[serde(rename = "Principal")]
pub principal: Principal,
#[serde(rename = "Action")]
pub actions: ActionSet,
#[serde(rename = "NotAction", default)]
pub not_actions: ActionSet,
#[serde(rename = "Resource", default)]
pub resources: ResourceSet,
#[serde(rename = "NotResource", default)]
pub not_resources: ResourceSet,
#[serde(rename = "Condition", default)]
pub conditions: Functions,
}
impl BPStatement {
pub fn is_allowed(&self, args: &BucketPolicyArgs) -> bool {
let check = 'c: {
if !self.principal.is_match(args.account) {
break 'c false;
}
if (!self.actions.is_match(&args.action) && !self.actions.is_empty()) || self.not_actions.is_match(&args.action) {
break 'c false;
}
let mut resource = String::from(args.bucket);
if !args.object.is_empty() {
if !args.object.starts_with('/') {
resource.push('/');
}
resource.push_str(args.object);
} else {
resource.push('/');
}
if !self.resources.is_empty() && !self.resources.is_match(&resource, args.conditions) {
break 'c false;
}
if !self.not_resources.is_empty() && self.not_resources.is_match(&resource, args.conditions) {
break 'c false;
}
self.conditions.evaluate(args.conditions)
};
self.effect.is_allowed(check)
}
}
impl Validator for BPStatement {
type Error = Error;
fn is_valid(&self) -> Result<()> {
self.effect.is_valid()?;
// check sid
self.sid.is_valid()?;
self.principal.is_valid()?;
if self.actions.is_empty() && self.not_actions.is_empty() {
return Err(IamError::NonAction.into());
}
if self.resources.is_empty() {
return Err(IamError::NonResource.into());
}
self.actions.is_valid()?;
self.not_actions.is_valid()?;
self.resources.is_valid()?;
Ok(())
}
}