diff --git a/conformance_pack/autoscaling.sp b/conformance_pack/autoscaling.sp index e98d7e82..d57b906e 100644 --- a/conformance_pack/autoscaling.sp +++ b/conformance_pack/autoscaling.sp @@ -96,6 +96,16 @@ control "autoscaling_launch_config_hop_limit" { }) } +control "autoscaling_ec2_launch_configuration_no_sensitive_data" { + title = "EC2 auto scaling group launch configurations user data should not have any sensitive data" + description = "Ensure that sensitive information is not included in the user data of the launch configuration. It is recommended to utilize Secrets Manager as an alternative for securely managing sensitive data." + query = query.autoscaling_ec2_launch_configuration_no_sensitive_data + + tags = merge(local.conformance_pack_autoscaling_common_tags, { + other_checks = "true" + }) +} + query "autoscaling_launch_config_requires_imdsv2" { sql = <<-EOQ select @@ -252,6 +262,29 @@ query "autoscaling_launch_config_hop_limit" { EOQ } +query "autoscaling_ec2_launch_configuration_no_sensitive_data" { + sql = <<-EOQ + select + launch_configuration_arn as resource, + case + when + user_data like any (array [ '%pass%', '%secret%', '%token%', '%key%' ]) + or user_data ~ '(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]' then 'alarm' + else 'ok' + end as status, + case + when + user_data like any (array [ '%pass%', '%secret%', '%token%', '%key%' ]) + or user_data ~ '(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]' then title || ' has potential secret patterns in user data.' + else title || ' does not contain secret patterns in user data.' + end as reason + ${local.common_dimensions_sql} + from + aws_ec2_launch_configuration; + EOQ +} + + # Non-Config rule query query "autoscaling_group_uses_ec2_launch_template" { diff --git a/conformance_pack/eks.sp b/conformance_pack/eks.sp index 7f69403d..90bcbf28 100644 --- a/conformance_pack/eks.sp +++ b/conformance_pack/eks.sp @@ -62,6 +62,16 @@ control "eks_cluster_with_latest_kubernetes_version" { }) } +control "eks_cluster_endpoint_public_access_restricted" { + title = "EKS clusters endpoint public access should be restricted" + description = "EKS clusters endpoint with private access allows communication between your nodes and the API server stays within. This control is non-compliant if clusters endpoint public access is enabled as cluster API server is accessible from the internet." + query = query.eks_cluster_endpoint_public_access_restricted + + tags = merge(local.conformance_pack_eks_common_tags, { + other_checks = "true" + }) +} + query "eks_cluster_secrets_encrypted" { sql = <<-EOQ with eks_secrets_encrypted as ( @@ -194,3 +204,24 @@ query "eks_cluster_with_latest_kubernetes_version" { aws_eks_cluster; EOQ } + +query "eks_cluster_endpoint_public_access_restricted" { + sql = <<-EOQ + select + arn as resource, + case + when resources_vpc_config ->> 'EndpointPrivateAccess' = 'true' and resources_vpc_config ->> 'EndpointPublicAccess' = 'false' then 'ok' + when resources_vpc_config ->> 'EndpointPublicAccess' = 'true' and resources_vpc_config -> 'PublicAccessCidrs' @> '["0.0.0.0/0"]' then 'alarm' + else 'ok' + end as status, + case + when resources_vpc_config ->> 'EndpointPrivateAccess' = 'true' and resources_vpc_config ->> 'EndpointPublicAccess' = 'false' then title || ' endpoint access is private.' + when resources_vpc_config ->> 'EndpointPublicAccess' = 'true' and resources_vpc_config -> 'PublicAccessCidrs' @> '["0.0.0.0/0"]' then title || ' endpoint access is public.' + else title || ' endpoint public access is restricted.' + end as reason + ${local.tag_dimensions_sql} + ${local.common_dimensions_sql} + from + aws_eks_cluster; + EOQ +} \ No newline at end of file diff --git a/conformance_pack/guardduty.sp b/conformance_pack/guardduty.sp index 2490acf8..3dd33842 100644 --- a/conformance_pack/guardduty.sp +++ b/conformance_pack/guardduty.sp @@ -60,6 +60,16 @@ control "guardduty_no_high_severity_findings" { }) } +control "guardduty_centrally_configured" { + title = "GuardDuty Detector should be centrally configured" + description = "Ensure that GuardDuty is centrally configured, if GuardDuty is not under central management, it becomes impossible to centrally manage GuardDuty findings, settings, and member accounts." + query = query.guardduty_centrally_configured + + tags = merge(local.conformance_pack_guardduty_common_tags, { + other_checks = "true" + }) +} + query "guardduty_enabled" { sql = <<-EOQ select @@ -135,3 +145,32 @@ query "guardduty_finding_archived" { aws_guardduty_finding; EOQ } + +query "guardduty_centrally_configured" { + sql = <<-EOQ + select + 'arn:' || r.partition || '::' || r.region || ':' || r.account_id as resource, + case + when r.region = any(array['af-south-1', 'ap-northeast-3', 'ap-southeast-3', 'eu-south-1', 'cn-north-1', 'cn-northwest-1', 'me-south-1', 'us-gov-east-1']) then 'skip' + -- Skip any regions that are disabled in the account. + when r.opt_in_status = 'not-opted-in' then 'skip' + when status is null then 'info' + when status = 'DISABLED' then 'alarm' + when status = 'ENABLED' and master_account ->> 'AccountId' is not null then 'ok' + else 'alarm' + end as status, + case + when r.region = any(array['af-south-1', 'ap-northeast-3', 'ap-southeast-3', 'eu-south-1', 'cn-north-1', 'cn-northwest-1', 'me-south-1', 'us-gov-east-1']) then r.region || ' region not supported.' + when r.opt_in_status = 'not-opted-in' then r.region || ' region is disabled.' + when status is null then 'No GuardDuty detector found in ' || r.region || '.' + when status = 'DISABLED' then r.region || ' detector ' || d.title || ' disabled.' + when status = 'ENABLED' and master_account ->> 'AccountId' is not null then r.region || ' detector ' || d.title || ' centrally configured.' + else r.region || ' detector ' || d.title || ' not centrally configured..' + end as reason + ${replace(local.common_dimensions_qualifier_sql, "__QUALIFIER__", "r.")} + from + aws_region as r + left join aws_guardduty_detector d on r.account_id = d.account_id and r.name = d.region; + EOQ +} + diff --git a/conformance_pack/iam.sp b/conformance_pack/iam.sp index 9464d9ce..9f35637e 100644 --- a/conformance_pack/iam.sp +++ b/conformance_pack/iam.sp @@ -523,9 +523,9 @@ control "iam_policy_unused" { } control "iam_access_analyzer_enabled_without_findings" { - title = "IAM Access analyzer should be enabled without findings" - description = "This control checks whether the IAM Access analyzer is enabled without findings. If you grant permissions to an S3 bucket in one of your organization member accounts to a principal in another organization member account, IAM Access Analyzer does not generate a finding. But if you grant permission to a principal in an account that is not a member of the organization, IAM Access Analyzer generates a finding." - query = query.iam_access_analyzer_enabled_without_findings + title = "IAM Access analyzer should be enabled without findings" + description = "This control checks whether the IAM Access analyzer is enabled without findings. If you grant permissions to an S3 bucket in one of your organization member accounts to a principal in another organization member account, IAM Access Analyzer does not generate a finding. But if you grant permission to a principal in an account that is not a member of the organization, IAM Access Analyzer generates a finding." + query = query.iam_access_analyzer_enabled_without_findings tags = merge(local.conformance_pack_iam_common_tags, { other_checks = "true" @@ -572,6 +572,36 @@ control "iam_policy_no_full_access_to_kms" { }) } +control "iam_role_cross_account_read_only_access_policy" { + title = "IAM roles should not have read only access for external AWS accounts" + description = "Ensure IAM Roles do not have ReadOnlyAccess access for external AWS account. The AWS-managed ReadOnlyAccess policy carries a high risk of potential data leakage, posing a significant threat to customer security and privacy." + query = query.iam_role_cross_account_read_only_access_policy + + tags = merge(local.conformance_pack_iam_common_tags, { + other_checks = "true" + }) +} + +control "iam_security_audit_role" { + title = "IAM Security Audit role should be created to conduct security audits" + description = "Ensure IAM Security Audit role is created. By creating an IAM role with a security audit policy, a distinct segregation of responsibilities is established between the security team and other teams within the organization." + query = query.iam_security_audit_role + + tags = merge(local.conformance_pack_iam_common_tags, { + other_checks = "true" + }) +} + +control "iam_policy_custom_no_permissive_role_assumption" { + title = "IAM custom policy should not have overly permissive STS role assumption" + description = "Ensure that no custom IAM policies exist which allow permissive role assumption." + query = query.iam_policy_custom_no_permissive_role_assumption + + tags = merge(local.conformance_pack_iam_common_tags, { + other_checks = "true" + }) +} + query "iam_account_password_policy_strong_min_reuse_24" { sql = <<-EOQ select @@ -1919,3 +1949,125 @@ query "iam_role_unused_60" { aws_iam_role; EOQ } + +query "iam_role_cross_account_read_only_access_policy" { + sql = <<-EOQ + with read_only_access_roles as ( + select + * + from + aws_iam_role, + jsonb_array_elements_text(attached_policy_arns) as a + where + a = 'arn:aws:iam::aws:policy/ReadOnlyAccess' + ), read_only_access_roles_with_cross_account_access as ( + select + arn + from + read_only_access_roles, + jsonb_array_elements(assume_role_policy_std -> 'Statement') as stmt, + jsonb_array_elements_text( stmt -> 'Principal' -> 'AWS' ) as p + where + stmt ->> 'Effect' = 'Allow' + and ( + p = '*' + or not (p like '%' || account_id || '%') + ) + ) + select + r.arn as resource, + case + when ar.arn is null then 'skip' + when c.arn is not null then 'alarm' + else 'ok' + end as status, + case + when ar.arn is null then r.title || ' not associated with ReadOnlyAccess policy.' + when c.arn is not null then r.title || ' associated with ReadOnlyAccess cross account access.' + else r.title || ' associated ReadOnlyAccess without cross account access.' + end as reason + ${replace(local.common_dimensions_qualifier_global_sql, "__QUALIFIER__", "r.")} + from + aws_iam_role as r + left join read_only_access_roles as ar on r.arn = ar.arn + left join read_only_access_roles_with_cross_account_access as c on c.arn = r.arn; + EOQ +} + +query "iam_security_audit_role" { + sql = <<-EOQ + with security_audit_role_count as( + select + 'arn:' || a.partition || ':::' || a.account_id as resource, + count(policy_arn), + a.account_id, + a._ctx + from + aws_account as a + left join aws_iam_role as r on r.account_id = a.account_id + left join jsonb_array_elements_text(attached_policy_arns) as policy_arn on true + where + policy_arn = 'arn:aws:iam::aws:policy/SecurityAudit' + group by + a.account_id, + a.partition, + a._ctx + ) + select + resource, + case + when count > 0 then 'ok' + else 'alarm' + end as status, + case + when count = 1 then 'SecurityAudit policy attached to 1 role.' + when count > 1 then 'SecurityAudit policy attached to ' || count || ' roles.' + else 'SecurityAudit policy not attached to any role.' + end as reason + ${local.common_dimensions_global_sql} + from + security_audit_role_count; + EOQ +} + +query "iam_policy_custom_no_permissive_role_assumption" { + sql = <<-EOQ + with bad_policies as ( + select + arn, + count(*) as num + from + aws_iam_policy, + jsonb_array_elements(policy_std -> 'Statement') as s, + jsonb_array_elements_text(s -> 'Resource') as resource, + jsonb_array_elements_text(s -> 'Action') as action + where + not is_aws_managed + and s ->> 'Effect' = 'Allow' + and resource = '*' + and ( + ( action = '*' + or action = 'sts:*' + or action = 'sts:AssumeRole' + ) + ) + group by + arn + ) + select + p.arn as resource, + case + when b.arn is not null then 'alarm' + else 'ok' + end as status, + p.name || ' contains ' || coalesce(b.num, 0) || + ' statements that allow overly permissive STS role assumption.' as reason + ${replace(local.tag_dimensions_qualifier_sql, "__QUALIFIER__", "p.")} + ${replace(local.common_dimensions_qualifier_sql, "__QUALIFIER__", "p.")} + from + aws_iam_policy as p + left join bad_policies as b on p.arn = b.arn + where + not is_aws_managed; + EOQ +} diff --git a/conformance_pack/lambda.sp b/conformance_pack/lambda.sp index 7d5fc82a..254f94f5 100644 --- a/conformance_pack/lambda.sp +++ b/conformance_pack/lambda.sp @@ -127,6 +127,26 @@ control "lambda_function_use_latest_runtime" { }) } +control "lambda_function_restrict_public_url" { + title = "Lambda functions should restrict public URL" + description = "This control verifies that the Lambda function does not have a publicly accessible URL. Exposing services publicly could potentially make sensitive data accessible to malicious actors." + query = query.lambda_function_restrict_public_url + + tags = merge(local.conformance_pack_lambda_common_tags, { + other_checks = "true" + }) +} + +control "lambda_function_variables_no_sensitive_data" { + title = "Lambda functions variable should not have any sensitive data" + description = "Ensure functions environment variables is not having any sensitive data. Leveraging Secrets Manager enables secure provisioning of database credentials to Lambda functions while also ensuring the security of databases. This approach eliminates the need to hardcode secrets in code or pass them through environmental variables. Additionally, Secrets Manager facilitates the secure retrieval of credentials for establishing connections to databases and performing queries, enhancing overall security measures." + query = query.lambda_function_variables_no_sensitive_data + + tags = merge(local.conformance_pack_lambda_common_tags, { + other_checks = "true" + }) +} + query "lambda_function_dead_letter_queue_configured" { sql = <<-EOQ select @@ -360,6 +380,60 @@ query "lambda_function_use_latest_runtime" { EOQ } +query "lambda_function_restrict_public_url" { + sql = <<-EOQ + select + arn as resource, + case + when url_config is null then 'info' + when url_config ->> 'AuthType' = 'AWS_IAM' then 'ok' + else 'alarm' + end as status, + case + when url_config is null then title || ' having no URL config.' + when url_config ->> 'AuthType' = 'AWS_IAM' then title || ' restricts public function URL.' + else title || ' public function URL configured.' + end as reason + ${local.tag_dimensions_sql} + ${local.common_dimensions_sql} + from + aws_lambda_function; + EOQ +} + +query "lambda_function_variables_no_sensitive_data" { + sql = <<-EOQ + with function_vaiable_with_sensitive_data as ( + select + distinct arn, + name + from + aws_lambda_function + join jsonb_each_text(environment_variables) d on true + where + d.key ilike any (array['%pass%', '%secret%', '%token%', '%key%']) + or d.key ~ '(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]' + or d.value ilike any (array['%pass%', '%secret%', '%token%', '%key%']) + or d.value ~ '(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]' + ) + select + f.arn as resource, + case + when b.arn is null then 'ok' + else 'alarm' + end as status, + case + when b.arn is null then f.title || ' has no sensitive data.' + else f.title || ' has potential sensitive data.' + end as reason + ${local.tag_dimensions_sql} + ${local.common_dimensions_sql} + from + aws_lambda_function as f + left join function_vaiable_with_sensitive_data b on f.arn = b.arn; + EOQ +} + # Non-Config rule query query "lambda_function_cors_configuration" { diff --git a/conformance_pack/rds.sp b/conformance_pack/rds.sp index ecef2ff9..42973e95 100644 --- a/conformance_pack/rds.sp +++ b/conformance_pack/rds.sp @@ -390,6 +390,16 @@ control "rds_db_cluster_multiple_az_enabled" { }) } +control "rds_db_instance_connections_encryption_enabled" { + title = "RDS DB instances connections should be encrypted" + description = "This control checks if RDS DB instance connections are encrypted. Secure Sockets Layer (SSL) is used to encrypt between client applications and Amazon RDS DB instances running Microsoft SQL Server or PostgreSQL." + query = query.rds_db_instance_connections_encryption_enabled + + tags = merge(local.conformance_pack_rds_common_tags, { + other_checks = "true" + }) +} + query "rds_db_instance_backup_enabled" { sql = <<-EOQ select @@ -1127,3 +1137,53 @@ query "rds_db_security_group_events_subscription" { aws_rds_db_event_subscription; EOQ } + +query "rds_db_instance_connections_encryption_enabled" { + sql = <<-EOQ + with instance_pg as ( + select + g ->> 'DBParameterGroupName' as pg_name, + i.engine, + i.title, + i.arn, + i.tags, + i.region, + i.account_id, + i._ctx + from + aws_rds_db_instance as i, + jsonb_array_elements(db_parameter_groups) as g + ), pg_with_ssl_enabled as ( + select + g.name + from + instance_pg as i, + aws_rds_db_parameter_group as g, + jsonb_array_elements(parameters) as p + where + i.pg_name = g.name + and g.account_id = i.account_id + and g.region = i.region + and p ->> 'ParameterName' = 'rds.force_ssl' + and p ->> 'ParameterValue' = '1' + ) + select + i.arn as resource, + i.engine, + case + when i.engine not in ('sqlserver', 'postgres') then 'skip' + when p.name is not null then 'ok' + else 'alarm' + end as status, + case + when i.engine not in ('sqlserver', 'postgres') then title || ' has ' || engine || ' engine type.' + when p.name is not null then title || ' connections are SSL encrypted.' + else title || ' connections are not SSL encrypted.' + end as reason + ${local.tag_dimensions_sql} + ${local.common_dimensions_sql} + from + instance_pg as i + left join pg_with_ssl_enabled as p on p.name = i.pg_name + EOQ +} diff --git a/conformance_pack/vpc.sp b/conformance_pack/vpc.sp index 2ef065d4..05e25f67 100644 --- a/conformance_pack/vpc.sp +++ b/conformance_pack/vpc.sp @@ -373,6 +373,36 @@ control "vpc_security_group_allows_ingress_to_oracle_ports" { }) } +control "vpc_in_more_than_one_region" { + title = "VPCs should exist in multiple regions" + description = "This control checks whether there are VPCs present in multiple regions." + query = query.vpc_in_more_than_one_region + + tags = merge(local.conformance_pack_vpc_common_tags, { + other_checks = "true" + }) +} + +control "vpc_subnet_multi_az_enabled" { + title = "VPCs subnets should exist in multiple availability zones" + description = "Ensure that each VPC has subnets spread across multiple availability zones." + query = query.vpc_subnet_multi_az_enabled + + tags = merge(local.conformance_pack_vpc_common_tags, { + other_checks = "true" + }) +} + +control "vpc_subnet_public_and_private" { + title = "VPCs should have both public and private subnets configured" + description = "Ensure that all VPCs have both public and private subnets configured." + query = query.vpc_subnet_public_and_private + + tags = merge(local.conformance_pack_vpc_common_tags, { + other_checks = "true" + }) +} + query "vpc_flow_logs_enabled" { sql = <<-EOQ select @@ -1578,4 +1608,154 @@ query "vpc_security_group_allows_ingress_authorized_ports" { aws_vpc_security_group as sg left join ingress_unauthorized_ports on ingress_unauthorized_ports.group_id = sg.group_id; EOQ -} \ No newline at end of file +} + +query "vpc_in_more_than_one_region" { + sql = <<-EOQ + with vpc_region_list as ( + select + distinct region, account_id + from + aws_vpc + ), vpc_count_in_account as ( + select + count(*) as num, + account_id + from + vpc_region_list + group by account_id + ) + select + arn as resource, + case + when v.num > 1 then 'ok' + when v.num = 1 then 'alarm' + else 'alarm' + end as status, + case + when v.num > 1 then 'VPCs exist in ' || v.num || ' regions.' + when v.num = 1 then 'VPCs exist only in one region.' + else 'VPC does not exist.' + end as reason + ${replace(local.common_dimensions_qualifier_sql, "__QUALIFIER__", "a.")} + from + aws_account as a + left join vpc_count_in_account as v on v.account_id = a.account_id; + EOQ +} + +query "vpc_subnet_multi_az_enabled" { + sql = <<-EOQ + with subnet_list as ( + select + distinct availability_zone, + vpc_id, + count(*) + from + aws_vpc_subnet + group by + vpc_id, availability_zone + ), zone_list as ( + select + vpc_id, + count(*) as num + from + subnet_list + group by + vpc_id + ) + select + arn as resource, + case + when l.num is null then 'alarm' + when l.num > 1 then 'ok' + else 'alarm' + end as status, + case + when l.num is null then v.title || ' no subnet exists.' + when l.num > 1 then v.title || ' subnets exist in ' || num || ' availability zones.' + else v.title || ' subnet(s) exist in single availability zone.' + end as reason + ${local.tag_dimensions_sql} + ${local.common_dimensions_sql} + from + aws_vpc as v + left join zone_list as l on l.vpc_id = v.vpc_id; + EOQ +} + +query "vpc_subnet_public_and_private" { + sql = <<-EOQ + with subnets_with_explicit_route as ( + select + distinct ( a ->> 'SubnetId') as all_sub + from + aws_vpc_route_table as t, + jsonb_array_elements(associations) as a + where + a ->> 'SubnetId' is not null + ), public_subnets_with_explicit_route as ( + select + distinct a ->> 'SubnetId' as SubnetId + from + aws_vpc_route_table as t, + jsonb_array_elements(associations) as a, + jsonb_array_elements(routes) as r + where + r ->> 'DestinationCidrBlock' = '0.0.0.0/0' + and + ( + r ->> 'GatewayId' like 'igw-%' + or r ->> 'NatGatewayId' like 'nat-%' + ) + and a ->> 'SubnetId' is not null + ), public_subnets_with_implicit_route as ( + select + distinct route_table_id, + vpc_id, + region + from + aws_vpc_route_table as t, + jsonb_array_elements(associations) as a, + jsonb_array_elements(routes) as r + where + a ->> 'Main' = 'true' + and r ->> 'DestinationCidrBlock' = '0.0.0.0/0' + and ( + r ->> 'GatewayId' like 'igw-%' + or r ->> 'NatGatewayId' like 'nat-%' + ) + ), subnet_accessibility as ( + select + subnet_id, + vpc_id, + case + when s.subnet_id in (select all_sub from subnets_with_explicit_route where all_sub not in (select SubnetId from public_subnets_with_explicit_route )) then 'private' + when p.SubnetId is not null or s.vpc_id in ( select vpc_id from public_subnets_with_implicit_route) then 'public' + else 'private' + end as access + from + aws_vpc_subnet as s + left join public_subnets_with_explicit_route as p on p.SubnetId = s.subnet_id + ) + select + arn as resource, + case + when v.vpc_id not in (select vpc_id from subnet_accessibility) then 'alarm' + when 'public' in (select access from subnet_accessibility where vpc_id = v.vpc_id) and 'private' in (select access from subnet_accessibility where vpc_id = v.vpc_id) then 'ok' + when 'public' in (select access from subnet_accessibility where vpc_id = v.vpc_id) and not 'private' in (select access from subnet_accessibility where vpc_id = v.vpc_id) then 'alarm' + when 'private' in (select access from subnet_accessibility where vpc_id = v.vpc_id) and not 'public' in (select access from subnet_accessibility where vpc_id = v.vpc_id) then 'alarm' + end as status, + case + when v.vpc_id not in (select vpc_id from subnet_accessibility) then v.title || ' has no subnet.' + when 'public' in (select access from subnet_accessibility where vpc_id = v.vpc_id) and 'private' in (select access from subnet_accessibility where vpc_id = v.vpc_id) then v.title || ' having both private and public subnet(s).' + when 'public' in (select access from subnet_accessibility where vpc_id = v.vpc_id) and not 'private' in (select access from subnet_accessibility where vpc_id = v.vpc_id) then v.title || ' having only public subnet(s).' + when 'private' in (select access from subnet_accessibility where vpc_id = v.vpc_id) and not 'public' in (select access from subnet_accessibility where vpc_id = v.vpc_id) then v.title || ' having only private subnet(s).' + end as reason + ${local.tag_dimensions_sql} + ${local.common_dimensions_sql} + from + aws_vpc as v; + EOQ +} + diff --git a/conformance_pack/workspaces.sp b/conformance_pack/workspaces.sp new file mode 100644 index 00000000..cd0f2e1d --- /dev/null +++ b/conformance_pack/workspaces.sp @@ -0,0 +1,39 @@ +locals { + conformance_pack_workspaces_common_tags = merge(local.aws_compliance_common_tags, { + service = "AWS/WorkSpaces" + }) +} + +control "workspaces_workspace_volume_encryption_enabled" { + title = "WorkSpaces root and user volume encryption should be enabled" + description = "To help protect data at rest, ensure encryption is enabled for your WorkSpaces root and user volumes." + query = query.workspaces_workspace_volume_encryption_enabled + + tags = merge(local.conformance_pack_workspaces_common_tags, { + other_checks = "true" + }) +} + +query "workspaces_workspace_volume_encryption_enabled" { + sql = <<-EOQ + select + arn as resource, + case + when user_volume_encryption_enabled and root_volume_encryption_enabled then 'ok' + else 'alarm' + end as status, + case + when user_volume_encryption_enabled and root_volume_encryption_enabled then title || ' user and root volume encryption enabled.' + else + case + when not user_volume_encryption_enabled and not root_volume_encryption_enabled then title || ' user and root volume encryption disabled.' + when not root_volume_encryption_enabled then title || ' root volume encryption disabled.' + else title || ' user volume encryption disabled.' + end + end as reason + ${local.tag_dimensions_sql} + ${local.common_dimensions_sql} + from + aws_workspaces_workspace; + EOQ +} \ No newline at end of file diff --git a/other_checks/other.sp b/other_checks/other.sp index bcd837a6..e8cb217b 100644 --- a/other_checks/other.sp +++ b/other_checks/other.sp @@ -13,6 +13,7 @@ benchmark "other" { control.api_gatewayv2_route_authorizer_configured, control.apigateway_rest_api_authorizers_configured, control.apigateway_rest_api_endpoint_restrict_public_access, + control.autoscaling_ec2_launch_configuration_no_sensitive_data, control.autoscaling_group_no_suspended_process, control.backup_plan_region_configured, control.backup_vault_region_configured, @@ -49,6 +50,7 @@ benchmark "other" { control.efs_file_system_enforces_ssl, control.efs_file_system_restrict_public_access, control.eks_cluster_control_plane_audit_logging_enabled, + control.eks_cluster_endpoint_public_access_restricted, control.eks_cluster_no_default_vpc, control.elb_application_classic_network_lb_prohibit_public_access, control.elb_application_lb_listener_certificate_expire_30_days, @@ -68,13 +70,17 @@ benchmark "other" { control.glue_job_bookmarks_encryption_enabled, control.glue_job_cloudwatch_logs_encryption_enabled, control.glue_job_s3_encryption_enabled, + control.guardduty_centrally_configured, control.guardduty_no_high_severity_findings, control.iam_access_analyzer_enabled_without_findings, control.iam_custom_policy_unattached_no_star_star, control.iam_policy_custom_no_assume_role, + control.iam_policy_custom_no_permissive_role_assumption, control.iam_policy_no_full_access_to_cloudtrail, control.iam_policy_no_full_access_to_kms, + control.iam_role_cross_account_read_only_access_policy, control.iam_role_unused_60, + control.iam_security_audit_role, control.iam_user_hardware_mfa_enabled, control.iam_user_with_administrator_access_mfa_enabled, control.kinesis_stream_encrypted_with_kms_cmk, @@ -83,13 +89,16 @@ benchmark "other" { control.kms_key_decryption_restricted_in_iam_customer_managed_policy, control.kms_key_decryption_restricted_in_iam_inline_policy, control.lambda_function_cloudtrail_logging_enabled, + control.lambda_function_restrict_public_url, control.lambda_function_tracing_enabled, + control.lambda_function_variables_no_sensitive_data, control.networkfirewall_firewall_in_vpc, control.opensearch_domain_cognito_authentication_enabled_for_kibana, control.opensearch_domain_internal_user_database_disabled, control.opensearch_domain_updated_with_latest_service_software_version, control.rds_db_instance_ca_certificate_expires_7_days, control.rds_db_instance_cloudwatch_logs_enabled, + control.rds_db_instance_connections_encryption_enabled, control.route53_domain_auto_renew_enabled, control.route53_domain_expires_30_days, control.route53_domain_expires_7_days, @@ -112,6 +121,7 @@ benchmark "other" { control.sqs_queue_dead_letter_queue_configured, control.sqs_queue_policy_prohibit_public_access, control.vpc_endpoint_service_acceptance_required_enabled, + control.vpc_in_more_than_one_region, control.vpc_security_group_allows_ingress_authorized_ports, control.vpc_security_group_allows_ingress_to_cassandra_ports, control.vpc_security_group_allows_ingress_to_memcached_port, @@ -121,7 +131,10 @@ benchmark "other" { control.vpc_security_group_restrict_ingress_kafka_port, control.vpc_security_group_restrict_ingress_kibana_port, control.vpc_security_group_restrict_ingress_redis_port, - control.vpc_security_group_restricted_common_ports + control.vpc_security_group_restricted_common_ports, + control.vpc_subnet_multi_az_enabled, + control.vpc_subnet_public_and_private, + control.workspaces_workspace_volume_encryption_enabled ] tags = merge(local.other_common_tags, {