use super::*;
pub(super) fn iam_client(base: &SdkConfig) -> IamClient {
let region = base.region().map(|r| r.to_string()).unwrap_or_default();
let cfg = base
.to_builder()
.region(Region::new(super::global_service_region(®ion)))
.build();
IamClient::new(&cfg)
}
#[derive(Clone, Debug)]
pub struct IamSimResult {
pub action: String,
pub resource: String,
pub decision: String,
pub matched_statements: Vec<String>,
pub missing_context: Vec<String>,
pub blocked_by_scp: bool,
pub blocked_by_boundary: bool,
}
impl AwsClient {
pub async fn instance_profile_role_arn(&self, profile: &str) -> Result<Option<String>> {
let name = profile.rsplit('/').next().unwrap_or(profile);
let resp = self
.iam()
.get_instance_profile()
.instance_profile_name(name)
.send()
.await
.wrap_err("GetInstanceProfile failed")?;
Ok(resp
.instance_profile
.and_then(|p| p.roles.into_iter().next())
.map(|r| r.arn))
}
pub(crate) async fn simulate_principal_policy(
&self,
principal_arn: &str,
action_names: &[String],
resource_arns: &[String],
) -> Result<Paged<IamSimResult>> {
if action_names.is_empty() {
return Ok(Paged::new(Vec::new(), false));
}
let resources: Vec<String> = if resource_arns.is_empty() {
vec!["*".to_string()]
} else {
resource_arns.to_vec()
};
const SIMULATE_MAX_PAGES: usize = 10;
let mut raw = Vec::new();
let mut marker: Option<String> = None;
let mut pages = 0usize;
let mut truncated = false;
loop {
let mut req = self
.iam()
.simulate_principal_policy()
.policy_source_arn(principal_arn);
for a in action_names {
req = req.action_names(a);
}
for r in &resources {
req = req.resource_arns(r);
}
if let Some(m) = marker.take() {
req = req.marker(m);
}
let resp = req
.send()
.await
.wrap_err("SimulatePrincipalPolicy failed")?;
raw.extend(resp.evaluation_results.unwrap_or_default());
pages += 1;
match resp.marker {
Some(m) if resp.is_truncated && !m.is_empty() && pages < SIMULATE_MAX_PAGES => {
marker = Some(m);
}
Some(m) if resp.is_truncated && !m.is_empty() => {
truncated = true;
tracing::warn!(
target: "ebman::aws",
pages,
collected = raw.len(),
"SimulatePrincipalPolicy page cap reached — some action \
decisions were not fetched"
);
break;
}
_ => break,
}
}
let mut out: Vec<IamSimResult> = Vec::new();
for r in raw {
let action = r.eval_action_name;
let resource = r.eval_resource_name.unwrap_or_default();
let decision = r.eval_decision.as_str().to_string();
let matched_statements: Vec<String> = r
.matched_statements
.unwrap_or_default()
.into_iter()
.filter_map(|s| {
let policy = s.source_policy_id?;
let sid = s
.start_position
.as_ref()
.map(|p| format!("{}:{}", p.line, p.column))
.unwrap_or_else(|| "0:0".into());
Some(format!("{policy} @ {sid}"))
})
.collect();
let missing_context: Vec<String> = r.missing_context_values.unwrap_or_default();
let blocked_by_scp = r
.organizations_decision_detail
.as_ref()
.is_some_and(|d| !d.allowed_by_organizations);
let blocked_by_boundary = r
.permissions_boundary_decision_detail
.as_ref()
.is_some_and(|d| !d.allowed_by_permissions_boundary);
out.push(IamSimResult {
action,
resource,
decision,
matched_statements,
missing_context,
blocked_by_scp,
blocked_by_boundary,
});
}
Ok(Paged::new(out, truncated))
}
}