Skip to content

Commit

Permalink
feat: organizational, add support for single-account deployment (#128)
Browse files Browse the repository at this point in the history
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.

-->
  • Loading branch information
iru authored Sep 7, 2022
1 parent 7b43f27 commit ce2df7e
Show file tree
Hide file tree
Showing 24 changed files with 278 additions and 47 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/ci-integration-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,23 @@ jobs:
if: ${{ failure() }}
run: bundle exec kitchen destroy "organizational-aws"


- name: Run organizational-single test
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_QA_MANAGED_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_QA_MANAGED_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
TF_VAR_sysdig_secure_for_cloud_member_account_id: ${{ secrets.AWS_QA_CLOUDNATIVE_ACCOUNT_ID }}
run: bundle exec kitchen test "organizational-single-aws"

- name: Destroy organizational-single resources
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_QA_MANAGED_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_QA_MANAGED_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }}
TF_VAR_sysdig_secure_for_cloud_member_account_id: ${{ secrets.AWS_QA_CLOUDNATIVE_ACCOUNT_ID }}
if: ${{ failure() }}
run: bundle exec kitchen destroy "organizational-single-aws"
integration_test_app_runner:
needs: integration_test_ecs
concurrency: terraform-account
Expand Down
15 changes: 9 additions & 6 deletions .kitchen.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ platforms:
- name: "aws"

suites:
- name: single-account-ecs
driver:
root_module_directory: test/fixtures/single-account-ecs
- name: single-account-k8s
driver:
root_module_directory: test/fixtures/single-account-k8s
- name: organizational
driver:
root_module_directory: test/fixtures/organizational
- name: organizational-k8s
driver:
root_module_directory: test/fixtures/organizational-k8s
- name: organizational-single
driver:
root_module_directory: test/fixtures/organizational-single
- name: single-account-apprunner
driver:
root_module_directory: test/fixtures/single-account-apprunner
- name: single-account-ecs
driver:
root_module_directory: test/fixtures/single-account-ecs
- name: single-account-k8s
driver:
root_module_directory: test/fixtures/single-account-k8s
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ For other Cloud providers check: [GCP](https://github.com/sysdiglabs/terraform-g

## Usage

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

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)

### General
### General

