Skip to content

Commit ce2df7e

Browse files
author
iru
authored
feat: organizational, add support for single-account deployment (#128)
small steps to go for organizational single-account setup as required by many customers; ⚠️ scanning still not supported - added use-case explanation - added `deploy_benchmark_organizational` to deploy `cloud-bench` module on single account OR stackset (organizational) - added test (wip, evaluating if required time makes sense) <!-- Thank you for your contribution! ## General recommendations Check contribution guidelines at https://github.com/sysdiglabs/terraform-aws-secure-for-cloud/blob/master/CONTRIBUTE.md#contribution-checklist For a cleaner PR make sure you follow these recommendations: - Review modified files and delete small changes that were not intended and maybe slip the commit. - Use Pull Request Drafts for visibility on Work-In-Progress branches and use them on daily mob/pairing for team review - Unless an external revision is desired, in order to validate or gather some feedback, you are free to merge as long as **validation checks are green-lighted** ## Checklist - [ ] If `test/fixtures/*/main.tf` files are modified. I have updated: - [ ] the snippets in the README.md file under root folder. - [ ] the snippets in the README.md file for the corresponding example. - [ ] If `examples` folder are modified. I have updated: - [ ] README.md file with pertinent changes. - [ ] `test/fixtures/*/main.tf` in case the snippet needs modifications. - [ ] If any architectural change has been made, I have updated the diagrams. -->
1 parent 7b43f27 commit ce2df7e

File tree

24 files changed

+278
-47
lines changed

24 files changed

+278
-47
lines changed

.github/workflows/ci-integration-tests.yaml

+17
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,23 @@ jobs:
150150
if: ${{ failure() }}
151151
run: bundle exec kitchen destroy "organizational-aws"
152152

153+
154+
- name: Run organizational-single test
155+
env:
156+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_QA_MANAGED_ACCESS_KEY_ID }}
157+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_QA_MANAGED_SECRET_ACCESS_KEY }}
158+
AWS_REGION: ${{ secrets.AWS_REGION }}
159+
TF_VAR_sysdig_secure_for_cloud_member_account_id: ${{ secrets.AWS_QA_CLOUDNATIVE_ACCOUNT_ID }}
160+
run: bundle exec kitchen test "organizational-single-aws"
161+
162+
- name: Destroy organizational-single resources
163+
env:
164+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_QA_MANAGED_ACCESS_KEY_ID }}
165+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_QA_MANAGED_SECRET_ACCESS_KEY }}
166+
AWS_REGION: ${{ secrets.AWS_REGION }}
167+
TF_VAR_sysdig_secure_for_cloud_member_account_id: ${{ secrets.AWS_QA_CLOUDNATIVE_ACCOUNT_ID }}
168+
if: ${{ failure() }}
169+
run: bundle exec kitchen destroy "organizational-single-aws"
153170
integration_test_app_runner:
154171
needs: integration_test_ecs
155172
concurrency: terraform-account

.kitchen.yml

+9-6
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@ platforms:
1313
- name: "aws"
1414

