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