Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
90 changes: 59 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Terraform AWS Session Manager

A Terraform module to setup [AWS Systems Manager Session Manager](https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html).
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

Expand All @@ -29,62 +29,90 @@ module "ssm" {
version = "0.2.0"
bucket_name = "my-session-logs"
access_log_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
vpc_id = "vpc-0dc9ef19c0c23aeaa"
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

| Name | Version |
|------|---------|
| terraform | >= 0.12 |
| aws | >= 1.36 |

## Providers

| Name | Version |
|------|---------|
| aws | >= 1.36 |

## Inputs

Below is a list of this modules input values:

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:-----:|
| bucket\_name | Name of S3 bucket to store session logs | `string` | | yes |
| bucket\_name | Name of S3 bucket to store session logs | `string` | `ssm-session-logs-<account-id>-<region-id>` | no |
| bucket\_key\_prefix | Name of S3 sub-folder (prefix) | `string` | | no |
| log\_archive\_days | Number of days to wait before archiving to Glacier | `number` | `30` | no |
| log\_expire\_days | Number of days to wait before deleting session logs | `number` | `365` | no |
| access\_log\_bucket\_name | Name of the S3 bucket to store bucket access logs | `string` | | yes |
| access\_log\_bucket\_name | Name of the S3 bucket to store bucket access logs | `string` | `ssm-session-access-logs-<account-id>-<region-id>` | no |
| access\_log\_expire\_days | Number of days to wait before deleting access logs | `number` | `30` | no |
| kms\_key\_deletion\_window | Waiting period for scheduled KMS Key deletion. Can be 7-30 days | `number` | `7` | no |
| kms\_key\_alias | Alias of the KMS key. Must start with alias/ followed by a name | `string` | `alias/ssm-key` | no |
| kms\_key\_deletion\_window | Number of days to wait for scheduled KMS Key deletion [7-30] | `number` | `7` | no |
| kms\_key\_alias | Alias of the KMS key. Must start with alias/ followed by a name | `string` | `alias/ssm-key` | no |
| cloudwatch\_logs\_retention | Number of days to retain Session Logs in CloudWatch | `number` | `30` | no |
| cloudwatch\_log\_group\_name | Name of the CloudWatch Log Group for storing SSM Session Logs | `string` | `/ssm/session-logs` | no |
| tags | A map of tags to add to all resources | `map(string)` | `{}` | no |
| vpc\_id | VPC ID to deploy endpoints to | `string` | `null` | no |
| idle\_session\_timeout| Number of minutes a user can be inactive before a session ends [1-60] | `number` | `20` | no |
| run\_as\_default\_user | OS default user name, if IAM user/role 'SSMSessionRunAs' tag is undefined | `string` | `ssm-user` | no |
| shell\_profile\_windows | Environment variables, shell preferences, or commands to run when session starts | `string` | | no |
| shell\_profile\_linux | Environment variables, shell preferences, or commands to run when session starts | `string` | | no |
| enable\_log\_to\_s3 | Enable Session Manager to Log to S3 | `bool` | `true` | no |
| enable\_log\_to\_cloudwatch | Enable Session Manager to Log to CloudWatch Logs | `bool` | `true` | no |
| enable\_run\_as\_user | Enable Run As support for Linux instances | `bool` | `false` | no |
| vpc\_endpoints\_enabled | Create VPC Endpoints | `bool` | `false` | no |


| vpc\_id | VPC ID to deploy endpoints to | `string` | `null` | no |
| tags | A map of tags to add to all resources | `map(string)` | `{}` | no |

## Outputs
| Name | Example Value | Description |
|------|----------------|-------------|
| session_logs_bucket_name | my-session-logs | S3 bucket for session logs |
| access_log_bucket_name | my-session-access-logs | S3 bucket for S3 access logs |
| cloudwatch_log_group_arn | arn:aws:logs:us-west-2:123456789012:log-group:/ssm/session-logs:* | CloudWatch Log group for session logs |
| kms_key_arn | arn:aws:kms:us-west-2:123456789012:key/2320fbba-d4e5-420d-82d3-1a4d6b8605e8 | KMS Key Arn for Encrypting logs and session |
| iam_role_arn | arn:aws:iam::123456789012:role/ssm_role | IAM Role for EC2 instances |
| iam_profile_name | ssm_profile | EC2 instance profile for SSM |
| ssm_security_group | ["sg-05e4f4cf12db5a191"] | Security Group used to access VPC Endpoints |
| vpc_endpoint_ssm | ["vpce-0cefc23e81d365733"] | VPC Endpoint for SSM |
| vpc_endpoint_ec2messages | ["vpce-0f507468fb9b06b8b"] | VPC Endpoint for EC2 Messages |
| vpc_endpoint_ssmmessages | ["vpce-0fe2cb670d40ec053"] | VPC Endpoint for SSM Messages |
| vpc_endpoint_s3 | ["vpce-0a8ebde94fa301a4a"] | VPC Endpoint for S3 |
| vpc_endpoint_logs | ["vpce-08c90d8df9ef37f90"] | VPC Endpoint for CloudWatch Logs |
| vpc_endpoint_kms | ["vpce-07ddc11beac1d4a3f"] | VPC Endpoint for KMS |

| Name | Example Value | Description |
|------|----------------|-------------|
| session\_logs\_bucket\_name | my-session-logs | S3 bucket for session logs |
| access\_log\_bucket\_name | my-session-access-logs | S3 bucket for S3 access logs |
| cloudwatch\_log\_group\_arn | arn:aws:logs:us-west-2:123456789012:log-group:/ssm/session-logs:\* | CloudWatch Log group for session logs |
| kms\_key\_arn | arn:aws:kms:us-west-2:123456789012:key/2320fbba-d4e5-420d-82d3-1a4d6b8605e8 | KMS Key Arn for Encrypting logs and session |
| iam\_policy\_arn | arn:aws:iam::123456789012:policy/ssm\_s3\_cwl\_kms\_access\_us-east-1 | IAM Policy for EC2 instances |
| iam\_role\_arn | arn:aws:iam::123456789012:role/ssm\_role\_us-east-1 | IAM Role for EC2 instances |
| iam\_profile\_name | ssm\_profile\_us-east-1 | EC2 instance profile for SSM |
| ssm\_security\_group | ["sg-05e4f4cf12db5a191"] | Security Group used to access VPC Endpoints |
| vpc\_endpoint\_ssm | ["vpce-0cefc23e81d365733"] | VPC Endpoint for SSM |
| vpc\_endpoint\_ec2messages | ["vpce-0f507468fb9b06b8b"] | VPC Endpoint for EC2 Messages |
| vpc\_endpoint\_ssmmessages | ["vpce-0fe2cb670d40ec053"] | VPC Endpoint for SSM Messages |
| vpc\_endpoint\_s3 | ["vpce-0a8ebde94fa301a4a"] | VPC Endpoint for S3 |
| vpc\_endpoint\_logs | ["vpce-08c90d8df9ef37f90"] | VPC Endpoint for CloudWatch Logs |
| vpc\_endpoint\_kms | ["vpce-07ddc11beac1d4a3f"] | VPC Endpoint for KMS |

## SSM Usage Example

* Launch an instance using the ssm_profile created by Terraform
* Launch an instance using the ssm\_profile created by Terraform

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you clarify what you mean by 'launch an instance using a profile'?

Does this only work for newly launched instances?

What is meant by an Instance Profile?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's been a while but my memory says that it refers to the instance profile that allows ssm actions for a given instance. You can apply that profile to existing insurances.

* Install the session-manager-plugin and start a session

```bash
Expand Down
150 changes: 79 additions & 71 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

resource "aws_s3_bucket" "session_logs_bucket" {
bucket = var.bucket_name
count = var.enable_log_to_s3 ? 1 : 0
bucket = var.bucket_name == "" ? "ssm-session-logs-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}" : var.bucket_name
acl = "private"
force_destroy = true
tags = var.tags
Expand Down Expand Up @@ -36,14 +37,15 @@ resource "aws_s3_bucket" "session_logs_bucket" {
}

logging {
target_bucket = aws_s3_bucket.access_log_bucket.id
target_bucket = aws_s3_bucket.access_log_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
Expand All @@ -52,11 +54,11 @@ resource "aws_s3_bucket_public_access_block" "session_logs_bucket" {


resource "aws_s3_bucket" "access_log_bucket" {
bucket = var.access_log_bucket_name
count = var.enable_log_to_s3 ? 1 : 0
bucket = var.access_log_bucket_name == "" ? "ssm-session-access-logs-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}" : var.access_log_bucket_name
acl = "log-delivery-write"
force_destroy = true

tags = var.tags
tags = var.tags

versioning {
enabled = true
Expand All @@ -82,14 +84,15 @@ resource "aws_s3_bucket" "access_log_bucket" {
}

resource "aws_s3_bucket_public_access_block" "access_log_bucket" {
bucket = aws_s3_bucket.access_log_bucket.id
count = var.enable_log_to_s3 ? 1 : 0
bucket = aws_s3_bucket.access_log_bucket[0].id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

data "aws_iam_policy_document" "kms_access" {
data "aws_iam_policy_document" "kms_key_default" {
statement {
sid = "KMS Key Default"
principals {
Expand Down Expand Up @@ -123,12 +126,11 @@ data "aws_iam_policy_document" "kms_access" {

}


resource "aws_kms_key" "ssmkey" {
description = "SSM Key"
description = "SSM key"
deletion_window_in_days = var.kms_key_deletion_window
enable_key_rotation = true
policy = data.aws_iam_policy_document.kms_access.json
policy = data.aws_iam_policy_document.kms_key_default.json
tags = var.tags
}

Expand All @@ -138,11 +140,11 @@ resource "aws_kms_alias" "ssmkey" {
}

resource "aws_cloudwatch_log_group" "session_manager_log_group" {
count = var.enable_log_to_cloudwatch ? 1 : 0
name = var.cloudwatch_log_group_name
retention_in_days = var.cloudwatch_logs_retention
kms_key_id = aws_kms_key.ssmkey.arn

tags = var.tags
tags = var.tags
}

resource "aws_ssm_document" "session_manager_prefs" {
Expand All @@ -151,30 +153,38 @@ resource "aws_ssm_document" "session_manager_prefs" {
document_format = "JSON"
tags = var.tags

# https://docs.aws.amazon.com/systems-manager/latest/userguide/getting-started-configure-preferences-cli.html
content = <<DOC
{
"schemaVersion": "1.0",
"description": "Document to hold regional settings for Session Manager",
"sessionType": "Standard_Stream",
"inputs": {
"s3BucketName": "${var.enable_log_to_s3 ? aws_s3_bucket.session_logs_bucket.id : ""}",
"s3BucketName": "${var.enable_log_to_s3 ? aws_s3_bucket.session_logs_bucket[0].id : ""}",
"s3KeyPrefix": "${var.enable_log_to_s3 ? var.bucket_key_prefix : ""}",
"s3EncryptionEnabled": ${var.enable_log_to_s3 ? "true" : "false"},
"cloudWatchLogGroupName": "${var.enable_log_to_cloudwatch ? var.cloudwatch_log_group_name : ""}",
"cloudWatchEncryptionEnabled": ${var.enable_log_to_cloudwatch ? "true" : "false"},
"kmsKeyId": "${aws_kms_key.ssmkey.key_id}"
"idleSessionTimeout": "${var.idle_session_timeout}",
"cloudWatchStreamingEnabled": true,
"kmsKeyId": "${aws_kms_key.ssmkey.key_id}",
"runAsEnabled": ${var.enable_run_as},
"runAsDefaultUser": "${var.enable_run_as ? var.run_as_default_user : ""}",
"shellProfile": {
"windows": "${var.shell_profile_windows}",
"linux": "${var.shell_profile_linux}"
}
}
}
DOC
}

#"kmsKeyId": "${aws_kms_key.ssmkey.key_id}",
#"kmsKeyId": "${aws_kms_key.ssmkey.arn}",

# Create EC2 Instance Role
# Create EC2 Instance Role
resource "aws_iam_role" "ssm_role" {
name = "ssm_role"
path = "/"
tags = var.tags
name = "ssm_role_${data.aws_region.current.name}"
description = "Allows access to SSM resources"
path = "/"
tags = var.tags

assume_role_policy = <<EOF
{
Expand All @@ -193,54 +203,52 @@ resource "aws_iam_role" "ssm_role" {
EOF
}

data "aws_iam_policy" "AmazonSSMManagedInstanceCore" {
arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

data "aws_iam_policy_document" "ssm_s3_cwl_access" {
data "aws_iam_policy_document" "ssm_s3_cwl_kms_access" {
# A custom policy for S3 bucket access
# https://docs.aws.amazon.com/en_us/systems-manager/latest/userguide/setup-instance-profile.html#instance-profile-custom-s3-policy
statement {
sid = "S3BucketAccessForSessionManager"

actions = [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:PutObjectVersionAcl",
]

resources = [
aws_s3_bucket.session_logs_bucket.arn,
"${aws_s3_bucket.session_logs_bucket.arn}/*",
]
dynamic "statement" {
for_each = var.enable_log_to_s3 ? [1] : []
content {
sid = "S3BucketAccessForSessionManager"
actions = [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:PutObjectVersionAcl",
]
resources = [
aws_s3_bucket.session_logs_bucket[0].arn,
"${aws_s3_bucket.session_logs_bucket[0].arn}/*",
]
}
}

statement {
sid = "S3EncryptionForSessionManager"

actions = [
"s3:GetEncryptionConfiguration",
]

resources = [
aws_s3_bucket.session_logs_bucket.arn
]
dynamic "statement" {
for_each = var.enable_log_to_s3 ? [1] : []
content {
sid = "S3EncryptionForSessionManager"
actions = [
"s3:GetEncryptionConfiguration",
]
resources = [
aws_s3_bucket.session_logs_bucket[0].arn
]
}
}


# A custom policy for CloudWatch Logs access
# https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/permissions-reference-cwl.html
statement {
sid = "CloudWatchLogsAccessForSessionManager"

actions = [
"logs:PutLogEvents",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
]

resources = ["*"]
dynamic "statement" {
for_each = var.enable_log_to_cloudwatch ? [1] : []
content {
sid = "CloudWatchLogsAccessForSessionManager"
actions = [
"logs:PutLogEvents",
"logs:CreateLogStream",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
]
resources = ["*"]
}
}

statement {
Expand All @@ -255,27 +263,27 @@ data "aws_iam_policy_document" "ssm_s3_cwl_access" {

resources = [aws_kms_key.ssmkey.arn]
}

}

resource "aws_iam_policy" "ssm_s3_cwl_access" {
name = "ssm_s3_cwl_access"
path = "/"
policy = data.aws_iam_policy_document.ssm_s3_cwl_access.json
resource "aws_iam_policy" "ssm_s3_cwl_kms_access" {
name = "ssm_s3_cwl_kms_access_${data.aws_region.current.name}"
description = "Allows access to SSM resources"
path = "/"
policy = data.aws_iam_policy_document.ssm_s3_cwl_kms_access.json
}

resource "aws_iam_role_policy_attachment" "SSM-role-policy-attach" {
role = aws_iam_role.ssm_role.name
policy_arn = data.aws_iam_policy.AmazonSSMManagedInstanceCore.arn
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

resource "aws_iam_role_policy_attachment" "SSM-s3-cwl-policy-attach" {
resource "aws_iam_role_policy_attachment" "SSM-s3-cwl-kms-policy-attach" {
role = aws_iam_role.ssm_role.name
policy_arn = aws_iam_policy.ssm_s3_cwl_access.arn
policy_arn = aws_iam_policy.ssm_s3_cwl_kms_access.arn
}

resource "aws_iam_instance_profile" "ssm_profile" {
name = "ssm_profile"
name = "ssm_profile_${data.aws_region.current.name}"
role = aws_iam_role.ssm_role.name
}

Loading