diff --git a/iam/src/policy/action.rs b/iam/src/policy/action.rs index a4f35ffd..03204aae 100644 --- a/iam/src/policy/action.rs +++ b/iam/src/policy/action.rs @@ -114,14 +114,124 @@ pub enum S3Action { #[cfg_attr(test, default)] #[strum(serialize = "s3:*")] AllActions, + #[strum(serialize = "s3:AbortMultipartUpload")] + AbortMultipartUploadAction, + #[strum(serialize = "s3:CreateBucket")] + CreateBucketAction, + #[strum(serialize = "s3:DeleteBucket")] + DeleteBucketAction, + #[strum(serialize = "s3:ForceDeleteBucket")] + ForceDeleteBucketAction, + #[strum(serialize = "s3:DeleteBucketPolicy")] + DeleteBucketPolicyAction, + #[strum(serialize = "s3:DeleteBucketCors")] + DeleteBucketCorsAction, + #[strum(serialize = "s3:DeleteObject")] + DeleteObjectAction, #[strum(serialize = "s3:GetBucketLocation")] GetBucketLocationAction, + #[strum(serialize = "s3:GetBucketNotification")] + GetBucketNotificationAction, + #[strum(serialize = "s3:GetBucketPolicy")] + GetBucketPolicyAction, + #[strum(serialize = "s3:GetBucketCors")] + GetBucketCorsAction, #[strum(serialize = "s3:GetObject")] GetObjectAction, + #[strum(serialize = "s3:GetObjectAttributes")] + GetObjectAttributesAction, + #[strum(serialize = "s3:HeadBucket")] + HeadBucketAction, + #[strum(serialize = "s3:ListAllMyBuckets")] + ListAllMyBucketsAction, + #[strum(serialize = "s3:ListBucket")] + ListBucketAction, + #[strum(serialize = "s3:GetBucketPolicyStatus")] + GetBucketPolicyStatusAction, + #[strum(serialize = "s3:ListBucketVersions")] + ListBucketVersionsAction, + #[strum(serialize = "s3:ListBucketMultipartUploads")] + ListBucketMultipartUploadsAction, + #[strum(serialize = "s3:ListenNotification")] + ListenNotificationAction, + #[strum(serialize = "s3:ListenBucketNotification")] + ListenBucketNotificationAction, + #[strum(serialize = "s3:ListMultipartUploadParts")] + ListMultipartUploadPartsAction, + #[strum(serialize = "s3:PutBucketLifecycle")] + PutBucketLifecycleAction, + #[strum(serialize = "s3:GetBucketLifecycle")] + GetBucketLifecycleAction, + #[strum(serialize = "s3:PutBucketNotification")] + PutBucketNotificationAction, + #[strum(serialize = "s3:PutBucketPolicy")] + PutBucketPolicyAction, + #[strum(serialize = "s3:PutBucketCors")] + PutBucketCorsAction, #[strum(serialize = "s3:PutObject")] PutObjectAction, + #[strum(serialize = "s3:DeleteObjectVersion")] + DeleteObjectVersionAction, + #[strum(serialize = "s3:DeleteObjectVersionTagging")] + DeleteObjectVersionTaggingAction, #[strum(serialize = "s3:GetObjectVersion")] GetObjectVersionAction, + #[strum(serialize = "s3:GetObjectVersionAttributes")] + GetObjectVersionAttributesAction, + #[strum(serialize = "s3:GetObjectVersionTagging")] + GetObjectVersionTaggingAction, + #[strum(serialize = "s3:PutObjectVersionTagging")] + PutObjectVersionTaggingAction, + #[strum(serialize = "s3:BypassGovernanceRetention")] + BypassGovernanceRetentionAction, + #[strum(serialize = "s3:PutObjectRetention")] + PutObjectRetentionAction, + #[strum(serialize = "s3:GetObjectRetention")] + GetObjectRetentionAction, + #[strum(serialize = "s3:GetObjectLegalHold")] + GetObjectLegalHoldAction, + #[strum(serialize = "s3:PutObjectLegalHold")] + PutObjectLegalHoldAction, + #[strum(serialize = "s3:GetBucketObjectLockConfiguration")] + GetBucketObjectLockConfigurationAction, + #[strum(serialize = "s3:PutBucketObjectLockConfiguration")] + PutBucketObjectLockConfigurationAction, + #[strum(serialize = "s3:GetBucketTagging")] + GetBucketTaggingAction, + #[strum(serialize = "s3:PutBucketTagging")] + PutBucketTaggingAction, + #[strum(serialize = "s3:GetObjectTagging")] + GetObjectTaggingAction, + #[strum(serialize = "s3:PutObjectTagging")] + PutObjectTaggingAction, + #[strum(serialize = "s3:DeleteObjectTagging")] + DeleteObjectTaggingAction, + #[strum(serialize = "s3:PutBucketEncryption")] + PutBucketEncryptionAction, + #[strum(serialize = "s3:GetBucketEncryption")] + GetBucketEncryptionAction, + #[strum(serialize = "s3:PutBucketVersioning")] + PutBucketVersioningAction, + #[strum(serialize = "s3:GetBucketVersioning")] + GetBucketVersioningAction, + #[strum(serialize = "s3:GetReplicationConfiguration")] + GetReplicationConfigurationAction, + #[strum(serialize = "s3:PutReplicationConfiguration")] + PutReplicationConfigurationAction, + #[strum(serialize = "s3:ReplicateObject")] + ReplicateObjectAction, + #[strum(serialize = "s3:ReplicateDelete")] + ReplicateDeleteAction, + #[strum(serialize = "s3:ReplicateTags")] + ReplicateTagsAction, + #[strum(serialize = "s3:GetObjectVersionForReplication")] + GetObjectVersionForReplicationAction, + #[strum(serialize = "s3:RestoreObject")] + RestoreObjectAction, + #[strum(serialize = "s3:ResetBucketReplicationState")] + ResetBucketReplicationStateAction, + #[strum(serialize = "s3:PutObjectFanOut")] + PutObjectFanOutAction, } #[derive(Serialize, Deserialize, Hash, PartialEq, Eq, Clone, EnumString, IntoStaticStr, Debug)] diff --git a/iam/src/policy/function.rs b/iam/src/policy/function.rs index 03a020dc..27870d15 100644 --- a/iam/src/policy/function.rs +++ b/iam/src/policy/function.rs @@ -131,9 +131,9 @@ impl<'de> Deserialize<'de> for Functions { } } - if inner_data.is_empty() { - return Err(Error::custom("has no condition element")); - } + // if inner_data.is_empty() { + // return Err(Error::custom("has no condition element")); + // } Ok(inner_data) } diff --git a/iam/src/policy/function/key_name.rs b/iam/src/policy/function/key_name.rs index 321771ac..4457cee0 100644 --- a/iam/src/policy/function/key_name.rs +++ b/iam/src/policy/function/key_name.rs @@ -169,6 +169,10 @@ pub enum S3KeyName { #[strum(serialize = "s3:ExistingObjectTag")] S3ExistingObjectTag, + #[strum(serialize = "s3:RequestObjectTagKeys")] + S3RequestObjectTagKeys, + #[strum(serialize = "s3:RequestObjectTag")] + S3RequestObjectTag, } #[derive(Clone, EnumString, Debug, IntoStaticStr, Eq, PartialEq, Serialize, Deserialize)] diff --git a/iam/src/policy/policy.rs b/iam/src/policy/policy.rs index 38ec5dff..bec59983 100644 --- a/iam/src/policy/policy.rs +++ b/iam/src/policy/policy.rs @@ -6,8 +6,11 @@ use std::collections::HashSet; #[derive(Serialize, Deserialize, Clone, Default, Debug)] pub struct Policy { + #[serde(default, rename = "ID")] pub id: ID, + #[serde(rename = "Version")] pub version: String, + #[serde(rename = "Statement")] pub statements: Vec, } @@ -153,6 +156,7 @@ pub mod default { hash_set }), conditions: Functions::default(), + ..Default::default() }], }, ), @@ -177,6 +181,7 @@ pub mod default { hash_set }), conditions: Functions::default(), + ..Default::default() }], }, ), @@ -200,6 +205,7 @@ pub mod default { hash_set }), conditions: Functions::default(), + ..Default::default() }], }, ), @@ -223,6 +229,7 @@ pub mod default { hash_set }), conditions: Functions::default(), + ..Default::default() }], }, ), @@ -253,6 +260,7 @@ pub mod default { hash_set }), conditions: Functions::default(), + ..Default::default() }], }, ), @@ -273,6 +281,7 @@ pub mod default { not_actions: ActionSet(Default::default()), resources: ResourceSet(HashSet::new()), conditions: Functions::default(), + ..Default::default() }, Statement { sid: "".into(), @@ -285,6 +294,7 @@ pub mod default { not_actions: ActionSet(Default::default()), resources: ResourceSet(HashSet::new()), conditions: Functions::default(), + ..Default::default() }, Statement { sid: "".into(), @@ -301,6 +311,7 @@ pub mod default { hash_set }), conditions: Functions::default(), + ..Default::default() }, ], }, @@ -308,3 +319,76 @@ pub mod default { ] }); } + +#[cfg(test)] +mod test { + use super::*; + use ecstore::error::Result; + + #[tokio::test] + async fn test_parse_policy() -> Result<()> { + let data = r#" +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": ["s3:GetObject"], + "Resource": ["arn:aws:s3:::dada/*"], + "Condition": { + "StringEquals": { + "s3:ExistingObjectTag/security": "public" + } + } + }, + { + "Effect": "Allow", + "Action": ["s3:DeleteObjectTagging"], + "Resource": ["arn:aws:s3:::dada/*"], + "Condition": { + "StringEquals": { + "s3:ExistingObjectTag/security": "public" + } + } + }, + { + "Effect": "Allow", + "Action": ["s3:DeleteObject"], + "Resource": ["arn:aws:s3:::dada/*"] + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::dada/*" + ], + "Condition": { + "ForAllValues:StringLike": { + "s3:RequestObjectTagKeys": [ + "security", + "virus" + ] + } + } + } + ] +} +"#; + + let p = Policy::parse_config(data.as_bytes())?; + + // println!("{:?}", p); + + let str = serde_json::to_string(&p)?; + + // println!("----- {}", str); + + let _p2 = Policy::parse_config(str.as_bytes())?; + // println!("33{:?}", p2); + + // assert_eq!(p, p2); + Ok(()) + } +} diff --git a/iam/src/policy/resource.rs b/iam/src/policy/resource.rs index 529ec9b8..9c33e8d5 100644 --- a/iam/src/policy/resource.rs +++ b/iam/src/policy/resource.rs @@ -64,7 +64,7 @@ impl PartialEq for ResourceSet { } } -#[derive(Hash, Eq, PartialEq, Serialize, Deserialize, Clone, Debug)] +#[derive(Hash, Eq, PartialEq, Clone, Debug)] pub enum Resource { S3(String), Kms(String), @@ -140,6 +140,28 @@ impl Validator for Resource { } } +impl Serialize for Resource { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + Resource::S3(s) => serializer.serialize_str(&format!("{}{}", Self::S3_PREFIX, s)), + Resource::Kms(s) => serializer.serialize_str(s), + } + } +} + +impl<'de> Deserialize<'de> for Resource { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Resource::try_from(value.as_str()).map_err(serde::de::Error::custom) + } +} + #[cfg(test)] mod tests { use crate::policy::resource::Resource; diff --git a/iam/src/policy/statement.rs b/iam/src/policy/statement.rs index ddbfa2cf..142d19fd 100644 --- a/iam/src/policy/statement.rs +++ b/iam/src/policy/statement.rs @@ -6,11 +6,19 @@ use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Default, Debug)] pub struct Statement { + #[serde(rename = "Sid", default)] pub sid: ID, + #[serde(rename = "Effect")] pub effect: Effect, + #[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, } @@ -84,7 +92,7 @@ impl Validator for Statement { // check sid self.sid.is_valid()?; - if self.actions.is_empty() || self.not_actions.is_empty() { + if self.actions.is_empty() && self.not_actions.is_empty() { return Err(IamError::NonAction.into()); } diff --git a/scripts/test_policy.json b/scripts/test_policy.json new file mode 100644 index 00000000..37fd782f --- /dev/null +++ b/scripts/test_policy.json @@ -0,0 +1,59 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject" + ], + "Resource": [ + "arn:aws:s3:::dada/*" + ], + "Condition": { + "StringEquals": { + "s3:ExistingObjectTag/security": "public" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "s3:DeleteObjectTagging" + ], + "Resource": [ + "arn:aws:s3:::dada/*" + ], + "Condition": { + "StringEquals": { + "s3:ExistingObjectTag/security": "public" + } + } + }, + { + "Effect": "Allow", + "Action": [ + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::dada/*" + ] + }, + { + "Effect": "Allow", + "Action": [ + "s3:PutObject" + ], + "Resource": [ + "arn:aws:s3:::dada/*" + ], + "Condition": { + "ForAllValues:StringLike": { + "s3:RequestObjectTagKeys": [ + "security", + "virus" + ] + } + } + } + ] +} \ No newline at end of file