1515
suites:
16-
- name: single-account-ecs
17-
driver:
18-
root_module_directory: test/fixtures/single-account-ecs
19-
- name: single-account-k8s
20-
driver:
21-
root_module_directory: test/fixtures/single-account-k8s
2216
- name: organizational
2317
driver:
2418
root_module_directory: test/fixtures/organizational
2519
- name: organizational-k8s
2620
driver:
2721
root_module_directory: test/fixtures/organizational-k8s
22+
- name: organizational-single
23+
driver:
24+
root_module_directory: test/fixtures/organizational-single
2825
- name: single-account-apprunner
2926
driver:
3027
root_module_directory: test/fixtures/single-account-apprunner
28+
- name: single-account-ecs
29+
driver:
30+
root_module_directory: test/fixtures/single-account-ecs
31+
- name: single-account-k8s
32+
driver:
33+
root_module_directory: test/fixtures/single-account-k8s

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ For other Cloud providers check: [GCP](https://github.com/sysdiglabs/terraform-g
2020

2121
## Usage
2222

23-
There are several ways to deploy Secure for Cloud in you AWS infrastructure,
23+
There are several ways to deploy Secure for Cloud in you AWS infrastructure,
2424
- **[`/examples`](https://github.com/sysdiglabs/terraform-aws-secure-for-cloud/tree/master/examples)** for the most common scenarios
2525
- [Single Account on ECS](https://github.com/sysdiglabs/terraform-aws-secure-for-cloud/tree/master/examples/single-account-ecs/)
2626
- [Single Account on AppRunner](https://github.com/sysdiglabs/terraform-aws-secure-for-cloud/tree/master/examples/single-account-apprunner/)
@@ -115,7 +115,7 @@ ecs:DescribeTaskDefinition
115115

116116
Check official documentation on [Secure for cloud - AWS, Confirm the Services are working](https://docs.sysdig.com/en/docs/installation/sysdig-secure-for-cloud/deploy-sysdig-secure-for-cloud-on-aws/#confirm-the-services-are-working)
117117

118-
### General
118+
### General
119119

120120
Generally speaking, a triggered situation (threat or image-scanning) whould be check (from more functional-side to more technical)
121121
- Secure UI > Events / Insights / ...

examples/organizational/README.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,10 @@ provider "aws" {
107107
alias = "member"
108108
region = "<AWS_REGION> # same region in both providers. ex. us-east-1"
109109
assume_role {
110+
# ORG_MEMBER_SFC_ACCOUNT_ID is the organizational account where sysdig secure for cloud compute component is to be deployed
110111
# 'OrganizationAccountAccessRole' is the default role created by AWS for managed-account users to be able to admin member accounts.
111112
# <br/>https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_accounts_access.html
112-
role_arn = "arn:aws:iam::${var.sysdig_secure_for_cloud_member_account_id}:role/OrganizationAccountAccessRole"
113+
role_arn = "arn:aws:iam::${ORG_MEMBER_SFC_ACCOUNT_ID}:role/OrganizationAccountAccessRole"
113114
}
114115
}
115116
@@ -118,6 +119,7 @@ module "secure_for_cloud_organizational" {
118119
aws.member = aws.member
119120
}
120121
source = "sysdiglabs/secure-for-cloud/aws//examples/organizational"
122+
sysdig_secure_for_cloud_member_account_id = "<ORG_MEMBER_SFC_ACCOUNT_ID>"
121123
}
122124
```
123125

@@ -152,7 +154,8 @@ $ terraform apply
152154

153155
| Name | Source | Version |
154156
|------|--------|---------|
155-
| <a name="module_cloud_bench"></a> [cloud\_bench](#module\_cloud\_bench) | ../../modules/services/cloud-bench | n/a |
157+
| <a name="module_cloud_bench_org"></a> [cloud\_bench\_org](#module\_cloud\_bench\_org) | ../../modules/services/cloud-bench | n/a |
158+
| <a name="module_cloud_bench_single"></a> [cloud\_bench\_single](#module\_cloud\_bench\_single) | ../../modules/services/cloud-bench | n/a |
156159
| <a name="module_cloud_connector"></a> [cloud\_connector](#module\_cloud\_connector) | ../../modules/services/cloud-connector-ecs | n/a |
157160
| <a name="module_cloudtrail"></a> [cloudtrail](#module\_cloudtrail) | ../../modules/infrastructure/cloudtrail | n/a |
158161
| <a name="module_codebuild"></a> [codebuild](#module\_codebuild) | ../../modules/infrastructure/codebuild | n/a |
@@ -182,6 +185,7 @@ $ terraform apply
182185
| <a name="input_cloudtrail_kms_enable"></a> [cloudtrail\_kms\_enable](#input\_cloudtrail\_kms\_enable) | true/false whether the created cloudtrail should deliver encrypted events to s3 | `bool` | `true` | no |
183186
| <a name="input_connector_ecs_task_role_name"></a> [connector\_ecs\_task\_role\_name](#input\_connector\_ecs\_task\_role\_name) | Name for the ecs task role. This is only required to resolve cyclic dependency with organizational approach | `string` | `"organizational-ECSTaskRole"` | no |
184187
| <a name="input_deploy_benchmark"></a> [deploy\_benchmark](#input\_deploy\_benchmark) | Whether to deploy or not the cloud benchmarking | `bool` | `true` | no |
188+
| <a name="input_deploy_benchmark_organizational"></a> [deploy\_benchmark\_organizational](#input\_deploy\_benchmark\_organizational) | true/false whether benchmark module should be deployed on organizational or single-account mode (1 role per org accounts if true, 1 role in default aws provider account if false)</li></ul> | `bool` | `true` | no |
185189
| <a name="input_deploy_image_scanning_ecr"></a> [deploy\_image\_scanning\_ecr](#input\_deploy\_image\_scanning\_ecr) | true/false whether to deploy the image scanning on ECR pushed images | `bool` | `false` | no |
186190
| <a name="input_deploy_image_scanning_ecs"></a> [deploy\_image\_scanning\_ecs](#input\_deploy\_image\_scanning\_ecs) | true/false whether to deploy the image scanning on ECS running images | `bool` | `false` | no |
187191
| <a name="input_ecs_cluster_name"></a> [ecs\_cluster\_name](#input\_ecs\_cluster\_name) | Name of a pre-existing ECS (elastic container service) cluster. If defaulted, a new ECS cluster/VPC/Security Group will be created. If specified all three parameters `ecs_cluster_name`, `ecs_vpc_id` and `ecs_vpc_subnets_private_ids` are required. ECS location will/must be within the `sysdig_secure_for_cloud_member_account_id` parameter accountID | `string` | `"create"` | no |
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# note; had to split cloud_bench module due to not being able to use dynamics on provider
2+
# https://github.com/hashicorp/terraform/issues/25244
3+
4+
module "cloud_bench_org" {
5+
count = var.deploy_benchmark && var.deploy_benchmark_organizational ? 1 : 0
6+
7+
source = "../../modules/services/cloud-bench"
8+
9+
name = "${var.name}-cloudbench"
10+
is_organizational = true
11+
region = data.aws_region.current.name
12+
benchmark_regions = var.benchmark_regions
13+
14+
tags = var.tags
15+
}
16+
17+
module "cloud_bench_single" {
18+
count = var.deploy_benchmark && !var.deploy_benchmark_organizational ? 1 : 0
19+
providers = {
20+
aws = aws.member
21+
}
22+
23+
source = "../../modules/services/cloud-bench"
24+
25+
name = "${var.name}-cloudbench"
26+
is_organizational = false
27+
region = data.aws_region.current.name
28+
benchmark_regions = var.benchmark_regions
29+
30+
tags = var.tags
31+
}

examples/organizational/main.tf

+11-16
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ module "cloud_connector" {
6767
deploy_image_scanning_ecr = var.deploy_image_scanning_ecr
6868
deploy_image_scanning_ecs = var.deploy_image_scanning_ecs
6969

70+
#
71+
# note;
72+
# these two variables `is_organizational` and `organizational_config` is for image-scanning requirements (double inception)
73+
# this must still be true to be able to handle future image-scanning
74+
# is_organizational means that it will attempt an assumeRole on management account, as cloud_connector is deployed on `aws.member` alias
75+
#
76+
# TODO
77+
# - avoid all these parameters if `deploy_image_scanning_ecr` and `deploy_image_scanning_ecs` == false
78+
# - is_organizational to be renamed to enable_management_account_assume_role?
79+
# - we could check whether aws.member = aws (management account) infer the value of the variable
80+
#
7081
is_organizational = true
7182
organizational_config = {
7283
# see local.deploy_org_management_sysdig_role notes
@@ -93,19 +104,3 @@ module "cloud_connector" {
93104
tags = var.tags
94105
depends_on = [local.cloudtrail_sns_arn, module.ssm]
95106
}
96-
97-
#-------------------------------------
98-
# cloud-bench
99-
#-------------------------------------
100-
101-
module "cloud_bench" {
102-
count = var.deploy_benchmark ? 1 : 0
103-
source = "../../modules/services/cloud-bench"
104-
105-
name = "${var.name}-cloudbench"
106-
is_organizational = true
107-
region = data.aws_region.current.name
108-
benchmark_regions = var.benchmark_regions
109-
110-
tags = var.tags
111-
}

examples/organizational/variables.tf

+6
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ variable "deploy_benchmark" {
9898
default = true
9999
}
100100

101+
variable "deploy_benchmark_organizational" {
102+
type = bool
103+
default = true
104+
description = "true/false whether benchmark module should be deployed on organizational or single-account mode (1 role per org accounts if true, 1 role in default aws provider account if false)</li></ul>"
105+
}
106+
101107
variable "benchmark_regions" {
102108
type = list(string)
103109
description = "List of regions in which to run the benchmark. If empty, the task will contain all aws regions by default."

modules/infrastructure/cloudtrail/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ No modules.
4343
|------|-------------|------|---------|:--------:|
4444
| <a name="input_cloudtrail_kms_enable"></a> [cloudtrail\_kms\_enable](#input\_cloudtrail\_kms\_enable) | true/false whether s3 should be encrypted | `bool` | `true` | no |
4545
| <a name="input_is_multi_region_trail"></a> [is\_multi\_region\_trail](#input\_is\_multi\_region\_trail) | true/false whether cloudtrail will ingest multiregional events | `bool` | `true` | no |
46-
| <a name="input_is_organizational"></a> [is\_organizational](#input\_is\_organizational) | whether secure-for-cloud should be deployed in an organizational setup | `bool` | `false` | no |
46+
| <a name="input_is_organizational"></a> [is\_organizational](#input\_is\_organizational) | true/false whether cloudtrail is organizational or not | `bool` | `false` | no |
4747
| <a name="input_name"></a> [name](#input\_name) | Name to be assigned to all child resources. A suffix may be added internally when required. Use default value unless you need to install multiple instances | `string` | `"sfc"` | no |
4848
| <a name="input_organizational_config"></a> [organizational\_config](#input\_organizational\_config) | organizational\_config. following attributes must be given<br><ul><li>`sysdig_secure_for_cloud_member_account_id` to enable reading permission</li><br><li>`organizational_role_per_account` to enable SNS topic subscription. by default "OrganizationAccountAccessRole"</li></ul> | <pre>object({<br> sysdig_secure_for_cloud_member_account_id = string<br> organizational_role_per_account = string<br> })</pre> | <pre>{<br> "organizational_role_per_account": null,<br> "sysdig_secure_for_cloud_member_account_id": null<br>}</pre> | no |
4949
| <a name="input_s3_bucket_expiration_days"></a> [s3\_bucket\_expiration\_days](#input\_s3\_bucket\_expiration\_days) | Number of days that the logs will persist in the bucket | `number` | `5` | no |

modules/infrastructure/cloudtrail/variables.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
variable "is_organizational" {
1010
type = bool
1111
default = false
12-
description = "whether secure-for-cloud should be deployed in an organizational setup"
12+
description = "true/false whether cloudtrail is organizational or not"
1313
}
1414

1515

modules/services/cloud-bench/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ No modules.
5959
| Name | Description | Type | Default | Required |
6060
|------|-------------|------|---------|:--------:|
6161
| <a name="input_benchmark_regions"></a> [benchmark\_regions](#input\_benchmark\_regions) | List of regions in which to run the benchmark. If empty, the task will contain all aws regions by default. | `list(string)` | `[]` | no |
62-
| <a name="input_is_organizational"></a> [is\_organizational](#input\_is\_organizational) | whether secure-for-cloud should be deployed in an organizational setup | `bool` | `false` | no |
62+
| <a name="input_is_organizational"></a> [is\_organizational](#input\_is\_organizational) | true/false whether secure-for-cloud should be deployed in an organizational setup (all accounts of org) or not (only on default aws provider account) | `bool` | `false` | no |
6363
| <a name="input_name"></a> [name](#input\_name) | The name of the IAM Role that will be created. | `string` | `"sfc-cloudbench"` | no |
64-
| <a name="input_provision_in_management_account"></a> [provision\_in\_management\_account](#input\_provision\_in\_management\_account) | Whether to deploy the stack in the management account | `bool` | `true` | no |
64+
| <a name="input_provision_caller_account"></a> [provision\_caller\_account](#input\_provision\_caller\_account) | true/false whether to provision the aws provider account (if is\_organizational=true management account, if is\_organizational=false it will depend on the provider setup on the caller module | `bool` | `true` | no |
6565
| <a name="input_region"></a> [region](#input\_region) | Default region for resource creation in organization mode | `string` | `"eu-central-1"` | no |
6666
| <a name="input_tags"></a> [tags](#input\_tags) | sysdig secure-for-cloud tags. always include 'product' default tag for resource-group proper functioning | `map(string)` | <pre>{<br> "product": "sysdig-secure-for-cloud"<br>}</pre> | no |
6767

modules/services/cloud-bench/main.tf

+8-7
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ data "sysdig_secure_trusted_cloud_identity" "trusted_identity" {
1313
}
1414

1515
locals {
16+
caller_account = data.aws_caller_identity.me.account_id
1617
member_account_ids = var.is_organizational ? [for a in data.aws_organizations_organization.org[0].non_master_accounts : a.id] : []
17-
account_ids_to_deploy = var.is_organizational && var.provision_in_management_account ? concat(local.member_account_ids, [data.aws_organizations_organization.org[0].master_account_id]) : local.member_account_ids
18+
account_ids_to_deploy = var.is_organizational && var.provision_caller_account ? concat(local.member_account_ids, [data.aws_organizations_organization.org[0].master_account_id]) : local.member_account_ids
1819

19-
benchmark_task_name = var.is_organizational ? "Organization: ${data.aws_organizations_organization.org[0].id}" : data.aws_caller_identity.me.account_id
20-
accounts_scope_clause = var.is_organizational ? "aws.accountId in (\"${join("\", \"", local.account_ids_to_deploy)}\")" : "aws.accountId = \"${data.aws_caller_identity.me.account_id}\""
20+
benchmark_task_name = var.is_organizational ? "Organization: ${data.aws_organizations_organization.org[0].id}" : local.caller_account
21+
accounts_scope_clause = var.is_organizational ? "aws.accountId in (\"${join("\", \"", local.account_ids_to_deploy)}\")" : "aws.accountId = \"${local.caller_account}\""
2122
regions_scope_clause = length(var.benchmark_regions) == 0 ? "" : " and aws.region in (\"${join("\", \"", var.benchmark_regions)}\")"
2223
}
2324

@@ -26,7 +27,7 @@ locals {
2627
#----------------------------------------------------------
2728

2829
resource "sysdig_secure_cloud_account" "cloud_account" {
29-
for_each = var.is_organizational ? toset(local.account_ids_to_deploy) : [data.aws_caller_identity.me.account_id]
30+
for_each = var.is_organizational ? toset(local.account_ids_to_deploy) : [local.caller_account]
3031

3132
account_id = each.value
3233
cloud_provider = "aws"
@@ -37,7 +38,7 @@ resource "sysdig_secure_cloud_account" "cloud_account" {
3738
locals {
3839
external_id = try(
3940
sysdig_secure_cloud_account.cloud_account[local.account_ids_to_deploy[0]].external_id,
40-
sysdig_secure_cloud_account.cloud_account[data.aws_caller_identity.me.account_id].external_id,
41+
sysdig_secure_cloud_account.cloud_account[local.caller_account].external_id,
4142
)
4243
}
4344

@@ -91,7 +92,7 @@ data "aws_iam_policy_document" "trust_relationship" {
9192
}
9293

9394
resource "aws_iam_role" "cloudbench_role" {
94-
count = var.is_organizational && !var.provision_in_management_account ? 0 : 1
95+
count = var.is_organizational && !var.provision_caller_account ? 0 : 1
9596

9697
name = var.name
9798
assume_role_policy = data.aws_iam_policy_document.trust_relationship.json
@@ -100,7 +101,7 @@ resource "aws_iam_role" "cloudbench_role" {
100101

101102

102103
resource "aws_iam_role_policy_attachment" "cloudbench_security_audit" {
103-
count = var.is_organizational && !var.provision_in_management_account ? 0 : 1
104+
count = var.is_organizational && !var.provision_caller_account ? 0 : 1
104105

105106
role = aws_iam_role.cloudbench_role[0].id
106107
policy_arn = data.aws_iam_policy.security_audit.arn

modules/services/cloud-bench/variables.tf

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ variable "name" {
1111
variable "is_organizational" {
1212
type = bool
1313
default = false
14-
description = "whether secure-for-cloud should be deployed in an organizational setup"
14+
description = "true/false whether secure-for-cloud should be deployed in an organizational setup (all accounts of org) or not (only on default aws provider account)"
1515
}
1616

1717
variable "region" {
@@ -26,10 +26,10 @@ variable "benchmark_regions" {
2626
default = []
2727
}
2828

29-
variable "provision_in_management_account" {
29+
variable "provision_caller_account" {
3030
type = bool
3131
default = true
32-
description = "Whether to deploy the stack in the management account"
32+
description = "true/false whether to provision the aws provider account (if is_organizational=true management account, if is_organizational=false it will depend on the provider setup on the caller module"
3333
}
3434

3535
variable "tags" {

0 commit comments

Comments
 (0)