Generally speaking, a triggered situation (threat or image-scanning) whould be check (from more functional-side to more technical)
- Secure UI > Events / Insights / ...
Expand Down
8 changes: 6 additions & 2 deletions examples/organizational/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,10 @@ provider "aws" {
alias = "member"
region = "<AWS_REGION> # same region in both providers. ex. us-east-1"
assume_role {
# ORG_MEMBER_SFC_ACCOUNT_ID is the organizational account where sysdig secure for cloud compute component is to be deployed
# 'OrganizationAccountAccessRole' is the default role created by AWS for managed-account users to be able to admin member accounts.
# <br/>https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_accounts_access.html
role_arn = "arn:aws:iam::${var.sysdig_secure_for_cloud_member_account_id}:role/OrganizationAccountAccessRole"
role_arn = "arn:aws:iam::${ORG_MEMBER_SFC_ACCOUNT_ID}:role/OrganizationAccountAccessRole"
}
}
Expand All @@ -118,6 +119,7 @@ module "secure_for_cloud_organizational" {
aws.member = aws.member
}
source = "sysdiglabs/secure-for-cloud/aws//examples/organizational"
sysdig_secure_for_cloud_member_account_id = "<ORG_MEMBER_SFC_ACCOUNT_ID>"
}
```

Expand Down Expand Up @@ -152,7 +154,8 @@ $ terraform apply

| Name | Source | Version |
|------|--------|---------|
| <a name="module_cloud_bench"></a> [cloud\_bench](#module\_cloud\_bench) | ../../modules/services/cloud-bench | n/a |
| <a name="module_cloud_bench_org"></a> [cloud\_bench\_org](#module\_cloud\_bench\_org) | ../../modules/services/cloud-bench | n/a |
| <a name="module_cloud_bench_single"></a> [cloud\_bench\_single](#module\_cloud\_bench\_single) | ../../modules/services/cloud-bench | n/a |
| <a name="module_cloud_connector"></a> [cloud\_connector](#module\_cloud\_connector) | ../../modules/services/cloud-connector-ecs | n/a |
| <a name="module_cloudtrail"></a> [cloudtrail](#module\_cloudtrail) | ../../modules/infrastructure/cloudtrail | n/a |
| <a name="module_codebuild"></a> [codebuild](#module\_codebuild) | ../../modules/infrastructure/codebuild | n/a |
Expand Down Expand Up @@ -182,6 +185,7 @@ $ terraform apply
| <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 |
| <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 |
| <a name="input_deploy_benchmark"></a> [deploy\_benchmark](#input\_deploy\_benchmark) | Whether to deploy or not the cloud benchmarking | `bool` | `true` | no |
| <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 |
| <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 |
| <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 |
| <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 |
Expand Down
31 changes: 31 additions & 0 deletions examples/organizational/cloud-bench.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# note; had to split cloud_bench module due to not being able to use dynamics on provider
# https://github.com/hashicorp/terraform/issues/25244

module "cloud_bench_org" {
count = var.deploy_benchmark && var.deploy_benchmark_organizational ? 1 : 0

source = "../../modules/services/cloud-bench"

name = "${var.name}-cloudbench"
is_organizational = true
region = data.aws_region.current.name
benchmark_regions = var.benchmark_regions

tags = var.tags
}

module "cloud_bench_single" {
count = var.deploy_benchmark && !var.deploy_benchmark_organizational ? 1 : 0
providers = {
aws = aws.member
}

source = "../../modules/services/cloud-bench"

name = "${var.name}-cloudbench"
is_organizational = false
region = data.aws_region.current.name
benchmark_regions = var.benchmark_regions

tags = var.tags
}
27 changes: 11 additions & 16 deletions examples/organizational/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ module "cloud_connector" {
deploy_image_scanning_ecr = var.deploy_image_scanning_ecr
deploy_image_scanning_ecs = var.deploy_image_scanning_ecs

#
# note;
# these two variables `is_organizational` and `organizational_config` is for image-scanning requirements (double inception)
# this must still be true to be able to handle future image-scanning
# is_organizational means that it will attempt an assumeRole on management account, as cloud_connector is deployed on `aws.member` alias
#
# TODO
# - avoid all these parameters if `deploy_image_scanning_ecr` and `deploy_image_scanning_ecs` == false
# - is_organizational to be renamed to enable_management_account_assume_role?
# - we could check whether aws.member = aws (management account) infer the value of the variable
#
is_organizational = true
organizational_config = {
# see local.deploy_org_management_sysdig_role notes
Expand All @@ -93,19 +104,3 @@ module "cloud_connector" {
tags = var.tags
depends_on = [local.cloudtrail_sns_arn, module.ssm]
}

#-------------------------------------
# cloud-bench
#-------------------------------------

module "cloud_bench" {
count = var.deploy_benchmark ? 1 : 0
source = "../../modules/services/cloud-bench"

name = "${var.name}-cloudbench"
is_organizational = true
region = data.aws_region.current.name
benchmark_regions = var.benchmark_regions

tags = var.tags
}
6 changes: 6 additions & 0 deletions examples/organizational/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ variable "deploy_benchmark" {
default = true
}

variable "deploy_benchmark_organizational" {
type = bool
default = true
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>"
}

variable "benchmark_regions" {
type = list(string)
description = "List of regions in which to run the benchmark. If empty, the task will contain all aws regions by default."
Expand Down
2 changes: 1 addition & 1 deletion modules/infrastructure/cloudtrail/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ No modules.
|------|-------------|------|---------|:--------:|
| <a name="input_cloudtrail_kms_enable"></a> [cloudtrail\_kms\_enable](#input\_cloudtrail\_kms\_enable) | true/false whether s3 should be encrypted | `bool` | `true` | no |
| <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 |
| <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 |
| <a name="input_is_organizational"></a> [is\_organizational](#input\_is\_organizational) | true/false whether cloudtrail is organizational or not | `bool` | `false` | no |
| <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 |
| <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 |
| <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 |
Expand Down
2 changes: 1 addition & 1 deletion modules/infrastructure/cloudtrail/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
variable "is_organizational" {
type = bool
default = false
description = "whether secure-for-cloud should be deployed in an organizational setup"
description = "true/false whether cloudtrail is organizational or not"
}


Expand Down
4 changes: 2 additions & 2 deletions modules/services/cloud-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <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 |
| <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 |
| <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 |
| <a name="input_name"></a> [name](#input\_name) | The name of the IAM Role that will be created. | `string` | `"sfc-cloudbench"` | no |
| <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 |
| <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 |
| <a name="input_region"></a> [region](#input\_region) | Default region for resource creation in organization mode | `string` | `"eu-central-1"` | no |
| <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 |

Expand Down
15 changes: 8 additions & 7 deletions modules/services/cloud-bench/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ data "sysdig_secure_trusted_cloud_identity" "trusted_identity" {
}

locals {
caller_account = data.aws_caller_identity.me.account_id
member_account_ids = var.is_organizational ? [for a in data.aws_organizations_organization.org[0].non_master_accounts : a.id] : []
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
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

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

Expand All @@ -26,7 +27,7 @@ locals {
#----------------------------------------------------------

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

account_id = each.value
cloud_provider = "aws"
Expand All @@ -37,7 +38,7 @@ resource "sysdig_secure_cloud_account" "cloud_account" {
locals {
external_id = try(
sysdig_secure_cloud_account.cloud_account[local.account_ids_to_deploy[0]].external_id,
sysdig_secure_cloud_account.cloud_account[data.aws_caller_identity.me.account_id].external_id,
sysdig_secure_cloud_account.cloud_account[local.caller_account].external_id,
)
}

Expand Down Expand Up @@ -91,7 +92,7 @@ data "aws_iam_policy_document" "trust_relationship" {
}

resource "aws_iam_role" "cloudbench_role" {
count = var.is_organizational && !var.provision_in_management_account ? 0 : 1
count = var.is_organizational && !var.provision_caller_account ? 0 : 1

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


resource "aws_iam_role_policy_attachment" "cloudbench_security_audit" {
count = var.is_organizational && !var.provision_in_management_account ? 0 : 1
count = var.is_organizational && !var.provision_caller_account ? 0 : 1

role = aws_iam_role.cloudbench_role[0].id
policy_arn = data.aws_iam_policy.security_audit.arn
Expand Down
6 changes: 3 additions & 3 deletions modules/services/cloud-bench/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ variable "name" {
variable "is_organizational" {
type = bool
default = false
description = "whether secure-for-cloud should be deployed in an organizational setup"
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)"
}

variable "region" {
Expand All @@ -26,10 +26,10 @@ variable "benchmark_regions" {
default = []
}

variable "provision_in_management_account" {
variable "provision_caller_account" {
type = bool
default = true
description = "Whether to deploy the stack in the management account"
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"
}

variable "tags" {
Expand Down
Loading

0 comments on commit ce2df7e

Please sign in to comment.