mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-31 09:18:28 +00:00
feat: Replace LRU cache with Moka async cache in policy variables (#1166)
Co-authored-by: houseme <housemecn@gmail.com>
This commit is contained in:
Generated
+25
-38
@@ -63,17 +63,6 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.7.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.2.16",
|
|
||||||
"once_cell",
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.12"
|
version = "0.8.12"
|
||||||
@@ -296,7 +285,7 @@ version = "57.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a23eaff85a44e9fa914660fb0d0bb00b79c4a3d888b5334adb3ea4330c84f002"
|
checksum = "a23eaff85a44e9fa914660fb0d0bb00b79c4a3d888b5334adb3ea4330c84f002"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
"arrow-data",
|
"arrow-data",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -453,7 +442,7 @@ version = "57.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ae980d021879ea119dd6e2a13912d81e64abed372d53163e804dfe84639d8010"
|
checksum = "ae980d021879ea119dd6e2a13912d81e64abed372d53163e804dfe84639d8010"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
"arrow-data",
|
"arrow-data",
|
||||||
@@ -726,7 +715,7 @@ dependencies = [
|
|||||||
"http 0.2.12",
|
"http 0.2.12",
|
||||||
"http 1.4.0",
|
"http 1.4.0",
|
||||||
"http-body 0.4.6",
|
"http-body 0.4.6",
|
||||||
"lru 0.12.5",
|
"lru",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"regex-lite",
|
"regex-lite",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
@@ -2293,7 +2282,7 @@ version = "51.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c10f7659e96127d25e8366be7c8be4109595d6a2c3eac70421f380a7006a1b0"
|
checksum = "7c10f7659e96127d25e8366be7c8be4109595d6a2c3eac70421f380a7006a1b0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-ipc",
|
"arrow-ipc",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -2554,7 +2543,7 @@ version = "51.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c25210520a9dcf9c2b2cbbce31ebd4131ef5af7fc60ee92b266dc7d159cb305"
|
checksum = "1c25210520a9dcf9c2b2cbbce31ebd4131ef5af7fc60ee92b266dc7d159cb305"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow",
|
"arrow",
|
||||||
"datafusion-common",
|
"datafusion-common",
|
||||||
"datafusion-doc",
|
"datafusion-doc",
|
||||||
@@ -2575,7 +2564,7 @@ version = "51.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "62f4a66f3b87300bb70f4124b55434d2ae3fe80455f3574701d0348da040b55d"
|
checksum = "62f4a66f3b87300bb70f4124b55434d2ae3fe80455f3574701d0348da040b55d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow",
|
"arrow",
|
||||||
"datafusion-common",
|
"datafusion-common",
|
||||||
"datafusion-expr-common",
|
"datafusion-expr-common",
|
||||||
@@ -2686,7 +2675,7 @@ version = "51.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c30cc8012e9eedcb48bbe112c6eff4ae5ed19cf3003cb0f505662e88b7014c5d"
|
checksum = "c30cc8012e9eedcb48bbe112c6eff4ae5ed19cf3003cb0f505662e88b7014c5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow",
|
"arrow",
|
||||||
"datafusion-common",
|
"datafusion-common",
|
||||||
"datafusion-expr",
|
"datafusion-expr",
|
||||||
@@ -2723,7 +2712,7 @@ version = "51.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90da43e1ec550b172f34c87ec68161986ced70fd05c8d2a2add66eef9c276f03"
|
checksum = "90da43e1ec550b172f34c87ec68161986ced70fd05c8d2a2add66eef9c276f03"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow",
|
"arrow",
|
||||||
"datafusion-common",
|
"datafusion-common",
|
||||||
"datafusion-expr-common",
|
"datafusion-expr-common",
|
||||||
@@ -2756,7 +2745,7 @@ version = "51.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0acf0ad6b6924c6b1aa7d213b181e012e2d3ec0a64ff5b10ee6282ab0f8532ac"
|
checksum = "0acf0ad6b6924c6b1aa7d213b181e012e2d3ec0a64ff5b10ee6282ab0f8532ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow",
|
"arrow",
|
||||||
"arrow-ord",
|
"arrow-ord",
|
||||||
"arrow-schema",
|
"arrow-schema",
|
||||||
@@ -3955,9 +3944,6 @@ name = "hashbrown"
|
|||||||
version = "0.12.3"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
dependencies = [
|
|
||||||
"ahash 0.7.8",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
@@ -3965,7 +3951,7 @@ version = "0.14.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4475,7 +4461,7 @@ version = "0.11.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88"
|
checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"indexmap 2.12.1",
|
"indexmap 2.12.1",
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
"itoa",
|
"itoa",
|
||||||
@@ -4493,7 +4479,7 @@ version = "0.12.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d35223c50fdd26419a4ccea2c73be68bd2b29a3d7d6123ffe101c17f4c20a52a"
|
checksum = "d35223c50fdd26419a4ccea2c73be68bd2b29a3d7d6123ffe101c17f4c20a52a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"clap",
|
"clap",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
@@ -4907,15 +4893,6 @@ dependencies = [
|
|||||||
"value-bag",
|
"value-bag",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lru"
|
|
||||||
version = "0.7.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
|
|
||||||
dependencies = [
|
|
||||||
"hashbrown 0.12.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lru"
|
name = "lru"
|
||||||
version = "0.12.5"
|
version = "0.12.5"
|
||||||
@@ -5070,7 +5047,7 @@ version = "0.24.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8"
|
checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5732,7 +5709,7 @@ version = "57.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "be3e4f6d320dd92bfa7d612e265d7d08bba0a240bab86af3425e1d255a511d89"
|
checksum = "be3e4f6d320dd92bfa7d612e265d7d08bba0a240bab86af3425e1d255a511d89"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash",
|
||||||
"arrow-array",
|
"arrow-array",
|
||||||
"arrow-buffer",
|
"arrow-buffer",
|
||||||
"arrow-cast",
|
"arrow-cast",
|
||||||
@@ -6073,6 +6050,12 @@ dependencies = [
|
|||||||
"plotters-backend",
|
"plotters-backend",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pollster"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "poly1305"
|
name = "poly1305"
|
||||||
version = "0.9.0-rc.3"
|
version = "0.9.0-rc.3"
|
||||||
@@ -7330,6 +7313,7 @@ dependencies = [
|
|||||||
"base64-simd",
|
"base64-simd",
|
||||||
"futures",
|
"futures",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
|
"pollster",
|
||||||
"rand 0.10.0-rc.5",
|
"rand 0.10.0-rc.5",
|
||||||
"rustfs-crypto",
|
"rustfs-crypto",
|
||||||
"rustfs-ecstore",
|
"rustfs-ecstore",
|
||||||
@@ -7483,11 +7467,14 @@ dependencies = [
|
|||||||
name = "rustfs-policy"
|
name = "rustfs-policy"
|
||||||
version = "0.0.5"
|
version = "0.0.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
"base64-simd",
|
"base64-simd",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"futures",
|
||||||
"ipnetwork",
|
"ipnetwork",
|
||||||
"jsonwebtoken",
|
"jsonwebtoken",
|
||||||
"lru 0.7.8",
|
"moka",
|
||||||
|
"pollster",
|
||||||
"rand 0.10.0-rc.5",
|
"rand 0.10.0-rc.5",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
|||||||
+1
-1
@@ -103,6 +103,7 @@ axum-server = { version = "0.8.0", features = ["tls-rustls-no-provider"], defaul
|
|||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
futures-core = "0.3.31"
|
futures-core = "0.3.31"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
|
pollster = "0.4.0"
|
||||||
hyper = { version = "1.8.1", features = ["http2", "http1", "server"] }
|
hyper = { version = "1.8.1", features = ["http2", "http1", "server"] }
|
||||||
hyper-rustls = { version = "0.27.7", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "http2", "ring", "webpki-roots"] }
|
hyper-rustls = { version = "0.27.7", default-features = false, features = ["native-tokio", "http1", "tls12", "logging", "http2", "ring", "webpki-roots"] }
|
||||||
hyper-util = { version = "0.1.19", features = ["tokio", "server-auto", "server-graceful"] }
|
hyper-util = { version = "0.1.19", features = ["tokio", "server-auto", "server-graceful"] }
|
||||||
@@ -251,7 +252,6 @@ walkdir = "2.5.0"
|
|||||||
wildmatch = { version = "2.6.1", features = ["serde"] }
|
wildmatch = { version = "2.6.1", features = ["serde"] }
|
||||||
winapi = { version = "0.3.9" }
|
winapi = { version = "0.3.9" }
|
||||||
xxhash-rust = { version = "0.8.15", features = ["xxh64", "xxh3"] }
|
xxhash-rust = { version = "0.8.15", features = ["xxh64", "xxh3"] }
|
||||||
lru = "0.7.1"
|
|
||||||
zip = "6.0.0"
|
zip = "6.0.0"
|
||||||
zstd = "0.13.3"
|
zstd = "0.13.3"
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ pub struct PolicySys {}
|
|||||||
impl PolicySys {
|
impl PolicySys {
|
||||||
pub async fn is_allowed(args: &BucketPolicyArgs<'_>) -> bool {
|
pub async fn is_allowed(args: &BucketPolicyArgs<'_>) -> bool {
|
||||||
match Self::get(args.bucket).await {
|
match Self::get(args.bucket).await {
|
||||||
Ok(cfg) => return cfg.is_allowed(args),
|
Ok(cfg) => return cfg.is_allowed(args).await,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err != StorageError::ConfigNotFound {
|
if err != StorageError::ConfigNotFound {
|
||||||
info!("config get err {:?}", err);
|
info!("config get err {:?}", err);
|
||||||
|
|||||||
@@ -47,5 +47,7 @@ tracing.workspace = true
|
|||||||
rustfs-madmin.workspace = true
|
rustfs-madmin.workspace = true
|
||||||
rustfs-utils = { workspace = true, features = ["path"] }
|
rustfs-utils = { workspace = true, features = ["path"] }
|
||||||
tokio-util.workspace = true
|
tokio-util.workspace = true
|
||||||
|
pollster.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
pollster.workspace = true
|
||||||
|
|||||||
+57
-15
@@ -23,6 +23,7 @@ use crate::{
|
|||||||
UpdateServiceAccountOpts,
|
UpdateServiceAccountOpts,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use futures::future::join_all;
|
||||||
use rustfs_ecstore::global::get_global_action_cred;
|
use rustfs_ecstore::global::get_global_action_cred;
|
||||||
use rustfs_madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc};
|
use rustfs_madmin::{AccountStatus, AddOrUpdateUserReq, GroupDesc};
|
||||||
use rustfs_policy::{
|
use rustfs_policy::{
|
||||||
@@ -402,13 +403,25 @@ where
|
|||||||
|
|
||||||
self.cache.policy_docs.store(Arc::new(cache));
|
self.cache.policy_docs.store(Arc::new(cache));
|
||||||
|
|
||||||
let ret = m
|
let items: Vec<_> = m.into_iter().map(|(k, v)| (k, v.policy.clone())).collect();
|
||||||
|
|
||||||
|
let futures: Vec<_> = items.iter().map(|(_, policy)| policy.match_resource(bucket_name)).collect();
|
||||||
|
|
||||||
|
let results = join_all(futures).await;
|
||||||
|
|
||||||
|
let filtered = items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|(_, v)| bucket_name.is_empty() || v.policy.match_resource(bucket_name))
|
.zip(results)
|
||||||
.map(|(k, v)| (k, v.policy))
|
.filter_map(|((k, policy), matches)| {
|
||||||
|
if bucket_name.is_empty() || matches {
|
||||||
|
Some((k, policy))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(ret)
|
Ok(filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn merge_policies(&self, name: &str) -> (String, Policy) {
|
pub async fn merge_policies(&self, name: &str) -> (String, Policy) {
|
||||||
@@ -456,22 +469,51 @@ where
|
|||||||
|
|
||||||
self.cache.policy_docs.store(Arc::new(cache));
|
self.cache.policy_docs.store(Arc::new(cache));
|
||||||
|
|
||||||
let ret = m
|
let items: Vec<_> = m.into_iter().map(|(k, v)| (k, v.clone())).collect();
|
||||||
.into_iter()
|
|
||||||
.filter(|(_, v)| bucket_name.is_empty() || v.policy.match_resource(bucket_name))
|
let futures: Vec<_> = items
|
||||||
|
.iter()
|
||||||
|
.map(|(_, policy_doc)| policy_doc.policy.match_resource(bucket_name))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(ret)
|
let results = join_all(futures).await;
|
||||||
|
|
||||||
|
let filtered = items
|
||||||
|
.into_iter()
|
||||||
|
.zip(results)
|
||||||
|
.filter_map(|((k, policy_doc), matches)| {
|
||||||
|
if bucket_name.is_empty() || matches {
|
||||||
|
Some((k, policy_doc))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(filtered)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_policy_docs_internal(&self, bucket_name: &str) -> Result<HashMap<String, PolicyDoc>> {
|
pub async fn list_policy_docs_internal(&self, bucket_name: &str) -> Result<HashMap<String, PolicyDoc>> {
|
||||||
let ret = self
|
let cache = self.cache.policy_docs.load();
|
||||||
.cache
|
let items: Vec<_> = cache.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
|
||||||
.policy_docs
|
|
||||||
.load()
|
let futures: Vec<_> = items
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, v)| bucket_name.is_empty() || v.policy.match_resource(bucket_name))
|
.map(|(_, policy_doc)| policy_doc.policy.match_resource(bucket_name))
|
||||||
.map(|(k, v)| (k.clone(), v.clone()))
|
.collect();
|
||||||
|
|
||||||
|
let results = join_all(futures).await;
|
||||||
|
|
||||||
|
let ret = items
|
||||||
|
.into_iter()
|
||||||
|
.zip(results)
|
||||||
|
.filter_map(|((k, policy_doc), matches)| {
|
||||||
|
if bucket_name.is_empty() || matches {
|
||||||
|
Some((k, policy_doc))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
@@ -1753,7 +1795,7 @@ fn filter_policies(cache: &Cache, policy_name: &str, bucket_name: &str) -> (Stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(p) = cache.policy_docs.load().get(&policy) {
|
if let Some(p) = cache.policy_docs.load().get(&policy) {
|
||||||
if bucket_name.is_empty() || p.policy.match_resource(bucket_name) {
|
if bucket_name.is_empty() || pollster::block_on(p.policy.match_resource(bucket_name)) {
|
||||||
policies.push(policy);
|
policies.push(policy);
|
||||||
to_merge.push(p.policy.clone());
|
to_merge.push(p.policy.clone());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -755,10 +755,10 @@ impl<T: Store> IamSys<T> {
|
|||||||
|
|
||||||
let (has_session_policy, is_allowed_sp) = is_allowed_by_session_policy(args);
|
let (has_session_policy, is_allowed_sp) = is_allowed_by_session_policy(args);
|
||||||
if has_session_policy {
|
if has_session_policy {
|
||||||
return is_allowed_sp && (is_owner || combined_policy.is_allowed(args));
|
return is_allowed_sp && (is_owner || combined_policy.is_allowed(args).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
is_owner || combined_policy.is_allowed(args)
|
is_owner || combined_policy.is_allowed(args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn is_allowed_service_account(&self, args: &Args<'_>, parent_user: &str) -> bool {
|
pub async fn is_allowed_service_account(&self, args: &Args<'_>, parent_user: &str) -> bool {
|
||||||
@@ -814,15 +814,15 @@ impl<T: Store> IamSys<T> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if sa_str == INHERITED_POLICY_TYPE {
|
if sa_str == INHERITED_POLICY_TYPE {
|
||||||
return is_owner || combined_policy.is_allowed(&parent_args);
|
return is_owner || combined_policy.is_allowed(&parent_args).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (has_session_policy, is_allowed_sp) = is_allowed_by_session_policy_for_service_account(args);
|
let (has_session_policy, is_allowed_sp) = is_allowed_by_session_policy_for_service_account(args);
|
||||||
if has_session_policy {
|
if has_session_policy {
|
||||||
return is_allowed_sp && (is_owner || combined_policy.is_allowed(&parent_args));
|
return is_allowed_sp && (is_owner || combined_policy.is_allowed(&parent_args).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
is_owner || combined_policy.is_allowed(&parent_args)
|
is_owner || combined_policy.is_allowed(&parent_args).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_combined_policy(&self, policies: &[String]) -> Policy {
|
pub async fn get_combined_policy(&self, policies: &[String]) -> Policy {
|
||||||
@@ -857,7 +857,7 @@ impl<T: Store> IamSys<T> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.get_combined_policy(&policies).await.is_allowed(args)
|
self.get_combined_policy(&policies).await.is_allowed(args).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -883,7 +883,7 @@ fn is_allowed_by_session_policy(args: &Args<'_>) -> (bool, bool) {
|
|||||||
let mut session_policy_args = args.clone();
|
let mut session_policy_args = args.clone();
|
||||||
session_policy_args.is_owner = false;
|
session_policy_args.is_owner = false;
|
||||||
|
|
||||||
(has_session_policy, sub_policy.is_allowed(&session_policy_args))
|
(has_session_policy, pollster::block_on(sub_policy.is_allowed(&session_policy_args)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_allowed_by_session_policy_for_service_account(args: &Args<'_>) -> (bool, bool) {
|
fn is_allowed_by_session_policy_for_service_account(args: &Args<'_>) -> (bool, bool) {
|
||||||
@@ -909,7 +909,7 @@ fn is_allowed_by_session_policy_for_service_account(args: &Args<'_>) -> (bool, b
|
|||||||
let mut session_policy_args = args.clone();
|
let mut session_policy_args = args.clone();
|
||||||
session_policy_args.is_owner = false;
|
session_policy_args.is_owner = false;
|
||||||
|
|
||||||
(has_session_policy, sub_policy.is_allowed(&session_policy_args))
|
(has_session_policy, pollster::block_on(sub_policy.is_allowed(&session_policy_args)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|||||||
@@ -45,8 +45,12 @@ regex = { workspace = true }
|
|||||||
reqwest.workspace = true
|
reqwest.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
lru.workspace = true
|
moka.workspace = true
|
||||||
|
async-trait.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
pollster.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
pollster.workspace = true
|
||||||
test-case.workspace = true
|
test-case.workspace = true
|
||||||
temp-env = { workspace = true }
|
temp-env = { workspace = true }
|
||||||
|
|||||||
@@ -38,29 +38,29 @@ pub struct Functions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Functions {
|
impl Functions {
|
||||||
pub fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool {
|
pub async fn evaluate(&self, values: &HashMap<String, Vec<String>>) -> bool {
|
||||||
self.evaluate_with_resolver(values, None)
|
self.evaluate_with_resolver(values, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate_with_resolver(
|
pub async fn evaluate_with_resolver(
|
||||||
&self,
|
&self,
|
||||||
values: &HashMap<String, Vec<String>>,
|
values: &HashMap<String, Vec<String>>,
|
||||||
resolver: Option<&dyn PolicyVariableResolver>,
|
resolver: Option<&dyn PolicyVariableResolver>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
for c in self.for_any_value.iter() {
|
for c in self.for_any_value.iter() {
|
||||||
if !c.evaluate_with_resolver(false, values, resolver) {
|
if !c.evaluate_with_resolver(false, values, resolver).await {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in self.for_all_values.iter() {
|
for c in self.for_all_values.iter() {
|
||||||
if !c.evaluate_with_resolver(true, values, resolver) {
|
if !c.evaluate_with_resolver(true, values, resolver).await {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in self.for_normal.iter() {
|
for c in self.for_normal.iter() {
|
||||||
if !c.evaluate_with_resolver(false, values, resolver) {
|
if !c.evaluate_with_resolver(false, values, resolver).await {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ impl Condition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate_with_resolver(
|
pub async fn evaluate_with_resolver(
|
||||||
&self,
|
&self,
|
||||||
for_all: bool,
|
for_all: bool,
|
||||||
values: &HashMap<String, Vec<String>>,
|
values: &HashMap<String, Vec<String>>,
|
||||||
@@ -116,12 +116,12 @@ impl Condition {
|
|||||||
use Condition::*;
|
use Condition::*;
|
||||||
|
|
||||||
let r = match self {
|
let r = match self {
|
||||||
StringEquals(s) => s.evaluate_with_resolver(for_all, false, false, false, values, resolver),
|
StringEquals(s) => s.evaluate_with_resolver(for_all, false, false, false, values, resolver).await,
|
||||||
StringNotEquals(s) => s.evaluate_with_resolver(for_all, false, false, true, values, resolver),
|
StringNotEquals(s) => s.evaluate_with_resolver(for_all, false, false, true, values, resolver).await,
|
||||||
StringEqualsIgnoreCase(s) => s.evaluate_with_resolver(for_all, true, false, false, values, resolver),
|
StringEqualsIgnoreCase(s) => s.evaluate_with_resolver(for_all, true, false, false, values, resolver).await,
|
||||||
StringNotEqualsIgnoreCase(s) => s.evaluate_with_resolver(for_all, true, false, true, values, resolver),
|
StringNotEqualsIgnoreCase(s) => s.evaluate_with_resolver(for_all, true, false, true, values, resolver).await,
|
||||||
StringLike(s) => s.evaluate_with_resolver(for_all, false, true, false, values, resolver),
|
StringLike(s) => s.evaluate_with_resolver(for_all, false, true, false, values, resolver).await,
|
||||||
StringNotLike(s) => s.evaluate_with_resolver(for_all, false, true, true, values, resolver),
|
StringNotLike(s) => s.evaluate_with_resolver(for_all, false, true, true, values, resolver).await,
|
||||||
BinaryEquals(s) => s.evaluate(values),
|
BinaryEquals(s) => s.evaluate(values),
|
||||||
IpAddress(s) => s.evaluate(values),
|
IpAddress(s) => s.evaluate(values),
|
||||||
NotIpAddress(s) => s.evaluate(values),
|
NotIpAddress(s) => s.evaluate(values),
|
||||||
|
|||||||
@@ -21,16 +21,17 @@ use std::{borrow::Cow, collections::HashMap};
|
|||||||
|
|
||||||
use crate::policy::function::func::FuncKeyValue;
|
use crate::policy::function::func::FuncKeyValue;
|
||||||
use crate::policy::utils::wildcard;
|
use crate::policy::utils::wildcard;
|
||||||
|
use futures::future;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, de, ser::SerializeSeq};
|
use serde::{Deserialize, Deserializer, Serialize, de, ser::SerializeSeq};
|
||||||
|
|
||||||
use super::{func::InnerFunc, key_name::KeyName};
|
use super::{func::InnerFunc, key_name::KeyName};
|
||||||
use crate::policy::variables::{PolicyVariableResolver, resolve_aws_variables};
|
use crate::policy::variables::PolicyVariableResolver;
|
||||||
|
|
||||||
pub type StringFunc = InnerFunc<StringFuncValue>;
|
pub type StringFunc = InnerFunc<StringFuncValue>;
|
||||||
|
|
||||||
impl StringFunc {
|
impl StringFunc {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) fn evaluate_with_resolver(
|
pub(crate) async fn evaluate_with_resolver(
|
||||||
&self,
|
&self,
|
||||||
for_all: bool,
|
for_all: bool,
|
||||||
ignore_case: bool,
|
ignore_case: bool,
|
||||||
@@ -41,9 +42,9 @@ impl StringFunc {
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
for inner in self.0.iter() {
|
for inner in self.0.iter() {
|
||||||
let result = if like {
|
let result = if like {
|
||||||
inner.eval_like(for_all, values, resolver) ^ negate
|
inner.eval_like(for_all, values, resolver).await ^ negate
|
||||||
} else {
|
} else {
|
||||||
inner.eval(for_all, ignore_case, values, resolver) ^ negate
|
inner.eval(for_all, ignore_case, values, resolver).await ^ negate
|
||||||
};
|
};
|
||||||
|
|
||||||
if !result {
|
if !result {
|
||||||
@@ -56,7 +57,7 @@ impl StringFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FuncKeyValue<StringFuncValue> {
|
impl FuncKeyValue<StringFuncValue> {
|
||||||
fn eval(
|
async fn eval(
|
||||||
&self,
|
&self,
|
||||||
for_all: bool,
|
for_all: bool,
|
||||||
ignore_case: bool,
|
ignore_case: bool,
|
||||||
@@ -79,17 +80,18 @@ impl FuncKeyValue<StringFuncValue> {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let fvalues = self
|
let resolved_values: Vec<Vec<String>> = futures::future::join_all(self.values.0.iter().map(|c| async {
|
||||||
.values
|
if let Some(res) = resolver {
|
||||||
.0
|
super::super::variables::resolve_aws_variables(c, res).await
|
||||||
.iter()
|
} else {
|
||||||
.flat_map(|c| {
|
vec![c.to_string()]
|
||||||
if let Some(res) = resolver {
|
}
|
||||||
resolve_aws_variables(c, res)
|
}))
|
||||||
} else {
|
.await;
|
||||||
vec![c.to_string()]
|
|
||||||
}
|
let fvalues = resolved_values
|
||||||
})
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
.map(|resolved_c| {
|
.map(|resolved_c| {
|
||||||
let mut c = Cow::from(resolved_c);
|
let mut c = Cow::from(resolved_c);
|
||||||
for key in KeyName::COMMON_KEYS {
|
for key in KeyName::COMMON_KEYS {
|
||||||
@@ -113,7 +115,7 @@ impl FuncKeyValue<StringFuncValue> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_like(
|
async fn eval_like(
|
||||||
&self,
|
&self,
|
||||||
for_all: bool,
|
for_all: bool,
|
||||||
values: &HashMap<String, Vec<String>>,
|
values: &HashMap<String, Vec<String>>,
|
||||||
@@ -121,17 +123,22 @@ impl FuncKeyValue<StringFuncValue> {
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
if let Some(rvalues) = values.get(self.key.name().as_str()) {
|
if let Some(rvalues) = values.get(self.key.name().as_str()) {
|
||||||
for v in rvalues.iter() {
|
for v in rvalues.iter() {
|
||||||
let matched = self
|
let resolved_futures: Vec<_> = self
|
||||||
.values
|
.values
|
||||||
.0
|
.0
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|c| {
|
.map(|c| async {
|
||||||
if let Some(res) = resolver {
|
if let Some(res) = resolver {
|
||||||
resolve_aws_variables(c, res)
|
super::super::variables::resolve_aws_variables(c, res).await
|
||||||
} else {
|
} else {
|
||||||
vec![c.to_string()]
|
vec![c.to_string()]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.collect();
|
||||||
|
let resolved_values = future::join_all(resolved_futures).await;
|
||||||
|
let matched = resolved_values
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
.map(|resolved_c| {
|
.map(|resolved_c| {
|
||||||
let mut c = Cow::from(resolved_c);
|
let mut c = Cow::from(resolved_c);
|
||||||
for key in KeyName::COMMON_KEYS {
|
for key in KeyName::COMMON_KEYS {
|
||||||
@@ -242,6 +249,7 @@ mod tests {
|
|||||||
key_name::AwsKeyName::*,
|
key_name::AwsKeyName::*,
|
||||||
key_name::KeyName::{self, *},
|
key_name::KeyName::{self, *},
|
||||||
};
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::policy::function::key_name::S3KeyName::S3LocationConstraint;
|
use crate::policy::function::key_name::S3KeyName::S3LocationConstraint;
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
@@ -303,17 +311,13 @@ mod tests {
|
|||||||
negate: bool,
|
negate: bool,
|
||||||
values: Vec<(&str, Vec<&str>)>,
|
values: Vec<(&str, Vec<&str>)>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let result = s.eval(
|
let map: HashMap<String, Vec<String>> = values
|
||||||
for_all,
|
.into_iter()
|
||||||
ignore_case,
|
.map(|(k, v)| (k.to_owned(), v.into_iter().map(ToOwned::to_owned).collect::<Vec<String>>()))
|
||||||
&values
|
.collect();
|
||||||
.into_iter()
|
let result = s.eval(for_all, ignore_case, &map, None);
|
||||||
.map(|(k, v)| (k.to_owned(), v.into_iter().map(ToOwned::to_owned).collect::<Vec<String>>()))
|
|
||||||
.collect(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
result ^ negate
|
pollster::block_on(result) ^ negate
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => true ; "1")]
|
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => true ; "1")]
|
||||||
@@ -409,16 +413,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_eval_like(s: FuncKeyValue<StringFuncValue>, for_all: bool, negate: bool, values: Vec<(&str, Vec<&str>)>) -> bool {
|
fn test_eval_like(s: FuncKeyValue<StringFuncValue>, for_all: bool, negate: bool, values: Vec<(&str, Vec<&str>)>) -> bool {
|
||||||
let result = s.eval_like(
|
let map: HashMap<String, Vec<String>> = values
|
||||||
for_all,
|
.into_iter()
|
||||||
&values
|
.map(|(k, v)| (k.to_owned(), v.into_iter().map(ToOwned::to_owned).collect::<Vec<String>>()))
|
||||||
.into_iter()
|
.collect();
|
||||||
.map(|(k, v)| (k.to_owned(), v.into_iter().map(ToOwned::to_owned).collect::<Vec<String>>()))
|
let result = s.eval_like(for_all, &map, None);
|
||||||
.collect(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
result ^ negate
|
pollster::block_on(result) ^ negate
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => true ; "1")]
|
#[test_case(new_fkv("s3:x-amz-copy-source", vec!["mybucket/myobject"]), false, vec![("x-amz-copy-source", vec!["mybucket/myobject"])] => true ; "1")]
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ pub struct Policy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Policy {
|
impl Policy {
|
||||||
pub fn is_allowed(&self, args: &Args) -> bool {
|
pub async fn is_allowed(&self, args: &Args<'_>) -> bool {
|
||||||
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Deny)) {
|
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Deny)) {
|
||||||
if !statement.is_allowed(args) {
|
if !statement.is_allowed(args).await {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ impl Policy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Allow)) {
|
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Allow)) {
|
||||||
if statement.is_allowed(args) {
|
if statement.is_allowed(args).await {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,9 +82,9 @@ impl Policy {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn match_resource(&self, resource: &str) -> bool {
|
pub async fn match_resource(&self, resource: &str) -> bool {
|
||||||
for statement in self.statements.iter() {
|
for statement in self.statements.iter() {
|
||||||
if statement.resources.match_resource(resource) {
|
if statement.resources.match_resource(resource).await {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,9 +188,9 @@ pub struct BucketPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BucketPolicy {
|
impl BucketPolicy {
|
||||||
pub fn is_allowed(&self, args: &BucketPolicyArgs) -> bool {
|
pub async fn is_allowed(&self, args: &BucketPolicyArgs<'_>) -> bool {
|
||||||
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Deny)) {
|
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Deny)) {
|
||||||
if !statement.is_allowed(args) {
|
if !statement.is_allowed(args).await {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@ impl BucketPolicy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Allow)) {
|
for statement in self.statements.iter().filter(|s| matches!(s.effect, Effect::Allow)) {
|
||||||
if statement.is_allowed(args) {
|
if statement.is_allowed(args).await {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -577,8 +577,8 @@ mod test {
|
|||||||
deny_only: false,
|
deny_only: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(policy.is_allowed(&args1));
|
assert!(pollster::block_on(policy.is_allowed(&args1)));
|
||||||
assert!(!policy.is_allowed(&args2));
|
assert!(!pollster::block_on(policy.is_allowed(&args2)));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -631,8 +631,8 @@ mod test {
|
|||||||
deny_only: false,
|
deny_only: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(policy.is_allowed(&args1));
|
assert!(pollster::block_on(policy.is_allowed(&args1)));
|
||||||
assert!(!policy.is_allowed(&args2));
|
assert!(!pollster::block_on(policy.is_allowed(&args2)));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -686,8 +686,8 @@ mod test {
|
|||||||
deny_only: false,
|
deny_only: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(policy.is_allowed(&args1));
|
assert!(pollster::block_on(policy.is_allowed(&args1)));
|
||||||
assert!(!policy.is_allowed(&args2));
|
assert!(!pollster::block_on(policy.is_allowed(&args2)));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -741,8 +741,8 @@ mod test {
|
|||||||
deny_only: false,
|
deny_only: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert!(policy.is_allowed(&args1));
|
assert!(pollster::block_on(policy.is_allowed(&args1)));
|
||||||
assert!(!policy.is_allowed(&args2));
|
assert!(!pollster::block_on(policy.is_allowed(&args2)));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -798,7 +798,7 @@ mod test {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Either user1 or user2 should be allowed
|
// Either user1 or user2 should be allowed
|
||||||
assert!(policy.is_allowed(&args1) || policy.is_allowed(&args2));
|
assert!(pollster::block_on(policy.is_allowed(&args1)) || pollster::block_on(policy.is_allowed(&args2)));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,25 +24,25 @@ use super::{
|
|||||||
Error as IamError, Validator,
|
Error as IamError, Validator,
|
||||||
function::key_name::KeyName,
|
function::key_name::KeyName,
|
||||||
utils::{path, wildcard},
|
utils::{path, wildcard},
|
||||||
variables::{PolicyVariableResolver, resolve_aws_variables},
|
variables::PolicyVariableResolver,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
|
||||||
pub struct ResourceSet(pub HashSet<Resource>);
|
pub struct ResourceSet(pub HashSet<Resource>);
|
||||||
|
|
||||||
impl ResourceSet {
|
impl ResourceSet {
|
||||||
pub fn is_match(&self, resource: &str, conditions: &HashMap<String, Vec<String>>) -> bool {
|
pub async fn is_match(&self, resource: &str, conditions: &HashMap<String, Vec<String>>) -> bool {
|
||||||
self.is_match_with_resolver(resource, conditions, None)
|
self.is_match_with_resolver(resource, conditions, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_match_with_resolver(
|
pub async fn is_match_with_resolver(
|
||||||
&self,
|
&self,
|
||||||
resource: &str,
|
resource: &str,
|
||||||
conditions: &HashMap<String, Vec<String>>,
|
conditions: &HashMap<String, Vec<String>>,
|
||||||
resolver: Option<&dyn PolicyVariableResolver>,
|
resolver: Option<&dyn PolicyVariableResolver>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
for re in self.0.iter() {
|
for re in self.0.iter() {
|
||||||
if re.is_match_with_resolver(resource, conditions, resolver) {
|
if re.is_match_with_resolver(resource, conditions, resolver).await {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,9 +50,9 @@ impl ResourceSet {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn match_resource(&self, resource: &str) -> bool {
|
pub async fn match_resource(&self, resource: &str) -> bool {
|
||||||
for re in self.0.iter() {
|
for re in self.0.iter() {
|
||||||
if re.match_resource(resource) {
|
if re.match_resource(resource).await {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,11 +95,11 @@ pub enum Resource {
|
|||||||
impl Resource {
|
impl Resource {
|
||||||
pub const S3_PREFIX: &'static str = "arn:aws:s3:::";
|
pub const S3_PREFIX: &'static str = "arn:aws:s3:::";
|
||||||
|
|
||||||
pub fn is_match(&self, resource: &str, conditions: &HashMap<String, Vec<String>>) -> bool {
|
pub async fn is_match(&self, resource: &str, conditions: &HashMap<String, Vec<String>>) -> bool {
|
||||||
self.is_match_with_resolver(resource, conditions, None)
|
self.is_match_with_resolver(resource, conditions, None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_match_with_resolver(
|
pub async fn is_match_with_resolver(
|
||||||
&self,
|
&self,
|
||||||
resource: &str,
|
resource: &str,
|
||||||
conditions: &HashMap<String, Vec<String>>,
|
conditions: &HashMap<String, Vec<String>>,
|
||||||
@@ -111,7 +111,7 @@ impl Resource {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let patterns = if let Some(res) = resolver {
|
let patterns = if let Some(res) = resolver {
|
||||||
resolve_aws_variables(&pattern, res)
|
super::variables::resolve_aws_variables(&pattern, res).await
|
||||||
} else {
|
} else {
|
||||||
vec![pattern.clone()]
|
vec![pattern.clone()]
|
||||||
};
|
};
|
||||||
@@ -143,8 +143,8 @@ impl Resource {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn match_resource(&self, resource: &str) -> bool {
|
pub async fn match_resource(&self, resource: &str) -> bool {
|
||||||
self.is_match(resource, &HashMap::new())
|
self.is_match(resource, &HashMap::new()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,6 +232,7 @@ mod tests {
|
|||||||
#[test_case("arn:aws:s3:::mybucket","mybucket/myobject" => false; "15")]
|
#[test_case("arn:aws:s3:::mybucket","mybucket/myobject" => false; "15")]
|
||||||
fn test_resource_is_match(resource: &str, object: &str) -> bool {
|
fn test_resource_is_match(resource: &str, object: &str) -> bool {
|
||||||
let resource: Resource = resource.try_into().unwrap();
|
let resource: Resource = resource.try_into().unwrap();
|
||||||
resource.is_match(object, &HashMap::new())
|
|
||||||
|
pollster::block_on(resource.is_match(object, &HashMap::new()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ impl Statement {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_allowed(&self, args: &Args) -> bool {
|
pub async fn is_allowed(&self, args: &Args<'_>) -> bool {
|
||||||
let mut context = VariableContext::new();
|
let mut context = VariableContext::new();
|
||||||
context.claims = Some(args.claims.clone());
|
context.claims = Some(args.claims.clone());
|
||||||
context.conditions = args.conditions.clone();
|
context.conditions = args.conditions.clone();
|
||||||
@@ -104,19 +104,20 @@ impl Statement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.is_kms() && (resource == "/" || self.resources.is_empty()) {
|
if self.is_kms() && (resource == "/" || self.resources.is_empty()) {
|
||||||
break 'c self.conditions.evaluate_with_resolver(args.conditions, Some(&resolver));
|
break 'c self.conditions.evaluate_with_resolver(args.conditions, Some(&resolver)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self
|
if !self
|
||||||
.resources
|
.resources
|
||||||
.is_match_with_resolver(&resource, args.conditions, Some(&resolver))
|
.is_match_with_resolver(&resource, args.conditions, Some(&resolver))
|
||||||
|
.await
|
||||||
&& !self.is_admin()
|
&& !self.is_admin()
|
||||||
&& !self.is_sts()
|
&& !self.is_sts()
|
||||||
{
|
{
|
||||||
break 'c false;
|
break 'c false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.conditions.evaluate_with_resolver(args.conditions, Some(&resolver))
|
self.conditions.evaluate_with_resolver(args.conditions, Some(&resolver)).await
|
||||||
};
|
};
|
||||||
|
|
||||||
self.effect.is_allowed(check)
|
self.effect.is_allowed(check)
|
||||||
@@ -178,7 +179,7 @@ pub struct BPStatement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BPStatement {
|
impl BPStatement {
|
||||||
pub fn is_allowed(&self, args: &BucketPolicyArgs) -> bool {
|
pub async fn is_allowed(&self, args: &BucketPolicyArgs<'_>) -> bool {
|
||||||
let check = 'c: {
|
let check = 'c: {
|
||||||
if !self.principal.is_match(args.account) {
|
if !self.principal.is_match(args.account) {
|
||||||
break 'c false;
|
break 'c false;
|
||||||
@@ -199,15 +200,15 @@ impl BPStatement {
|
|||||||
resource.push('/');
|
resource.push('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.resources.is_empty() && !self.resources.is_match(&resource, args.conditions) {
|
if !self.resources.is_empty() && !self.resources.is_match(&resource, args.conditions).await {
|
||||||
break 'c false;
|
break 'c false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.not_resources.is_empty() && self.not_resources.is_match(&resource, args.conditions) {
|
if !self.not_resources.is_empty() && self.not_resources.is_match(&resource, args.conditions).await {
|
||||||
break 'c false;
|
break 'c false;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.conditions.evaluate(args.conditions)
|
self.conditions.evaluate(args.conditions).await
|
||||||
};
|
};
|
||||||
|
|
||||||
self.effect.is_allowed(check)
|
self.effect.is_allowed(check)
|
||||||
|
|||||||
@@ -12,16 +12,16 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use lru::LruCache;
|
use async_trait::async_trait;
|
||||||
|
use moka::future::Cache;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::num::NonZeroUsize;
|
use std::future::Future;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Duration;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
/// Context information for variable resolution
|
/// Context information for variable resolution
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct VariableContext {
|
pub struct VariableContext {
|
||||||
pub is_https: bool,
|
pub is_https: bool,
|
||||||
pub source_ip: Option<String>,
|
pub source_ip: Option<String>,
|
||||||
@@ -35,109 +35,75 @@ pub struct VariableContext {
|
|||||||
|
|
||||||
impl VariableContext {
|
impl VariableContext {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self::default()
|
||||||
is_https: false,
|
|
||||||
source_ip: None,
|
|
||||||
account_id: None,
|
|
||||||
region: None,
|
|
||||||
username: None,
|
|
||||||
claims: None,
|
|
||||||
conditions: HashMap::new(),
|
|
||||||
custom_variables: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VariableContext {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Variable resolution cache
|
|
||||||
struct CachedVariable {
|
|
||||||
value: String,
|
|
||||||
timestamp: Instant,
|
|
||||||
is_dynamic: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VariableResolverCache {
|
pub struct VariableResolverCache {
|
||||||
/// LRU cache storing resolved results
|
/// Moka cache storing resolved results
|
||||||
cache: LruCache<String, CachedVariable>,
|
cache: Cache<String, String>,
|
||||||
/// Cache expiration time
|
|
||||||
ttl: Duration,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VariableResolverCache {
|
impl VariableResolverCache {
|
||||||
pub fn new(capacity: usize, ttl_seconds: u64) -> Self {
|
pub fn new(capacity: usize, ttl_seconds: u64) -> Self {
|
||||||
Self {
|
let cache = Cache::builder()
|
||||||
cache: LruCache::new(usize::from(NonZeroUsize::new(capacity).unwrap_or(NonZeroUsize::new(100).unwrap()))),
|
.max_capacity(capacity as u64)
|
||||||
ttl: Duration::from_secs(ttl_seconds),
|
.time_to_live(Duration::from_secs(ttl_seconds))
|
||||||
}
|
.build();
|
||||||
|
|
||||||
|
Self { cache }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&mut self, key: &str) -> Option<String> {
|
pub async fn get(&self, key: &str) -> Option<String> {
|
||||||
if let Some(cached) = self.cache.get(key) {
|
self.cache.get(key).await
|
||||||
// Check if expired
|
|
||||||
if !cached.is_dynamic && cached.timestamp.elapsed() < self.ttl {
|
|
||||||
return Some(cached.value.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put(&mut self, key: String, value: String, is_dynamic: bool) {
|
pub async fn put(&self, key: String, value: String) {
|
||||||
let cached = CachedVariable {
|
self.cache.insert(key, value).await;
|
||||||
value,
|
|
||||||
timestamp: Instant::now(),
|
|
||||||
is_dynamic,
|
|
||||||
};
|
|
||||||
self.cache.put(key, cached);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub async fn clear(&self) {
|
||||||
self.cache.clear();
|
self.cache.invalidate_all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cached dynamic AWS variable resolver
|
/// Cached dynamic AWS variable resolver
|
||||||
pub struct CachedAwsVariableResolver {
|
pub struct CachedAwsVariableResolver {
|
||||||
inner: VariableResolver,
|
inner: VariableResolver,
|
||||||
cache: RefCell<VariableResolverCache>,
|
cache: VariableResolverCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CachedAwsVariableResolver {
|
impl CachedAwsVariableResolver {
|
||||||
pub fn new(context: VariableContext) -> Self {
|
pub fn new(context: VariableContext) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: VariableResolver::new(context),
|
inner: VariableResolver::new(context),
|
||||||
cache: RefCell::new(VariableResolverCache::new(100, 300)), // 100 entries, 5 minutes expiration
|
cache: VariableResolverCache::new(100, 300), // 100 entries, 5 minutes expiration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_dynamic(&self, variable_name: &str) -> bool {
|
||||||
|
self.inner.is_dynamic(variable_name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl PolicyVariableResolver for CachedAwsVariableResolver {
|
impl PolicyVariableResolver for CachedAwsVariableResolver {
|
||||||
fn resolve(&self, variable_name: &str) -> Option<String> {
|
async fn resolve(&self, variable_name: &str) -> Option<String> {
|
||||||
if self.is_dynamic(variable_name) {
|
if self.is_dynamic(variable_name) {
|
||||||
return self.inner.resolve(variable_name);
|
return self.inner.resolve(variable_name).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(cached) = self.cache.borrow_mut().get(variable_name) {
|
if let Some(cached) = self.cache.get(variable_name).await {
|
||||||
return Some(cached);
|
return Some(cached);
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = self.inner.resolve(variable_name)?;
|
let value = self.inner.resolve(variable_name).await?;
|
||||||
|
self.cache.put(variable_name.to_string(), value.clone()).await;
|
||||||
self.cache.borrow_mut().put(variable_name.to_string(), value.clone(), false);
|
|
||||||
|
|
||||||
Some(value)
|
Some(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_multiple(&self, variable_name: &str) -> Option<Vec<String>> {
|
async fn resolve_multiple(&self, variable_name: &str) -> Option<Vec<String>> {
|
||||||
if self.is_dynamic(variable_name) {
|
self.inner.resolve_multiple(variable_name).await
|
||||||
return self.inner.resolve_multiple(variable_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.inner.resolve_multiple(variable_name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_dynamic(&self, variable_name: &str) -> bool {
|
fn is_dynamic(&self, variable_name: &str) -> bool {
|
||||||
@@ -146,10 +112,11 @@ impl PolicyVariableResolver for CachedAwsVariableResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Policy variable resolver trait
|
/// Policy variable resolver trait
|
||||||
pub trait PolicyVariableResolver {
|
#[async_trait]
|
||||||
fn resolve(&self, variable_name: &str) -> Option<String>;
|
pub trait PolicyVariableResolver: Sync {
|
||||||
fn resolve_multiple(&self, variable_name: &str) -> Option<Vec<String>> {
|
async fn resolve(&self, variable_name: &str) -> Option<String>;
|
||||||
self.resolve(variable_name).map(|s| vec![s])
|
async fn resolve_multiple(&self, variable_name: &str) -> Option<Vec<String>> {
|
||||||
|
self.resolve(variable_name).await.map(|s| vec![s])
|
||||||
}
|
}
|
||||||
fn is_dynamic(&self, variable_name: &str) -> bool;
|
fn is_dynamic(&self, variable_name: &str) -> bool;
|
||||||
}
|
}
|
||||||
@@ -192,18 +159,9 @@ impl VariableResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_userid(&self) -> Option<String> {
|
fn resolve_userid(&self) -> Option<String> {
|
||||||
// Check claims for sub or parent
|
self.get_claim_as_strings("sub")
|
||||||
if let Some(claims) = &self.context.claims {
|
.or_else(|| self.get_claim_as_strings("parent"))
|
||||||
if let Some(sub) = claims.get("sub").and_then(|v| v.as_str()) {
|
.and_then(|mut vec| vec.pop()) // 取第一个值,保持原有逻辑
|
||||||
return Some(sub.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(parent) = claims.get("parent").and_then(|v| v.as_str()) {
|
|
||||||
return Some(parent.to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_principal_type(&self) -> String {
|
fn resolve_principal_type(&self) -> String {
|
||||||
@@ -252,8 +210,9 @@ impl VariableResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
impl PolicyVariableResolver for VariableResolver {
|
impl PolicyVariableResolver for VariableResolver {
|
||||||
fn resolve(&self, variable_name: &str) -> Option<String> {
|
async fn resolve(&self, variable_name: &str) -> Option<String> {
|
||||||
match variable_name {
|
match variable_name {
|
||||||
"aws:username" => self.resolve_username(),
|
"aws:username" => self.resolve_username(),
|
||||||
"aws:userid" => self.resolve_userid(),
|
"aws:userid" => self.resolve_userid(),
|
||||||
@@ -275,22 +234,15 @@ impl PolicyVariableResolver for VariableResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_multiple(&self, variable_name: &str) -> Option<Vec<String>> {
|
async fn resolve_multiple(&self, variable_name: &str) -> Option<Vec<String>> {
|
||||||
match variable_name {
|
match variable_name {
|
||||||
"aws:username" => {
|
"aws:username" => self.resolve_username().map(|s| vec![s]),
|
||||||
// Check context.username
|
|
||||||
if let Some(ref username) = self.context.username {
|
"aws:userid" => self
|
||||||
Some(vec![username.clone()])
|
.get_claim_as_strings("sub")
|
||||||
} else {
|
.or_else(|| self.get_claim_as_strings("parent")),
|
||||||
None
|
|
||||||
}
|
_ => self.resolve(variable_name).await.map(|s| vec![s]),
|
||||||
}
|
|
||||||
"aws:userid" => {
|
|
||||||
// Check claims for sub or parent
|
|
||||||
self.get_claim_as_strings("sub")
|
|
||||||
.or_else(|| self.get_claim_as_strings("parent"))
|
|
||||||
}
|
|
||||||
_ => self.resolve(variable_name).map(|s| vec![s]),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,8 +251,7 @@ impl PolicyVariableResolver for VariableResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dynamically resolve AWS variables
|
pub async fn resolve_aws_variables(pattern: &str, resolver: &dyn PolicyVariableResolver) -> Vec<String> {
|
||||||
pub fn resolve_aws_variables(pattern: &str, resolver: &dyn PolicyVariableResolver) -> Vec<String> {
|
|
||||||
let mut results = vec![pattern.to_string()];
|
let mut results = vec![pattern.to_string()];
|
||||||
|
|
||||||
let mut changed = true;
|
let mut changed = true;
|
||||||
@@ -313,7 +264,7 @@ pub fn resolve_aws_variables(pattern: &str, resolver: &dyn PolicyVariableResolve
|
|||||||
|
|
||||||
let mut new_results = Vec::new();
|
let mut new_results = Vec::new();
|
||||||
for result in &results {
|
for result in &results {
|
||||||
let resolved = resolve_single_pass(result, resolver);
|
let resolved = resolve_single_pass(result, resolver).await;
|
||||||
if resolved.len() > 1 || (resolved.len() == 1 && &resolved[0] != result) {
|
if resolved.len() > 1 || (resolved.len() == 1 && &resolved[0] != result) {
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
@@ -333,8 +284,16 @@ pub fn resolve_aws_variables(pattern: &str, resolver: &dyn PolicyVariableResolve
|
|||||||
results
|
results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Need to box the future to avoid infinite size due to recursion
|
||||||
|
fn resolve_aws_variables_boxed<'a>(
|
||||||
|
pattern: &'a str,
|
||||||
|
resolver: &'a dyn PolicyVariableResolver,
|
||||||
|
) -> std::pin::Pin<Box<dyn Future<Output = Vec<String>> + Send + 'a>> {
|
||||||
|
Box::pin(resolve_aws_variables(pattern, resolver))
|
||||||
|
}
|
||||||
|
|
||||||
/// Single pass resolution of variables in a string
|
/// Single pass resolution of variables in a string
|
||||||
fn resolve_single_pass(pattern: &str, resolver: &dyn PolicyVariableResolver) -> Vec<String> {
|
async fn resolve_single_pass(pattern: &str, resolver: &dyn PolicyVariableResolver) -> Vec<String> {
|
||||||
// Find all ${...} format variables
|
// Find all ${...} format variables
|
||||||
let mut results = vec![pattern.to_string()];
|
let mut results = vec![pattern.to_string()];
|
||||||
|
|
||||||
@@ -370,7 +329,7 @@ fn resolve_single_pass(pattern: &str, resolver: &dyn PolicyVariableResolver) ->
|
|||||||
if var_name.contains("${") {
|
if var_name.contains("${") {
|
||||||
// For nested variables like ${${a}-${b}}, we need to resolve the inner variables first
|
// For nested variables like ${${a}-${b}}, we need to resolve the inner variables first
|
||||||
// Then use the resolved result as a new variable to resolve
|
// Then use the resolved result as a new variable to resolve
|
||||||
let resolved_inner = resolve_aws_variables(var_name, resolver);
|
let resolved_inner = resolve_aws_variables_boxed(var_name, resolver).await;
|
||||||
let mut new_results = Vec::new();
|
let mut new_results = Vec::new();
|
||||||
|
|
||||||
for resolved_var_name in resolved_inner {
|
for resolved_var_name in resolved_inner {
|
||||||
@@ -390,7 +349,7 @@ fn resolve_single_pass(pattern: &str, resolver: &dyn PolicyVariableResolver) ->
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Regular variable resolution
|
// Regular variable resolution
|
||||||
if let Some(values) = resolver.resolve_multiple(var_name) {
|
if let Some(values) = resolver.resolve_multiple(var_name).await {
|
||||||
if !values.is_empty() {
|
if !values.is_empty() {
|
||||||
// If there are multiple values, create a new result for each value
|
// If there are multiple values, create a new result for each value
|
||||||
let mut new_results = Vec::new();
|
let mut new_results = Vec::new();
|
||||||
@@ -440,19 +399,18 @@ mod tests {
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_resolve_aws_variables_with_username() {
|
async fn test_resolve_aws_variables_with_username() {
|
||||||
let mut context = VariableContext::new();
|
let mut context = VariableContext::new();
|
||||||
context.username = Some("testuser".to_string());
|
context.username = Some("testuser".to_string());
|
||||||
|
|
||||||
let resolver = VariableResolver::new(context);
|
let resolver = VariableResolver::new(context);
|
||||||
|
let result = resolve_aws_variables("${aws:username}-bucket", &resolver).await;
|
||||||
let result = resolve_aws_variables("${aws:username}-bucket", &resolver);
|
|
||||||
assert_eq!(result, vec!["testuser-bucket".to_string()]);
|
assert_eq!(result, vec!["testuser-bucket".to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_resolve_aws_variables_with_userid() {
|
async fn test_resolve_aws_variables_with_userid() {
|
||||||
let mut claims = HashMap::new();
|
let mut claims = HashMap::new();
|
||||||
claims.insert("sub".to_string(), Value::String("AIDACKCEVSQ6C2EXAMPLE".to_string()));
|
claims.insert("sub".to_string(), Value::String("AIDACKCEVSQ6C2EXAMPLE".to_string()));
|
||||||
|
|
||||||
@@ -460,13 +418,12 @@ mod tests {
|
|||||||
context.claims = Some(claims);
|
context.claims = Some(claims);
|
||||||
|
|
||||||
let resolver = VariableResolver::new(context);
|
let resolver = VariableResolver::new(context);
|
||||||
|
let result = resolve_aws_variables("${aws:userid}-bucket", &resolver).await;
|
||||||
let result = resolve_aws_variables("${aws:userid}-bucket", &resolver);
|
|
||||||
assert_eq!(result, vec!["AIDACKCEVSQ6C2EXAMPLE-bucket".to_string()]);
|
assert_eq!(result, vec!["AIDACKCEVSQ6C2EXAMPLE-bucket".to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_resolve_aws_variables_with_multiple_variables() {
|
async fn test_resolve_aws_variables_with_multiple_variables() {
|
||||||
let mut claims = HashMap::new();
|
let mut claims = HashMap::new();
|
||||||
claims.insert("sub".to_string(), Value::String("AIDACKCEVSQ6C2EXAMPLE".to_string()));
|
claims.insert("sub".to_string(), Value::String("AIDACKCEVSQ6C2EXAMPLE".to_string()));
|
||||||
|
|
||||||
@@ -475,17 +432,34 @@ mod tests {
|
|||||||
context.username = Some("testuser".to_string());
|
context.username = Some("testuser".to_string());
|
||||||
|
|
||||||
let resolver = VariableResolver::new(context);
|
let resolver = VariableResolver::new(context);
|
||||||
|
let result = resolve_aws_variables("${aws:username}-${aws:userid}-bucket", &resolver).await;
|
||||||
let result = resolve_aws_variables("${aws:username}-${aws:userid}-bucket", &resolver);
|
|
||||||
assert_eq!(result, vec!["testuser-AIDACKCEVSQ6C2EXAMPLE-bucket".to_string()]);
|
assert_eq!(result, vec!["testuser-AIDACKCEVSQ6C2EXAMPLE-bucket".to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn test_resolve_aws_variables_no_variables() {
|
async fn test_resolve_aws_variables_no_variables() {
|
||||||
let context = VariableContext::new();
|
let context = VariableContext::new();
|
||||||
let resolver = VariableResolver::new(context);
|
let resolver = VariableResolver::new(context);
|
||||||
|
|
||||||
let result = resolve_aws_variables("test-bucket", &resolver);
|
let result = resolve_aws_variables("test-bucket", &resolver).await;
|
||||||
assert_eq!(result, vec!["test-bucket".to_string()]);
|
assert_eq!(result, vec!["test-bucket".to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_cached_aws_variable_resolver_dynamic_variables() {
|
||||||
|
let context = VariableContext::new();
|
||||||
|
|
||||||
|
let cached_resolver = CachedAwsVariableResolver::new(context);
|
||||||
|
|
||||||
|
// Dynamic variables should not be cached
|
||||||
|
let result1 = resolve_aws_variables("${aws:EpochTime}-bucket", &cached_resolver).await;
|
||||||
|
|
||||||
|
// Add a delay of 1 second to ensure different timestamps
|
||||||
|
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||||
|
|
||||||
|
let result2 = resolve_aws_variables("${aws:EpochTime}-bucket", &cached_resolver).await;
|
||||||
|
|
||||||
|
// Both results should be different (different timestamps)
|
||||||
|
assert_ne!(result1, result2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -612,7 +612,7 @@ struct ArgsBuilder {
|
|||||||
"24"
|
"24"
|
||||||
)]
|
)]
|
||||||
fn policy_is_allowed(policy: Policy, args: ArgsBuilder) -> bool {
|
fn policy_is_allowed(policy: Policy, args: ArgsBuilder) -> bool {
|
||||||
policy.is_allowed(&Args {
|
pollster::block_on(policy.is_allowed(&Args {
|
||||||
account: &args.account,
|
account: &args.account,
|
||||||
groups: &{
|
groups: &{
|
||||||
if args.groups.is_empty() {
|
if args.groups.is_empty() {
|
||||||
@@ -628,5 +628,5 @@ fn policy_is_allowed(policy: Policy, args: ArgsBuilder) -> bool {
|
|||||||
object: &args.object,
|
object: &args.object,
|
||||||
claims: &args.claims,
|
claims: &args.claims,
|
||||||
deny_only: args.deny_only,
|
deny_only: args.deny_only,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user