1use crate::dynamodb::TableRef;
2use crate::iam::{AWSPrincipal, AssumeRolePolicyDocument, IamRoleProperties, Policy, PolicyDocument, Principal, Role, RoleRef, ServicePrincipal, Statement};
3use crate::intrinsic::{get_arn, get_ref, join, AWS_ACCOUNT_PSEUDO_PARAM};
4use crate::s3::BucketRef;
5use crate::shared::Id;
6use crate::sqs::QueueRef;
7use crate::stack::{Resource, StackBuilder};
8use crate::wrappers::IamAction;
9use serde_json::Value;
10use std::marker::PhantomData;
11use std::vec;
12use crate::appconfig::{ApplicationRef, ConfigurationProfileRef, EnvironmentRef};
13use crate::secretsmanager::SecretRef;
14use crate::type_state;
15
16type_state!(
17 PrincipalState,
18 StartState,
19 ChosenState,
20);
21
22pub struct PrincipalBuilder<T: PrincipalState> {
40 phantom_data: PhantomData<T>,
41 service: Option<String>,
42 aws: Option<String>,
43 normal: Option<String>
44}
45
46impl Default for PrincipalBuilder<StartState> {
47 fn default() -> Self {
48 Self::new()
49 }
50}
51
52impl PrincipalBuilder<StartState> {
53 pub fn new() -> PrincipalBuilder<StartState> {
54 PrincipalBuilder {
55 phantom_data: Default::default(),
56 service: None,
57 aws: None,
58 normal: None,
59 }
60 }
61
62 pub fn service<T: Into<String>>(self, service: T) -> PrincipalBuilder<ChosenState> {
63 PrincipalBuilder {
64 phantom_data: Default::default(),
65 service: Some(service.into()),
66 aws: self.aws,
67 normal: self.normal
68 }
69 }
70
71 pub fn aws<T: Into<String>>(self, aws: T) -> PrincipalBuilder<ChosenState> {
72 PrincipalBuilder {
73 phantom_data: Default::default(),
74 aws: Some(aws.into()),
75 service: self.service,
76 normal: self.normal
77 }
78 }
79
80 pub fn normal<T: Into<String>>(self, normal: T) -> PrincipalBuilder<ChosenState> {
81 PrincipalBuilder {
82 phantom_data: Default::default(),
83 normal: Some(normal.into()),
84 service: self.service,
85 aws: self.aws,
86 }
87 }
88}
89
90impl PrincipalBuilder<ChosenState> {
91 pub fn build(self) -> Principal {
92 if let Some(aws) = self.aws {
93 Principal::AWS(AWSPrincipal {
94 aws,
95 })
96 } else if let Some(service) = self.service {
97 Principal::Service(ServicePrincipal {
98 service,
99 })
100 } else if let Some(normal) = self.normal {
101 Principal::Custom(normal)
102 } else {
103 unreachable!("can only reach build state when one of the above is present")
104 }
105 }
106}
107
108pub struct RoleBuilder {
137 id: Id,
138 resource_id: Option<String>,
139 properties: IamRoleProperties,
140 potentially_missing: Vec<String>
141}
142
143impl RoleBuilder {
144 pub fn new(id: &str, properties: IamRoleProperties) -> RoleBuilder {
150 RoleBuilder {
151 id: Id(id.to_string()),
152 resource_id: None,
153 properties,
154 potentially_missing: vec![],
155 }
156 }
157
158 pub(crate) fn new_with_info_on_missing(id: &str, resource_id: &str, properties: IamRoleProperties, potentially_missing: Vec<String>) -> RoleBuilder {
159 Self {
160 id: Id(id.to_string()),
161 resource_id: Some(resource_id.to_string()),
162 properties,
163 potentially_missing,
164 }
165 }
166
167 pub fn build(self, stack_builder: &mut StackBuilder) -> RoleRef {
168 let resource_id = self.resource_id.unwrap_or_else(|| Resource::generate_id("Role"));
169
170 stack_builder.add_resource(Role {
171 id: self.id,
172 resource_id: resource_id.clone(),
173 potentially_missing_services: self.potentially_missing,
174 r#type: "AWS::IAM::Role".to_string(),
175 properties: self.properties,
176 });
177 RoleRef::new(resource_id)
178 }
179}
180
181pub struct RolePropertiesBuilder {
183 assumed_role_policy_document: AssumeRolePolicyDocument,
184 managed_policy_arns: Vec<Value>,
185 policies: Option<Vec<Policy>>,
186 role_name: Option<String>,
187}
188
189impl RolePropertiesBuilder {
190 pub fn new(assumed_role_policy_document: AssumeRolePolicyDocument, managed_policy_arns: Vec<Value>) -> RolePropertiesBuilder {
191 RolePropertiesBuilder {
192 assumed_role_policy_document,
193 managed_policy_arns,
194 policies: None,
195 role_name: None,
196 }
197 }
198
199 pub fn policies(self, policies: Vec<Policy>) -> RolePropertiesBuilder {
200 Self {
201 policies: Some(policies),
202 ..self
203 }
204 }
205
206 pub fn role_name<T: Into<String>>(self, role_name: T) -> RolePropertiesBuilder {
207 Self {
208 role_name: Some(role_name.into()),
209 ..self
210 }
211 }
212
213 #[must_use]
214 pub fn build(self) -> IamRoleProperties {
215 IamRoleProperties {
216 assumed_role_policy_document: self.assumed_role_policy_document,
217 managed_policy_arns: self.managed_policy_arns,
218 policies: self.policies,
219 role_name: self.role_name,
220 }
221 }
222}
223
224pub struct PolicyBuilder {
244 policy_name: String,
245 policy_document: PolicyDocument,
246}
247
248impl PolicyBuilder {
249 pub fn new<T: Into<String>>(policy_name: T, policy_document: PolicyDocument) -> Self {
252 PolicyBuilder {
253 policy_name: policy_name.into(),
254 policy_document,
255 }
256 }
257
258 #[must_use]
259 pub fn build(self) -> Policy {
260 Policy {
261 policy_name: self.policy_name,
262 policy_document: self.policy_document,
263 }
264 }
265}
266
267pub struct PolicyDocumentBuilder {
286 statements: Vec<Statement>
287}
288
289impl PolicyDocumentBuilder {
290 pub fn new(statements: Vec<Statement>) -> PolicyDocumentBuilder {
291 Self {
292 statements
293 }
294 }
295
296 pub fn build(self) -> PolicyDocument {
297 PolicyDocument {
298 version: "2012-10-17".to_string(),
299 statements: self.statements,
300 }
301 }
302}
303
304pub struct AssumeRolePolicyDocumentBuilder {
305 statements: Vec<Statement>
306}
307
308impl AssumeRolePolicyDocumentBuilder {
309 pub fn new(statements: Vec<Statement>) -> Self {
310 Self {
311 statements,
312 }
313 }
314
315 pub fn build(self) -> AssumeRolePolicyDocument {
316 AssumeRolePolicyDocument {
317 version: "2012-10-17".to_string(),
318 statements: self.statements,
319 }
320 }
321}
322
323#[derive(Debug, Clone)]
324pub enum Effect {
325 Allow,
326 Deny,
327}
328
329impl From<Effect> for String {
330 fn from(value: Effect) -> Self {
331 match value {
332 Effect::Allow => "Allow".to_string(),
333 Effect::Deny => "Deny".to_string(),
334 }
335 }
336}
337
338pub trait StatementState {}
339pub struct StatementStartState {}
340impl StatementState for StatementStartState {}
341
342pub struct StatementBuilder {
361 action: Vec<String>,
362 effect: Effect,
363 principal: Option<Principal>,
364 resource: Option<Vec<Value>>,
365 condition: Option<Value>
366}
367
368impl StatementBuilder {
369 pub(crate) fn internal_new(action: Vec<String>, effect: Effect) -> Self {
370 Self {
371 action,
372 effect,
373 principal: None,
374 resource: None,
375 condition: None,
376 }
377 }
378
379 pub fn new(actions: Vec<IamAction>, effect: Effect) -> Self {
380 Self::internal_new(actions.into_iter().map(|a| a.0).collect(), effect)
381 }
382
383 pub fn principal(self, principal: Principal) -> Self {
384 Self {
385 principal: Some(principal),
386 ..self
387 }
388 }
389
390 pub fn condition(self, condition: Value) -> Self {
391 Self {
392 condition: Some(condition),
393 ..self
394 }
395 }
396
397 pub fn resources(self, resources: Vec<Value>) -> Self {
398 Self {
399 resource: Some(resources),
400 ..self
401 }
402 }
403
404 pub fn all_resources(self) -> Self {
405 Self {
406 resource: Some(vec![Value::String("*".to_string())]),
407 ..self
408 }
409 }
410
411 #[must_use]
412 pub fn build(self) -> Statement {
413 Statement {
414 action: self.action,
415 effect: self.effect.into(),
416 principal: self.principal,
417 resource: self.resource,
418 condition: self.condition,
419 }
420 }
421}
422
423pub struct CustomPermission {
424 id: String,
425 statement: Statement,
426}
427
428impl CustomPermission {
429 pub fn new(id: &str, statement: Statement) -> Self {
435 Self {
436 id: id.to_string(),
437 statement,
438 }
439 }
440}
441
442pub enum Permission<'a> {
443 AppConfigRead(&'a ApplicationRef, &'a EnvironmentRef, &'a ConfigurationProfileRef),
444 DynamoDBRead(&'a TableRef),
445 DynamoDBReadWrite(&'a TableRef),
446 SecretsManagerRead(&'a SecretRef),
447 SqsRead(&'a QueueRef),
448 S3ReadWrite(&'a BucketRef),
449 Custom(CustomPermission),
450}
451
452impl Permission<'_> {
453 pub(crate) fn into_policy(self) -> Policy {
454 match self {
455 Permission::DynamoDBRead(table) => {
456 let id = table.get_resource_id();
457 let statement = Statement {
458 action: vec![
459 "dynamodb:Get*".to_string(),
460 "dynamodb:DescribeTable".to_string(),
461 "dynamodb:BatchGetItem".to_string(),
462 "dynamodb:ConditionCheckItem".to_string(),
463 "dynamodb:Query".to_string(),
464 "dynamodb:Scan".to_string(),
465 ],
466 effect: "Allow".to_string(),
467 resource: Some(vec![get_arn(id)]),
468 principal: None,
469 condition: None,
470 };
471 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
472 PolicyBuilder::new(format!("{}Read", id), policy_document).build()
473 }
474 Permission::DynamoDBReadWrite(table) => {
475 let id = table.get_resource_id();
476 let statement = Statement {
477 action: vec![
478 "dynamodb:Get*".to_string(),
479 "dynamodb:DescribeTable".to_string(),
480 "dynamodb:BatchGetItem".to_string(),
481 "dynamodb:BatchWriteItem".to_string(),
482 "dynamodb:ConditionCheckItem".to_string(),
483 "dynamodb:Query".to_string(),
484 "dynamodb:Scan".to_string(),
485 "dynamodb:DeleteItem".to_string(),
486 "dynamodb:PutItem".to_string(),
487 "dynamodb:UpdateItem".to_string(),
488 ],
489 effect: "Allow".to_string(),
490 resource: Some(vec![get_arn(id)]),
491 principal: None,
492 condition: None,
493 };
494 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
495 PolicyBuilder::new(format!("{}ReadWrite", id), policy_document).build()
496 }
497 Permission::SqsRead(queue) => {
498 let id = queue.get_resource_id();
499 let sqs_permissions_statement = StatementBuilder::internal_new(
500 vec![
501 "sqs:ChangeMessageVisibility".to_string(),
502 "sqs:DeleteMessage".to_string(),
503 "sqs:GetQueueAttributes".to_string(),
504 "sqs:GetQueueUrl".to_string(),
505 "sqs:ReceiveMessage".to_string(),
506 ],
507 Effect::Allow,
508 )
509 .resources(vec![get_arn(id)])
510 .build();
511 let policy_document = PolicyDocumentBuilder::new(vec![sqs_permissions_statement]).build();
512 PolicyBuilder::new(format!("{}Read", id), policy_document).build()
513 }
514 Permission::S3ReadWrite(bucket) => {
515 let id = bucket.get_resource_id();
516 let arn = get_arn(id);
517 let s3_permissions_statement = StatementBuilder::internal_new(
518 vec![
519 "s3:Abort*".to_string(),
520 "s3:DeleteObject*".to_string(),
521 "s3:GetBucket*".to_string(),
522 "s3:GetObject*".to_string(),
523 "s3:List*".to_string(),
524 "s3:PutObject".to_string(),
525 "s3:PutObjectLegalHold".to_string(),
526 "s3:PutObjectRetention".to_string(),
527 "s3:PutObjectTagging".to_string(),
528 "s3:PutObjectVersionTagging".to_string(),
529 ],
530 Effect::Allow,
531 )
532 .resources(vec![arn.clone(), join("/", vec![arn, Value::String("*".to_string())])])
533 .build();
534
535 let policy_document = PolicyDocumentBuilder::new(vec![s3_permissions_statement]).build();
536 PolicyBuilder::new(format!("{}ReadWrite", id), policy_document).build()
537 }
538 Permission::SecretsManagerRead(secret) => {
539 let id = secret.get_resource_id();
540 let statement = StatementBuilder::internal_new(vec!["secretsmanager:GetSecretValue".to_string()], Effect::Allow)
541 .resources(vec![secret.get_ref()])
542 .build();
543 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
544 PolicyBuilder::new(format!("{}Read", id), policy_document).build()
545 }
546 Permission::AppConfigRead(app, env, profile) => {
547 let id = app.get_resource_id();
548 let resource = join(
549 "",
550 vec![
551 Value::String("arn:aws:appconfig:*:".to_string()),
552 get_ref(AWS_ACCOUNT_PSEUDO_PARAM),
553 Value::String(":application/".to_string()),
554 app.get_ref(),
555 Value::String("/environment/".to_string()),
556 env.get_ref(),
557 Value::String("/configuration/".to_string()),
558 profile.get_ref(),
559 ],
560 );
561
562 let statement = StatementBuilder::internal_new(vec![
563 "appconfig:StartConfigurationSession".to_string(),
564 "appconfig:GetLatestConfiguration".to_string(),
565 ], Effect::Allow)
566 .resources(vec![resource])
567 .build();
568 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
569 PolicyBuilder::new(format!("{}Read", id), policy_document).build()
570 }
571 Permission::Custom(CustomPermission { id, statement }) => {
572 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
573 PolicyBuilder::new(id, policy_document).build()
574 }
575 }
576 }
577}