use crate::aws::AwsClientConfigExt;
use std::fmt::Debug;
use crate::aws::aws_request_utils::{AwsRequestBuilderExt, AwsSignConfig};
use crate::aws::AwsClientConfig;
use alien_client_core::RequestBuilderExt;
use alien_client_core::{ErrorData, Result};
use alien_error::{AlienError, ContextError};
use async_trait::async_trait;
use aws_credential_types::Credentials;
use bon::Builder;
use form_urlencoded;
#[cfg(feature = "test-utils")]
use mockall::automock;
use quick_xml;
use reqwest::{Client, StatusCode};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "test-utils", automock)]
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait IamApi: Send + Sync + Debug {
async fn create_role(&self, request: CreateRoleRequest) -> Result<CreateRoleResponse>;
async fn get_role(&self, role_name: &str) -> Result<GetRoleResponse>;
async fn delete_role(&self, role_name: &str) -> Result<()>;
async fn put_role_policy(
&self,
role_name: &str,
policy_name: &str,
policy_document: &str,
) -> Result<()>;
async fn get_role_policy(
&self,
role_name: &str,
policy_name: &str,
) -> Result<GetRolePolicyResponse>;
async fn delete_role_policy(&self, role_name: &str, policy_name: &str) -> Result<()>;
async fn update_assume_role_policy(&self, role_name: &str, policy_document: &str)
-> Result<()>;
async fn list_attached_role_policies(
&self,
role_name: &str,
) -> Result<ListAttachedRolePoliciesResponse>;
async fn attach_role_policy(&self, role_name: &str, policy_arn: &str) -> Result<()>;
async fn detach_role_policy(&self, role_name: &str, policy_arn: &str) -> Result<()>;
async fn list_role_policies(&self, role_name: &str) -> Result<ListRolePoliciesResponse>;
async fn create_instance_profile(
&self,
request: CreateInstanceProfileRequest,
) -> Result<CreateInstanceProfileResponse>;
async fn get_instance_profile(
&self,
instance_profile_name: &str,
) -> Result<GetInstanceProfileResponse>;
async fn delete_instance_profile(&self, instance_profile_name: &str) -> Result<()>;
async fn add_role_to_instance_profile(
&self,
instance_profile_name: &str,
role_name: &str,
) -> Result<()>;
async fn remove_role_from_instance_profile(
&self,
instance_profile_name: &str,
role_name: &str,
) -> Result<()>;
async fn list_instance_profiles(
&self,
request: ListInstanceProfilesRequest,
) -> Result<ListInstanceProfilesResponse>;
}
#[derive(Debug, Clone)]
pub struct IamClient {
client: Client,
config: AwsClientConfig,
}
impl IamClient {
pub fn new(client: Client, config: AwsClientConfig) -> Self {
Self { client, config }
}
fn sign_config(&self) -> AwsSignConfig {
AwsSignConfig {
service_name: "iam".into(),
region: self.config.region.clone(),
credentials: self.config.get_credentials(),
signing_region: Some("us-east-1".into()),
}
}
fn get_base_url(&self) -> String {
if let Some(override_url) = self.config.get_service_endpoint_option("iam") {
override_url.to_string()
} else {
"https://iam.amazonaws.com".to_string()
}
}
fn build_form_body(action: &str, version: &str, params: Vec<(String, String)>) -> String {
let mut all = vec![
("Action".to_string(), action.to_string()),
("Version".to_string(), version.to_string()),
];
all.extend(params);
all.into_iter()
.map(|(k, v)| {
format!(
"{}={}",
k,
form_urlencoded::byte_serialize(v.as_bytes()).collect::<String>()
)
})
.collect::<Vec<String>>()
.join("&")
}
async fn post_xml<T: DeserializeOwned + Send + 'static>(
&self,
body: String,
operation_name: &str,
resource_name: &str,
) -> Result<T> {
let base_url = self.get_base_url();
let url = format!("{}/", base_url.trim_end_matches('/'));
let builder = self
.client
.post(&url)
.host("iam.amazonaws.com")
.content_type_form()
.body(body.clone());
let result =
crate::aws::aws_request_utils::sign_send_xml(builder, &self.sign_config()).await;
match result {
Ok(v) => Ok(v),
Err(e) => {
if let Some(ErrorData::HttpResponseError {
http_status,
http_response_text: Some(ref text),
..
}) = &e.error
{
let status = StatusCode::from_u16(*http_status)
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
if let Some(mapped) =
Self::map_iam_error(status, text, operation_name, resource_name, &body)
{
Err(e.context(mapped))
} else {
Err(e)
}
} else {
Err(e)
}
}
}
}
async fn post_no_response(
&self,
body: String,
operation_name: &str,
resource_name: &str,
) -> Result<()> {
let base_url = self.get_base_url();
let url = format!("{}/", base_url.trim_end_matches('/'));
let builder = self
.client
.post(&url)
.host("iam.amazonaws.com")
.content_type_form()
.body(body.clone());
let result =
crate::aws::aws_request_utils::sign_send_no_response(builder, &self.sign_config())
.await;
match result {
Ok(_) => Ok(()),
Err(e) => {
if let Some(ErrorData::HttpResponseError {
http_status,
http_response_text: Some(ref text),
..
}) = &e.error
{
let status = StatusCode::from_u16(*http_status)
.unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
if let Some(mapped) =
Self::map_iam_error(status, text, operation_name, resource_name, &body)
{
Err(e.context(mapped))
} else {
Err(e)
}
} else {
Err(e)
}
}
}
}
fn map_iam_error(
status: StatusCode,
error_body: &str,
operation: &str,
resource_name: &str,
request_body: &str,
) -> Option<ErrorData> {
let parsed_error: std::result::Result<IamErrorResponse, _> =
quick_xml::de::from_str(error_body);
let (error_code, error_message) = match parsed_error {
Ok(e) => (
e.error.code.unwrap_or_else(|| "UnknownErrorCode".into()),
e.error.message.unwrap_or_else(|| "Unknown error".into()),
),
Err(_) => {
return None;
}
};
Some(match error_code.as_str() {
"AccessDenied"
| "AccessDeniedException"
| "UnauthorizedOperation"
| "InvalidUserID.NotFound"
| "AuthFailure"
| "SignatureDoesNotMatch"
| "TokenRefreshRequired"
| "NotAuthorized"
| "InvalidClientTokenId"
| "MissingAuthenticationToken"
| "OptInRequired" => ErrorData::RemoteAccessDenied {
resource_type: "IAM Resource".into(),
resource_name: resource_name.into(),
},
"Throttling" | "ThrottlingException" | "RequestLimitExceeded" => {
ErrorData::RateLimitExceeded {
message: error_message,
}
}
"ServiceUnavailable" | "InternalFailure" | "ServiceFailure" => {
ErrorData::RemoteServiceUnavailable {
message: error_message,
}
}
"NoSuchEntity" | "NoSuchRole" | "NoSuchPolicy" => ErrorData::RemoteResourceNotFound {
resource_type: "IAM Resource".into(),
resource_name: resource_name.into(),
},
"EntityAlreadyExists" | "RoleAlreadyExists" => ErrorData::RemoteResourceConflict {
message: error_message,
resource_type: "IAM Resource".into(),
resource_name: resource_name.into(),
},
"LimitExceeded"
| "LimitExceededException"
| "PolicySizeLimitExceeded"
| "RoleSizeLimitExceeded" => ErrorData::QuotaExceeded {
message: error_message,
},
_ => match status {
StatusCode::CONFLICT => ErrorData::RemoteResourceConflict {
message: error_message,
resource_type: "IAM Resource".into(),
resource_name: resource_name.into(),
},
StatusCode::NOT_FOUND => ErrorData::RemoteResourceNotFound {
resource_type: "IAM Resource".into(),
resource_name: resource_name.into(),
},
StatusCode::FORBIDDEN | StatusCode::UNAUTHORIZED => ErrorData::RemoteAccessDenied {
resource_type: "IAM Resource".into(),
resource_name: resource_name.into(),
},
StatusCode::TOO_MANY_REQUESTS => ErrorData::RateLimitExceeded {
message: error_message,
},
StatusCode::SERVICE_UNAVAILABLE
| StatusCode::BAD_GATEWAY
| StatusCode::GATEWAY_TIMEOUT => ErrorData::RemoteServiceUnavailable {
message: error_message,
},
_ => ErrorData::HttpResponseError {
message: format!("IAM operation failed: {}", error_message),
url: "iam.amazonaws.com".into(),
http_status: status.as_u16(),
http_response_text: Some(error_body.into()),
http_request_text: Some(request_body.into()),
},
},
})
}
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl IamApi for IamClient {
async fn create_role(&self, request: CreateRoleRequest) -> Result<CreateRoleResponse> {
let mut params: Vec<(String, String)> = vec![
("RoleName".to_string(), request.role_name.clone()),
(
"AssumeRolePolicyDocument".to_string(),
request.assume_role_policy_document.clone(),
),
];
if let Some(ref path) = request.path {
params.push(("Path".to_string(), path.clone()));
}
if let Some(ref description) = request.description {
params.push(("Description".to_string(), description.clone()));
}
if let Some(max_session) = request.max_session_duration {
params.push(("MaxSessionDuration".to_string(), max_session.to_string()));
}
if let Some(ref tags) = request.tags {
for (i, tag) in tags.iter().enumerate() {
params.push((format!("Tags.member.{}.Key", i + 1), tag.key.clone()));
params.push((format!("Tags.member.{}.Value", i + 1), tag.value.clone()));
}
}
let body = Self::build_form_body("CreateRole", "2010-05-08", params);
self.post_xml(body, "CreateRole", &request.role_name).await
}
async fn get_role(&self, role_name: &str) -> Result<GetRoleResponse> {
let params = vec![("RoleName".to_string(), role_name.to_string())];
let body = Self::build_form_body("GetRole", "2010-05-08", params);
self.post_xml(body, "GetRole", role_name).await
}
async fn delete_role(&self, role_name: &str) -> Result<()> {
let params = vec![("RoleName".to_string(), role_name.to_string())];
let body = Self::build_form_body("DeleteRole", "2010-05-08", params);
self.post_no_response(body, "DeleteRole", role_name).await
}
async fn put_role_policy(
&self,
role_name: &str,
policy_name: &str,
policy_document: &str,
) -> Result<()> {
let params = vec![
("RoleName".to_string(), role_name.to_string()),
("PolicyName".to_string(), policy_name.to_string()),
("PolicyDocument".to_string(), policy_document.to_string()),
];
let body = Self::build_form_body("PutRolePolicy", "2010-05-08", params);
let resource = format!("{}:{}", role_name, policy_name);
self.post_no_response(body, "PutRolePolicy", &resource)
.await
}
async fn get_role_policy(
&self,
role_name: &str,
policy_name: &str,
) -> Result<GetRolePolicyResponse> {
let params = vec![
("RoleName".to_string(), role_name.to_string()),
("PolicyName".to_string(), policy_name.to_string()),
];
let body = Self::build_form_body("GetRolePolicy", "2010-05-08", params);
let resource = format!("{}:{}", role_name, policy_name);
self.post_xml(body, "GetRolePolicy", &resource).await
}
async fn delete_role_policy(&self, role_name: &str, policy_name: &str) -> Result<()> {
let params = vec![
("RoleName".to_string(), role_name.to_string()),
("PolicyName".to_string(), policy_name.to_string()),
];
let body = Self::build_form_body("DeleteRolePolicy", "2010-05-08", params);
let resource = format!("{}:{}", role_name, policy_name);
self.post_no_response(body, "DeleteRolePolicy", &resource)
.await
}
async fn update_assume_role_policy(
&self,
role_name: &str,
policy_document: &str,
) -> Result<()> {
let params = vec![
("RoleName".to_string(), role_name.to_string()),
("PolicyDocument".to_string(), policy_document.to_string()),
];
let body = Self::build_form_body("UpdateAssumeRolePolicy", "2010-05-08", params);
self.post_no_response(body, "UpdateAssumeRolePolicy", role_name)
.await
}
async fn list_attached_role_policies(
&self,
role_name: &str,
) -> Result<ListAttachedRolePoliciesResponse> {
let params = vec![("RoleName".to_string(), role_name.to_string())];
let body = Self::build_form_body("ListAttachedRolePolicies", "2010-05-08", params);
self.post_xml(body, "ListAttachedRolePolicies", role_name)
.await
}
async fn detach_role_policy(&self, role_name: &str, policy_arn: &str) -> Result<()> {
let params = vec![
("RoleName".to_string(), role_name.to_string()),
("PolicyArn".to_string(), policy_arn.to_string()),
];
let body = Self::build_form_body("DetachRolePolicy", "2010-05-08", params);
let resource = format!("{}:{}", role_name, policy_arn);
self.post_no_response(body, "DetachRolePolicy", &resource)
.await
}
async fn list_role_policies(&self, role_name: &str) -> Result<ListRolePoliciesResponse> {
let params = vec![("RoleName".to_string(), role_name.to_string())];
let body = Self::build_form_body("ListRolePolicies", "2010-05-08", params);
self.post_xml(body, "ListRolePolicies", role_name).await
}
async fn attach_role_policy(&self, role_name: &str, policy_arn: &str) -> Result<()> {
let params = vec![
("RoleName".to_string(), role_name.to_string()),
("PolicyArn".to_string(), policy_arn.to_string()),
];
let body = Self::build_form_body("AttachRolePolicy", "2010-05-08", params);
let resource = format!("{}:{}", role_name, policy_arn);
self.post_no_response(body, "AttachRolePolicy", &resource)
.await
}
async fn create_instance_profile(
&self,
request: CreateInstanceProfileRequest,
) -> Result<CreateInstanceProfileResponse> {
let mut params: Vec<(String, String)> = vec![(
"InstanceProfileName".to_string(),
request.instance_profile_name.clone(),
)];
if let Some(ref path) = request.path {
params.push(("Path".to_string(), path.clone()));
}
if let Some(ref tags) = request.tags {
for (i, tag) in tags.iter().enumerate() {
params.push((format!("Tags.member.{}.Key", i + 1), tag.key.clone()));
params.push((format!("Tags.member.{}.Value", i + 1), tag.value.clone()));
}
}
let body = Self::build_form_body("CreateInstanceProfile", "2010-05-08", params);
self.post_xml(
body,
"CreateInstanceProfile",
&request.instance_profile_name,
)
.await
}
async fn get_instance_profile(
&self,
instance_profile_name: &str,
) -> Result<GetInstanceProfileResponse> {
let params = vec![(
"InstanceProfileName".to_string(),
instance_profile_name.to_string(),
)];
let body = Self::build_form_body("GetInstanceProfile", "2010-05-08", params);
self.post_xml(body, "GetInstanceProfile", instance_profile_name)
.await
}
async fn delete_instance_profile(&self, instance_profile_name: &str) -> Result<()> {
let params = vec![(
"InstanceProfileName".to_string(),
instance_profile_name.to_string(),
)];
let body = Self::build_form_body("DeleteInstanceProfile", "2010-05-08", params);
self.post_no_response(body, "DeleteInstanceProfile", instance_profile_name)
.await
}
async fn add_role_to_instance_profile(
&self,
instance_profile_name: &str,
role_name: &str,
) -> Result<()> {
let params = vec![
(
"InstanceProfileName".to_string(),
instance_profile_name.to_string(),
),
("RoleName".to_string(), role_name.to_string()),
];
let body = Self::build_form_body("AddRoleToInstanceProfile", "2010-05-08", params);
let resource = format!("{}:{}", instance_profile_name, role_name);
self.post_no_response(body, "AddRoleToInstanceProfile", &resource)
.await
}
async fn remove_role_from_instance_profile(
&self,
instance_profile_name: &str,
role_name: &str,
) -> Result<()> {
let params = vec![
(
"InstanceProfileName".to_string(),
instance_profile_name.to_string(),
),
("RoleName".to_string(), role_name.to_string()),
];
let body = Self::build_form_body("RemoveRoleFromInstanceProfile", "2010-05-08", params);
let resource = format!("{}:{}", instance_profile_name, role_name);
self.post_no_response(body, "RemoveRoleFromInstanceProfile", &resource)
.await
}
async fn list_instance_profiles(
&self,
request: ListInstanceProfilesRequest,
) -> Result<ListInstanceProfilesResponse> {
let mut params: Vec<(String, String)> = Vec::new();
if let Some(ref path_prefix) = request.path_prefix {
params.push(("PathPrefix".to_string(), path_prefix.clone()));
}
if let Some(ref marker) = request.marker {
params.push(("Marker".to_string(), marker.clone()));
}
if let Some(max_items) = request.max_items {
params.push(("MaxItems".to_string(), max_items.to_string()));
}
let body = Self::build_form_body("ListInstanceProfiles", "2010-05-08", params);
self.post_xml(body, "ListInstanceProfiles", "instance-profiles")
.await
}
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct IamErrorResponse {
pub error: IamErrorDetails,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
struct IamErrorDetails {
pub code: Option<String>,
pub message: Option<String>,
}
#[derive(Serialize, Debug, Clone, Builder)]
#[serde(rename_all = "PascalCase")]
pub struct CreateRoleRequest {
pub role_name: String,
pub assume_role_policy_document: String,
pub path: Option<String>,
pub description: Option<String>,
pub max_session_duration: Option<i32>,
#[serde(skip)]
pub tags: Option<Vec<CreateRoleTag>>,
}
#[derive(Serialize, Debug, Clone)]
pub struct CreateRoleTag {
pub key: String,
pub value: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct CreateRoleResponse {
pub create_role_result: CreateRoleResult,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct CreateRoleResult {
pub role: Role,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Role {
pub path: String,
pub role_name: String,
pub role_id: String,
pub arn: String,
pub create_date: String,
pub assume_role_policy_document: Option<String>,
pub description: Option<String>,
pub max_session_duration: Option<i32>,
pub permissions_boundary: Option<AttachedPermissionsBoundary>,
pub tags: Option<Tags>,
pub role_last_used: Option<RoleLastUsed>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct AttachedPermissionsBoundary {
pub permissions_boundary_type: Option<String>,
pub permissions_boundary_arn: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Tags {
#[serde(rename = "member", default)]
pub member: Vec<Tag>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct Tag {
pub key: String,
pub value: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct RoleLastUsed {
pub last_used_date: Option<String>,
pub region: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct GetRoleResponse {
pub get_role_result: GetRoleResult,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct GetRoleResult {
pub role: Role,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct GetRolePolicyResponse {
pub get_role_policy_result: GetRolePolicyResult,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct GetRolePolicyResult {
pub role_name: String,
pub policy_name: String,
pub policy_document: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ListAttachedRolePoliciesResponse {
pub list_attached_role_policies_result: ListAttachedRolePoliciesResult,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ListAttachedRolePoliciesResult {
pub attached_policies: Option<AttachedPolicies>,
pub is_truncated: Option<bool>,
pub marker: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct AttachedPolicies {
#[serde(rename = "member", default)]
pub member: Vec<AttachedPolicy>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct AttachedPolicy {
pub policy_name: String,
pub policy_arn: String,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ListRolePoliciesResponse {
pub list_role_policies_result: ListRolePoliciesResult,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ListRolePoliciesResult {
pub policy_names: Option<PolicyNames>,
pub is_truncated: Option<bool>,
pub marker: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct PolicyNames {
#[serde(rename = "member", default)]
pub member: Vec<String>,
}
#[derive(Serialize, Debug, Clone, Builder)]
#[serde(rename_all = "PascalCase")]
pub struct CreateInstanceProfileRequest {
pub instance_profile_name: String,
pub path: Option<String>,
#[serde(skip)]
pub tags: Option<Vec<CreateRoleTag>>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct CreateInstanceProfileResponse {
pub create_instance_profile_result: CreateInstanceProfileResult,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct CreateInstanceProfileResult {
pub instance_profile: InstanceProfile,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct GetInstanceProfileResponse {
pub get_instance_profile_result: GetInstanceProfileResult,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct GetInstanceProfileResult {
pub instance_profile: InstanceProfile,
}
#[derive(Serialize, Debug, Clone, Builder, Default)]
#[serde(rename_all = "PascalCase")]
pub struct ListInstanceProfilesRequest {
pub path_prefix: Option<String>,
pub marker: Option<String>,
pub max_items: Option<i32>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ListInstanceProfilesResponse {
pub list_instance_profiles_result: ListInstanceProfilesResult,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ListInstanceProfilesResult {
pub instance_profiles: Option<InstanceProfiles>,
pub is_truncated: Option<bool>,
pub marker: Option<String>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct InstanceProfiles {
#[serde(rename = "member", default)]
pub member: Vec<InstanceProfile>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct InstanceProfile {
pub path: String,
pub instance_profile_name: String,
pub instance_profile_id: String,
pub arn: String,
pub create_date: String,
pub roles: Option<InstanceProfileRoles>,
pub tags: Option<Tags>,
}
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct InstanceProfileRoles {
#[serde(rename = "member", default)]
pub member: Vec<Role>,
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum TrustPolicyPrincipal {
Service {
#[serde(rename = "Service")]
service: TrustPolicyPrincipalValue,
},
Aws {
#[serde(rename = "AWS")]
aws: TrustPolicyPrincipalValue,
},
}
#[derive(Debug, Serialize)]
#[serde(untagged)]
pub enum TrustPolicyPrincipalValue {
Single(String),
Multiple(Vec<String>),
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct TrustPolicyStatement {
pub effect: String,
pub principal: TrustPolicyPrincipal,
pub action: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "PascalCase")]
pub struct TrustPolicyDocument {
pub version: String,
pub statement: Vec<TrustPolicyStatement>,
}