From 35d4d1ca548f6c1338af362db0e91f104e6ed24d Mon Sep 17 00:00:00 2001 From: Chris Mazanec Date: Mon, 15 Aug 2022 10:14:42 +0200 Subject: [PATCH 1/2] - conditional resources creation based `enable_log_to_s3` and `enable_log_to_cloudwatch` - hardening iam - writing to our log groups only - all data resources moved into data.tf - adding missing description for outputs and roles - regenerated README - simplified `ssm_role` IAM role - general cleanup and consolidation - unified IAM polices -> terraform format - fixed access logs being written to the session bucket instead of access log bucket (target_bucket = aws_s3_bucket.access_logs_bucket[0].id) https://github.com/bridgecrewio/terraform-aws-session-manager/pull/15 - unify naming - `access_log_bucket_name` -> `access_logs_bucket_name` (`access_log_bucket` was not in use anyway ) --- README.md | 87 ++++++++------- aws_s3_bucket.access_log_bucket.tf | 62 ----------- aws_s3_bucket.access_logs_bucket.tf | 60 +++++++++++ aws_s3_bucket.session_logs_bucket.tf | 31 +++--- data.tf | 25 +++++ example/examplea/module.ssm.tf | 2 +- example/examplea/outputs.tf | 4 +- iam.tf | 153 ++++++++++++--------------- main.tf | 18 ++-- outputs.tf | 41 ++++--- variables.tf | 12 ++- vpce.tf | 15 --- 12 files changed, 261 insertions(+), 249 deletions(-) delete mode 100644 aws_s3_bucket.access_log_bucket.tf create mode 100644 aws_s3_bucket.access_logs_bucket.tf diff --git a/README.md b/README.md index b63ab8d..ae85e94 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A Terraform module to setup [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html). -This module creates the a SSM document to support encrypted session manager communication and logs. It also creates a KMS key, S3 bucket, and CloudWatch Log group to store logs. In addition, for EC2 instances without a public IP address it can create VPC endpoints to enable private session manager communication. However, the VPC endpoint creation can also be facilitated by other modules such as [this](https://github.com/terraform-aws-modules/terraform-aws-vpc). Be aware of the [AWS PrivateLink pricing](https://aws.amazon.com/privatelink/pricing/) before deployment. +This module creates the a SSM document to support encrypted session manager communication and logs. It also creates a KMS key, S3 bucket, and CloudWatch Log group to store logs. In addition, for EC2 instances without a public IP address it can create VPC endpoints to enable private session manager communication. However, the VPC endpoint creation can also be facilitated by other modules such as [this](https://github.com/terraform-aws-modules/terraform-aws-vpc). Be aware of the [AWS PrivateLink pricing](https://aws.amazon.com/privatelink/pricing/) before deployment. ## Usage @@ -15,7 +15,7 @@ module "ssm" { source = "bridgecrewio/session-manager/aws" version = "0.2.0" bucket_name = "my-session-logs" - access_log_bucket_name = "my-session-access-logs" + access_logs_bucket_name = "my-session-access-logs" enable_log_to_s3 = true enable_log_to_cloudwatch = true linux_shell_profile = "date" @@ -29,18 +29,28 @@ module "ssm" { source = "bridgecrewio/session-manager/aws" version = "0.2.0" bucket_name = "my-session-logs" - access_log_bucket_name = "my-session-access-logs" + access_logs_bucket_name = "my-session-access-logs" vpc_id = "vpc-0dc9ef19c0c23aeaa" - tags = { - Function = "ssm" - } enable_log_to_s3 = true enable_log_to_cloudwatch = true vpc_endpoints_enabled = true + tags = { + Function = "ssm" + } } ``` -This module does not create any IAM policies for access to session manager. To do that, look at example policies in the [AWS Documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-restrict-access-quickstart.html) +This module does not create any IAM policies for access to session manager. To do that, look at example policies in the [AWS Documentation](https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-restrict-access-quickstart.html) + +## Notes + +In case `Session Manager` has already been accessed using AWS console, `SSM-SessionManagerRunShell` document - which is otherwise managed by this module - may need to be deleted prior to running this module. The following error may occur if the document has not been deleted upfront: + +`Error: Error creating SSM document: DocumentAlreadyExists: Document with same name SSM-SessionManagerRunShell already exists` + +To delete the document, issue: + +`aws ssm delete-document --name SSM-SessionManagerRunShell` ## Requirements @@ -66,24 +76,22 @@ No modules. |------|------| | [aws_cloudwatch_log_group.session_manager_log_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | | [aws_iam_instance_profile.ssm_profile](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | -| [aws_iam_policy.ssm_s3_cwl_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.ssm_s3_cwl_kms_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_role.ssm_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy_attachment.SSM-role-policy-attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.SSM-s3-cwl-policy-attach](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_kms_alias.ssmkey](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | | [aws_kms_key.ssmkey](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | -| [aws_s3_bucket.access_log_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket.access_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket.session_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | -| [aws_s3_bucket_acl.access_log_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_acl.access_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | | [aws_s3_bucket_acl.session_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | -| [aws_s3_bucket_lifecycle_configuration.access_log_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.access_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_lifecycle_configuration.session_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.session_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | -| [aws_s3_bucket_public_access_block.access_log_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_public_access_block.access_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.session_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | -| [aws_s3_bucket_server_side_encryption_configuration.access_log_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.access_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.session_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | -| [aws_s3_bucket_versioning.access_log_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_bucket_versioning.access_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_versioning.session_logs_bucket](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_security_group.ssm_sg](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_ssm_document.session_manager_prefs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_document) | resource | @@ -96,29 +104,28 @@ No modules. | [aws_vpc_endpoint_route_table_association.private_s3_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint_route_table_association) | resource | | [aws_vpc_endpoint_route_table_association.private_s3_subnet_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_endpoint_route_table_association) | resource | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | -| [aws_iam_policy.AmazonSSMManagedInstanceCore](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy) | data source | -| [aws_iam_policy_document.kms_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | -| [aws_iam_policy_document.ssm_s3_cwl_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.kms_key_default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.ssm_s3_cwl_kms_access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | | [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | | [aws_route_table.selected](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route_table) | data source | -| [aws_subnet_ids.selected](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet_ids) | data source | +| [aws_subnets.selected](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnets) | data source | | [aws_vpc.selected](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [access\_log\_bucket\_name](#input\_access\_log\_bucket\_name) | Name prefix of S3 bucket to store access logs from session logs bucket | `string` | n/a | yes | | [access\_log\_expire\_days](#input\_access\_log\_expire\_days) | Number of days to wait before deleting access logs | `number` | `30` | no | -| [bucket\_name](#input\_bucket\_name) | Name prefix of S3 bucket to store session logs | `string` | n/a | yes | +| [access\_logs\_bucket\_name](#input\_access\_logs\_bucket\_name) | Name prefix of S3 bucket to store access logs from session logs bucket | `string` | `"ssm-session-access-logs"` | no | +| [bucket\_name](#input\_bucket\_name) | Name prefix of S3 bucket to store session logs | `string` | `"ssm-session-logs"` | no | | [cloudwatch\_log\_group\_name](#input\_cloudwatch\_log\_group\_name) | Name of the CloudWatch Log Group for storing SSM Session Logs | `string` | `"/ssm/session-logs"` | no | | [cloudwatch\_logs\_retention](#input\_cloudwatch\_logs\_retention) | Number of days to retain Session Logs in CloudWatch | `number` | `30` | no | | [enable\_log\_to\_cloudwatch](#input\_enable\_log\_to\_cloudwatch) | Enable Session Manager to Log to CloudWatch Logs | `bool` | `true` | no | | [enable\_log\_to\_s3](#input\_enable\_log\_to\_s3) | Enable Session Manager to Log to S3 | `bool` | `true` | no | -| [kms\_key\_alias](#input\_kms\_key\_alias) | Alias prefix of the KMS key. Must start with alias/ followed by a name | `string` | `"alias/ssm-key"` | no | -| [kms\_key\_deletion\_window](#input\_kms\_key\_deletion\_window) | Waiting period for scheduled KMS Key deletion. Can be 7-30 days. | `number` | `7` | no | -| [linux\_shell\_profile](#input\_linux\_shell\_profile) | The ShellProfile to use for linux based machines. | `string` | `""` | no | +| [kms\_key\_alias](#input\_kms\_key\_alias) | Alias prefix of the KMS key. Must start with alias/ followed by a name | `string` | `"alias/ssm-key"` | no | +| [kms\_key\_deletion\_window](#input\_kms\_key\_deletion\_window) | Number of days to wait for scheduled KMS Key deletion [7-30] | `number` | `7` | no | +| [linux\_shell\_profile](#input\_linux\_shell\_profile) | The ShellProfile to use for Linux based machines | `string` | `""` | no | | [log\_archive\_days](#input\_log\_archive\_days) | Number of days to wait before archiving to Glacier | `number` | `30` | no | | [log\_expire\_days](#input\_log\_expire\_days) | Number of days to wait before deleting | `number` | `365` | no | | [subnet\_ids](#input\_subnet\_ids) | Subnet Ids to deploy endpoints into | `set(string)` | `[]` | no | @@ -126,30 +133,30 @@ No modules. | [vpc\_endpoint\_private\_dns\_enabled](#input\_vpc\_endpoint\_private\_dns\_enabled) | Enable private dns for endpoints | `bool` | `true` | no | | [vpc\_endpoints\_enabled](#input\_vpc\_endpoints\_enabled) | Create VPC Endpoints | `bool` | `false` | no | | [vpc\_id](#input\_vpc\_id) | VPC ID to deploy endpoints into | `string` | `null` | no | -| [windows\_shell\_profile](#input\_windows\_shell\_profile) | The ShellProfile to use for windows based machines. | `string` | `""` | no | +| [windows\_shell\_profile](#input\_windows\_shell\_profile) | The ShellProfile to use for Windows based machines | `string` | `""` | no | ## Outputs | Name | Description | |------|-------------| -| [access\_log\_bucket\_name](#output\_access\_log\_bucket\_name) | n/a | -| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | n/a | -| [iam\_profile\_name](#output\_iam\_profile\_name) | n/a | -| [iam\_role\_arn](#output\_iam\_role\_arn) | n/a | -| [kms\_key\_arn](#output\_kms\_key\_arn) | n/a | -| [session\_logs\_bucket\_name](#output\_session\_logs\_bucket\_name) | n/a | -| [ssm\_security\_group](#output\_ssm\_security\_group) | n/a | -| [vpc\_endpoint\_ec2messages](#output\_vpc\_endpoint\_ec2messages) | n/a | -| [vpc\_endpoint\_kms](#output\_vpc\_endpoint\_kms) | n/a | -| [vpc\_endpoint\_logs](#output\_vpc\_endpoint\_logs) | n/a | -| [vpc\_endpoint\_s3](#output\_vpc\_endpoint\_s3) | n/a | -| [vpc\_endpoint\_ssm](#output\_vpc\_endpoint\_ssm) | n/a | -| [vpc\_endpoint\_ssmmessages](#output\_vpc\_endpoint\_ssmmessages) | n/a | +| [access\_logs\_bucket\_name](#output\_access\_logs\_bucket\_name) | S3 bucket for S3 access logs | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | CloudWatch Log group for session logs | +| [iam\_profile\_name](#output\_iam\_profile\_name) | EC2 instance profile for SSM | +| [iam\_role\_arn](#output\_iam\_role\_arn) | IAM Role assumable by EC2 instances with access to SSM resources | +| [kms\_key\_arn](#output\_kms\_key\_arn) | KMS Key Arn for Encrypting logs and session | +| [session\_logs\_bucket\_name](#output\_session\_logs\_bucket\_name) | S3 bucket for session logs | +| [ssm\_security\_group](#output\_ssm\_security\_group) | Security Group used to access VPC Endpoints | +| [vpc\_endpoint\_ec2messages](#output\_vpc\_endpoint\_ec2messages) | VPC Endpoint for EC2 Messages | +| [vpc\_endpoint\_kms](#output\_vpc\_endpoint\_kms) | VPC Endpoint for KMS | +| [vpc\_endpoint\_logs](#output\_vpc\_endpoint\_logs) | VPC Endpoint for CloudWatch Logs | +| [vpc\_endpoint\_s3](#output\_vpc\_endpoint\_s3) | VPC Endpoint for S3 | +| [vpc\_endpoint\_ssm](#output\_vpc\_endpoint\_ssm) | VPC Endpoint for SSM | +| [vpc\_endpoint\_ssmmessages](#output\_vpc\_endpoint\_ssmmessages) | VPC Endpoint for SSM Messages | ## SSM Usage Example -* Launch an instance using the ssm_profile created by Terraform +* Launch an instance using the ssm\_profile created by Terraform * Install the session-manager-plugin and start a session ```bash diff --git a/aws_s3_bucket.access_log_bucket.tf b/aws_s3_bucket.access_log_bucket.tf deleted file mode 100644 index 6e8d6d5..0000000 --- a/aws_s3_bucket.access_log_bucket.tf +++ /dev/null @@ -1,62 +0,0 @@ -resource "aws_s3_bucket" "access_log_bucket" { - # checkov:skip=CKV_AWS_144: Cross region replication is overkill - # checkov:skip=CKV_AWS_18: - # checkov:skip=CKV_AWS_52: - # checkov:skip=CKV_AWS_145:v4 provider legacy - bucket_prefix = "${var.access_log_bucket_name}-" - force_destroy = true - - tags = var.tags - - -} - -resource "aws_s3_bucket_acl" "access_log_bucket" { - bucket = aws_s3_bucket.access_log_bucket.id - - acl = "log-delivery-write" -} - - -resource "aws_s3_bucket_versioning" "access_log_bucket" { - bucket = aws_s3_bucket.access_log_bucket.id - - versioning_configuration { - status = "Enabled" - } -} - - -resource "aws_s3_bucket_server_side_encryption_configuration" "access_log_bucket" { - bucket = aws_s3_bucket.access_log_bucket.id - - rule { - apply_server_side_encryption_by_default { - kms_master_key_id = aws_kms_key.ssmkey.arn - sse_algorithm = "aws:kms" - } - } -} - - -resource "aws_s3_bucket_lifecycle_configuration" "access_log_bucket" { - bucket = aws_s3_bucket.access_log_bucket.id - - rule { - id = "delete_after_X_days" - status = "Enabled" - - expiration { - days = var.access_log_expire_days - } - } -} - - -resource "aws_s3_bucket_public_access_block" "access_log_bucket" { - bucket = aws_s3_bucket.access_log_bucket.id - block_public_acls = true - block_public_policy = true - ignore_public_acls = true - restrict_public_buckets = true -} diff --git a/aws_s3_bucket.access_logs_bucket.tf b/aws_s3_bucket.access_logs_bucket.tf new file mode 100644 index 0000000..e809d87 --- /dev/null +++ b/aws_s3_bucket.access_logs_bucket.tf @@ -0,0 +1,60 @@ +resource "aws_s3_bucket" "access_logs_bucket" { + # checkov:skip=CKV_AWS_144: Cross region replication is overkill + # checkov:skip=CKV_AWS_18: + # checkov:skip=CKV_AWS_52: + # checkov:skip=CKV_AWS_145:v4 provider legacy + count = var.enable_log_to_s3 ? 1 : 0 + bucket_prefix = "${var.access_logs_bucket_name}-" + force_destroy = true + tags = var.tags +} + +resource "aws_s3_bucket_acl" "access_logs_bucket" { + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.access_logs_bucket[0].id + acl = "log-delivery-write" +} + +resource "aws_s3_bucket_versioning" "access_logs_bucket" { + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.access_logs_bucket[0].id + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "access_logs_bucket" { + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.access_logs_bucket[0].id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.ssmkey.arn + sse_algorithm = "aws:kms" + } + } +} + +resource "aws_s3_bucket_lifecycle_configuration" "access_logs_bucket" { + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.access_logs_bucket[0].id + + rule { + id = "delete_after_X_days" + status = "Enabled" + + expiration { + days = var.access_log_expire_days + } + } +} + +resource "aws_s3_bucket_public_access_block" "access_logs_bucket" { + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.access_logs_bucket[0].id + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} diff --git a/aws_s3_bucket.session_logs_bucket.tf b/aws_s3_bucket.session_logs_bucket.tf index f1cd0a2..96fa4e7 100644 --- a/aws_s3_bucket.session_logs_bucket.tf +++ b/aws_s3_bucket.session_logs_bucket.tf @@ -2,30 +2,31 @@ resource "aws_s3_bucket" "session_logs_bucket" { # checkov:skip=CKV_AWS_144: Cross region replication overkill # checkov:skip=CKV_AWS_52: # checkov:skip=CKV_AWS_145:v4 provider legacy + count = var.enable_log_to_s3 ? 1 : 0 bucket_prefix = "${var.bucket_name}-" force_destroy = true tags = var.tags - } -resource "aws_s3_bucket_acl" "session_logs_bucket" { - bucket = aws_s3_bucket.session_logs_bucket.id - acl = "private" +resource "aws_s3_bucket_acl" "session_logs_bucket" { + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.session_logs_bucket[0].id + acl = "private" } - resource "aws_s3_bucket_versioning" "session_logs_bucket" { - bucket = aws_s3_bucket.session_logs_bucket.id + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.session_logs_bucket[0].id versioning_configuration { status = "Enabled" } } - resource "aws_s3_bucket_server_side_encryption_configuration" "session_logs_bucket" { - bucket = aws_s3_bucket.session_logs_bucket.id + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.session_logs_bucket[0].id rule { apply_server_side_encryption_by_default { @@ -35,9 +36,9 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "session_logs_buck } } - resource "aws_s3_bucket_lifecycle_configuration" "session_logs_bucket" { - bucket = aws_s3_bucket.session_logs_bucket.id + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.session_logs_bucket[0].id rule { id = "archive_after_X_days" @@ -54,17 +55,17 @@ resource "aws_s3_bucket_lifecycle_configuration" "session_logs_bucket" { } } - resource "aws_s3_bucket_logging" "session_logs_bucket" { - bucket = aws_s3_bucket.session_logs_bucket.id + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.session_logs_bucket[0].id - target_bucket = aws_s3_bucket.session_logs_bucket.id + target_bucket = aws_s3_bucket.access_logs_bucket[0].id target_prefix = "log/" } - resource "aws_s3_bucket_public_access_block" "session_logs_bucket" { - bucket = aws_s3_bucket.session_logs_bucket.id + count = var.enable_log_to_s3 ? 1 : 0 + bucket = aws_s3_bucket.session_logs_bucket[0].id block_public_acls = true block_public_policy = true ignore_public_acls = true diff --git a/data.tf b/data.tf index 3d32652..236be0c 100644 --- a/data.tf +++ b/data.tf @@ -1,4 +1,29 @@ +locals { + region = var.vpc_endpoints_enabled && var.vpc_id != null ? split(":", data.aws_vpc.selected[0].arn)[3] : data.aws_region.current.name + subnets = var.vpc_endpoints_enabled ? var.subnet_ids != [] ? var.subnet_ids : data.aws_subnets.selected[0].ids : [] +} + +data "aws_caller_identity" "current" {} + +data "aws_region" "current" {} + +data "aws_partition" "current" {} + data "aws_vpc" "selected" { count = var.vpc_endpoints_enabled ? 1 : 0 id = var.vpc_id } + +data "aws_subnets" "selected" { + count = var.vpc_endpoints_enabled ? 1 : 0 + filter { + name = "vpc-id" + values = [var.vpc_id] + } +} + +data "aws_route_table" "selected" { + count = var.vpc_endpoints_enabled ? length(local.subnets) : 0 + subnet_id = sort(local.subnets)[count.index] +} + diff --git a/example/examplea/module.ssm.tf b/example/examplea/module.ssm.tf index 7ca2838..6184798 100644 --- a/example/examplea/module.ssm.tf +++ b/example/examplea/module.ssm.tf @@ -1,7 +1,7 @@ module "ssm" { source = "../../" bucket_name = "my-session-logs" - access_log_bucket_name = "my-session-access-logs" + access_logs_bucket_name = "my-session-access-logs" enable_log_to_s3 = true enable_log_to_cloudwatch = true linux_shell_profile = "date" diff --git a/example/examplea/outputs.tf b/example/examplea/outputs.tf index 42904be..2ae39f3 100644 --- a/example/examplea/outputs.tf +++ b/example/examplea/outputs.tf @@ -2,8 +2,8 @@ output "session_logs_bucket_name" { value = module.ssm.session_logs_bucket_name } -output "access_log_bucket_name" { - value = module.ssm.access_log_bucket_name +output "access_logs_bucket_name" { + value = module.ssm.access_logs_bucket_name } output "cloudwatch_log_group_arn" { diff --git a/iam.tf b/iam.tf index 6ff3333..6699988 100644 --- a/iam.tf +++ b/iam.tf @@ -1,21 +1,17 @@ - -data "aws_iam_policy_document" "kms_access" { +data "aws_iam_policy_document" "kms_key_default" { # checkov:skip=CKV_AWS_111: todo reduce perms on key # checkov:skip=CKV_AWS_109: ADD REASON statement { - sid = "KMS Key Default" + sid = "KMS Key Default" + actions = ["kms:*"] principals { type = "AWS" identifiers = ["arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"] } - actions = [ - "kms:*", - ] - resources = ["*"] - } + # allow CloudWatch to use this key statement { sid = "CloudWatchLogsEncryption" principals { @@ -29,118 +25,107 @@ data "aws_iam_policy_document" "kms_access" { "kms:GenerateDataKey*", "kms:Describe*", ] - resources = ["*"] } - } - -#"kmsKeyId": "${aws_kms_key.ssmkey.key_id}", -#"kmsKeyId": "${aws_kms_key.ssmkey.arn}", - # Create EC2 Instance Role resource "aws_iam_role" "ssm_role" { name_prefix = "ssm_role-" + description = "Allows access to SSM resources" path = "/" tags = var.tags - assume_role_policy = < Date: Mon, 15 Aug 2022 11:36:59 +0200 Subject: [PATCH 2/2] - bugfix; `var.linux_shell_profile == "" ? var.linux_shell_profile : ""` -> always return empty string - simplification of `session_manager_prefs` `aws_ssm_document` - use bool values directly - added new variables/module options to better cover SSM preferences : `bucket_key_prefix`, `idle_session_timeout`, `max_session_duration`, `run_as_default_user`, `enable_run_as` - added new outputs: `iam_policy_arn`, `document_name`, `document_arn` - regenerated `README.md` --- README.md | 8 ++++++ data.tf | 1 - main.tf | 15 ++++++++--- outputs.tf | 15 +++++++++++ variables.tf | 75 +++++++++++++++++++++++++++++++++++++--------------- 5 files changed, 87 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index ae85e94..98247a8 100644 --- a/README.md +++ b/README.md @@ -118,16 +118,21 @@ No modules. |------|-------------|------|---------|:--------:| | [access\_log\_expire\_days](#input\_access\_log\_expire\_days) | Number of days to wait before deleting access logs | `number` | `30` | no | | [access\_logs\_bucket\_name](#input\_access\_logs\_bucket\_name) | Name prefix of S3 bucket to store access logs from session logs bucket | `string` | `"ssm-session-access-logs"` | no | +| [bucket\_key\_prefix](#input\_bucket\_key\_prefix) | Name of S3 sub-folder (prefix) | `string` | `""` | no | | [bucket\_name](#input\_bucket\_name) | Name prefix of S3 bucket to store session logs | `string` | `"ssm-session-logs"` | no | | [cloudwatch\_log\_group\_name](#input\_cloudwatch\_log\_group\_name) | Name of the CloudWatch Log Group for storing SSM Session Logs | `string` | `"/ssm/session-logs"` | no | | [cloudwatch\_logs\_retention](#input\_cloudwatch\_logs\_retention) | Number of days to retain Session Logs in CloudWatch | `number` | `30` | no | | [enable\_log\_to\_cloudwatch](#input\_enable\_log\_to\_cloudwatch) | Enable Session Manager to Log to CloudWatch Logs | `bool` | `true` | no | | [enable\_log\_to\_s3](#input\_enable\_log\_to\_s3) | Enable Session Manager to Log to S3 | `bool` | `true` | no | +| [enable\_run\_as](#input\_enable\_run\_as) | Enable Run As support for Linux instances | `bool` | `false` | no | +| [idle\_session\_timeout](#input\_idle\_session\_timeout) | Number of minutes a user can be inactive before a session ends [1-60] | `number` | `20` | no | | [kms\_key\_alias](#input\_kms\_key\_alias) | Alias prefix of the KMS key. Must start with alias/ followed by a name | `string` | `"alias/ssm-key"` | no | | [kms\_key\_deletion\_window](#input\_kms\_key\_deletion\_window) | Number of days to wait for scheduled KMS Key deletion [7-30] | `number` | `7` | no | | [linux\_shell\_profile](#input\_linux\_shell\_profile) | The ShellProfile to use for Linux based machines | `string` | `""` | no | | [log\_archive\_days](#input\_log\_archive\_days) | Number of days to wait before archiving to Glacier | `number` | `30` | no | | [log\_expire\_days](#input\_log\_expire\_days) | Number of days to wait before deleting | `number` | `365` | no | +| [max\_session\_duration](#input\_max\_session\_duration) | Number of minutes before a session ends (default: infinite) [1-1440] | `number` | `-1` | no | +| [run\_as\_default\_user](#input\_run\_as\_default\_user) | OS default user name, if IAM user/role 'SSMSessionRunAs' tag is undefined | `string` | `"ssm-user"` | no | | [subnet\_ids](#input\_subnet\_ids) | Subnet Ids to deploy endpoints into | `set(string)` | `[]` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | | [vpc\_endpoint\_private\_dns\_enabled](#input\_vpc\_endpoint\_private\_dns\_enabled) | Enable private dns for endpoints | `bool` | `true` | no | @@ -141,6 +146,9 @@ No modules. |------|-------------| | [access\_logs\_bucket\_name](#output\_access\_logs\_bucket\_name) | S3 bucket for S3 access logs | | [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | CloudWatch Log group for session logs | +| [document\_arn](#output\_document\_arn) | ARN of the created document. This can be used to create IAM policies that prevent changes to session manager preferences | +| [document\_name](#output\_document\_name) | Name of the created document | +| [iam\_policy\_arn](#output\_iam\_policy\_arn) | IAM Policy for EC2 instances with access to SSM resources | | [iam\_profile\_name](#output\_iam\_profile\_name) | EC2 instance profile for SSM | | [iam\_role\_arn](#output\_iam\_role\_arn) | IAM Role assumable by EC2 instances with access to SSM resources | | [kms\_key\_arn](#output\_kms\_key\_arn) | KMS Key Arn for Encrypting logs and session | diff --git a/data.tf b/data.tf index 236be0c..5c27ec3 100644 --- a/data.tf +++ b/data.tf @@ -26,4 +26,3 @@ data "aws_route_table" "selected" { count = var.vpc_endpoints_enabled ? length(local.subnets) : 0 subnet_id = sort(local.subnets)[count.index] } - diff --git a/main.tf b/main.tf index 6a434ec..263e98c 100644 --- a/main.tf +++ b/main.tf @@ -25,6 +25,7 @@ resource "aws_ssm_document" "session_manager_prefs" { document_format = "JSON" tags = var.tags + # https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-schema.html # https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-configure-preferences-cli.html content = jsonencode({ schemaVersion = "1.0" @@ -32,13 +33,19 @@ resource "aws_ssm_document" "session_manager_prefs" { sessionType = "Standard_Stream" inputs = { s3BucketName = var.enable_log_to_s3 ? aws_s3_bucket.session_logs_bucket[0].id : "" - s3EncryptionEnabled = var.enable_log_to_s3 ? "true" : "false" + s3KeyPrefix = var.enable_log_to_s3 ? var.bucket_key_prefix : "" + s3EncryptionEnabled = var.enable_log_to_s3 cloudWatchLogGroupName = var.enable_log_to_cloudwatch ? aws_cloudwatch_log_group.session_manager_log_group[0].name : "" - cloudWatchEncryptionEnabled = var.enable_log_to_cloudwatch ? "true" : "false" + cloudWatchEncryptionEnabled = var.enable_log_to_cloudwatch + cloudWatchStreamingEnabled = var.enable_log_to_cloudwatch kmsKeyId = aws_kms_key.ssmkey.key_id + runAsEnabled = var.enable_run_as + runAsDefaultUser = var.enable_run_as ? var.run_as_default_user : "" + idleSessionTimeout = var.idle_session_timeout + maxSessionDuration = var.max_session_duration != -1 ? var.max_session_duration : "" shellProfile = { - linux = var.linux_shell_profile == "" ? var.linux_shell_profile : "" - windows = var.windows_shell_profile == "" ? var.windows_shell_profile : "" + linux = var.linux_shell_profile + windows = var.windows_shell_profile } } }) diff --git a/outputs.tf b/outputs.tf index 264afb8..24f21e0 100644 --- a/outputs.tf +++ b/outputs.tf @@ -18,6 +18,11 @@ output "kms_key_arn" { value = aws_kms_key.ssmkey.arn } +output "iam_policy_arn" { + description = "IAM Policy for EC2 instances with access to SSM resources" + value = aws_iam_policy.ssm_s3_cwl_kms_access.arn +} + output "iam_role_arn" { description = "IAM Role assumable by EC2 instances with access to SSM resources" value = aws_iam_role.ssm_role.arn @@ -28,6 +33,16 @@ output "iam_profile_name" { value = aws_iam_instance_profile.ssm_profile.name } +output "document_name" { + description = "Name of the created document" + value = aws_ssm_document.session_manager_prefs.name +} + +output "document_arn" { + description = "ARN of the created document. This can be used to create IAM policies that prevent changes to session manager preferences" + value = aws_ssm_document.session_manager_prefs.arn +} + output "ssm_security_group" { description = "Security Group used to access VPC Endpoints" value = aws_security_group.ssm_sg.*.id diff --git a/variables.tf b/variables.tf index 3bd3935..a8b3ef6 100644 --- a/variables.tf +++ b/variables.tf @@ -4,6 +4,12 @@ variable "bucket_name" { default = "ssm-session-logs" } +variable "bucket_key_prefix" { + description = "Name of S3 sub-folder (prefix)" + type = string + default = "" +} + variable "log_archive_days" { description = "Number of days to wait before archiving to Glacier" type = number @@ -52,28 +58,34 @@ variable "cloudwatch_log_group_name" { default = "/ssm/session-logs" } -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} +variable "idle_session_timeout" { + description = "Number of minutes a user can be inactive before a session ends [1-60]" + type = number + default = 20 } -variable "vpc_id" { - description = "VPC ID to deploy endpoints into" +variable "max_session_duration" { + description = "Number of minutes before a session ends (default: infinite) [1-1440]" + type = number + default = -1 +} + +variable "run_as_default_user" { + description = "OS default user name, if IAM user/role 'SSMSessionRunAs' tag is undefined" type = string - default = null + default = "ssm-user" } -variable "subnet_ids" { - description = "Subnet Ids to deploy endpoints into" - type = set(string) - default = [] +variable "linux_shell_profile" { + description = "The ShellProfile to use for Linux based machines" + default = "" + type = string } -variable "vpc_endpoint_private_dns_enabled" { - description = "Enable private dns for endpoints" - type = bool - default = true +variable "windows_shell_profile" { + description = "The ShellProfile to use for Windows based machines" + default = "" + type = string } variable "enable_log_to_s3" { @@ -88,20 +100,39 @@ variable "enable_log_to_cloudwatch" { default = true } +variable "enable_run_as" { + description = "Enable Run As support for Linux instances" + type = bool + default = false +} + +variable "vpc_endpoint_private_dns_enabled" { + description = "Enable private dns for endpoints" + type = bool + default = true +} + variable "vpc_endpoints_enabled" { description = "Create VPC Endpoints" type = bool default = false } -variable "linux_shell_profile" { - description = "The ShellProfile to use for Linux based machines" - default = "" +variable "vpc_id" { + description = "VPC ID to deploy endpoints into" type = string + default = null } -variable "windows_shell_profile" { - description = "The ShellProfile to use for Windows based machines" - default = "" - type = string +variable "subnet_ids" { + description = "Subnet Ids to deploy endpoints into" + type = set(string) + default = [] } + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} +