> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Cloudwatch log groups with cmk remediation

### Triage and Remediation

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Using Console" defaultOpen="true">
        Below are the exact console steps to ensure a CloudWatch Log Group is encrypted with a customer-managed KMS key (CMK).

        ***

        ## Prerequisites: Create or identify a CMK

        1. Sign in to the **AWS Management Console**.
        2. Go to **Key Management Service (KMS)**:
           * In the search bar, type **KMS**, choose **Key Management Service**.
        3. Create a new CMK (if you don’t already have one for logs):
           * In the left pane, choose **Customer managed keys**.
           * Click **Create key**.
           * Key type: **Symmetric**.
           * Key usage: **Encrypt and decrypt**.
           * Click **Next** and:
             * Set an alias (e.g., `alias/cloudwatch-logs-key`).
             * Choose key administrators and key users (IAM roles/users that need to write/read logs).
           * Complete the steps and click **Finish**.

        > Note: Ensure the IAM roles/services that write to CloudWatch Logs (e.g., Lambda, ECS, EC2, etc.) are added as **Key users** so they can use the CMK.

        ***

        ## Step-by-step: Encrypt an existing CloudWatch Log Group with CMK

        1. In the AWS console, go to **CloudWatch**.
        2. In the left navigation pane, select **Log groups**.
        3. Find and click the **log group** you want to encrypt.
        4. At the top right, choose **Actions** → **Edit** (or **Edit encryption** depending on UI).
        5. Under **Encryption**:
           * Check/enable **Encrypt log group** (if shown).
           * For **KMS key**, choose:
             * **Select KMS key** and pick your **customer-managed key** (e.g., `alias/cloudwatch-logs-key`), not `aws/logs` (the AWS-managed key).
        6. Click **Save changes**.

        CloudWatch Logs will now store new log data in that log group encrypted using your CMK.

        ***

        ## Step-by-step: Set CMK encryption by default for new log groups (optional)

        There is no global “default CMK for all log groups” setting in the console, but you can:

        * Create log groups manually (instead of auto-created), and during creation:
          1. In **CloudWatch → Log groups → Create log group**.
          2. Enter **Log group name**.
          3. Under **Encryption**, choose your CMK.
          4. Click **Create**.

        * Or enforce via automation (CloudFormation, Terraform, or a Lambda that:
          * Monitors for new log groups.
          * Calls `AssociateKmsKey` to attach your CMK to them.)

        ***

        ## Validate encryption

        1. In **CloudWatch → Log groups**, click the log group.
        2. Check the **Encryption** section:
           * It should show **KMS** with your CMK alias/ARN.
        3. Optionally, in **KMS → Customer managed keys**, select your key and:
           * Check **Key usage** and **CloudTrail** logs to confirm encryption operations.
      </Accordion>

      <Accordion title="Using CLI">
        Below are AWS CLI steps to ensure CloudWatch Log Groups are encrypted with a customer-managed KMS CMK.

        ***

        ### 1. (Optional) Create a KMS CMK for CloudWatch Logs

        If you don’t already have a CMK you want to use:

        ```bash theme={null}
        aws kms create-key \
          --description "CMK for CloudWatch Log Group encryption" \
          --key-usage ENCRYPT_DECRYPT \
          --origin AWS_KMS
        ```

        Note the `KeyId` from the output. You can also create an alias:

        ```bash theme={null}
        aws kms create-alias \
          --alias-name alias/cloudwatch-logs-cmk \
          --target-key-id <KEY_ID_FROM_PREVIOUS_COMMAND>
        ```

        You can then use either the `KeyId` or the alias ARN as the `--kms-key-id`.

        ***

        ### 2. Identify Log Groups Without CMK Encryption

        List all log groups:

        ```bash theme={null}
        aws logs describe-log-groups --output json
        ```

        Filter those without a `kmsKeyId` using `jq` (recommended):

        ```bash theme={null}
        aws logs describe-log-groups --output json \
          | jq -r '.logGroups[] | select(.kmsKeyId == null) | .logGroupName'
        ```

        This will output the names of log groups that are not using CMK encryption.

        ***

        ### 3. Associate a CMK with a Single Log Group

        Use `associate-kms-key` to enable CMK encryption:

        ```bash theme={null}
        aws logs associate-kms-key \
          --log-group-name "<LOG_GROUP_NAME>" \
          --kms-key-id "arn:aws:kms:<REGION>:<ACCOUNT_ID>:alias/cloudwatch-logs-cmk"
        ```

        Or directly with the KeyId/KeyArn:

        ```bash theme={null}
        aws logs associate-kms-key \
          --log-group-name "<LOG_GROUP_NAME>" \
          --kms-key-id "<KEY_ID_OR_KEY_ARN>"
        ```

        ***

        ### 4. Apply CMK Encryption to All Unencrypted Log Groups (Batch)

        Example Bash loop for all unencrypted log groups:

        ```bash theme={null}
        KMS_KEY_ARN="arn:aws:kms:<REGION>:<ACCOUNT_ID>:alias/cloudwatch-logs-cmk"

        for lg in $(aws logs describe-log-groups --output json \
          | jq -r '.logGroups[] | select(.kmsKeyId == null) | .logGroupName'); do
          echo "Associating KMS key with log group: $lg"
          aws logs associate-kms-key \
            --log-group-name "$lg" \
            --kms-key-id "$KMS_KEY_ARN"
        done
        ```

        ***

        ### 5. (Important) KMS Key Policy Permissions

        Ensure the CMK key policy allows CloudWatch Logs and any writers/readers to use it. Minimal example snippet in the KMS key policy:

        ```json theme={null}
        {
          "Sid": "Allow CloudWatch Logs to use the key",
          "Effect": "Allow",
          "Principal": {
            "Service": "logs.<REGION>.amazonaws.com"
          },
          "Action": [
            "kms:Encrypt",
            "kms:Decrypt",
            "kms:ReEncrypt*",
            "kms:GenerateDataKey*",
            "kms:DescribeKey"
          ],
          "Resource": "*"
        }
        ```

        Update policy via:

        ```bash theme={null}
        aws kms put-key-policy \
          --key-id "<KEY_ID_OR_ARN>" \
          --policy-name "default" \
          --policy file://kms-policy.json
        ```

        ***

        ### 6. Verify Encryption

        Check a specific log group:

        ```bash theme={null}
        aws logs describe-log-groups \
          --log-group-name-prefix "<LOG_GROUP_NAME>" \
          --output json \
          | jq '.logGroups[] | {logGroupName, kmsKeyId}'
        ```

        You should see `kmsKeyId` populated with the CMK ARN.
      </Accordion>

      <Accordion title="Using Python">
        Below is a concise, step‑by‑step way to remediate “CloudWatch Log Groups should be encrypted with CMK” using Python (boto3).

        ***

        ## 1. Prerequisites

        * Python 3.x
        * `boto3` installed:
          ```bash theme={null}
          pip install boto3
          ```
        * AWS credentials configured (via `~/.aws/credentials`, environment variables, or instance profile).
        * Permissions:
          * `logs:DescribeLogGroups`, `logs:AssociateKmsKey`
          * `kms:CreateKey`, `kms:DescribeKey`, `kms:ListAliases` (if creating/using CMK)

        ***

        ## 2. Option A – Use an Existing KMS CMK

        If you already have a KMS CMK (recommended), you just need its ARN or alias.

        ### 2.1. Find CMK by alias (optional helper)

        ```python theme={null}
        import boto3

        def get_kms_key_arn_by_alias(alias_name: str) -> str:
            """
            Returns the KMS key ARN for a given alias like 'alias/cloudwatch-logs'.
            """
            kms = boto3.client("kms")
            paginator = kms.get_paginator("list_aliases")
            for page in paginator.paginate():
                for alias in page.get("Aliases", []):
                    if alias.get("AliasName") == alias_name and "TargetKeyId" in alias:
                        key_id = alias["TargetKeyId"]
                        key = kms.describe_key(KeyId=key_id)
                        return key["KeyMetadata"]["Arn"]
            raise ValueError(f"Alias {alias_name} not found or has no target key.")
        ```

        ***

        ## 3. Option B – Create a New CMK for CloudWatch Logs

        ```python theme={null}
        import boto3
        import json

        def create_cloudwatch_logs_cmk(alias_name: str = "alias/cloudwatch-logs") -> str:
            kms = boto3.client("kms")

            # Optional: key policy allowing CloudWatch Logs to use this CMK
            # Adjust principals and conditions for your org requirements.
            account_id = boto3.client("sts").get_caller_identity()["Account"]
            region = boto3.session.Session().region_name

            key_policy = {
                "Version": "2012-10-17",
                "Statement": [
                    # Allow account root full access to the CMK
                    {
                        "Sid": "EnableRootPermissions",
                        "Effect": "Allow",
                        "Principal": {"AWS": f"arn:aws:iam::{account_id}:root"},
                        "Action": "kms:*",
                        "Resource": "*"
                    },
                    # Allow CloudWatch Logs to use the CMK for encryption/decryption
                    {
                        "Sid": "AllowCloudWatchLogsUseOfTheKey",
                        "Effect": "Allow",
                        "Principal": {"Service": "logs.amazonaws.com"},
                        "Action": [
                            "kms:Encrypt",
                            "kms:Decrypt",
                            "kms:ReEncrypt*",
                            "kms:GenerateDataKey*",
                            "kms:DescribeKey"
                        ],
                        "Resource": "*",
                        "Condition": {
                            "StringEquals": {
                                "kms:EncryptionContext:aws:logs:arn": f"arn:aws:logs:{region}:{account_id}:*"
                            }
                        }
                    }
                ]
            }

            response = kms.create_key(
                Policy=json.dumps(key_policy),
                Description="CMK for encrypting CloudWatch Log Groups",
                KeyUsage="ENCRYPT_DECRYPT",
                Origin="AWS_KMS"
            )

            key_id = response["KeyMetadata"]["KeyId"]

            # Create alias for easier future reference
            kms.create_alias(AliasName=alias_name, TargetKeyId=key_id)

            return response["KeyMetadata"]["Arn"]
        ```

        Call this once to create the key:

        ```python theme={null}
        cmk_arn = create_cloudwatch_logs_cmk("alias/cloudwatch-logs")
        print("Created CMK:", cmk_arn)
        ```

        ***

        ## 4. Associate CMK with All (or Selected) Log Groups

        This script:

        * Lists all CloudWatch log groups.
        * Identifies those without a `kmsKeyId` (i.e., not using CMK).
        * Associates them with the specified CMK.

        ```python theme={null}
        import boto3

        def associate_cmk_with_log_groups(kms_key_arn: str, prefix_filter: str = None):
            logs = boto3.client("logs")

            paginator = logs.get_paginator("describe_log_groups")
            for page in paginator.paginate():
                for lg in page.get("logGroups", []):
                    log_group_name = lg["logGroupName"]
                    kms_key_id = lg.get("kmsKeyId")

                    # Optional: only act on log groups matching a prefix
                    if prefix_filter and not log_group_name.startswith(prefix_filter):
                        continue

                    # Skip if already encrypted with *some* CMK
                    if kms_key_id:
                        print(f"Skipping {log_group_name}: already encrypted with {kms_key_id}")
                        continue

                    print(f"Associating CMK with log group: {log_group_name}")
                    logs.associate_kms_key(
                        logGroupName=log_group_name,
                        kmsKeyId=kms_key_arn
                    )

        if __name__ == "__main__":
            # Option 1: use existing CMK ARN directly
            # kms_key_arn = "arn:aws:kms:us-east-1:123456789012:key/xxxx-xxxx-xxxx-xxxx"

            # Option 2: get by alias
            kms_key_arn = get_kms_key_arn_by_alias("alias/cloudwatch-logs")

            # Optionally, limit to specific app prefix: prefix_filter="/aws/lambda/"
            associate_cmk_with_log_groups(kms_key_arn)
        ```

        ***

        ## 5. Verify Encryption

        Programmatically:

        ```python theme={null}
        import boto3

        def verify_encryption():
            logs = boto3.client("logs")
            paginator = logs.get_paginator("describe_log_groups")
            for page in paginator.paginate():
                for lg in page.get("logGroups", []):
                    print(lg["logGroupName"], "->", lg.get("kmsKeyId", "NOT ENCRYPTED"))

        verify_encryption()
        ```

        ***

        Summary of flow:

        1. Create or choose a KMS CMK (steps 2–3).
        2. Run the association script (step 4).
        3. Verify all log groups show `kmsKeyId` set (step 5).
      </Accordion>

      <Accordion title="Using Terraform">
        ```hcl theme={null}
        resource "aws_kms_key" "cloudwatch_logs" {
          description             = "KMS CMK for CloudWatch Log Group encryption"
          deletion_window_in_days = 30

          # Optional: restrict key usage to CloudWatch Logs
          policy = data.aws_iam_policy_document.cloudwatch_kms.json
        }

        data "aws_iam_policy_document" "cloudwatch_kms" {
          statement {
            sid    = "AllowCloudWatchLogsUseOfTheKey"
            effect = "Allow"

            principals {
              type        = "Service"
              identifiers = ["logs.${var.AWS_REGION}.amazonaws.com"]
            }

            actions = [
              "kms:Encrypt",
              "kms:Decrypt",
              "kms:GenerateDataKey*",
              "kms:DescribeKey",
            ]

            resources = ["*"]
          }
        }

        resource "aws_cloudwatch_log_group" "this" {
          name              = "/aws/my/app"                 # replace with your log group name
          retention_in_days = 30                            # optional
          kms_key_id        = aws_kms_key.cloudwatch_logs.arn
        }
        ```

        * Replace `var.AWS_REGION` with your region variable or hard-code the region string as needed.
        * If the log group already exists and is imported into Terraform, adding `kms_key_id` is an in-place update and does not force replacement.

        Verification: `terraform plan` should show an in-place update on `aws_cloudwatch_log_group.this` with `kms_key_id` changing from `null` to the CMK ARN, and creation of `aws_kms_key.cloudwatch_logs`.
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
