Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ Notice that:

<br/>

## Organizational Install Configurations

There are four new parameters to configure organizational deployments on the cloud for Sysdig Secure for Cloud :-
1. `include_ouids` - List of AWS Organizational Unit IDs to deploy the Sysdig Secure for Cloud stack resources in.
2. `exclude_ouids` - List of AWS Organizational Unit IDs to exclude deploying the Sysdig Secure for Cloud stack resources in.
3. `include_accounts` - List of AWS Accounts to deploy the Sysdig Secure for Cloud stack resources in.
4. `exclude_accounts` - List of AWS Accounts to exclude deploying the Sysdig Secure for Cloud stack resources in.

Note: module variable `organizational_unit_ids` / `org_units` will be DEPRECATED soon going forward. You can use `include_ouids` instead to achieve the same deployment outcome.

## Best practices

For contributing to existing modules or adding new modules, below are some of the best practices recommended :-
Expand Down
5 changes: 5 additions & 0 deletions modules/agentless-scanning/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ No modules.
| <a name="input_sysdig_secure_account_id"></a> [sysdig\_secure\_account\_id](#input\_sysdig\_secure\_account\_id) | ID of the Sysdig Cloud Account to enable Agentless Scanning for (incase of organization, ID of the Sysdig management account) | `string` | n/a | yes |
| <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 |
| <a name="input_timeout"></a> [timeout](#input\_timeout) | Default timeout values for create, update, and delete operations | `string` | `"30m"` | no |
| <a name="input_include_ouids"></a> [include\_ouids](#input\_include\_ouids) | ouids to include for organization | `set(string)` | `[]` | no |
| <a name="input_exclude_ouids"></a> [exclude\_ouids](#input\_exclude\_ouids) | ouids to exclude for organization | `set(string)` | `[]` | no |
| <a name="input_include_accounts"></a> [include\_accounts](#input\_include\_accounts) | accounts to include for organization | `set(string)` | `[]` | no |
| <a name="input_exclude_accounts"></a> [exclude\_accounts](#input\_exclude\_accounts) | accounts to exclude for organization | `set(string)` | `[]` | no |


## Outputs

Expand Down
138 changes: 138 additions & 0 deletions modules/agentless-scanning/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#----------------------------------------------------------
# Fetch & compute required data for organizational install
#----------------------------------------------------------

data "aws_organizations_organization" "org" {
count = var.is_organizational ? 1 : 0
}

locals {
# fetch the AWS Root OU
root_org_units = var.is_organizational ? [for root in data.aws_organizations_organization.org[0].roots : root.id] : []
}

# if only excluded ouids provided, fetch children ous in org to filter exclusions
# note: AWS fetches only first level immediate children, since it has no data source to give all OUs recursively
data "aws_organizations_organizational_units" "ou" {
count = var.is_organizational && length(var.include_ouids) == 0 && length(var.exclude_ouids) > 0 ? 1 : 0
parent_id = data.aws_organizations_organization.org[0].roots[0].id
}

# if both include and exclude accounts are provided, fetch all child accounts of final org_units_to_deploy to filter exclusions
data "aws_organizations_organizational_unit_descendant_accounts" "ou_children" {
for_each = toset(local.deployment_targets.org_units_to_deploy)
parent_id = each.key
}

#----------------------------------------------------------
# Manage configurations to determine targets to deploy in
#----------------------------------------------------------

locals {
# OU CONFIGURATION (determine user provided org configuration)
org_configuration = (
# case1 - if no include/exclude ous provided, include entire org
var.is_organizational && length(var.include_ouids) == 0 && length(var.exclude_ouids) == 0 ? (
"entire_org"
) : (
# case2 - if only included ouids provided, include those ous only
var.is_organizational && length(var.include_ouids) > 0 && length(var.exclude_ouids) == 0 ? (
"included_ous_only"
) : (
# case3 - if only excluded ouids provided, exclude their accounts from rest of org
var.is_organizational && length(var.include_ouids) == 0 && length(var.exclude_ouids) > 0 ? (
"excluded_ous_only"
) : (
# case4 - if both include and exclude ouids are provided, includes override excludes
var.is_organizational && length(var.include_ouids) > 0 && length(var.exclude_ouids) > 0 ? (
"mixed_ous"
) : ""
)
)
)
)

# handling exclusions when only excluded ouids are provided
# fetch list of all ouids to filter exclusions (AWS data source only returns first level immediate children)
ou_list = local.org_configuration == "excluded_ous_only" ? toset([for ou in data.aws_organizations_organizational_units.ou[0].children: ou.id]) : toset([])

# switch cases for various user provided org configuration to be onboarded
deployment_options = {
entire_org = {
org_units_to_deploy = local.root_org_units
}
included_ous_only = {
org_units_to_deploy = var.include_ouids
}
excluded_ous_only = {
# check if user provided excluded ouids are in ou_list to determine whether or not we can make exclusions, else we ignore and onboard entire org
# TODO: update this if we find alternative to get all OUs in tree to filter exclusions for nested ouids as well
org_units_to_deploy = length(setintersection(local.ou_list, var.exclude_ouids)) > 0 ? setsubtract(local.ou_list, var.exclude_ouids) : local.root_org_units
}
mixed_ous = {
# if both include and exclude ouids are provided, includes override excludes
org_units_to_deploy = var.include_ouids
}
default = {
org_units_to_deploy = []
}
}

# final targets to deploy organizational resources in
deployment_targets = lookup(local.deployment_options, local.org_configuration, local.deployment_options.default)
}

locals {
# ACCOUNTS CONFIGURATION (determine user provided accounts configuration)
accounts_configuration = (
# case1 - if no include/exclude accounts provided
var.is_organizational && length(var.include_accounts) == 0 && length(var.exclude_accounts) == 0 ? (
"NONE"
) : (
# case2 - if only included accounts provided, include those accts as well
var.is_organizational && length(var.include_accounts) > 0 && length(var.exclude_accounts) == 0 ? (
"UNION"
) : (
# case3 - if only excluded accounts provided, exclude those accounts
var.is_organizational && length(var.include_accounts) == 0 && length(var.exclude_accounts) > 0 ? (
"DIFFERENCE"
) : (
# case4 - if both include and exclude accounts are provided, includes override excludes
var.is_organizational && length(var.include_accounts) > 0 && length(var.exclude_accounts) > 0 ? (
"MIXED"
) : ""
)
)
)
)

# handling exclusions when both include and exclude accounts are provided - fetch all accounts of every ou and filter exclusions
org_accounts_list = local.accounts_configuration == "MIXED" ? flatten([ for ou_child_accounts in data.aws_organizations_organizational_unit_descendant_accounts.ou_children: [ ou_child_accounts.accounts[*].id ] ]) : []

# switch cases for various user provided accounts configuration to be onboarded
deployment_account_options = {
NONE = {
accounts_to_deploy = []
account_filter_type = "NONE"
}
UNION = {
accounts_to_deploy = var.include_accounts
account_filter_type = "UNION"
}
DIFFERENCE = {
accounts_to_deploy = var.exclude_accounts
account_filter_type = "DIFFERENCE"
}
MIXED = {
accounts_to_deploy = setunion(var.include_accounts, setsubtract(toset(local.org_accounts_list), var.exclude_accounts))
account_filter_type = "UNION"
}
default = {
accounts_to_deploy = []
account_filter_type = "NONE"
}
}

# list of accounts to deploy organizational resources in
deployment_accounts = lookup(local.deployment_account_options, local.accounts_configuration, local.deployment_account_options.default)
}
12 changes: 3 additions & 9 deletions modules/agentless-scanning/organizational.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,6 @@
# - an Alias by the same name for the respective key, in each region of region list.
#-----------------------------------------------------------------------------------------------------------------------

data "aws_organizations_organization" "org" {
count = var.is_organizational ? 1 : 0
}

locals {
organizational_unit_ids = var.is_organizational && length(var.org_units) == 0 ? [for root in data.aws_organizations_organization.org[0].roots : root.id] : toset(var.org_units)
}

#-----------------------------------------------------------------------------------------------------------------------
# stackset and stackset instance deployed for all accounts in all organization units
# - IAM Role
Expand Down Expand Up @@ -196,7 +188,9 @@ resource "aws_cloudformation_stack_set_instance" "ou_stackset_instance" {

stack_set_name = aws_cloudformation_stack_set.ou_resources_stackset[0].name
deployment_targets {
organizational_unit_ids = local.organizational_unit_ids
organizational_unit_ids = local.deployment_targets.org_units_to_deploy
accounts = local.deployment_accounts.account_filter_type == "NONE" ? null : local.deployment_accounts.accounts_to_deploy
account_filter_type = local.deployment_accounts.account_filter_type
}
operation_preferences {
max_concurrent_percentage = 100
Expand Down
29 changes: 28 additions & 1 deletion modules/agentless-scanning/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ variable "is_organizational" {
}

variable "org_units" {
description = "(Optional) List of Organization Unit IDs in which to setup Agentless Scanning. By default, Agentless Scanning will be setup in all accounts within the Organization. This field is ignored if `is_organizational = false`"
description = <<-EOF
DEPRECATED: Defaults to `[]`, use `include_ouids` instead.
When set, list of Organization Unit IDs to setup Agentless Scanning. By default, Agentless Scanning will be setup in all accounts within the Organization."
EOF
type = set(string)
default = []
}
Expand Down Expand Up @@ -81,4 +84,28 @@ variable "failure_tolerance_percentage" {
variable "sysdig_secure_account_id" {
type = string
description = "ID of the Sysdig Cloud Account to enable Agentless Scanning for (incase of organization, ID of the Sysdig management account)"
}

variable "include_ouids" {
description = "(Optional) ouids to include for organization"
type = set(string)
default = []
}

variable "exclude_ouids" {
description = "(Optional) ouids to exclude for organization"
type = set(string)
default = []
}

variable "include_accounts" {
description = "(Optional) accounts to include for organization"
type = set(string)
default = []
}

variable "exclude_accounts" {
description = "(Optional) accounts to exclude for organization"
type = set(string)
default = []
}
2 changes: 1 addition & 1 deletion modules/config-posture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ If instrumenting an AWS Gov account/organization, IAM policies and resources wil
|---------------------------------------------------------------------------|-----------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.0.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 5.60.0 |
| <a name="requirement_sysdig"></a> [sysdig](#requirement\_sysdig) | ~>1.39 |
| <a name="requirement_sysdig"></a> [sysdig](#requirement\_sysdig) | ~>1.47 |

## Providers

Expand Down
138 changes: 138 additions & 0 deletions modules/config-posture/locals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#----------------------------------------------------------
# Fetch & compute required data for organizational install
#----------------------------------------------------------

data "aws_organizations_organization" "org" {
count = var.is_organizational ? 1 : 0
}

locals {
# fetch the AWS Root OU
root_org_units = var.is_organizational ? [for root in data.aws_organizations_organization.org[0].roots : root.id] : []
}

# if only excluded ouids provided, fetch children ous in org to filter exclusions
# note: AWS fetches only first level immediate children, since it has no data source to give all OUs recursively
data "aws_organizations_organizational_units" "ou" {
count = var.is_organizational && length(var.include_ouids) == 0 && length(var.exclude_ouids) > 0 ? 1 : 0
parent_id = data.aws_organizations_organization.org[0].roots[0].id
}

# if both include and exclude accounts are provided, fetch all child accounts of final org_units_to_deploy to filter exclusions
data "aws_organizations_organizational_unit_descendant_accounts" "ou_children" {
for_each = toset(local.deployment_targets.org_units_to_deploy)
parent_id = each.key
}

#----------------------------------------------------------
# Manage configurations to determine targets to deploy in
#----------------------------------------------------------

locals {
# OU CONFIGURATION (determine user provided org configuration)
org_configuration = (
# case1 - if no include/exclude ous provided, include entire org
var.is_organizational && length(var.include_ouids) == 0 && length(var.exclude_ouids) == 0 ? (
"entire_org"
) : (
# case2 - if only included ouids provided, include those ous only
var.is_organizational && length(var.include_ouids) > 0 && length(var.exclude_ouids) == 0 ? (
"included_ous_only"
) : (
# case3 - if only excluded ouids provided, exclude their accounts from rest of org
var.is_organizational && length(var.include_ouids) == 0 && length(var.exclude_ouids) > 0 ? (
"excluded_ous_only"
) : (
# case4 - if both include and exclude ouids are provided, includes override excludes
var.is_organizational && length(var.include_ouids) > 0 && length(var.exclude_ouids) > 0 ? (
"mixed_ous"
) : ""
)
)
)
)

# handling exclusions when only excluded ouids are provided
# fetch list of all ouids to filter exclusions (AWS data source only returns first level immediate children)
ou_list = local.org_configuration == "excluded_ous_only" ? toset([for ou in data.aws_organizations_organizational_units.ou[0].children: ou.id]) : toset([])

# switch cases for various user provided org configuration to be onboarded
deployment_options = {
entire_org = {
org_units_to_deploy = local.root_org_units
}
included_ous_only = {
org_units_to_deploy = var.include_ouids
}
excluded_ous_only = {
# check if user provided excluded ouids are in ou_list to determine whether or not we can make exclusions, else we ignore and onboard entire org
# TODO: update this if we find alternative to get all OUs in tree to filter exclusions for nested ouids as well
org_units_to_deploy = length(setintersection(local.ou_list, var.exclude_ouids)) > 0 ? setsubtract(local.ou_list, var.exclude_ouids) : local.root_org_units
}
mixed_ous = {
# if both include and exclude ouids are provided, includes override excludes
org_units_to_deploy = var.include_ouids
}
default = {
org_units_to_deploy = []
}
}

# final targets to deploy organizational resources in
deployment_targets = lookup(local.deployment_options, local.org_configuration, local.deployment_options.default)
}

locals {
# ACCOUNTS CONFIGURATION (determine user provided accounts configuration)
accounts_configuration = (
# case1 - if no include/exclude accounts provided
var.is_organizational && length(var.include_accounts) == 0 && length(var.exclude_accounts) == 0 ? (
"NONE"
) : (
# case2 - if only included accounts provided, include those accts as well
var.is_organizational && length(var.include_accounts) > 0 && length(var.exclude_accounts) == 0 ? (
"UNION"
) : (
# case3 - if only excluded accounts provided, exclude those accounts
var.is_organizational && length(var.include_accounts) == 0 && length(var.exclude_accounts) > 0 ? (
"DIFFERENCE"
) : (
# case4 - if both include and exclude accounts are provided, includes override excludes
var.is_organizational && length(var.include_accounts) > 0 && length(var.exclude_accounts) > 0 ? (
"MIXED"
) : ""
)
)
)
)

# handling exclusions when both include and exclude accounts are provided - fetch all accounts of every ou and filter exclusions
org_accounts_list = local.accounts_configuration == "MIXED" ? flatten([ for ou_child_accounts in data.aws_organizations_organizational_unit_descendant_accounts.ou_children: [ ou_child_accounts.accounts[*].id ] ]) : []

# switch cases for various user provided accounts configuration to be onboarded
deployment_account_options = {
NONE = {
accounts_to_deploy = []
account_filter_type = "NONE"
}
UNION = {
accounts_to_deploy = var.include_accounts
account_filter_type = "UNION"
}
DIFFERENCE = {
accounts_to_deploy = var.exclude_accounts
account_filter_type = "DIFFERENCE"
}
MIXED = {
accounts_to_deploy = setunion(var.include_accounts, setsubtract(toset(local.org_accounts_list), var.exclude_accounts))
account_filter_type = "UNION"
}
default = {
accounts_to_deploy = []
account_filter_type = "NONE"
}
}

# list of accounts to deploy organizational resources in
deployment_accounts = lookup(local.deployment_account_options, local.accounts_configuration, local.deployment_account_options.default)
}
Loading