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, PolicyName};
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::internal_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(policy_name: PolicyName, policy_document: PolicyDocument) -> Self {
250 PolicyBuilder {
251 policy_name: policy_name.0,
252 policy_document,
253 }
254 }
255
256 #[must_use]
257 pub fn build(self) -> Policy {
258 Policy {
259 policy_name: self.policy_name,
260 policy_document: self.policy_document,
261 }
262 }
263}
264
265pub struct PolicyDocumentBuilder {
284 statements: Vec<Statement>
285}
286
287impl PolicyDocumentBuilder {
288 pub fn new(statements: Vec<Statement>) -> PolicyDocumentBuilder {
289 Self {
290 statements
291 }
292 }
293
294 pub fn build(self) -> PolicyDocument {
295 PolicyDocument {
296 version: "2012-10-17".to_string(),
297 statements: self.statements,
298 }
299 }
300}
301
302pub struct AssumeRolePolicyDocumentBuilder {
303 statements: Vec<Statement>
304}
305
306impl AssumeRolePolicyDocumentBuilder {
307 pub fn new(statements: Vec<Statement>) -> Self {
308 Self {
309 statements,
310 }
311 }
312
313 pub fn build(self) -> AssumeRolePolicyDocument {
314 AssumeRolePolicyDocument {
315 version: "2012-10-17".to_string(),
316 statements: self.statements,
317 }
318 }
319}
320
321#[derive(Debug, Clone)]
322pub enum Effect {
323 Allow,
324 Deny,
325}
326
327impl From<Effect> for String {
328 fn from(value: Effect) -> Self {
329 match value {
330 Effect::Allow => "Allow".to_string(),
331 Effect::Deny => "Deny".to_string(),
332 }
333 }
334}
335
336pub trait StatementState {}
337pub struct StatementStartState {}
338impl StatementState for StatementStartState {}
339
340pub struct StatementBuilder {
359 action: Vec<String>,
360 effect: Effect,
361 principal: Option<Principal>,
362 resource: Option<Vec<Value>>,
363 condition: Option<Value>
364}
365
366impl StatementBuilder {
367 pub(crate) fn internal_new(action: Vec<String>, effect: Effect) -> Self {
368 Self {
369 action,
370 effect,
371 principal: None,
372 resource: None,
373 condition: None,
374 }
375 }
376
377 pub fn new(actions: Vec<IamAction>, effect: Effect) -> Self {
378 Self::internal_new(actions.into_iter().map(|a| a.0).collect(), effect)
379 }
380
381 pub fn principal(self, principal: Principal) -> Self {
382 Self {
383 principal: Some(principal),
384 ..self
385 }
386 }
387
388 pub fn condition(self, condition: Value) -> Self {
389 Self {
390 condition: Some(condition),
391 ..self
392 }
393 }
394
395 pub fn resources(self, resources: Vec<Value>) -> Self {
396 Self {
397 resource: Some(resources),
398 ..self
399 }
400 }
401
402 pub fn all_resources(self) -> Self {
403 Self {
404 resource: Some(vec![Value::String("*".to_string())]),
405 ..self
406 }
407 }
408
409 #[must_use]
410 pub fn build(self) -> Statement {
411 Statement {
412 action: self.action,
413 effect: self.effect.into(),
414 principal: self.principal,
415 resource: self.resource,
416 condition: self.condition,
417 }
418 }
419}
420
421pub struct CustomPermission {
422 id: PolicyName,
423 statement: Statement,
424}
425
426impl CustomPermission {
427 pub fn new(id: PolicyName, statement: Statement) -> Self {
433 Self {
434 id,
435 statement,
436 }
437 }
438}
439
440pub enum Permission<'a> {
441 AppConfigRead(&'a ApplicationRef, &'a EnvironmentRef, &'a ConfigurationProfileRef),
442 DynamoDBRead(&'a TableRef),
443 DynamoDBReadWrite(&'a TableRef),
444 SecretsManagerRead(&'a SecretRef),
445 SqsRead(&'a QueueRef),
446 S3ReadWrite(&'a BucketRef),
447 Custom(CustomPermission),
448}
449
450impl Permission<'_> {
451 pub(crate) fn into_policy(self) -> Policy {
452 match self {
453 Permission::DynamoDBRead(table) => {
454 let id = table.get_resource_id();
455 let statement = Statement {
456 action: vec![
457 "dynamodb:Get*".to_string(),
458 "dynamodb:DescribeTable".to_string(),
459 "dynamodb:BatchGetItem".to_string(),
460 "dynamodb:ConditionCheckItem".to_string(),
461 "dynamodb:Query".to_string(),
462 "dynamodb:Scan".to_string(),
463 ],
464 effect: "Allow".to_string(),
465 resource: Some(vec![get_arn(id)]),
466 principal: None,
467 condition: None,
468 };
469 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
470 PolicyBuilder::new(PolicyName(format!("{}Read", id)), policy_document).build()
471 }
472 Permission::DynamoDBReadWrite(table) => {
473 let id = table.get_resource_id();
474 let statement = Statement {
475 action: vec![
476 "dynamodb:Get*".to_string(),
477 "dynamodb:DescribeTable".to_string(),
478 "dynamodb:BatchGetItem".to_string(),
479 "dynamodb:BatchWriteItem".to_string(),
480 "dynamodb:ConditionCheckItem".to_string(),
481 "dynamodb:Query".to_string(),
482 "dynamodb:Scan".to_string(),
483 "dynamodb:DeleteItem".to_string(),
484 "dynamodb:PutItem".to_string(),
485 "dynamodb:UpdateItem".to_string(),
486 ],
487 effect: "Allow".to_string(),
488 resource: Some(vec![get_arn(id)]),
489 principal: None,
490 condition: None,
491 };
492 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
493 PolicyBuilder::new(PolicyName(format!("{}ReadWrite", id)), policy_document).build()
494 }
495 Permission::SqsRead(queue) => {
496 let id = queue.get_resource_id();
497 let sqs_permissions_statement = StatementBuilder::internal_new(
498 vec![
499 "sqs:ChangeMessageVisibility".to_string(),
500 "sqs:DeleteMessage".to_string(),
501 "sqs:GetQueueAttributes".to_string(),
502 "sqs:GetQueueUrl".to_string(),
503 "sqs:ReceiveMessage".to_string(),
504 ],
505 Effect::Allow,
506 )
507 .resources(vec![get_arn(id)])
508 .build();
509 let policy_document = PolicyDocumentBuilder::new(vec![sqs_permissions_statement]).build();
510 PolicyBuilder::new(PolicyName(format!("{}Read", id)), policy_document).build()
511 }
512 Permission::S3ReadWrite(bucket) => {
513 let id = bucket.get_resource_id();
514 let arn = get_arn(id);
515 let s3_permissions_statement = StatementBuilder::internal_new(
516 vec![
517 "s3:Abort*".to_string(),
518 "s3:DeleteObject*".to_string(),
519 "s3:GetBucket*".to_string(),
520 "s3:GetObject*".to_string(),
521 "s3:List*".to_string(),
522 "s3:PutObject".to_string(),
523 "s3:PutObjectLegalHold".to_string(),
524 "s3:PutObjectRetention".to_string(),
525 "s3:PutObjectTagging".to_string(),
526 "s3:PutObjectVersionTagging".to_string(),
527 ],
528 Effect::Allow,
529 )
530 .resources(vec![arn.clone(), join("/", vec![arn, Value::String("*".to_string())])])
531 .build();
532
533 let policy_document = PolicyDocumentBuilder::new(vec![s3_permissions_statement]).build();
534 PolicyBuilder::new(PolicyName(format!("{}ReadWrite", id)), policy_document).build()
535 }
536 Permission::SecretsManagerRead(secret) => {
537 let id = secret.get_resource_id();
538 let statement = StatementBuilder::internal_new(vec!["secretsmanager:GetSecretValue".to_string()], Effect::Allow)
539 .resources(vec![secret.get_ref()])
540 .build();
541 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
542 PolicyBuilder::new(PolicyName(format!("{}Read", id)), policy_document).build()
543 }
544 Permission::AppConfigRead(app, env, profile) => {
545 let id = app.get_resource_id();
546 let resource = join(
547 "",
548 vec![
549 Value::String("arn:aws:appconfig:*:".to_string()),
550 get_ref(AWS_ACCOUNT_PSEUDO_PARAM),
551 Value::String(":application/".to_string()),
552 app.get_ref(),
553 Value::String("/environment/".to_string()),
554 env.get_ref(),
555 Value::String("/configuration/".to_string()),
556 profile.get_ref(),
557 ],
558 );
559
560 let statement = StatementBuilder::internal_new(vec![
561 "appconfig:StartConfigurationSession".to_string(),
562 "appconfig:GetLatestConfiguration".to_string(),
563 ], Effect::Allow)
564 .resources(vec![resource])
565 .build();
566 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
567 PolicyBuilder::new(PolicyName(format!("{}Read", id)), policy_document).build()
568 }
569 Permission::Custom(CustomPermission { id, statement }) => {
570 let policy_document = PolicyDocumentBuilder::new(vec![statement]).build();
571 PolicyBuilder::new(id, policy_document).build()
572 }
573 }
574 }
575}