From 63adb4f0f60a2d2019c35a5d4e0a8644f6160c8c Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 20 Nov 2024 17:14:40 +0530 Subject: [PATCH 01/44] add codebundles/aws-c7n-ebs-health/sli.robot --- codebundles/aws-c7n-ebs-health/sli.robot | 92 ++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 codebundles/aws-c7n-ebs-health/sli.robot diff --git a/codebundles/aws-c7n-ebs-health/sli.robot b/codebundles/aws-c7n-ebs-health/sli.robot new file mode 100644 index 0000000..57cfd34 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/sli.robot @@ -0,0 +1,92 @@ +*** Settings *** +Metadata Author runwhen +Metadata Support AWS EBS +Documentation Counts the number of EBS resources by identifying unattached volumes, unused and aged snapshots, and unencrypted volumes. +Force Tags EBS Volume AWS Storage Secure + +Library RW.Core +Library RW.CLI + +Suite Setup Suite Initialization + + + +*** Tasks *** +Check Unattached EBS Volumes in `${AWS_REGION}` + [Documentation] Check for unattached EBS volumes in the specified region. + [Tags] ebs storage aws volume + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unattached-ebs-volumes.yaml --cache-period 0 + ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ${count}= RW.CLI.Run Cli + ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' + ${unattached_ebs_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${EVENT_THRESHOLD}) else 0 + Set Global Variable ${unattached_ebs_event_score} + +Check Unencrypted EBS Volumes in `${AWS_REGION}` + [Documentation] Check for unencrypted EBS volumes and report any found that do not meet encryption requirements. + [Tags] ebs storage aws security volume + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unencrypted-ebs-volumes.yaml --cache-period 0 + ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ${count}= RW.CLI.Run Cli + ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unencrypted-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' + ${unencrypted_ebs_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${SECURITY_EVENT_THRESHOLD}) else 0 + Set Global Variable ${unencrypted_ebs_event_score} + + +Check Unused EBS Snapshots in `${AWS_REGION}` + [Documentation] Check for unused EBS snapshots. + [Tags] ebs storage aws snapshots volume + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unused-ebs-snapshots.yaml --cache-period 0 + ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ${count}= RW.CLI.Run Cli + ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' + ${unsued_ebs_snapshot_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${EVENT_THRESHOLD}) else 0 + Set Global Variable ${unsued_ebs_snapshot_event_score} + + +Generate EBS Score + ${ebs_health_score}= Evaluate (${unattached_ebs_event_score} + ${unencrypted_ebs_event_score} + ${unsued_ebs_snapshot_event_score}) / 3 + ${health_score}= Convert to Number ${ebs_health_score} 2 + RW.Core.Push Metric ${health_score} + +** Keywords *** +Suite Initialization + ${AWS_REGION}= RW.Core.Import User Variable AWS_REGION + ... type=string + ... description=AWS Region + ... pattern=\w* + ${AWS_ACCOUNT_ID}= RW.Core.Import User Variable AWS_ACCOUNT_ID + ... type=string + ... description=AWS Account ID + ... pattern=\w* + ${AWS_ACCESS_KEY_ID}= RW.Core.Import Secret AWS_ACCESS_KEY_ID + ... type=string + ... description=AWS Access Key ID + ... pattern=\w* + ${AWS_SECRET_ACCESS_KEY}= RW.Core.Import Secret AWS_SECRET_ACCESS_KEY + ... type=string + ... description=AWS Access Key Secret + ... pattern=\w* + ${EVENT_THRESHOLD}= RW.Core.Import User Variable EVENT_THRESHOLD + ... type=string + ... description=The minimum number of EBS volumes | snapshots to consider unhealthy. + ... pattern=^\d+$ + ... example=2 + ... default=1 + ${SECURITY_EVENT_THRESHOLD}= RW.Core.Import User Variable SECURITY_EVENT_THRESHOLD + ... type=string + ... description=The minimum number of security-related EBS volumes to consider unhealthy. + ... pattern=^\d+$ + ... example=2 + ... default=1 + ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health # Note: Clean out the cloud custoding report dir to ensure accurate data + Set Suite Variable ${AWS_REGION} ${AWS_REGION} + Set Suite Variable ${AWS_ACCOUNT_ID} ${AWS_ACCOUNT_ID} + Set Suite Variable ${EVENT_THRESHOLD} ${EVENT_THRESHOLD} + Set Suite Variable ${SECURITY_EVENT_THRESHOLD} ${SECURITY_EVENT_THRESHOLD} \ No newline at end of file From 7a205bd529dc8bb5708968e9e298e8ff3cebb5fd Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 20 Nov 2024 17:15:58 +0530 Subject: [PATCH 02/44] add c7n ebs policies --- .../aws-c7n-ebs-health/unattached-ebs-volumes.yaml | 7 +++++++ .../aws-c7n-ebs-health/unencrypted-ebs-volumes.yaml | 5 +++++ codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml | 9 +++++++++ 3 files changed, 21 insertions(+) create mode 100644 codebundles/aws-c7n-ebs-health/unattached-ebs-volumes.yaml create mode 100644 codebundles/aws-c7n-ebs-health/unencrypted-ebs-volumes.yaml create mode 100644 codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml diff --git a/codebundles/aws-c7n-ebs-health/unattached-ebs-volumes.yaml b/codebundles/aws-c7n-ebs-health/unattached-ebs-volumes.yaml new file mode 100644 index 0000000..f7df9ce --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/unattached-ebs-volumes.yaml @@ -0,0 +1,7 @@ +policies: + - name: unattached-ebs-volumes + resource: ebs + filters: + - type: value + key: "State" + value: "available" diff --git a/codebundles/aws-c7n-ebs-health/unencrypted-ebs-volumes.yaml b/codebundles/aws-c7n-ebs-health/unencrypted-ebs-volumes.yaml new file mode 100644 index 0000000..fbd5b48 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/unencrypted-ebs-volumes.yaml @@ -0,0 +1,5 @@ +policies: + - name: unencrypted-ebs-volumes + resource: ebs + filters: + - Encrypted: false diff --git a/codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml b/codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml new file mode 100644 index 0000000..c9656ee --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml @@ -0,0 +1,9 @@ +policies: + - name: snapshot-unused + resource: ebs-snapshot + filters: + - type: unused + value: true + - type: volume + key: VolumeId + value: absent \ No newline at end of file From 9ee4894bbd6de0944316fa67ba9e2dfd9b59da65 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 20 Nov 2024 17:21:16 +0530 Subject: [PATCH 03/44] add script to create test infra --- codebundles/aws-c7n-ebs-health/.test/Makefile | 11 +++ .../aws-c7n-ebs-health/.test/README.md | 14 ++++ .../aws-c7n-ebs-health/.test/create_ebs.sh | 27 +++++++ .../.test/create_snapshot.sh | 73 +++++++++++++++++++ .../aws-c7n-ebs-health/.test/delete_ebs.sh | 22 ++++++ .../.test/delete_snapshot.sh | 49 +++++++++++++ 6 files changed, 196 insertions(+) create mode 100644 codebundles/aws-c7n-ebs-health/.test/Makefile create mode 100644 codebundles/aws-c7n-ebs-health/.test/README.md create mode 100755 codebundles/aws-c7n-ebs-health/.test/create_ebs.sh create mode 100755 codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh create mode 100755 codebundles/aws-c7n-ebs-health/.test/delete_ebs.sh create mode 100755 codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh diff --git a/codebundles/aws-c7n-ebs-health/.test/Makefile b/codebundles/aws-c7n-ebs-health/.test/Makefile new file mode 100644 index 0000000..54fd688 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/Makefile @@ -0,0 +1,11 @@ +create-ebs-volume: + ./create_ebs.sh + +delete-ebs-volume: + ./delete_ebs.sh + +create-ebs-snapshot: + ./create_snapshot.sh + +delete-ebs-snapshot: + ./delete_snapshot.sh \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/README.md b/codebundles/aws-c7n-ebs-health/.test/README.md new file mode 100644 index 0000000..f5c7f0b --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/README.md @@ -0,0 +1,14 @@ +### To create/delete unattached unencrypted volume run: + +```sh +make create-ebs-volume + +make delete-ebs-volume +``` + +### [WIP] To create/delete unused snapshot run: + +```sh +make create-ebs-snapshot +make delete-ebs-snapshot +``` \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/create_ebs.sh b/codebundles/aws-c7n-ebs-health/.test/create_ebs.sh new file mode 100755 index 0000000..37f95cd --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/create_ebs.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# GIT_TLD=`git rev-parse --show-toplevel` +REGION="us-west-2" +AZ="${REGION}b" +EBS_VOLUME_NAME="ebs-test" +EBS_VOLUME_SIZE="1" + + +EBS_VOLUME_EXISTS=$( + aws ec2 describe-volumes \ + --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ + --query 'Volumes[0].VolumeId' \ + --region=$REGION \ + --output text \ + --no-cli-pager +) + + +if [[ "$EBS_VOLUME_EXISTS" == "None" ]]; then + aws ec2 create-volume --region=$REGION \ + --availability-zone=$AZ --size=$EBS_VOLUME_SIZE \ + --tag-specifications "ResourceType=volume,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ + --output text --no-encrypted --no-cli-pager +else + echo "$EBS_VOLUME_EXISTS ebs already exists" +fi \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh b/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh new file mode 100755 index 0000000..f6fda8b --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Configuration +REGION="us-west-2" +AZ="${REGION}b" +EBS_VOLUME_NAME="ebs-test" +EBS_VOLUME_SIZE="1" + +# Check if the EBS volume exists +EBS_VOLUME_EXISTS=$( + aws ec2 describe-volumes \ + --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ + --query 'Volumes[0].VolumeId' \ + --region=$REGION \ + --output text \ + --no-cli-pager +) + +if [[ "$EBS_VOLUME_EXISTS" == "None" ]]; then + echo "Creating EBS volume $EBS_VOLUME_NAME..." + EBS_VOLUME_ID=$( + aws ec2 create-volume \ + --region=$REGION \ + --availability-zone=$AZ \ + --size=$EBS_VOLUME_SIZE \ + --tag-specifications "ResourceType=volume,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ + --query 'VolumeId' \ + --output text \ + --no-encrypted \ + --no-cli-pager + ) + echo "Created EBS volume: $EBS_VOLUME_ID" +else + EBS_VOLUME_ID=$EBS_VOLUME_EXISTS + echo "EBS volume $EBS_VOLUME_NAME already exists: $EBS_VOLUME_ID" +fi + +# Create a snapshot from the EBS volume +SNAPSHOT_DESCRIPTION="Snapshot of volume $EBS_VOLUME_ID" +SNAPSHOT_ID=$( + aws ec2 describe-snapshots \ + --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ + --region=$REGION \ + --query 'Snapshots[0].SnapshotId' \ + --output text \ + --no-cli-pager +) + +if [[ "$SNAPSHOT_ID" != "None" ]]; then + echo "$SNAPSHOT_ID snapshot already exists" +else + echo "create Snapshot $EBS_VOLUME_NAME" + aws ec2 create-snapshot \ + --region=$REGION \ + --volume-id=$EBS_VOLUME_ID \ + --description="$SNAPSHOT_DESCRIPTION" \ + --tag-specifications "ResourceType=snapshot,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ + --query 'SnapshotId' \ + --output text \ + --no-cli-pager +fi + +if [[ "$EBS_VOLUME_ID" == "None" ]]; then + echo "EBS volume with name $EBS_VOLUME_NAME does not exist." +else + # Delete the volume + echo "Deleting EBS volume: $EBS_VOLUME_ID..." + aws ec2 delete-volume \ + --volume-id=$EBS_VOLUME_ID \ + --region=$REGION \ + --no-cli-pager + echo "EBS volume $EBS_VOLUME_ID deleted successfully." +fi \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/delete_ebs.sh b/codebundles/aws-c7n-ebs-health/.test/delete_ebs.sh new file mode 100755 index 0000000..1049271 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/delete_ebs.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# GIT_TLD=`git rev-parse --show-toplevel` + +REGION="us-west-2" +EBS_VOLUME_NAME="ebs-test" + +EBS_VOLUME_EXISTS=$( + aws ec2 describe-volumes \ + --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ + --query 'Volumes[0].VolumeId' \ + --region=$REGION \ + --output text \ + --no-cli-pager +) + +if [[ "$EBS_VOLUME_EXISTS" != "None" ]]; then + echo "Deleting EBS volume $EBS_VOLUME_EXISTS..." + aws ec2 delete-volume --volume-id "$EBS_VOLUME_EXISTS" --region=$REGION --no-cli-pager +else + echo "No EBS volume with tag Name=ebs-test exists." +fi diff --git a/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh b/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh new file mode 100755 index 0000000..7713b59 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +# Configuration +REGION="us-west-2" +EBS_VOLUME_NAME="ebs-test" + +# Check if the EBS volume exists +EBS_VOLUME_ID=$( + aws ec2 describe-volumes \ + --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ + --query 'Volumes[0].VolumeId' \ + --region=$REGION \ + --output text \ + --no-cli-pager +) + +if [[ "$EBS_VOLUME_ID" == "None" ]]; then + echo "EBS volume with name $EBS_VOLUME_NAME does not exist." +else + # Check for associated snapshots + SNAPSHOT_ID=$( + aws ec2 describe-snapshots \ + --filters Name=volume-id,Values=$EBS_VOLUME_ID \ + --query 'Snapshots[0].SnapshotId' \ + --region=$REGION \ + --output text \ + --no-cli-pager + ) + + # Delete the snapshot if it exists + if [[ "$SNAPSHOT_ID" != "None" ]]; then + echo "Deleting snapshot: $SNAPSHOT_ID..." + aws ec2 delete-snapshot \ + --snapshot-id=$SNAPSHOT_ID \ + --region=$REGION \ + --no-cli-pager + echo "Snapshot $SNAPSHOT_ID deleted successfully." + else + echo "No snapshot associated with volume $EBS_VOLUME_ID." + fi + + # Delete the volume + echo "Deleting EBS volume: $EBS_VOLUME_ID..." + aws ec2 delete-volume \ + --volume-id=$EBS_VOLUME_ID \ + --region=$REGION \ + --no-cli-pager + echo "EBS volume $EBS_VOLUME_ID deleted successfully." +fi From b86124b3acfc823dbf3f34b46379231afeef5163 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 20 Nov 2024 17:22:24 +0530 Subject: [PATCH 04/44] added runbook.robot with List Unattached EBS Volumes task --- codebundles/aws-c7n-ebs-health/runbook.robot | 91 ++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 codebundles/aws-c7n-ebs-health/runbook.robot diff --git a/codebundles/aws-c7n-ebs-health/runbook.robot b/codebundles/aws-c7n-ebs-health/runbook.robot new file mode 100644 index 0000000..1d2c792 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/runbook.robot @@ -0,0 +1,91 @@ +*** Settings *** +Metadata Author runwhen +Metadata Support AWS EBS +Documentation Audit EBS resources by identifying unattached volumes, unused and aged snapshots, and unencrypted volumes. +Force Tags EBS Volume AWS Storage Secure + +Library RW.Core +Library RW.CLI +Library CloudCustodian.Core + +Suite Setup Suite Initialization + + + +*** Tasks *** +List Unattached EBS Volumes in `${AWS_REGION}` + [Documentation] Check for unattached EBS volumes in the specified region. + [Tags] ebs storage aws volume + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unattached-ebs-volumes.yaml --cache-period 0 + ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ${report_data}= RW.CLI.Run Cli + ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes/resources.json + RW.Core.Add Pre To Report ${c7n_output.stdout} # Data needs to be parsed to be usable in the report. + + ${parsed_results}= CloudCustodian.Core.Parse Custodian Results + ... input_dir=${OUTPUT_DIR}/aws-c7n-ebs-health + RW.Core.Add Pre To Report ${parsed_results} + # Convert custodian json output to a list. + TRY + ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json + Log ${report_data.stdout} + EXCEPT + Log Failed to load JSON payload, defaulting to empty list. WARN + ${ebs_volume_list}= Create List + END + + # Generate issues if any unused EBS volumes are in the list + IF len(@{ebs_volume_list}) > 0 + FOR ${item} IN @{ebs_volume_list} + RW.Core.Add Issue + ... severity=2 + ... expected=EBS volumes in AWS Account `${AWS_ACCOUNT_NAME}` should be attached or in use + ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_NAME}` is unused + ... title=Unused EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_NAME}` + ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. + ... details=${item} # Include refined details such as volume ID, size, and region. + ... next_steps=Escalate to service owner for review of unattached volume `${item["VolumeId"]}` in ${AWS_ACCOUNT_NAME} AWS account in AWS Region ${AWS_REGION}". + END + END + + + + +** Keywords *** +Suite Initialization + ${AWS_REGION}= RW.Core.Import User Variable AWS_REGION + ... type=string + ... description=AWS Region + ... pattern=\w* + ${AWS_ACCOUNT_ID}= RW.Core.Import User Variable AWS_ACCOUNT_ID + ... type=string + ... description=AWS Account ID + ... pattern=\w* + ${AWS_ACCESS_KEY_ID}= RW.Core.Import Secret AWS_ACCESS_KEY_ID + ... type=string + ... description=AWS Access Key ID + ... pattern=\w* + ${AWS_SECRET_ACCESS_KEY}= RW.Core.Import Secret AWS_SECRET_ACCESS_KEY + ... type=string + ... description=AWS Access Key Secret + ... pattern=\w* + ${EVENT_THRESHOLD}= RW.Core.Import User Variable EVENT_THRESHOLD + ... type=string + ... description=The minimum number of EBS volumes | snapshots to consider unhealthy. + ... pattern=^\d+$ + ... example=2 + ... default=1 + ${SECURITY_EVENT_THRESHOLD}= RW.Core.Import User Variable SECURITY_EVENT_THRESHOLD + ... type=string + ... description=The minimum number of security-related EBS volumes to consider unhealthy. + ... pattern=^\d+$ + ... example=2 + ... default=1 + ${aws_account_name_query}= RW.CLI.Run Cli + ... cmd=aws organizations describe-account --account-id $(aws sts get-caller-identity --query 'Account' --output text) --query "Account.Name" --output text | tr -d '\n' + ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health # Note: Clean out the cloud custoding report dir to ensure accurate data + Set Suite Variable ${AWS_ACCOUNT_NAME} ${aws_account_name_query.stdout} + Set Suite Variable ${AWS_REGION} ${AWS_REGION} + Set Suite Variable ${AWS_ACCOUNT_ID} ${AWS_ACCOUNT_ID} \ No newline at end of file From 774460f023fca61004d93cb3adb8ca271d252412 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 22 Nov 2024 15:22:44 +0530 Subject: [PATCH 05/44] added parse_ebs_results func in Core.py --- libraries/CloudCustodian/Core/Core.py | 126 ++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/libraries/CloudCustodian/Core/Core.py b/libraries/CloudCustodian/Core/Core.py index 4ca87a1..c6a725a 100644 --- a/libraries/CloudCustodian/Core/Core.py +++ b/libraries/CloudCustodian/Core/Core.py @@ -166,3 +166,129 @@ def parse_custodian_results(input_dir: str): return "\n".join(results) + + +def parse_ebs_results(input_dir: str): + input_path = Path(input_dir) + + if not input_path.exists(): + return f"Input directory does not exist: {input_dir}" + + if not input_path.is_dir(): + return f"Input path is not a directory: {input_dir}" + + # Initialize data structures for report + resource_summary = [] + policy_summary = [] + log_summary = [] + + # Recursively traverse subdirectories + for subdir in input_path.iterdir(): + if subdir.is_dir(): + resources_file = subdir / "resources.json" + metadata_file = subdir / "metadata.json" + log_file = subdir / "custodian-run.log" + + # Parse resources.json + if resources_file.exists(): + try: + with open(resources_file, "r") as f: + resources = json.load(f) + if not isinstance(resources, list): + print(f"Skipping {resources_file}: Expected a list of resources.") + continue + + for resource in resources: + if not isinstance(resource, dict): + print(f"Skipping malformed resource in {resources_file}: {resource}") + continue + + resource_id = "" + resource_type = "" + if "VolumeType" in resource: + resource_type = "EBS volume" + resource_id = resource.get("VolumeId", "Unknown ID") + elif "SnapshotId" in resource: + resource_type = "EBS snapshot" + resource_id = resource.get("SnapshotId", "Unknown ID") + + resource_location = resource.get("AvailabilityZone", "Unknown Location")[:-1] + tags = resource.get("Tags", []) + if isinstance(tags, list): + tags_str = ", ".join(f"{tag.get('Key', 'Unknown')}={tag.get('Value', 'Unknown')}" for tag in tags) + else: + tags_str = "Unknown Tags" + + # Append to resource summary + resource_summary.append([ + subdir.name, # Policy name + resource_id, + resource_type, + resource_location, + tags_str + ]) + except json.JSONDecodeError: + print(f"Error reading resources.json in {subdir}: Invalid JSON format.") + except Exception as e: + print(f"Error reading resources.json in {subdir}: {e}") + + # Parse metadata.json + if metadata_file.exists(): + print("directory...",subdir) + if not resource_summary: + continue + try: + with open(metadata_file, "r") as f: + metadata = json.load(f) + policy = metadata.get("policy", {}) + policy_name = policy.get("name", "Unknown Policy") + + # Extract metrics timestamps for start and end time + metrics = metadata.get("metrics", []) + if metrics: + timestamps = [m.get("Timestamp") for m in metrics if m.get("Timestamp")] + start_time = min(timestamps) if timestamps else "Unknown Start Time" + end_time = max(timestamps) if timestamps else "Unknown End Time" + else: + start_time = "Unknown Start Time" + end_time = "Unknown End Time" + + # Add to policy summary + policy_summary.append([policy_name, start_time, end_time]) + except Exception as e: + print(f"Error reading metadata.json in {subdir}: {e}") + + # Parse custodian-run.log + if log_file.exists(): + if not resource_summary: + continue + try: + with open(log_file, "r") as f: + logs = f.readlines() + error_count = sum(1 for line in logs if "ERROR" in line) + warning_count = sum(1 for line in logs if "WARNING" in line) + log_summary.append([subdir.name, error_count, warning_count]) + except Exception as e: + print(f"Error reading custodian-run.log in {subdir}: {e}") + + + results = [] + + if resource_summary: + results.append("Resource Summary:") + results.append(tabulate(resource_summary, headers=[ + "Policy Name", "Resource ID", "Resource Type", "Location", "Tags" + ], tablefmt="grid")) + + if policy_summary: + results.append("\nPolicy Summary:") + results.append(tabulate(policy_summary, headers=["Policy Name", "Start Time", "End Time"], tablefmt="grid")) + + if log_summary: + results.append("\nRun Health Summary:") + results.append(tabulate(log_summary, headers=["Policy Name", "Errors", "Warnings"], tablefmt="grid")) + + if not results: + return "No valid results found in the specified directory." + + return "\n".join(results) \ No newline at end of file From 9d5dd28b2e646db7364d3634a06de1441533add0 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 22 Nov 2024 15:23:17 +0530 Subject: [PATCH 06/44] change name of unused-ebs-snapshots policy --- codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml b/codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml index c9656ee..df8f3b3 100644 --- a/codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml +++ b/codebundles/aws-c7n-ebs-health/unused-ebs-snapshots.yaml @@ -1,5 +1,5 @@ policies: - - name: snapshot-unused + - name: unused-ebs-snapshots resource: ebs-snapshot filters: - type: unused From 3dd731460e80768d2661b2727cf8cbba2f6e397d Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 22 Nov 2024 15:25:04 +0530 Subject: [PATCH 07/44] change secret__aws_account_id -> secret__aws_access_key_id --- codebundles/aws-c7n-ebs-health/sli.robot | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/sli.robot b/codebundles/aws-c7n-ebs-health/sli.robot index 57cfd34..34e6025 100644 --- a/codebundles/aws-c7n-ebs-health/sli.robot +++ b/codebundles/aws-c7n-ebs-health/sli.robot @@ -17,7 +17,7 @@ Check Unattached EBS Volumes in `${AWS_REGION}` [Tags] ebs storage aws volume ${c7n_output}= RW.CLI.Run Cli ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unattached-ebs-volumes.yaml --cache-period 0 - ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' @@ -29,7 +29,7 @@ Check Unencrypted EBS Volumes in `${AWS_REGION}` [Tags] ebs storage aws security volume ${c7n_output}= RW.CLI.Run Cli ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unencrypted-ebs-volumes.yaml --cache-period 0 - ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unencrypted-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' @@ -42,7 +42,7 @@ Check Unused EBS Snapshots in `${AWS_REGION}` [Tags] ebs storage aws snapshots volume ${c7n_output}= RW.CLI.Run Cli ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unused-ebs-snapshots.yaml --cache-period 0 - ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' From b9505d02e87ab80246259f117b2b642fbfe61d5a Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 22 Nov 2024 15:26:51 +0530 Subject: [PATCH 08/44] updated create/delete snapshot script in .test --- .../.test/create_snapshot.sh | 215 +++++++++++++----- .../.test/delete_snapshot.sh | 58 ++--- 2 files changed, 178 insertions(+), 95 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh b/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh index f6fda8b..134da5b 100755 --- a/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh +++ b/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh @@ -1,73 +1,170 @@ #!/bin/bash +# Exit on error +set -e + # Configuration REGION="us-west-2" AZ="${REGION}b" -EBS_VOLUME_NAME="ebs-test" +EBS_VOLUME_NAME="ebs-snapshot-test" EBS_VOLUME_SIZE="1" +MAX_WAIT_TIME=300 # 5 minutes timeout + + +# Function to check volume status +wait_for_volume_state() { + local volume_id=$1 + local desired_state=$2 + local start_time=$(date +%s) + + while true; do + local current_state=$(aws ec2 describe-volumes \ + --volume-ids "$volume_id" \ + --region="$REGION" \ + --query 'Volumes[0].State' \ + --output text \ + --no-cli-pager) + + if [[ "$current_state" == "$desired_state" ]]; then + echo "Volume $volume_id is now $desired_state" + return 0 + fi + + if [[ "$current_state" == "error" ]]; then + echo "Error: Volume $volume_id is in error state" + return 1 + fi + + if (( $(date +%s) - start_time >= MAX_WAIT_TIME )); then + echo "Timeout waiting for volume $volume_id to become $desired_state" + return 1 + fi + + echo "Waiting for volume $volume_id (current state: $current_state)..." + sleep 5 + done +} -# Check if the EBS volume exists -EBS_VOLUME_EXISTS=$( - aws ec2 describe-volumes \ - --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ - --query 'Volumes[0].VolumeId' \ - --region=$REGION \ - --output text \ - --no-cli-pager -) +# Function to wait for snapshot completion +wait_for_snapshot_completion() { + local snapshot_id=$1 + local start_time=$(date +%s) + + while true; do + local status=$(aws ec2 describe-snapshots \ + --snapshot-ids "$snapshot_id" \ + --region="$REGION" \ + --query 'Snapshots[0].State' \ + --output text \ + --no-cli-pager) + + if [[ "$status" == "completed" ]]; then + echo "Snapshot $snapshot_id completed successfully" + return 0 + fi + + if [[ "$status" == "error" ]]; then + echo "Error: Snapshot $snapshot_id failed" + return 1 + fi + + if (( $(date +%s) - start_time >= MAX_WAIT_TIME )); then + echo "Timeout waiting for snapshot $snapshot_id to complete" + return 1 + fi + + echo "Waiting for snapshot $snapshot_id (current state: $status)..." + sleep 10 + done +} -if [[ "$EBS_VOLUME_EXISTS" == "None" ]]; then - echo "Creating EBS volume $EBS_VOLUME_NAME..." - EBS_VOLUME_ID=$( - aws ec2 create-volume \ - --region=$REGION \ - --availability-zone=$AZ \ - --size=$EBS_VOLUME_SIZE \ - --tag-specifications "ResourceType=volume,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ - --query 'VolumeId' \ +# Main execution +main() { + + # Check if the EBS volume exists + echo "Checking for existing volume with name $EBS_VOLUME_NAME..." + EBS_VOLUME_EXISTS=$( + aws ec2 describe-volumes \ + --filters Name=tag:Name,Values="$EBS_VOLUME_NAME" \ + --region="$REGION" \ + --query 'Volumes[0].VolumeId' \ --output text \ - --no-encrypted \ --no-cli-pager ) - echo "Created EBS volume: $EBS_VOLUME_ID" -else - EBS_VOLUME_ID=$EBS_VOLUME_EXISTS - echo "EBS volume $EBS_VOLUME_NAME already exists: $EBS_VOLUME_ID" -fi - -# Create a snapshot from the EBS volume -SNAPSHOT_DESCRIPTION="Snapshot of volume $EBS_VOLUME_ID" -SNAPSHOT_ID=$( - aws ec2 describe-snapshots \ - --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ - --region=$REGION \ - --query 'Snapshots[0].SnapshotId' \ - --output text \ - --no-cli-pager -) -if [[ "$SNAPSHOT_ID" != "None" ]]; then - echo "$SNAPSHOT_ID snapshot already exists" -else - echo "create Snapshot $EBS_VOLUME_NAME" - aws ec2 create-snapshot \ - --region=$REGION \ - --volume-id=$EBS_VOLUME_ID \ - --description="$SNAPSHOT_DESCRIPTION" \ - --tag-specifications "ResourceType=snapshot,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ - --query 'SnapshotId' \ - --output text \ - --no-cli-pager -fi + if [[ "$EBS_VOLUME_EXISTS" == "None" ]]; then + echo "Creating new EBS volume $EBS_VOLUME_NAME..." + EBS_VOLUME_ID=$( + aws ec2 create-volume \ + --region="$REGION" \ + --availability-zone="$AZ" \ + --size="$EBS_VOLUME_SIZE" \ + --tag-specifications "ResourceType=volume,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ + --query 'VolumeId' \ + --output text \ + --no-cli-pager + ) + echo "Created EBS volume: $EBS_VOLUME_ID" + + # Wait for volume to become available + wait_for_volume_state "$EBS_VOLUME_ID" "available" || exit 1 + else + EBS_VOLUME_ID=$EBS_VOLUME_EXISTS + echo "Using existing EBS volume: $EBS_VOLUME_ID" + fi -if [[ "$EBS_VOLUME_ID" == "None" ]]; then - echo "EBS volume with name $EBS_VOLUME_NAME does not exist." -else - # Delete the volume - echo "Deleting EBS volume: $EBS_VOLUME_ID..." - aws ec2 delete-volume \ - --volume-id=$EBS_VOLUME_ID \ - --region=$REGION \ + # Check for existing snapshot + echo "Checking for existing snapshot..." + SNAPSHOT_ID=$( + aws ec2 describe-snapshots \ + --filters Name=tag:Name,Values="$EBS_VOLUME_NAME" \ + --region="$REGION" \ + --query 'Snapshots[0].SnapshotId' \ + --output text \ --no-cli-pager - echo "EBS volume $EBS_VOLUME_ID deleted successfully." -fi \ No newline at end of file + ) + + if [[ "$SNAPSHOT_ID" != "None" ]]; then + echo "Snapshot $SNAPSHOT_ID already exists" + else + echo "Creating new snapshot from volume $EBS_VOLUME_ID..." + SNAPSHOT_ID=$( + aws ec2 create-snapshot \ + --region="$REGION" \ + --volume-id="$EBS_VOLUME_ID" \ + --description="Snapshot of volume $EBS_VOLUME_ID" \ + --tag-specifications "ResourceType=snapshot,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ + --query 'SnapshotId' \ + --output text \ + --no-cli-pager + ) + + # Wait for snapshot completion + wait_for_snapshot_completion "$SNAPSHOT_ID" || exit 1 + fi + + # Delete the volume if it exists + if [[ -n "$EBS_VOLUME_ID" && "$EBS_VOLUME_ID" != "None" ]]; then + echo "Deleting EBS volume: $EBS_VOLUME_ID..." + aws ec2 delete-volume \ + --volume-id="$EBS_VOLUME_ID" \ + --region="$REGION" \ + --no-cli-pager + + # Wait for volume deletion + while aws ec2 describe-volumes --volume-ids "$EBS_VOLUME_ID" --region="$REGION" --no-cli-pager &>/dev/null; do + echo "Waiting for volume deletion..." + sleep 5 + done + echo "EBS volume $EBS_VOLUME_ID deleted successfully" + fi + + echo "Script completed successfully" + echo "Snapshot ID: $SNAPSHOT_ID" +} + +# Execute main function with error handling +main || { + echo "Script failed with error code $?" + exit 1 +} \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh b/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh index 7713b59..82b86b6 100755 --- a/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh +++ b/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh @@ -1,49 +1,35 @@ #!/bin/bash +# Exit on error +set -e + # Configuration REGION="us-west-2" -EBS_VOLUME_NAME="ebs-test" - -# Check if the EBS volume exists -EBS_VOLUME_ID=$( - aws ec2 describe-volumes \ - --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ - --query 'Volumes[0].VolumeId' \ - --region=$REGION \ - --output text \ - --no-cli-pager -) - -if [[ "$EBS_VOLUME_ID" == "None" ]]; then - echo "EBS volume with name $EBS_VOLUME_NAME does not exist." -else - # Check for associated snapshots +EBS_VOLUME_NAME="ebs-snapshot-test" + + +# Main function +delete_snapshot() { + + echo "Checking for existing snapshot with name $EBS_VOLUME_NAME..." SNAPSHOT_ID=$( aws ec2 describe-snapshots \ - --filters Name=volume-id,Values=$EBS_VOLUME_ID \ + --filters Name=tag:Name,Values="$EBS_VOLUME_NAME" \ + --region="$REGION" \ --query 'Snapshots[0].SnapshotId' \ - --region=$REGION \ --output text \ --no-cli-pager ) - # Delete the snapshot if it exists - if [[ "$SNAPSHOT_ID" != "None" ]]; then - echo "Deleting snapshot: $SNAPSHOT_ID..." - aws ec2 delete-snapshot \ - --snapshot-id=$SNAPSHOT_ID \ - --region=$REGION \ - --no-cli-pager - echo "Snapshot $SNAPSHOT_ID deleted successfully." - else - echo "No snapshot associated with volume $EBS_VOLUME_ID." + if [[ "$SNAPSHOT_ID" == "None" ]]; then + echo "No snapshot found with the name $EBS_VOLUME_NAME. Exiting." + exit 0 fi - # Delete the volume - echo "Deleting EBS volume: $EBS_VOLUME_ID..." - aws ec2 delete-volume \ - --volume-id=$EBS_VOLUME_ID \ - --region=$REGION \ - --no-cli-pager - echo "EBS volume $EBS_VOLUME_ID deleted successfully." -fi + echo "Found snapshot: $SNAPSHOT_ID. Deleting..." + aws ec2 delete-snapshot --snapshot-id "$SNAPSHOT_ID" --region "$REGION" --no-cli-pager + echo "Snapshot $SNAPSHOT_ID deleted successfully." +} + +# Execute the function +delete_snapshot From aa77f67ca084b33dcc0aa0e3737a1668a8c8a84c Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 22 Nov 2024 15:28:56 +0530 Subject: [PATCH 09/44] added List Unused EBS Snapshots and List Unencrypted EBS Volumes tasks in runbook --- codebundles/aws-c7n-ebs-health/runbook.robot | 96 +++++++++++++++++--- 1 file changed, 81 insertions(+), 15 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/runbook.robot b/codebundles/aws-c7n-ebs-health/runbook.robot index 1d2c792..fe4ec31 100644 --- a/codebundles/aws-c7n-ebs-health/runbook.robot +++ b/codebundles/aws-c7n-ebs-health/runbook.robot @@ -24,9 +24,12 @@ List Unattached EBS Volumes in `${AWS_REGION}` ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes/resources.json RW.Core.Add Pre To Report ${c7n_output.stdout} # Data needs to be parsed to be usable in the report. - ${parsed_results}= CloudCustodian.Core.Parse Custodian Results + ${parsed_results}= CloudCustodian.Core.Parse EBS Results ... input_dir=${OUTPUT_DIR}/aws-c7n-ebs-health - RW.Core.Add Pre To Report ${parsed_results} + RW.Core.Add Pre To Report ${parsed_results} + + ${clean_output_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes + # Convert custodian json output to a list. TRY ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json @@ -45,12 +48,87 @@ List Unattached EBS Volumes in `${AWS_REGION}` ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_NAME}` is unused ... title=Unused EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_NAME}` ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. - ... details=${item} # Include refined details such as volume ID, size, and region. + ... details=${item} # Include details such as volume ID, size, and region. ... next_steps=Escalate to service owner for review of unattached volume `${item["VolumeId"]}` in ${AWS_ACCOUNT_NAME} AWS account in AWS Region ${AWS_REGION}". END END +List Unencrypted EBS Volumes in `${AWS_REGION}` + [Documentation] Check for unattached EBS volumes in the specified region. + [Tags] ebs storage aws volume + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unencrypted-ebs-volumes.yaml --cache-period 0 + ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ${report_data}= RW.CLI.Run Cli + ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unencrypted-ebs-volumes/resources.json + RW.Core.Add Pre To Report ${c7n_output.stdout} + + ${parsed_results}= CloudCustodian.Core.Parse EBS Results + ... input_dir=${OUTPUT_DIR}/aws-c7n-ebs-health + RW.Core.Add Pre To Report ${parsed_results} + + ${clean_output_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health/unencrypted-ebs-volumes + + TRY + ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json + Log ${report_data.stdout} + EXCEPT + Log Failed to load JSON payload, defaulting to empty list. WARN + ${ebs_volume_list}= Create List + END + + IF len(@{ebs_volume_list}) > 0 + FOR ${item} IN @{ebs_volume_list} + RW.Core.Add Issue + ... severity=2 + ... expected=EBS volumes in AWS Account `${AWS_ACCOUNT_NAME}` should be unencrypted or in use + ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_NAME}` is unencrypted + ... title=Unencrypted EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_NAME}` + ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. + ... details=${item} + ... next_steps=Escalate to service owner to review Unencrypted AWS EBS volume `${item["VolumeId"]}` found in ${AWS_ACCOUNT_NAME} AWS account in AWS Region ${AWS_REGION}". + END + END + + +List Unused EBS Snapshots in`${AWS_REGION}` + [Documentation] Check for unattached EBS volumes in the specified region. + [Tags] ebs storage aws volume + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unused-ebs-snapshots.yaml --cache-period 0 + ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ${report_data}= RW.CLI.Run Cli + ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots/resources.json + RW.Core.Add Pre To Report ${c7n_output.stdout} + + ${parsed_results}= CloudCustodian.Core.Parse EBS Results + ... input_dir=${OUTPUT_DIR}/aws-c7n-ebs-health + RW.Core.Add Pre To Report ${parsed_results} + ${clean_output_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots + TRY + ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json + Log ${report_data.stdout} + EXCEPT + Log Failed to load JSON payload, defaulting to empty list. WARN + ${ebs_volume_list}= Create List + END + + IF len(@{ebs_volume_list}) > 0 + FOR ${item} IN @{ebs_volume_list} + RW.Core.Add Issue + ... severity=2 + ... expected=EBS snapshots in AWS Account `${AWS_ACCOUNT_NAME}` should be attached or in use + ... actual=EBS Snapshots `${item["SnapshotId"]}` in AWS Account `${AWS_ACCOUNT_NAME}` is unused + ... title=Unused EBS Snapshot `${item["SnapshotId"]}` detected in AWS Account `${AWS_ACCOUNT_NAME}` + ... reproduce_hint=Review the Snapshots details and usage in the AWS Management Console or CLI. + ... details=${item} # Include details such as volume ID, size, and region. + ... next_steps=Escalate to service owner for review of unused EBS Snapshot `${item["SnapshotId"]}` in ${AWS_ACCOUNT_NAME} AWS account in AWS Region ${AWS_REGION}". + END + END + ** Keywords *** @@ -71,18 +149,6 @@ Suite Initialization ... type=string ... description=AWS Access Key Secret ... pattern=\w* - ${EVENT_THRESHOLD}= RW.Core.Import User Variable EVENT_THRESHOLD - ... type=string - ... description=The minimum number of EBS volumes | snapshots to consider unhealthy. - ... pattern=^\d+$ - ... example=2 - ... default=1 - ${SECURITY_EVENT_THRESHOLD}= RW.Core.Import User Variable SECURITY_EVENT_THRESHOLD - ... type=string - ... description=The minimum number of security-related EBS volumes to consider unhealthy. - ... pattern=^\d+$ - ... example=2 - ... default=1 ${aws_account_name_query}= RW.CLI.Run Cli ... cmd=aws organizations describe-account --account-id $(aws sts get-caller-identity --query 'Account' --output text) --query "Account.Name" --output text | tr -d '\n' ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health # Note: Clean out the cloud custoding report dir to ensure accurate data From 780854e54ba2f924b0133ae643e727d3a677a007 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 22 Nov 2024 16:42:12 +0530 Subject: [PATCH 10/44] add runwhen generation rule and template yaml --- .../generation-rules/aws-c7n-s3-health.yaml | 23 ++++++++++++ .../templates/aws-c7n-ebs-health-sli.yaml | 37 +++++++++++++++++++ .../templates/aws-c7n-ebs-health-slx.yaml | 21 +++++++++++ .../templates/aws-c7n-ebs-health-taskset.yaml | 33 +++++++++++++++++ 4 files changed, 114 insertions(+) create mode 100644 codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml create mode 100644 codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml create mode 100644 codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-slx.yaml create mode 100644 codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-taskset.yaml diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml new file mode 100644 index 0000000..8115421 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml @@ -0,0 +1,23 @@ +apiVersion: runwhen.com/v1 +kind: GenerationRules +spec: + platform: aws + generationRules: + - resourceTypes: + - aws_ec2_ebs_volumes + - aws_ec2_ebs_snapshots + matchRules: + - type: pattern + pattern: ".+" + properties: [name] + mode: substring + slxs: + - baseName: aws-c7n-ebs-health + qualifiers: ["region"] + baseTemplateName: aws-c7n-ebs-health + levelOfDetail: basic + outputItems: + - type: slx + - type: sli + - type: runbook + templateName: aws-c7n-ebs-health-taskset.yaml \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml new file mode 100644 index 0000000..44a67aa --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml @@ -0,0 +1,37 @@ +apiVersion: runwhen.com/v1 +kind: ServiceLevelIndicator +metadata: + name: {{slx_name}} + labels: + {% include "common-labels.yaml" %} + annotations: + {% include "common-annotations.yaml" %} +spec: + displayUnitsLong: OK + displayUnitsShort: ok + location: {{default_location}} + description: Measures securitiy and health of S3 buckets in this AWS region and account. + codeBundle: + {% if repo_url %} + repoUrl: {{repo_url}} + {% else %} + repoUrl: https://github.com/runwhen-contrib/rw-c7n-codecollection.git + {% endif %} + {% if ref %} + ref: {{ref}} + {% else %} + ref: main + {% endif %} + pathToRobot: codebundles/aws-c7n-ebs-health/sli.robot + intervalStrategy: intermezzo + intervalSeconds: 300 + configProvided: + - name: AWS_REGION + value: "{{match_resource.resource.region}}" + - name: AWS_ACCOUNT_ID + value: "{{match_resource.resource.account_id}}" + secretsProvided: + - name: AWS_ACCESS_KEY_ID + workspaceKey: {{custom.aws_access_key_id}} + - name: AWS_SECRET_ACCESS_KEY + workspaceKey: {{custom.aws_secret_access_key}} diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-slx.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-slx.yaml new file mode 100644 index 0000000..2d99591 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-slx.yaml @@ -0,0 +1,21 @@ +apiVersion: runwhen.com/v1 +kind: ServiceLevelX +metadata: + name: {{slx_name}} + labels: + {% include "common-labels.yaml" %} + annotations: + {% include "common-annotations.yaml" %} +spec: + imageURL: https://www.shareicon.net/data/128x128/2015/08/28/92177_content_512x512.png + alias: AWS EBS Health For Region {{match_resource.resource.region}} + asMeasuredBy: The number of AWS EBS volumes and snapshots in region {{match_resource.resource.region}} + configProvided: + - name: SLX_PLACEHOLDER + value: SLX_PLACEHOLDER + owners: + - {{workspace.owner_email}} + statement: The total count of unattached, unencrypted volumes and unused snapshots should be 0. + additionalContext: + region: "{{match_resource.resource.region}}" + account_id: "{{match_resource.resource.account_id}}" \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-taskset.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-taskset.yaml new file mode 100644 index 0000000..7deac36 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-taskset.yaml @@ -0,0 +1,33 @@ +apiVersion: runwhen.com/v1 +kind: Runbook +metadata: + name: {{slx_name}} + labels: + {% include "common-labels.yaml" %} + annotations: + {% include "common-annotations.yaml" %} +spec: + location: {{default_location}} + description: Runs tasks to identify and triage unused or insecure AWS EBS volumes and snapshots. + codeBundle: + {% if repo_url %} + repoUrl: {{repo_url}} + {% else %} + repoUrl: https://github.com/runwhen-contrib/rw-c7n-codecollection.git + {% endif %} + {% if ref %} + ref: {{ref}} + {% else %} + ref: main + {% endif %} + pathToRobot: codebundles/aws-c7n-ebs-health/runbook.robot + configProvided: + - name: AWS_REGION + value: "{{match_resource.resource.region}}" + - name: AWS_ACCOUNT_ID + value: "{{match_resource.resource.account_id}}" + secretsProvided: + - name: AWS_ACCESS_KEY_ID + workspaceKey: {{custom.aws_access_key_id}} + - name: AWS_SECRET_ACCESS_KEY + workspaceKey: {{custom.aws_secret_access_key}} From 3455556883a0b9d0a10d3b8efa89cd80f76368ff Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 25 Nov 2024 23:27:28 +0530 Subject: [PATCH 11/44] clean cc lib --- libraries/CloudCustodian/Core/Core.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libraries/CloudCustodian/Core/Core.py b/libraries/CloudCustodian/Core/Core.py index c6a725a..64b2a67 100644 --- a/libraries/CloudCustodian/Core/Core.py +++ b/libraries/CloudCustodian/Core/Core.py @@ -234,9 +234,6 @@ def parse_ebs_results(input_dir: str): # Parse metadata.json if metadata_file.exists(): - print("directory...",subdir) - if not resource_summary: - continue try: with open(metadata_file, "r") as f: metadata = json.load(f) @@ -260,8 +257,6 @@ def parse_ebs_results(input_dir: str): # Parse custodian-run.log if log_file.exists(): - if not resource_summary: - continue try: with open(log_file, "r") as f: logs = f.readlines() From ecc92ff615069f31f06d55aad27c25eb90d78941 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 27 Nov 2024 15:05:33 +0530 Subject: [PATCH 12/44] replace ebs test script with terraform --- .../aws-c7n-ebs-health/.test/create_ebs.sh | 27 --- .../.test/create_snapshot.sh | 170 ------------------ .../aws-c7n-ebs-health/.test/delete_ebs.sh | 22 --- .../.test/delete_snapshot.sh | 35 ---- .../aws-c7n-ebs-health/.test/terraform/ebs.tf | 23 +++ .../.test/terraform/provider.tf | 3 + .../.test/terraform/snapshot.tf | 51 ++++++ .../.test/terraform/vars.tf | 25 +++ 8 files changed, 102 insertions(+), 254 deletions(-) delete mode 100755 codebundles/aws-c7n-ebs-health/.test/create_ebs.sh delete mode 100755 codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh delete mode 100755 codebundles/aws-c7n-ebs-health/.test/delete_ebs.sh delete mode 100755 codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh create mode 100644 codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf create mode 100644 codebundles/aws-c7n-ebs-health/.test/terraform/provider.tf create mode 100644 codebundles/aws-c7n-ebs-health/.test/terraform/snapshot.tf create mode 100644 codebundles/aws-c7n-ebs-health/.test/terraform/vars.tf diff --git a/codebundles/aws-c7n-ebs-health/.test/create_ebs.sh b/codebundles/aws-c7n-ebs-health/.test/create_ebs.sh deleted file mode 100755 index 37f95cd..0000000 --- a/codebundles/aws-c7n-ebs-health/.test/create_ebs.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# GIT_TLD=`git rev-parse --show-toplevel` -REGION="us-west-2" -AZ="${REGION}b" -EBS_VOLUME_NAME="ebs-test" -EBS_VOLUME_SIZE="1" - - -EBS_VOLUME_EXISTS=$( - aws ec2 describe-volumes \ - --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ - --query 'Volumes[0].VolumeId' \ - --region=$REGION \ - --output text \ - --no-cli-pager -) - - -if [[ "$EBS_VOLUME_EXISTS" == "None" ]]; then - aws ec2 create-volume --region=$REGION \ - --availability-zone=$AZ --size=$EBS_VOLUME_SIZE \ - --tag-specifications "ResourceType=volume,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ - --output text --no-encrypted --no-cli-pager -else - echo "$EBS_VOLUME_EXISTS ebs already exists" -fi \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh b/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh deleted file mode 100755 index 134da5b..0000000 --- a/codebundles/aws-c7n-ebs-health/.test/create_snapshot.sh +++ /dev/null @@ -1,170 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -# Configuration -REGION="us-west-2" -AZ="${REGION}b" -EBS_VOLUME_NAME="ebs-snapshot-test" -EBS_VOLUME_SIZE="1" -MAX_WAIT_TIME=300 # 5 minutes timeout - - -# Function to check volume status -wait_for_volume_state() { - local volume_id=$1 - local desired_state=$2 - local start_time=$(date +%s) - - while true; do - local current_state=$(aws ec2 describe-volumes \ - --volume-ids "$volume_id" \ - --region="$REGION" \ - --query 'Volumes[0].State' \ - --output text \ - --no-cli-pager) - - if [[ "$current_state" == "$desired_state" ]]; then - echo "Volume $volume_id is now $desired_state" - return 0 - fi - - if [[ "$current_state" == "error" ]]; then - echo "Error: Volume $volume_id is in error state" - return 1 - fi - - if (( $(date +%s) - start_time >= MAX_WAIT_TIME )); then - echo "Timeout waiting for volume $volume_id to become $desired_state" - return 1 - fi - - echo "Waiting for volume $volume_id (current state: $current_state)..." - sleep 5 - done -} - -# Function to wait for snapshot completion -wait_for_snapshot_completion() { - local snapshot_id=$1 - local start_time=$(date +%s) - - while true; do - local status=$(aws ec2 describe-snapshots \ - --snapshot-ids "$snapshot_id" \ - --region="$REGION" \ - --query 'Snapshots[0].State' \ - --output text \ - --no-cli-pager) - - if [[ "$status" == "completed" ]]; then - echo "Snapshot $snapshot_id completed successfully" - return 0 - fi - - if [[ "$status" == "error" ]]; then - echo "Error: Snapshot $snapshot_id failed" - return 1 - fi - - if (( $(date +%s) - start_time >= MAX_WAIT_TIME )); then - echo "Timeout waiting for snapshot $snapshot_id to complete" - return 1 - fi - - echo "Waiting for snapshot $snapshot_id (current state: $status)..." - sleep 10 - done -} - -# Main execution -main() { - - # Check if the EBS volume exists - echo "Checking for existing volume with name $EBS_VOLUME_NAME..." - EBS_VOLUME_EXISTS=$( - aws ec2 describe-volumes \ - --filters Name=tag:Name,Values="$EBS_VOLUME_NAME" \ - --region="$REGION" \ - --query 'Volumes[0].VolumeId' \ - --output text \ - --no-cli-pager - ) - - if [[ "$EBS_VOLUME_EXISTS" == "None" ]]; then - echo "Creating new EBS volume $EBS_VOLUME_NAME..." - EBS_VOLUME_ID=$( - aws ec2 create-volume \ - --region="$REGION" \ - --availability-zone="$AZ" \ - --size="$EBS_VOLUME_SIZE" \ - --tag-specifications "ResourceType=volume,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ - --query 'VolumeId' \ - --output text \ - --no-cli-pager - ) - echo "Created EBS volume: $EBS_VOLUME_ID" - - # Wait for volume to become available - wait_for_volume_state "$EBS_VOLUME_ID" "available" || exit 1 - else - EBS_VOLUME_ID=$EBS_VOLUME_EXISTS - echo "Using existing EBS volume: $EBS_VOLUME_ID" - fi - - # Check for existing snapshot - echo "Checking for existing snapshot..." - SNAPSHOT_ID=$( - aws ec2 describe-snapshots \ - --filters Name=tag:Name,Values="$EBS_VOLUME_NAME" \ - --region="$REGION" \ - --query 'Snapshots[0].SnapshotId' \ - --output text \ - --no-cli-pager - ) - - if [[ "$SNAPSHOT_ID" != "None" ]]; then - echo "Snapshot $SNAPSHOT_ID already exists" - else - echo "Creating new snapshot from volume $EBS_VOLUME_ID..." - SNAPSHOT_ID=$( - aws ec2 create-snapshot \ - --region="$REGION" \ - --volume-id="$EBS_VOLUME_ID" \ - --description="Snapshot of volume $EBS_VOLUME_ID" \ - --tag-specifications "ResourceType=snapshot,Tags=[{Key=Name,Value=$EBS_VOLUME_NAME}]" \ - --query 'SnapshotId' \ - --output text \ - --no-cli-pager - ) - - # Wait for snapshot completion - wait_for_snapshot_completion "$SNAPSHOT_ID" || exit 1 - fi - - # Delete the volume if it exists - if [[ -n "$EBS_VOLUME_ID" && "$EBS_VOLUME_ID" != "None" ]]; then - echo "Deleting EBS volume: $EBS_VOLUME_ID..." - aws ec2 delete-volume \ - --volume-id="$EBS_VOLUME_ID" \ - --region="$REGION" \ - --no-cli-pager - - # Wait for volume deletion - while aws ec2 describe-volumes --volume-ids "$EBS_VOLUME_ID" --region="$REGION" --no-cli-pager &>/dev/null; do - echo "Waiting for volume deletion..." - sleep 5 - done - echo "EBS volume $EBS_VOLUME_ID deleted successfully" - fi - - echo "Script completed successfully" - echo "Snapshot ID: $SNAPSHOT_ID" -} - -# Execute main function with error handling -main || { - echo "Script failed with error code $?" - exit 1 -} \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/delete_ebs.sh b/codebundles/aws-c7n-ebs-health/.test/delete_ebs.sh deleted file mode 100755 index 1049271..0000000 --- a/codebundles/aws-c7n-ebs-health/.test/delete_ebs.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# GIT_TLD=`git rev-parse --show-toplevel` - -REGION="us-west-2" -EBS_VOLUME_NAME="ebs-test" - -EBS_VOLUME_EXISTS=$( - aws ec2 describe-volumes \ - --filters Name=tag:Name,Values=$EBS_VOLUME_NAME \ - --query 'Volumes[0].VolumeId' \ - --region=$REGION \ - --output text \ - --no-cli-pager -) - -if [[ "$EBS_VOLUME_EXISTS" != "None" ]]; then - echo "Deleting EBS volume $EBS_VOLUME_EXISTS..." - aws ec2 delete-volume --volume-id "$EBS_VOLUME_EXISTS" --region=$REGION --no-cli-pager -else - echo "No EBS volume with tag Name=ebs-test exists." -fi diff --git a/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh b/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh deleted file mode 100755 index 82b86b6..0000000 --- a/codebundles/aws-c7n-ebs-health/.test/delete_snapshot.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -# Configuration -REGION="us-west-2" -EBS_VOLUME_NAME="ebs-snapshot-test" - - -# Main function -delete_snapshot() { - - echo "Checking for existing snapshot with name $EBS_VOLUME_NAME..." - SNAPSHOT_ID=$( - aws ec2 describe-snapshots \ - --filters Name=tag:Name,Values="$EBS_VOLUME_NAME" \ - --region="$REGION" \ - --query 'Snapshots[0].SnapshotId' \ - --output text \ - --no-cli-pager - ) - - if [[ "$SNAPSHOT_ID" == "None" ]]; then - echo "No snapshot found with the name $EBS_VOLUME_NAME. Exiting." - exit 0 - fi - - echo "Found snapshot: $SNAPSHOT_ID. Deleting..." - aws ec2 delete-snapshot --snapshot-id "$SNAPSHOT_ID" --region "$REGION" --no-cli-pager - echo "Snapshot $SNAPSHOT_ID deleted successfully." -} - -# Execute the function -delete_snapshot diff --git a/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf b/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf new file mode 100644 index 0000000..65f08c6 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf @@ -0,0 +1,23 @@ +# Check if the EBS volume exists (data source will fail if not found) +data "aws_ebs_volumes" "existing_volumes" { + filter { + name = "tag:Name" + values = [var.ebs_volume_name] + } +} + +# Create the EBS volume if it doesn't exist +resource "aws_ebs_volume" "ebs_volume" { + count = length(data.aws_ebs_volumes.existing_volumes.ids) == 0 ? 1 : 0 + availability_zone = var.availability_zone + size = var.ebs_volume_size + + tags = { + Name = var.ebs_volume_name + } +} + +# Output +output "volume_id" { + value = aws_ebs_volume.ebs_volume[0].id +} \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/terraform/provider.tf b/codebundles/aws-c7n-ebs-health/.test/terraform/provider.tf new file mode 100644 index 0000000..aa39e39 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/terraform/provider.tf @@ -0,0 +1,3 @@ +provider "aws" { + region = "us-west-2" # Replace with your desired region +} \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/terraform/snapshot.tf b/codebundles/aws-c7n-ebs-health/.test/terraform/snapshot.tf new file mode 100644 index 0000000..f2c77d4 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/terraform/snapshot.tf @@ -0,0 +1,51 @@ +# Data Source: Check if the volume exists +data "aws_ebs_volumes" "existing_volume" { + filter { + name = "tag:Name" + values = [var.snapshot_volume_name] + } +} + +# Resource: Create EBS volume only if it doesn't exist +resource "aws_ebs_volume" "snapshot_volume" { + count = length(data.aws_ebs_volumes.existing_volume.ids) == 0 ? 1 : 0 + availability_zone = var.availability_zone + size = var.snapshot_volume_size + + tags = { + Name = var.snapshot_volume_name + } +} + + +# Resource: Create snapshot if it doesn't exist +resource "aws_ebs_snapshot" "new_snapshot" { + + volume_id = coalesce( + try(data.aws_ebs_volumes.existing_volume.ids[0], null), + try(aws_ebs_volume.snapshot_volume[0].id, null) + ) + + description = "Snapshot of volume" + tags = { + Name = var.snapshot_volume_name + } +} + +# Resource: Delete volume after snapshot creation +resource "null_resource" "delete_volume" { + depends_on = [aws_ebs_snapshot.new_snapshot] + provisioner "local-exec" { + command = < 0 ? data.aws_ebs_volumes.existing_volume.ids[0] : aws_ebs_volume.snapshot_volume[0].id} \ + --region ${var.region} \ + --no-cli-pager + EOT + } +} + +# Outputs +output "snapshot_id" { + value = aws_ebs_snapshot.new_snapshot.id +} \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/terraform/vars.tf b/codebundles/aws-c7n-ebs-health/.test/terraform/vars.tf new file mode 100644 index 0000000..2a90ab7 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/terraform/vars.tf @@ -0,0 +1,25 @@ +# Variables +variable "ebs_volume_name" { + default = "ebs-test" +} + +variable "ebs_volume_size" { + default = 1 +} + +variable "region" { + default = "us-west-2" +} + +variable "availability_zone" { + default = "us-west-2b" +} + + +variable "snapshot_volume_name" { + default = "ebs-snapshot-test" +} + +variable "snapshot_volume_size" { + default = 1 +} \ No newline at end of file From cfb684b8a19d53501548045dd0f0453b26e1c68a Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 27 Nov 2024 15:59:03 +0530 Subject: [PATCH 13/44] remove volume check and add encrypted false in ebs.tf --- .../aws-c7n-ebs-health/.test/terraform/ebs.tf | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf b/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf index 65f08c6..5b79855 100644 --- a/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf +++ b/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf @@ -1,17 +1,8 @@ -# Check if the EBS volume exists (data source will fail if not found) -data "aws_ebs_volumes" "existing_volumes" { - filter { - name = "tag:Name" - values = [var.ebs_volume_name] - } -} - # Create the EBS volume if it doesn't exist resource "aws_ebs_volume" "ebs_volume" { - count = length(data.aws_ebs_volumes.existing_volumes.ids) == 0 ? 1 : 0 availability_zone = var.availability_zone size = var.ebs_volume_size - + encrypted = false tags = { Name = var.ebs_volume_name } @@ -19,5 +10,5 @@ resource "aws_ebs_volume" "ebs_volume" { # Output output "volume_id" { - value = aws_ebs_volume.ebs_volume[0].id + value = aws_ebs_volume.ebs_volume.id } \ No newline at end of file From e9f45133f4d4479f38fe69f04ea1d91259f2015d Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 27 Nov 2024 17:08:44 +0530 Subject: [PATCH 14/44] added taskfile in ebs health codebundle --- codebundles/aws-c7n-ebs-health/.test/Makefile | 11 - .../aws-c7n-ebs-health/.test/Taskfile.yaml | 323 ++++++++++++++++++ .../.test/terraform/Taskfile.yaml | 69 ++++ 3 files changed, 392 insertions(+), 11 deletions(-) delete mode 100644 codebundles/aws-c7n-ebs-health/.test/Makefile create mode 100644 codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml create mode 100644 codebundles/aws-c7n-ebs-health/.test/terraform/Taskfile.yaml diff --git a/codebundles/aws-c7n-ebs-health/.test/Makefile b/codebundles/aws-c7n-ebs-health/.test/Makefile deleted file mode 100644 index 54fd688..0000000 --- a/codebundles/aws-c7n-ebs-health/.test/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -create-ebs-volume: - ./create_ebs.sh - -delete-ebs-volume: - ./delete_ebs.sh - -create-ebs-snapshot: - ./create_snapshot.sh - -delete-ebs-snapshot: - ./delete_snapshot.sh \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml b/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml new file mode 100644 index 0000000..582c0c6 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml @@ -0,0 +1,323 @@ +version: "3" + +tasks: + default: + desc: "Generate workspaceInfo and rebuild/test" + cmds: + - task: check-unpushed-commits + - task: generate-rwl-config + - task: run-rwl-discovery + + clean: + desc: "Run cleanup tasks" + cmds: + - task: check-and-cleanup-terraform + # - task: clean-rwl-discovery + + build-infra: + desc: "Build test infrastructure" + cmds: + - task: build-terraform-infra + + check-unpushed-commits: + desc: Check if outstanding commits or file updates need to be pushed before testing. + vars: + # Specify the base directory relative to your Taskfile location + BASE_DIR: "../" + cmds: + - | + echo "Checking for uncommitted changes in $BASE_DIR and $BASE_DIR.runwhen, excluding '.test'..." + UNCOMMITTED_FILES=$(git diff --name-only HEAD | grep -E "^${BASE_DIR}(\.runwhen|[^/]+)" | grep -v "/\.test/" || true) + if [ -n "$UNCOMMITTED_FILES" ]; then + echo "✗" + echo "Uncommitted changes found:" + echo "$UNCOMMITTED_FILES" + echo "Remember to commit & push changes before executing the `run-rwl-discovery` task." + echo "------------" + exit 1 + else + echo "√" + echo "No uncommitted changes in specified directories." + echo "------------" + fi + - | + echo "Checking for unpushed commits in $BASE_DIR and $BASE_DIR.runwhen, excluding '.test'..." + git fetch origin + UNPUSHED_FILES=$(git diff --name-only origin/$(git rev-parse --abbrev-ref HEAD) HEAD | grep -E "^${BASE_DIR}(\.runwhen|[^/]+)" | grep -v "/\.test/" || true) + if [ -n "$UNPUSHED_FILES" ]; then + echo "✗" + echo "Unpushed commits found:" + echo "$UNPUSHED_FILES" + echo "Remember to push changes before executing the `run-rwl-discovery` task." + echo "------------" + exit 1 + else + echo "√" + echo "No unpushed commits in specified directories." + echo "------------" + fi + silent: true + + generate-rwl-config: + desc: "Generate RunWhen Local configuration (workspaceInfo.yaml)" + env: + AWS_ACCESS_KEY_ID: "{{.AWS_ACCESS_KEY_ID}}" + AWS_SECRET_ACCESS_KEY: "{{.AWS_SECRET_ACCESS_KEY}}" + AWS_DEFAULT_REGION: "{{.AWS_DEFAULT_REGION}}" + RW_WORKSPACE: '{{.RW_WORKSPACE | default "my-workspace"}}' + cmds: + - | + source terraform/tf.secret + repo_url=$(git config --get remote.origin.url) + branch_name=$(git rev-parse --abbrev-ref HEAD) + codebundle=$(basename "$(dirname "$PWD")") + + # Fetch individual cluster details from Terraform state + # pushd terraform > /dev/null + # cluster1_name=$(terraform show -json terraform.tfstate | jq -r ' + # .values.outputs.cluster_1_name.value') + + # popd > /dev/null + + # # Check if any of the required cluster variables are empty + # if [ -z "$cluster1_name" ] || [ -z "$cluster1_server" ] || [ -z "$cluster1_resource_group" ]; then + # echo "Error: Missing cluster details. Ensure Terraform plan has been applied." + # exit 1 + # fi + + # Generate workspaceInfo.yaml with fetched cluster details + cat < workspaceInfo.yaml + workspaceName: "$RW_WORKSPACE" + workspaceOwnerEmail: authors@runwhen.com + defaultLocation: location-01 + defaultLOD: detailed + cloudConfig: + aws: + awsAccessKeyId: "$AWS_ACCESS_KEY_ID" + awsSecretAccessKey: "$AWS_SECRET_ACCESS_KEY" + codeCollections: + - repoURL: "$repo_url" + branch: "$branch_name" + codeBundles: ["$codebundle"] + custom: [] + EOF + silent: true + + run-rwl-discovery: + desc: "Run RunWhen Local Discovery on test infrastructure" + cmds: + - | + CONTAINER_NAME="RunWhenLocal" + if docker ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then + echo "Stopping and removing existing container $CONTAINER_NAME..." + docker stop $CONTAINER_NAME && docker rm $CONTAINER_NAME + elif docker ps -a -q --filter "name=$CONTAINER_NAME" | grep -q .; then + echo "Removing existing stopped container $CONTAINER_NAME..." + docker rm $CONTAINER_NAME + else + echo "No existing container named $CONTAINER_NAME found." + fi + + echo "Cleaning up output directory..." + sudo rm -rf output || { echo "Failed to remove output directory"; exit 1; } + mkdir output && chmod 777 output || { echo "Failed to set permissions"; exit 1; } + + echo "Starting new container $CONTAINER_NAME..." + + docker run --name $CONTAINER_NAME -e DEBUG_LOGGING=true -p 8081:8081 -v "$(pwd)":/shared -d ghcr.io/runwhen-contrib/runwhen-local:latest || { + echo "Failed to start container"; exit 1; + } + + echo "Running workspace builder script in container..." + docker exec -w /workspace-builder $CONTAINER_NAME ./run.sh $1 --verbose || { + echo "Error executing script in container"; exit 1; + } + + echo "Review generated config files under output/workspaces/" + silent: true + + check-terraform-infra: + desc: "Check if Terraform has any deployed infrastructure in the terraform subdirectory" + cmds: + - | + # Source Envs for Auth + source terraform/tf.secret + + # Navigate to the Terraform directory + if [ ! -d "terraform" ]; then + echo "Terraform directory not found." + exit 1 + fi + cd terraform + + # Check if Terraform state file exists + if [ ! -f "terraform.tfstate" ]; then + echo "No Terraform state file found in the terraform directory. No infrastructure is deployed." + exit 0 + fi + + # List resources in Terraform state + resources=$(terraform state list) + + # Check if any resources are listed in the state file + if [ -n "$resources" ]; then + echo "Deployed infrastructure detected." + echo "$resources" + exit 0 + else + echo "No deployed infrastructure found in Terraform state." + exit 0 + fi + silent: true + + build-terraform-infra: + desc: "Run terraform apply" + cmds: + - | + # Source Envs for Auth + source terraform/tf.secret + + + # Navigate to the Terraform directory + if [ -d "terraform" ]; then + cd terraform + else + echo "Terraform directory not found. Terraform apply aborted." + exit 1 + fi + task format-and-init-terraform + echo "Starting Terraform Build of Terraform infrastructure..." + terraform apply -auto-approve || { + echo "Failed to clean up Terraform infrastructure." + exit 1 + } + echo "Terraform infrastructure build completed." + silent: true + + upload-slxs: + desc: "Upload SLX files to the appropriate URL" + env: + RW_WORKSPACE: "{{.RW_WORKSPACE}}" + RW_API_URL: "{{.RW_API}}" + RW_PAT: "{{.RW_PAT}}" + cmds: + - task: check-rwp-config + - | + BASE_DIR="output/workspaces/${RW_WORKSPACE}/slxs" + if [ ! -d "$BASE_DIR" ]; then + echo "Directory $BASE_DIR does not exist. Upload aborted." + exit 1 + fi + + for dir in "$BASE_DIR"/*; do + if [ -d "$dir" ]; then + SLX_NAME=$(basename "$dir") + PAYLOAD=$(jq -n --arg commitMsg "Creating new SLX $SLX_NAME" '{ commitMsg: $commitMsg, files: {} }') + for file in slx.yaml runbook.yaml sli.yaml; do + if [ -f "$dir/$file" ]; then + CONTENT=$(cat "$dir/$file") + PAYLOAD=$(echo "$PAYLOAD" | jq --arg fileContent "$CONTENT" --arg fileName "$file" '.files[$fileName] = $fileContent') + fi + done + + URL="https://${RW_API_URL}/api/v3/workspaces/${RW_WORKSPACE}/branches/main/slxs/${SLX_NAME}" + echo "Uploading SLX: $SLX_NAME to $URL" + response_code=$(curl -X POST "$URL" \ + -H "Authorization: Bearer $RW_PAT" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + -w "%{http_code}" -o /dev/null -s) + + if [[ "$response_code" == "200" || "$response_code" == "201" ]]; then + echo "Successfully uploaded SLX: $SLX_NAME to $URL" + elif [[ "$response_code" == "405" ]]; then + echo "Failed to upload SLX: $SLX_NAME to $URL. Method not allowed (405)." + else + echo "Failed to upload SLX: $SLX_NAME to $URL. Unexpected response code: $response_code" + fi + fi + done + silent: true + + delete-slxs: + desc: "Delete SLX objects from the appropriate URL" + env: + RW_WORKSPACE: '{{.RW_WORKSPACE | default "my-workspace"}}' + RW_API_URL: "{{.RW_API}}" + RW_PAT: "{{.RW_PAT}}" + cmds: + - task: check-rwp-config + - | + BASE_DIR="output/workspaces/${RW_WORKSPACE}/slxs" + if [ ! -d "$BASE_DIR" ]; then + echo "Directory $BASE_DIR does not exist. Deletion aborted." + exit 1 + fi + + for dir in "$BASE_DIR"/*; do + if [ -d "$dir" ]; then + SLX_NAME=$(basename "$dir") + URL="https://${RW_API_URL}/api/v3/workspaces/${RW_WORKSPACE}/branches/main/slxs/${SLX_NAME}" + echo "Deleting SLX: $SLX_NAME from $URL" + response_code=$(curl -X DELETE "$URL" \ + -H "Authorization: Bearer $RW_PAT" \ + -H "Content-Type: application/json" \ + -w "%{http_code}" -o /dev/null -s) + + if [[ "$response_code" == "200" || "$response_code" == "204" ]]; then + echo "Successfully deleted SLX: $SLX_NAME from $URL" + elif [[ "$response_code" == "405" ]]; then + echo "Failed to delete SLX: $SLX_NAME from $URL. Method not allowed (405)." + else + echo "Failed to delete SLX: $SLX_NAME from $URL. Unexpected response code: $response_code" + fi + fi + done + silent: true + + cleanup-terraform-infra: + desc: "Cleanup deployed Terraform infrastructure" + cmds: + - | + # Source Envs for Auth + source terraform/tf.secret + + # Navigate to the Terraform directory + if [ -d "terraform" ]; then + cd terraform + else + echo "Terraform directory not found. Cleanup aborted." + exit 1 + fi + + echo "Starting cleanup of Terraform infrastructure..." + terraform destroy -auto-approve || { + echo "Failed to clean up Terraform infrastructure." + exit 1 + } + echo "Terraform infrastructure cleanup completed." + silent: true + + check-and-cleanup-terraform: + desc: "Check and clean up deployed Terraform infrastructure if it exists" + cmds: + - | + # Capture the output of check-terraform-infra + infra_output=$(task check-terraform-infra | tee /dev/tty) + + # Check if output contains indication of deployed infrastructure + if echo "$infra_output" | grep -q "Deployed infrastructure detected"; then + echo "Infrastructure detected; proceeding with cleanup." + task cleanup-terraform-infra + else + echo "No deployed infrastructure found; no cleanup required." + fi + silent: true + + clean-rwl-discovery: + desc: "Check and clean up RunWhen Local discovery output" + cmds: + - | + sudo rm -rf output + rm workspaceInfo.yaml + silent: true diff --git a/codebundles/aws-c7n-ebs-health/.test/terraform/Taskfile.yaml b/codebundles/aws-c7n-ebs-health/.test/terraform/Taskfile.yaml new file mode 100644 index 0000000..08e0e83 --- /dev/null +++ b/codebundles/aws-c7n-ebs-health/.test/terraform/Taskfile.yaml @@ -0,0 +1,69 @@ +version: '3' + +env: + TERM: screen-256color + +tasks: + default: + cmds: + - task: test + + test: + desc: Run tests. + cmds: + - task: test-terraform + + clean: + desc: Clean the environment. + cmds: + - task: clean-go + - task: clean-terraform + + clean-terraform: + desc: Clean the terraform environment (remove terraform directories and files) + cmds: + - find . -type d -name .terraform -exec rm -rf {} + + - find . -type f -name .terraform.lock.hcl -delete + + format-and-init-terraform: + desc: Run Terraform fmt and init + cmds: + - | + terraform fmt + terraform init + test-terraform: + desc: Run tests for all terraform directories. + silent: true + env: + DIRECTORIES: + sh: find . -path '*/.terraform/*' -prune -o -name '*.tf' -type f -exec dirname {} \; | sort -u + cmds: + - | + BOLD=$(tput bold) + NORM=$(tput sgr0) + + CWD=$PWD + + for d in $DIRECTORIES; do + cd $d + echo "${BOLD}$PWD:${NORM}" + if ! terraform fmt -check=true -list=false -recursive=false; then + echo " ✗ terraform fmt" && exit 1 + else + echo " √ terraform fmt" + fi + + if ! terraform init -backend=false -input=false -get=true -no-color > /dev/null; then + echo " ✗ terraform init" && exit 1 + else + echo " √ terraform init" + fi + + if ! terraform validate > /dev/null; then + echo " ✗ terraform validate" && exit 1 + else + echo " √ terraform validate" + fi + + cd $CWD + done \ No newline at end of file From 6102c5920407c5cda179fef832e3724fc551d12d Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 27 Nov 2024 19:44:12 +0530 Subject: [PATCH 15/44] add account_id in ebs gen rule qualifiers --- .../.runwhen/generation-rules/aws-c7n-s3-health.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml index 8115421..0494ad8 100644 --- a/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml +++ b/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml @@ -13,7 +13,7 @@ spec: mode: substring slxs: - baseName: aws-c7n-ebs-health - qualifiers: ["region"] + qualifiers: ["account_id", "region"] baseTemplateName: aws-c7n-ebs-health levelOfDetail: basic outputItems: From 4c59821ca4e512f78e30cea77b155420a792ecc7 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 27 Nov 2024 19:58:15 +0530 Subject: [PATCH 16/44] add check-rwp-config task in ebs cb test's taskfile --- .../aws-c7n-ebs-health/.test/Taskfile.yaml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml b/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml index 582c0c6..d2d52ec 100644 --- a/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml +++ b/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml @@ -194,6 +194,30 @@ tasks: echo "Terraform infrastructure build completed." silent: true + check-rwp-config: + desc: Check if env vars are set for RunWhen Platform + cmds: + - | + missing_vars=() + + if [ -z "$RW_WORKSPACE" ]; then + missing_vars+=("RW_WORKSPACE") + fi + + if [ -z "$RW_API_URL" ]; then + missing_vars+=("RW_API_URL") + fi + + if [ -z "$RW_PAT" ]; then + missing_vars+=("RW_PAT") + fi + + if [ ${#missing_vars[@]} -ne 0 ]; then + echo "The following required environment variables are missing: ${missing_vars[*]}" + exit 1 + fi + silent: true + upload-slxs: desc: "Upload SLX files to the appropriate URL" env: From 90b306babc0c780261f8f20e8db42bb430eeb5e4 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 27 Nov 2024 19:58:42 +0530 Subject: [PATCH 17/44] update ebs cb test README --- .../aws-c7n-ebs-health/.test/README.md | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/.test/README.md b/codebundles/aws-c7n-ebs-health/.test/README.md index f5c7f0b..5bbadde 100644 --- a/codebundles/aws-c7n-ebs-health/.test/README.md +++ b/codebundles/aws-c7n-ebs-health/.test/README.md @@ -1,14 +1,27 @@ -### To create/delete unattached unencrypted volume run: +### AWS IAM policy for running this codebundle: -```sh -make create-ebs-volume - -make delete-ebs-volume -``` - -### [WIP] To create/delete unused snapshot run: - -```sh -make create-ebs-snapshot -make delete-ebs-snapshot +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:DescribeVolumeStatus", + "ec2:DescribeTags", + "autoscaling:DescribeAutoScalingGroups", + "ec2:DescribeRegions", + "ec2:DescribeVolumes", + "autoscaling:DescribeTags", + "autoscaling:DescribeLaunchConfigurations", + "ec2:DescribeSnapshots", + "ec2:DescribeVolumeAttribute" + ], + "Resource": "*" + } + ] +} ``` \ No newline at end of file From 7f81bd3589c48dd1b08ce5e5dbe697572d2ea81e Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Thu, 28 Nov 2024 12:57:17 +0530 Subject: [PATCH 18/44] add encrypted filed in ebs tf file --- codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf b/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf index 5b79855..f9902af 100644 --- a/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf +++ b/codebundles/aws-c7n-ebs-health/.test/terraform/ebs.tf @@ -2,7 +2,7 @@ resource "aws_ebs_volume" "ebs_volume" { availability_zone = var.availability_zone size = var.ebs_volume_size - encrypted = false + encrypted = false tags = { Name = var.ebs_volume_name } From 9602af8183ba390047269485d38908e9edf78a21 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Thu, 28 Nov 2024 18:11:07 +0530 Subject: [PATCH 19/44] add suite variables AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in ebs health cb --- codebundles/aws-c7n-ebs-health/runbook.robot | 4 +++- codebundles/aws-c7n-ebs-health/sli.robot | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/runbook.robot b/codebundles/aws-c7n-ebs-health/runbook.robot index fe4ec31..8de18eb 100644 --- a/codebundles/aws-c7n-ebs-health/runbook.robot +++ b/codebundles/aws-c7n-ebs-health/runbook.robot @@ -154,4 +154,6 @@ Suite Initialization ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health # Note: Clean out the cloud custoding report dir to ensure accurate data Set Suite Variable ${AWS_ACCOUNT_NAME} ${aws_account_name_query.stdout} Set Suite Variable ${AWS_REGION} ${AWS_REGION} - Set Suite Variable ${AWS_ACCOUNT_ID} ${AWS_ACCOUNT_ID} \ No newline at end of file + Set Suite Variable ${AWS_ACCOUNT_ID} ${AWS_ACCOUNT_ID} + Set Suite Variable ${AWS_ACCESS_KEY_ID} ${AWS_ACCESS_KEY_ID} + Set Suite Variable ${AWS_SECRET_ACCESS_KEY} ${AWS_SECRET_ACCESS_KEY} \ No newline at end of file diff --git a/codebundles/aws-c7n-ebs-health/sli.robot b/codebundles/aws-c7n-ebs-health/sli.robot index 34e6025..5a44290 100644 --- a/codebundles/aws-c7n-ebs-health/sli.robot +++ b/codebundles/aws-c7n-ebs-health/sli.robot @@ -89,4 +89,6 @@ Suite Initialization Set Suite Variable ${AWS_REGION} ${AWS_REGION} Set Suite Variable ${AWS_ACCOUNT_ID} ${AWS_ACCOUNT_ID} Set Suite Variable ${EVENT_THRESHOLD} ${EVENT_THRESHOLD} - Set Suite Variable ${SECURITY_EVENT_THRESHOLD} ${SECURITY_EVENT_THRESHOLD} \ No newline at end of file + Set Suite Variable ${SECURITY_EVENT_THRESHOLD} ${SECURITY_EVENT_THRESHOLD} + Set Suite Variable ${AWS_ACCESS_KEY_ID} ${AWS_ACCESS_KEY_ID} + Set Suite Variable ${AWS_SECRET_ACCESS_KEY} ${AWS_SECRET_ACCESS_KEY} \ No newline at end of file From becadf91f532cabcef4f34394be5c85ea98566ac Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 29 Nov 2024 14:13:11 +0530 Subject: [PATCH 20/44] add rw-cli-keywords dependency in requirements.txt --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a9540ed..c737c58 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ robotframework>=4.1.2 ruamel.base>=1.0.0 ruamel.yaml>=0.17.20 requests>=2 -c7n>=0.9.41 \ No newline at end of file +c7n>=0.9.41 +rw-cli-keywords>=0.0.19 \ No newline at end of file From 37fd8b74dd28335963ad9ea8da80b3d9cf591796 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 29 Nov 2024 14:14:18 +0530 Subject: [PATCH 21/44] fix sli locations filed in both ebs and s3 cb --- .../.runwhen/templates/aws-c7n-ebs-health-sli.yaml | 3 ++- .../.runwhen/templates/aws-c7n-s3-health-sli.yaml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml index 44a67aa..b8faae9 100644 --- a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml +++ b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml @@ -9,7 +9,8 @@ metadata: spec: displayUnitsLong: OK displayUnitsShort: ok - location: {{default_location}} + locations: + - {{default_location}} description: Measures securitiy and health of S3 buckets in this AWS region and account. codeBundle: {% if repo_url %} diff --git a/codebundles/aws-c7n-s3-health/.runwhen/templates/aws-c7n-s3-health-sli.yaml b/codebundles/aws-c7n-s3-health/.runwhen/templates/aws-c7n-s3-health-sli.yaml index 6d9f4cb..fed4ac3 100644 --- a/codebundles/aws-c7n-s3-health/.runwhen/templates/aws-c7n-s3-health-sli.yaml +++ b/codebundles/aws-c7n-s3-health/.runwhen/templates/aws-c7n-s3-health-sli.yaml @@ -9,7 +9,8 @@ metadata: spec: displayUnitsLong: OK displayUnitsShort: ok - location: {{default_location}} + locations: + - {{default_location}} description: Measures securitiy and health of S3 buckets in this AWS region and account. codeBundle: {% if repo_url %} From 5672dc964ef04835a30df9ae683faaee630fa4b9 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 29 Nov 2024 14:15:08 +0530 Subject: [PATCH 22/44] update Author in sli --- codebundles/aws-c7n-ebs-health/sli.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebundles/aws-c7n-ebs-health/sli.robot b/codebundles/aws-c7n-ebs-health/sli.robot index 5a44290..81c7e87 100644 --- a/codebundles/aws-c7n-ebs-health/sli.robot +++ b/codebundles/aws-c7n-ebs-health/sli.robot @@ -1,5 +1,5 @@ *** Settings *** -Metadata Author runwhen +Metadata Author saurabh3460 Metadata Support AWS EBS Documentation Counts the number of EBS resources by identifying unattached volumes, unused and aged snapshots, and unencrypted volumes. Force Tags EBS Volume AWS Storage Secure From 53c55bc119db76202da6a15e297f96e2c3888d71 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 29 Nov 2024 14:16:04 +0530 Subject: [PATCH 23/44] fix Add Issue and change AWS_ACCOUNT_NAME -> AWS_ACCOUNT_ID --- codebundles/aws-c7n-ebs-health/runbook.robot | 31 ++++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/runbook.robot b/codebundles/aws-c7n-ebs-health/runbook.robot index 8de18eb..399d19b 100644 --- a/codebundles/aws-c7n-ebs-health/runbook.robot +++ b/codebundles/aws-c7n-ebs-health/runbook.robot @@ -1,5 +1,5 @@ *** Settings *** -Metadata Author runwhen +Metadata Author saurabh3460 Metadata Support AWS EBS Documentation Audit EBS resources by identifying unattached volumes, unused and aged snapshots, and unencrypted volumes. Force Tags EBS Volume AWS Storage Secure @@ -29,11 +29,10 @@ List Unattached EBS Volumes in `${AWS_REGION}` RW.Core.Add Pre To Report ${parsed_results} ${clean_output_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes - # Convert custodian json output to a list. TRY ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json - Log ${report_data.stdout} + Log ${report_data.stdout} EXCEPT Log Failed to load JSON payload, defaulting to empty list. WARN ${ebs_volume_list}= Create List @@ -45,11 +44,11 @@ List Unattached EBS Volumes in `${AWS_REGION}` RW.Core.Add Issue ... severity=2 ... expected=EBS volumes in AWS Account `${AWS_ACCOUNT_NAME}` should be attached or in use - ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_NAME}` is unused - ... title=Unused EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_NAME}` + ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_ID}` is unused + ... title=Unused EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. ... details=${item} # Include details such as volume ID, size, and region. - ... next_steps=Escalate to service owner for review of unattached volume `${item["VolumeId"]}` in ${AWS_ACCOUNT_NAME} AWS account in AWS Region ${AWS_REGION}". + ... next_steps=Escalate to service owner for review of unattached volume `${item["VolumeId"]}` in ${AWS_ACCOUNT_ID} AWS account in AWS Region ${AWS_REGION}". END END @@ -73,7 +72,7 @@ List Unencrypted EBS Volumes in `${AWS_REGION}` TRY ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json - Log ${report_data.stdout} + Log ${report_data.stdout} EXCEPT Log Failed to load JSON payload, defaulting to empty list. WARN ${ebs_volume_list}= Create List @@ -83,12 +82,12 @@ List Unencrypted EBS Volumes in `${AWS_REGION}` FOR ${item} IN @{ebs_volume_list} RW.Core.Add Issue ... severity=2 - ... expected=EBS volumes in AWS Account `${AWS_ACCOUNT_NAME}` should be unencrypted or in use - ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_NAME}` is unencrypted - ... title=Unencrypted EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_NAME}` + ... expected=EBS volumes in AWS Account `${AWS_ACCOUNT_ID}` should be unencrypted or in use + ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_ID}` is unencrypted + ... title=Unencrypted EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. ... details=${item} - ... next_steps=Escalate to service owner to review Unencrypted AWS EBS volume `${item["VolumeId"]}` found in ${AWS_ACCOUNT_NAME} AWS account in AWS Region ${AWS_REGION}". + ... next_steps=Escalate to service owner to review Unencrypted AWS EBS volume `${item["VolumeId"]}` found in ${AWS_ACCOUNT_ID} AWS account in AWS Region ${AWS_REGION}". END END @@ -110,7 +109,7 @@ List Unused EBS Snapshots in`${AWS_REGION}` ${clean_output_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots TRY ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json - Log ${report_data.stdout} + Log ${report_data.stdout} EXCEPT Log Failed to load JSON payload, defaulting to empty list. WARN ${ebs_volume_list}= Create List @@ -120,12 +119,12 @@ List Unused EBS Snapshots in`${AWS_REGION}` FOR ${item} IN @{ebs_volume_list} RW.Core.Add Issue ... severity=2 - ... expected=EBS snapshots in AWS Account `${AWS_ACCOUNT_NAME}` should be attached or in use - ... actual=EBS Snapshots `${item["SnapshotId"]}` in AWS Account `${AWS_ACCOUNT_NAME}` is unused - ... title=Unused EBS Snapshot `${item["SnapshotId"]}` detected in AWS Account `${AWS_ACCOUNT_NAME}` + ... expected=EBS snapshots in AWS Account `${AWS_ACCOUNT_ID}` should be attached or in use + ... actual=EBS Snapshots `${item["SnapshotId"]}` in AWS Account `${AWS_ACCOUNT_ID}` is unused + ... title=Unused EBS Snapshot `${item["SnapshotId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` ... reproduce_hint=Review the Snapshots details and usage in the AWS Management Console or CLI. ... details=${item} # Include details such as volume ID, size, and region. - ... next_steps=Escalate to service owner for review of unused EBS Snapshot `${item["SnapshotId"]}` in ${AWS_ACCOUNT_NAME} AWS account in AWS Region ${AWS_REGION}". + ... next_steps=Escalate to service owner for review of unused EBS Snapshot `${item["SnapshotId"]}` in ${AWS_ACCOUNT_ID} AWS account in AWS Region ${AWS_REGION}". END END From 406e2fddc6afba75d193bb4cba56f47343a3395f Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 29 Nov 2024 14:16:50 +0530 Subject: [PATCH 24/44] ebs Taskfile: add custom field and terraform/cb.secret --- codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml b/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml index d2d52ec..4319299 100644 --- a/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml +++ b/codebundles/aws-c7n-ebs-health/.test/Taskfile.yaml @@ -67,7 +67,7 @@ tasks: RW_WORKSPACE: '{{.RW_WORKSPACE | default "my-workspace"}}' cmds: - | - source terraform/tf.secret + source terraform/cb.secret repo_url=$(git config --get remote.origin.url) branch_name=$(git rev-parse --abbrev-ref HEAD) codebundle=$(basename "$(dirname "$PWD")") @@ -89,7 +89,7 @@ tasks: cat < workspaceInfo.yaml workspaceName: "$RW_WORKSPACE" workspaceOwnerEmail: authors@runwhen.com - defaultLocation: location-01 + defaultLocation: location-01-us-west1 defaultLOD: detailed cloudConfig: aws: @@ -99,7 +99,9 @@ tasks: - repoURL: "$repo_url" branch: "$branch_name" codeBundles: ["$codebundle"] - custom: [] + custom: + aws_access_key_id: AWS_ACCESS_KEY_ID + aws_secret_access_key: AWS_SECRET_ACCESS_KEY EOF silent: true @@ -198,6 +200,7 @@ tasks: desc: Check if env vars are set for RunWhen Platform cmds: - | + source terraform/cb.secret missing_vars=() if [ -z "$RW_WORKSPACE" ]; then @@ -227,6 +230,7 @@ tasks: cmds: - task: check-rwp-config - | + source terraform/cb.secret BASE_DIR="output/workspaces/${RW_WORKSPACE}/slxs" if [ ! -d "$BASE_DIR" ]; then echo "Directory $BASE_DIR does not exist. Upload aborted." @@ -272,6 +276,7 @@ tasks: cmds: - task: check-rwp-config - | + source terraform/cb.secret BASE_DIR="output/workspaces/${RW_WORKSPACE}/slxs" if [ ! -d "$BASE_DIR" ]; then echo "Directory $BASE_DIR does not exist. Deletion aborted." From b0d6153e24a061527fcd6e01bc5466bcb43b10af Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 29 Nov 2024 14:22:43 +0530 Subject: [PATCH 25/44] ebs sli: fix score logic --- codebundles/aws-c7n-ebs-health/sli.robot | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/sli.robot b/codebundles/aws-c7n-ebs-health/sli.robot index 81c7e87..a36cc00 100644 --- a/codebundles/aws-c7n-ebs-health/sli.robot +++ b/codebundles/aws-c7n-ebs-health/sli.robot @@ -21,7 +21,7 @@ Check Unattached EBS Volumes in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unattached_ebs_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${EVENT_THRESHOLD}) else 0 + ${unattached_ebs_event_score}= Evaluate 1 if int(${count.stdout}) >= int(${EVENT_THRESHOLD}) else 0 Set Global Variable ${unattached_ebs_event_score} Check Unencrypted EBS Volumes in `${AWS_REGION}` @@ -33,7 +33,7 @@ Check Unencrypted EBS Volumes in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unencrypted-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unencrypted_ebs_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${SECURITY_EVENT_THRESHOLD}) else 0 + ${unencrypted_ebs_event_score}= Evaluate 1 if int(${count.stdout}) >= int(${SECURITY_EVENT_THRESHOLD}) else 0 Set Global Variable ${unencrypted_ebs_event_score} @@ -46,7 +46,7 @@ Check Unused EBS Snapshots in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unsued_ebs_snapshot_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${EVENT_THRESHOLD}) else 0 + ${unsued_ebs_snapshot_event_score}= Evaluate 1 if int(${count.stdout}) >= int(${EVENT_THRESHOLD}) else 0 Set Global Variable ${unsued_ebs_snapshot_event_score} From 4580a3b50e44540fe5da74fd11f9a100418cf058 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 29 Nov 2024 19:17:13 +0530 Subject: [PATCH 26/44] ebs runbook: update next steps string and task title --- codebundles/aws-c7n-ebs-health/runbook.robot | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/runbook.robot b/codebundles/aws-c7n-ebs-health/runbook.robot index 399d19b..a310374 100644 --- a/codebundles/aws-c7n-ebs-health/runbook.robot +++ b/codebundles/aws-c7n-ebs-health/runbook.robot @@ -13,7 +13,7 @@ Suite Setup Suite Initialization *** Tasks *** -List Unattached EBS Volumes in `${AWS_REGION}` +List Unattached EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` [Documentation] Check for unattached EBS volumes in the specified region. [Tags] ebs storage aws volume ${c7n_output}= RW.CLI.Run Cli @@ -48,12 +48,12 @@ List Unattached EBS Volumes in `${AWS_REGION}` ... title=Unused EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. ... details=${item} # Include details such as volume ID, size, and region. - ... next_steps=Escalate to service owner for review of unattached volume `${item["VolumeId"]}` in ${AWS_ACCOUNT_ID} AWS account in AWS Region ${AWS_REGION}". + ... next_steps="Escalate to service owner to review of unattached AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Delete unattached AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." END END -List Unencrypted EBS Volumes in `${AWS_REGION}` +List Unencrypted EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` [Documentation] Check for unattached EBS volumes in the specified region. [Tags] ebs storage aws volume ${c7n_output}= RW.CLI.Run Cli @@ -87,12 +87,12 @@ List Unencrypted EBS Volumes in `${AWS_REGION}` ... title=Unencrypted EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. ... details=${item} - ... next_steps=Escalate to service owner to review Unencrypted AWS EBS volume `${item["VolumeId"]}` found in ${AWS_ACCOUNT_ID} AWS account in AWS Region ${AWS_REGION}". + ... next_steps="Escalate to service owner to review Unencrypted AWS EBS volume `${item["VolumeId"]}` found in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Enable encryption of AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Delete unencrypted AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." END END -List Unused EBS Snapshots in`${AWS_REGION}` +List Unused EBS Snapshots in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` [Documentation] Check for unattached EBS volumes in the specified region. [Tags] ebs storage aws volume ${c7n_output}= RW.CLI.Run Cli @@ -124,7 +124,7 @@ List Unused EBS Snapshots in`${AWS_REGION}` ... title=Unused EBS Snapshot `${item["SnapshotId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` ... reproduce_hint=Review the Snapshots details and usage in the AWS Management Console or CLI. ... details=${item} # Include details such as volume ID, size, and region. - ... next_steps=Escalate to service owner for review of unused EBS Snapshot `${item["SnapshotId"]}` in ${AWS_ACCOUNT_ID} AWS account in AWS Region ${AWS_REGION}". + ... next_steps="Escalate to service owner for review of unused EBS Snapshot `${item["SnapshotId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Delete unused EBS Snapshot `${item["SnapshotId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." END END From d7fce895e93fc6954cc3cc191d5af9c38267f9db Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 4 Dec 2024 13:02:48 +0530 Subject: [PATCH 27/44] EBS CB: fix typo and update image url in templates --- .../.runwhen/templates/aws-c7n-ebs-health-sli.yaml | 2 +- .../.runwhen/templates/aws-c7n-ebs-health-slx.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml index b8faae9..b342950 100644 --- a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml +++ b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml @@ -11,7 +11,7 @@ spec: displayUnitsShort: ok locations: - {{default_location}} - description: Measures securitiy and health of S3 buckets in this AWS region and account. + description: Measures security and health of AWS EBS in this AWS region and account. codeBundle: {% if repo_url %} repoUrl: {{repo_url}} diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-slx.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-slx.yaml index 2d99591..0c95bf5 100644 --- a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-slx.yaml +++ b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-slx.yaml @@ -7,7 +7,7 @@ metadata: annotations: {% include "common-annotations.yaml" %} spec: - imageURL: https://www.shareicon.net/data/128x128/2015/08/28/92177_content_512x512.png + imageURL: https://storage.googleapis.com/runwhen-nonprod-shared-images/icons/aws/Resource-Icons_06072024/Res_Storage/Res_Amazon-Elastic-Block-Store_Multiple-Volumes_48.png alias: AWS EBS Health For Region {{match_resource.resource.region}} asMeasuredBy: The number of AWS EBS volumes and snapshots in region {{match_resource.resource.region}} configProvided: From 59c54633b7031ef2fcb38609f5d341c087a1c8c3 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 6 Dec 2024 15:49:17 +0530 Subject: [PATCH 28/44] update intervalSeconds 300 -> 600 --- .../.runwhen/templates/aws-c7n-ebs-health-sli.yaml | 2 +- .../.runwhen/templates/aws-c7n-s3-health-sli.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml index b342950..d559e95 100644 --- a/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml +++ b/codebundles/aws-c7n-ebs-health/.runwhen/templates/aws-c7n-ebs-health-sli.yaml @@ -25,7 +25,7 @@ spec: {% endif %} pathToRobot: codebundles/aws-c7n-ebs-health/sli.robot intervalStrategy: intermezzo - intervalSeconds: 300 + intervalSeconds: 600 configProvided: - name: AWS_REGION value: "{{match_resource.resource.region}}" diff --git a/codebundles/aws-c7n-s3-health/.runwhen/templates/aws-c7n-s3-health-sli.yaml b/codebundles/aws-c7n-s3-health/.runwhen/templates/aws-c7n-s3-health-sli.yaml index fed4ac3..4a51942 100644 --- a/codebundles/aws-c7n-s3-health/.runwhen/templates/aws-c7n-s3-health-sli.yaml +++ b/codebundles/aws-c7n-s3-health/.runwhen/templates/aws-c7n-s3-health-sli.yaml @@ -25,7 +25,7 @@ spec: {% endif %} pathToRobot: codebundles/aws-c7n-d3-health/sli.robot intervalStrategy: intermezzo - intervalSeconds: 300 + intervalSeconds: 600 configProvided: - name: AWS_REGION value: "{{match_resource.resource.region}}" From d1caa2ec2677b00e887fc8098a96eb58bd595893 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 6 Dec 2024 15:54:13 +0530 Subject: [PATCH 29/44] EBS CB: update Metadata and thresholds defaults 1->0 --- codebundles/aws-c7n-ebs-health/sli.robot | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/sli.robot b/codebundles/aws-c7n-ebs-health/sli.robot index a36cc00..711c542 100644 --- a/codebundles/aws-c7n-ebs-health/sli.robot +++ b/codebundles/aws-c7n-ebs-health/sli.robot @@ -1,16 +1,15 @@ *** Settings *** Metadata Author saurabh3460 -Metadata Support AWS EBS +Metadata Supports AWS EBS CloudCustodian +Metadata Display Name AWS EBS Health Documentation Counts the number of EBS resources by identifying unattached volumes, unused and aged snapshots, and unencrypted volumes. -Force Tags EBS Volume AWS Storage Secure +Force Tags EBS Volume AWS Storage Library RW.Core Library RW.CLI Suite Setup Suite Initialization - - *** Tasks *** Check Unattached EBS Volumes in `${AWS_REGION}` [Documentation] Check for unattached EBS volumes in the specified region. @@ -21,7 +20,7 @@ Check Unattached EBS Volumes in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unattached_ebs_event_score}= Evaluate 1 if int(${count.stdout}) >= int(${EVENT_THRESHOLD}) else 0 + ${unattached_ebs_event_score}= Evaluate 1 if int(${count.stdout}) > int(${EVENT_THRESHOLD}) else 0 Set Global Variable ${unattached_ebs_event_score} Check Unencrypted EBS Volumes in `${AWS_REGION}` @@ -33,7 +32,7 @@ Check Unencrypted EBS Volumes in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unencrypted-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unencrypted_ebs_event_score}= Evaluate 1 if int(${count.stdout}) >= int(${SECURITY_EVENT_THRESHOLD}) else 0 + ${unencrypted_ebs_event_score}= Evaluate 1 if int(${count.stdout}) > int(${SECURITY_EVENT_THRESHOLD}) else 0 Set Global Variable ${unencrypted_ebs_event_score} @@ -46,7 +45,7 @@ Check Unused EBS Snapshots in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unsued_ebs_snapshot_event_score}= Evaluate 1 if int(${count.stdout}) >= int(${EVENT_THRESHOLD}) else 0 + ${unsued_ebs_snapshot_event_score}= Evaluate 1 if int(${count.stdout}) > int(${EVENT_THRESHOLD}) else 0 Set Global Variable ${unsued_ebs_snapshot_event_score} @@ -78,13 +77,13 @@ Suite Initialization ... description=The minimum number of EBS volumes | snapshots to consider unhealthy. ... pattern=^\d+$ ... example=2 - ... default=1 + ... default=0 ${SECURITY_EVENT_THRESHOLD}= RW.Core.Import User Variable SECURITY_EVENT_THRESHOLD ... type=string ... description=The minimum number of security-related EBS volumes to consider unhealthy. ... pattern=^\d+$ ... example=2 - ... default=1 + ... default=0 ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health # Note: Clean out the cloud custoding report dir to ensure accurate data Set Suite Variable ${AWS_REGION} ${AWS_REGION} Set Suite Variable ${AWS_ACCOUNT_ID} ${AWS_ACCOUNT_ID} From e4df799ba0afe16bf2900e8f4c6fa864f64d4654 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 6 Dec 2024 16:00:15 +0530 Subject: [PATCH 30/44] ebs cb: rename gereration rule file --- .../{aws-c7n-s3-health.yaml => aws-c7n-ebs-health.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/{aws-c7n-s3-health.yaml => aws-c7n-ebs-health.yaml} (100%) diff --git a/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml b/codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-ebs-health.yaml similarity index 100% rename from codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-s3-health.yaml rename to codebundles/aws-c7n-ebs-health/.runwhen/generation-rules/aws-c7n-ebs-health.yaml From b41e23df0873f2928e9008ec9b2c17e301d89e69 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 6 Dec 2024 16:49:43 +0530 Subject: [PATCH 31/44] ebs cb: update Metadata, statements in add issues in runbook.robot --- codebundles/aws-c7n-ebs-health/runbook.robot | 54 ++++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/runbook.robot b/codebundles/aws-c7n-ebs-health/runbook.robot index a310374..5e58614 100644 --- a/codebundles/aws-c7n-ebs-health/runbook.robot +++ b/codebundles/aws-c7n-ebs-health/runbook.robot @@ -1,7 +1,8 @@ *** Settings *** Metadata Author saurabh3460 -Metadata Support AWS EBS -Documentation Audit EBS resources by identifying unattached volumes, unused and aged snapshots, and unencrypted volumes. +Metadata Supports AWS EBS CloudCustodian +Metadata Display Name AWS EBS Health +Documentation Check for AWS EBS resources by identifying unattached volumes, unused snapshots, and unencrypted volumes. Force Tags EBS Volume AWS Storage Secure Library RW.Core @@ -38,17 +39,16 @@ List Unattached EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ ${ebs_volume_list}= Create List END - # Generate issues if any unused EBS volumes are in the list IF len(@{ebs_volume_list}) > 0 FOR ${item} IN @{ebs_volume_list} RW.Core.Add Issue - ... severity=2 - ... expected=EBS volumes in AWS Account `${AWS_ACCOUNT_NAME}` should be attached or in use - ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_ID}` is unused - ... title=Unused EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` - ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. - ... details=${item} # Include details such as volume ID, size, and region. - ... next_steps="Escalate to service owner to review of unattached AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Delete unattached AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." + ... severity=4 + ... expected=EBS volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` should be attached. + ... actual=EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` is unattached. + ... title=Unattached EBS volume `${item["VolumeId"]}` detected in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. + ... reproduce_hint=`${c7n_output.cmd}` + ... details=${item} + ... next_steps="Escalate to service owner to review of unattached AWS EBS volume in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. Delete unattached AWS EBS volume in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." END END @@ -81,13 +81,13 @@ List Unencrypted EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS IF len(@{ebs_volume_list}) > 0 FOR ${item} IN @{ebs_volume_list} RW.Core.Add Issue - ... severity=2 - ... expected=EBS volumes in AWS Account `${AWS_ACCOUNT_ID}` should be unencrypted or in use - ... actual=EBS volume `${item["VolumeId"]}` in AWS Account `${AWS_ACCOUNT_ID}` is unencrypted - ... title=Unencrypted EBS volume `${item["VolumeId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` - ... reproduce_hint=Review the volume details and usage in the AWS Management Console or CLI. + ... severity=3 + ... expected=EBS volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` should be encrypted. + ... actual=EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` is unencrypted. + ... title=Unencrypted EBS volume `${item["VolumeId"]}` detected in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. + ... reproduce_hint=`${c7n_output.cmd}` ... details=${item} - ... next_steps="Escalate to service owner to review Unencrypted AWS EBS volume `${item["VolumeId"]}` found in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Enable encryption of AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Delete unencrypted AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." + ... next_steps="Escalate to service owner to review Unencrypted AWS EBS volume found in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. Enable encryption of AWS EBS volume in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Delete unencrypted AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." END END @@ -108,23 +108,23 @@ List Unused EBS Snapshots in AWS Region `${AWS_REGION}` in AWS account `${AWS_AC RW.Core.Add Pre To Report ${parsed_results} ${clean_output_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots TRY - ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json + ${ebs_snapshot_list}= Evaluate json.loads(r'''${report_data.stdout}''') json Log ${report_data.stdout} EXCEPT Log Failed to load JSON payload, defaulting to empty list. WARN - ${ebs_volume_list}= Create List + ${ebs_snapshot_list}= Create List END - IF len(@{ebs_volume_list}) > 0 - FOR ${item} IN @{ebs_volume_list} + IF len(@{ebs_snapshot_list}) > 0 + FOR ${item} IN @{ebs_snapshot_list} RW.Core.Add Issue - ... severity=2 - ... expected=EBS snapshots in AWS Account `${AWS_ACCOUNT_ID}` should be attached or in use - ... actual=EBS Snapshots `${item["SnapshotId"]}` in AWS Account `${AWS_ACCOUNT_ID}` is unused - ... title=Unused EBS Snapshot `${item["SnapshotId"]}` detected in AWS Account `${AWS_ACCOUNT_ID}` - ... reproduce_hint=Review the Snapshots details and usage in the AWS Management Console or CLI. - ... details=${item} # Include details such as volume ID, size, and region. - ... next_steps="Escalate to service owner for review of unused EBS Snapshot `${item["SnapshotId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Delete unused EBS Snapshot `${item["SnapshotId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." + ... severity=4 + ... expected=EBS snapshots in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` should be in use. + ... actual=EBS Snapshots `${item["SnapshotId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` is unused. + ... title=Unused EBS Snapshot `${item["SnapshotId"]}` detected in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. + ... reproduce_hint=`${c7n_output.cmd}` + ... details=${item} + ... next_steps="Escalate to service owner for review of unused EBS Snapshot in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. Delete unused EBS Snapshot in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." END END From 5d5f2c8ee692bc687c3bfd7d204c89da8d925c3f Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 11 Dec 2024 14:11:47 +0530 Subject: [PATCH 32/44] ebs cb: fix score logic --- codebundles/aws-c7n-ebs-health/sli.robot | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/sli.robot b/codebundles/aws-c7n-ebs-health/sli.robot index 711c542..5211fa3 100644 --- a/codebundles/aws-c7n-ebs-health/sli.robot +++ b/codebundles/aws-c7n-ebs-health/sli.robot @@ -20,7 +20,7 @@ Check Unattached EBS Volumes in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unattached-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unattached_ebs_event_score}= Evaluate 1 if int(${count.stdout}) > int(${EVENT_THRESHOLD}) else 0 + ${unattached_ebs_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${EVENT_THRESHOLD}) else 0 Set Global Variable ${unattached_ebs_event_score} Check Unencrypted EBS Volumes in `${AWS_REGION}` @@ -32,7 +32,7 @@ Check Unencrypted EBS Volumes in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unencrypted-ebs-volumes/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unencrypted_ebs_event_score}= Evaluate 1 if int(${count.stdout}) > int(${SECURITY_EVENT_THRESHOLD}) else 0 + ${unencrypted_ebs_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${SECURITY_EVENT_THRESHOLD}) else 0 Set Global Variable ${unencrypted_ebs_event_score} @@ -45,7 +45,7 @@ Check Unused EBS Snapshots in `${AWS_REGION}` ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - ${unsued_ebs_snapshot_event_score}= Evaluate 1 if int(${count.stdout}) > int(${EVENT_THRESHOLD}) else 0 + ${unsued_ebs_snapshot_event_score}= Evaluate 1 if int(${count.stdout}) <= int(${EVENT_THRESHOLD}) else 0 Set Global Variable ${unsued_ebs_snapshot_event_score} From 7f557d06c630304beb5f81d60e706629560d6d36 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Thu, 12 Dec 2024 13:56:55 +0530 Subject: [PATCH 33/44] ebs cb: update REDME.md with how to test steps --- .../aws-c7n-ebs-health/.test/README.md | 74 ++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/.test/README.md b/codebundles/aws-c7n-ebs-health/.test/README.md index 5bbadde..07ab131 100644 --- a/codebundles/aws-c7n-ebs-health/.test/README.md +++ b/codebundles/aws-c7n-ebs-health/.test/README.md @@ -1,4 +1,14 @@ -### AWS IAM policy for running this codebundle: +### How to test this codebundle? + +#### IAM User Configuration + +We create two distinct AWS IAM users with carefully scoped access: + +**CloudCustodian IAM User** + +Purpose: Service Level Indicator (SLI) monitoring and runbook automation and configured with least privilege access principles + +with the following policy: ```json { @@ -24,4 +34,64 @@ } ] } -``` \ No newline at end of file +``` + +**Infrastructure Deployment User** + +Purpose: Cloud infrastructure provisioning and management using Terraform + +#### Credential Setup + +Navigate to the `.test/terraform` directory and configure two secret files for authentication: + +`cb.secret` - CloudCustodian and RunWhen Credentials + +Create this file with the following environment variables: + + ```sh + export RW_PAT="" + export RW_WORKSPACE="" + export RW_API_URL="papi.beta.runwhen.com" + + export AWS_DEFAULT_REGION="us-west-2" + export AWS_ACCESS_KEY_ID="" + export AWS_SECRET_ACCESS_KEY="" + export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) + ``` + + +`tf.secret` - Terraform Deployment Credentials + +Create this file with the following environment variables: + + ```sh + export AWS_DEFAULT_REGION="" + export AWS_ACCESS_KEY_ID="" + export AWS_SECRET_ACCESS_KEY="" + export AWS_SESSION_TOKEN="" # Optional: Include if using temporary credentials + ``` + +#### Testing Workflow + +1. Build test infra: + ```sh + task build-infra + ``` + +2. Generate RunWhen Configurations + ```sh + tasks + ``` + +3. Upload generated SLx to RunWhen Platform + + ```sh + task upload-slxs + ``` + +4. At last, after testing, clean up the test infrastructure. + +```sh + task clean +``` + From 1b2699e07029367ef18c66971dec139e09ecd96a Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Thu, 12 Dec 2024 19:12:34 +0530 Subject: [PATCH 34/44] ebs cb: update REDME.md with how to test steps --- codebundles/aws-c7n-ebs-health/runbook.robot | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/codebundles/aws-c7n-ebs-health/runbook.robot b/codebundles/aws-c7n-ebs-health/runbook.robot index 5e58614..f7de258 100644 --- a/codebundles/aws-c7n-ebs-health/runbook.robot +++ b/codebundles/aws-c7n-ebs-health/runbook.robot @@ -3,7 +3,7 @@ Metadata Author saurabh3460 Metadata Supports AWS EBS CloudCustodian Metadata Display Name AWS EBS Health Documentation Check for AWS EBS resources by identifying unattached volumes, unused snapshots, and unencrypted volumes. -Force Tags EBS Volume AWS Storage Secure +Force Tags EBS Volume AWS Storage Encryption Library RW.Core Library RW.CLI @@ -16,7 +16,7 @@ Suite Setup Suite Initialization *** Tasks *** List Unattached EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` [Documentation] Check for unattached EBS volumes in the specified region. - [Tags] ebs storage aws volume + [Tags] ebs storage aws volume unattached ${c7n_output}= RW.CLI.Run Cli ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unattached-ebs-volumes.yaml --cache-period 0 ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} @@ -33,7 +33,6 @@ List Unattached EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ # Convert custodian json output to a list. TRY ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json - Log ${report_data.stdout} EXCEPT Log Failed to load JSON payload, defaulting to empty list. WARN ${ebs_volume_list}= Create List @@ -48,14 +47,14 @@ List Unattached EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ ... title=Unattached EBS volume `${item["VolumeId"]}` detected in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. ... reproduce_hint=`${c7n_output.cmd}` ... details=${item} - ... next_steps="Escalate to service owner to review of unattached AWS EBS volume in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. Delete unattached AWS EBS volume in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." + ... next_steps=Escalate to service owner to review of unattached AWS EBS volume in AWS Region \`${AWS_REGION}\` in AWS account \`${AWS_ACCOUNT_ID}\`\nDelete unattached AWS EBS volume in AWS Region \`${AWS_REGION}\` in AWS account \`${AWS_ACCOUNT_ID}\` END END List Unencrypted EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` - [Documentation] Check for unattached EBS volumes in the specified region. - [Tags] ebs storage aws volume + [Documentation] Check for Unencrypted EBS Volumes in the specified region. + [Tags] ebs storage aws volume encryption ${c7n_output}= RW.CLI.Run Cli ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unencrypted-ebs-volumes.yaml --cache-period 0 ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} @@ -72,7 +71,6 @@ List Unencrypted EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS TRY ${ebs_volume_list}= Evaluate json.loads(r'''${report_data.stdout}''') json - Log ${report_data.stdout} EXCEPT Log Failed to load JSON payload, defaulting to empty list. WARN ${ebs_volume_list}= Create List @@ -87,14 +85,14 @@ List Unencrypted EBS Volumes in AWS Region `${AWS_REGION}` in AWS account `${AWS ... title=Unencrypted EBS volume `${item["VolumeId"]}` detected in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. ... reproduce_hint=`${c7n_output.cmd}` ... details=${item} - ... next_steps="Escalate to service owner to review Unencrypted AWS EBS volume found in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. Enable encryption of AWS EBS volume in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. \n Delete unencrypted AWS EBS volume `${item["VolumeId"]}` in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." + ... next_steps=Escalate to service owner to review Unencrypted AWS EBS volume found in AWS Region \`${AWS_REGION}\` in AWS account \`${AWS_ACCOUNT_ID}\`\nEnable encryption of AWS EBS volume in AWS Region \`${AWS_REGION}\` in AWS account \`${AWS_ACCOUNT_ID}\`\nDelete unencrypted AWS EBS volume in AWS Region \`${AWS_REGION}\` in AWS account \`${AWS_ACCOUNT_ID}\` END END List Unused EBS Snapshots in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}` - [Documentation] Check for unattached EBS volumes in the specified region. - [Tags] ebs storage aws volume + [Documentation] Check for Unused EBS Snapshots in the specified region. + [Tags] ebs storage aws volume unused ${c7n_output}= RW.CLI.Run Cli ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-ebs-health ${CURDIR}/unused-ebs-snapshots.yaml --cache-period 0 ... secret__aws_account_id=${AWS_ACCESS_KEY_ID} @@ -109,7 +107,6 @@ List Unused EBS Snapshots in AWS Region `${AWS_REGION}` in AWS account `${AWS_AC ${clean_output_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-ebs-health/unused-ebs-snapshots TRY ${ebs_snapshot_list}= Evaluate json.loads(r'''${report_data.stdout}''') json - Log ${report_data.stdout} EXCEPT Log Failed to load JSON payload, defaulting to empty list. WARN ${ebs_snapshot_list}= Create List @@ -124,7 +121,7 @@ List Unused EBS Snapshots in AWS Region `${AWS_REGION}` in AWS account `${AWS_AC ... title=Unused EBS Snapshot `${item["SnapshotId"]}` detected in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. ... reproduce_hint=`${c7n_output.cmd}` ... details=${item} - ... next_steps="Escalate to service owner for review of unused EBS Snapshot in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`. Delete unused EBS Snapshot in AWS Region `${AWS_REGION}` in AWS account `${AWS_ACCOUNT_ID}`." + ... next_steps=Escalate to service owner for review of unused EBS Snapshot in AWS Region \`${AWS_REGION}\` in AWS account \`${AWS_ACCOUNT_ID}\`\nDelete unused EBS Snapshot in AWS Region \`${AWS_REGION}\` in AWS account \`${AWS_ACCOUNT_ID}\` END END From 7b3a1d64814bad35eda94a9a9a899cd3b6671cbc Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 20 Jan 2025 14:10:27 +0530 Subject: [PATCH 35/44] sh cb: Add root README.md --- codebundles/aws-c7n-security-hub/README.md | 25 ++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 codebundles/aws-c7n-security-hub/README.md diff --git a/codebundles/aws-c7n-security-hub/README.md b/codebundles/aws-c7n-security-hub/README.md new file mode 100644 index 0000000..fa490a5 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/README.md @@ -0,0 +1,25 @@ +# AWS Cloud Custodian Security Hub + +This CodeBundle evaluates the Security Hub Findings in a given AWS Account and Region + +## SLI +The SLI produces a score of 0 (bad), 1(good), or a value in between. This score is generated by capturing the following: +- Security Hub Findings + + +## TaskSet +Similar to the SLI, but produces a report on the specific resources and raises issues for each Security Hub Findings that requires attention. + + +## Required Configuration + +``` +export AWS_ACCESS_KEY_ID=[] +export AWS_SECRET_ACCESS_KEY=[] +export AWS_DEFAULT_REGION=[] +export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) +``` + + +## Testing +See the .test directory for infrastructure test code. \ No newline at end of file From 9e0687cb7619e18d99b8efeac2fcf0a9b6c0a28d Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 20 Jan 2025 14:11:10 +0530 Subject: [PATCH 36/44] sh cb: add test infra and doc --- .../aws-c7n-security-hub/.test/README.md | 102 +++++ .../aws-c7n-security-hub/.test/Taskfile.yaml | 352 ++++++++++++++++++ .../.test/terraform/Taskfile.yaml | 69 ++++ .../.test/terraform/main.tf | 14 + .../.test/terraform/provider.tf | 3 + .../.test/terraform/vars.tf | 25 ++ 6 files changed, 565 insertions(+) create mode 100644 codebundles/aws-c7n-security-hub/.test/README.md create mode 100644 codebundles/aws-c7n-security-hub/.test/Taskfile.yaml create mode 100644 codebundles/aws-c7n-security-hub/.test/terraform/Taskfile.yaml create mode 100644 codebundles/aws-c7n-security-hub/.test/terraform/main.tf create mode 100644 codebundles/aws-c7n-security-hub/.test/terraform/provider.tf create mode 100644 codebundles/aws-c7n-security-hub/.test/terraform/vars.tf diff --git a/codebundles/aws-c7n-security-hub/.test/README.md b/codebundles/aws-c7n-security-hub/.test/README.md new file mode 100644 index 0000000..b4e8daa --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.test/README.md @@ -0,0 +1,102 @@ +### How to test this codebundle? + +#### IAM User Configuration + +We create two distinct AWS IAM users with carefully scoped access: + +**CloudCustodian IAM User** + +Purpose: Service Level Indicator (SLI) monitoring and runbook automation and configured with least privilege access principles + +With the following policy: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "VisualEditor0", + "Effect": "Allow", + "Action": [ + "tag:GetResources", + "securityhub:GetFindings", + "s3:List*", + "s3:Get*", + "ec2:Describe*", + "iam:List*", + "iam:Get*", + "rds:Describe*", + "cloudwatch:Get*", + "cloudformation:Describe*", + "dynamodb:Scan", + "dynamodb:Describe*", + "lambda:List*", + "lambda:Get*", + "sns:List*" + ], + "Resource": "*" + } + ] +} +``` +**Note** Please ensure to update the policy whenever new resources are added to the `AWS_RESOURCE_PROVIDERS` list in the `sli.robot` and `runbook.robot` + +**Infrastructure Deployment User** + +Purpose: Cloud infrastructure provisioning and management using Terraform + +#### Credential Setup + +Navigate to the `.test/terraform` directory and configure two secret files for authentication: + +`cb.secret` - CloudCustodian and RunWhen Credentials + +Create this file with the following environment variables: + + ```sh + export RW_PAT="" + export RW_WORKSPACE="" + export RW_API_URL="papi.beta.runwhen.com" + + export AWS_DEFAULT_REGION="us-west-2" + export AWS_ACCESS_KEY_ID="" + export AWS_SECRET_ACCESS_KEY="" + export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text) + ``` + + +`tf.secret` - Terraform Deployment Credentials + +Create this file with the following environment variables: + + ```sh + export AWS_DEFAULT_REGION="" + export AWS_ACCESS_KEY_ID="" + export AWS_SECRET_ACCESS_KEY="" + export AWS_SESSION_TOKEN="" # Optional: Include if using temporary credentials + ``` + +#### Testing Workflow + +1. Build test infra: + ```sh + task build-infra + ``` + +2. Generate RunWhen Configurations + ```sh + tasks + ``` + +3. Upload generated SLx to RunWhen Platform + + ```sh + task upload-slxs + ``` + +4. At last, after testing, clean up the test infrastructure. + + ```sh + task clean + ``` + diff --git a/codebundles/aws-c7n-security-hub/.test/Taskfile.yaml b/codebundles/aws-c7n-security-hub/.test/Taskfile.yaml new file mode 100644 index 0000000..4319299 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.test/Taskfile.yaml @@ -0,0 +1,352 @@ +version: "3" + +tasks: + default: + desc: "Generate workspaceInfo and rebuild/test" + cmds: + - task: check-unpushed-commits + - task: generate-rwl-config + - task: run-rwl-discovery + + clean: + desc: "Run cleanup tasks" + cmds: + - task: check-and-cleanup-terraform + # - task: clean-rwl-discovery + + build-infra: + desc: "Build test infrastructure" + cmds: + - task: build-terraform-infra + + check-unpushed-commits: + desc: Check if outstanding commits or file updates need to be pushed before testing. + vars: + # Specify the base directory relative to your Taskfile location + BASE_DIR: "../" + cmds: + - | + echo "Checking for uncommitted changes in $BASE_DIR and $BASE_DIR.runwhen, excluding '.test'..." + UNCOMMITTED_FILES=$(git diff --name-only HEAD | grep -E "^${BASE_DIR}(\.runwhen|[^/]+)" | grep -v "/\.test/" || true) + if [ -n "$UNCOMMITTED_FILES" ]; then + echo "✗" + echo "Uncommitted changes found:" + echo "$UNCOMMITTED_FILES" + echo "Remember to commit & push changes before executing the `run-rwl-discovery` task." + echo "------------" + exit 1 + else + echo "√" + echo "No uncommitted changes in specified directories." + echo "------------" + fi + - | + echo "Checking for unpushed commits in $BASE_DIR and $BASE_DIR.runwhen, excluding '.test'..." + git fetch origin + UNPUSHED_FILES=$(git diff --name-only origin/$(git rev-parse --abbrev-ref HEAD) HEAD | grep -E "^${BASE_DIR}(\.runwhen|[^/]+)" | grep -v "/\.test/" || true) + if [ -n "$UNPUSHED_FILES" ]; then + echo "✗" + echo "Unpushed commits found:" + echo "$UNPUSHED_FILES" + echo "Remember to push changes before executing the `run-rwl-discovery` task." + echo "------------" + exit 1 + else + echo "√" + echo "No unpushed commits in specified directories." + echo "------------" + fi + silent: true + + generate-rwl-config: + desc: "Generate RunWhen Local configuration (workspaceInfo.yaml)" + env: + AWS_ACCESS_KEY_ID: "{{.AWS_ACCESS_KEY_ID}}" + AWS_SECRET_ACCESS_KEY: "{{.AWS_SECRET_ACCESS_KEY}}" + AWS_DEFAULT_REGION: "{{.AWS_DEFAULT_REGION}}" + RW_WORKSPACE: '{{.RW_WORKSPACE | default "my-workspace"}}' + cmds: + - | + source terraform/cb.secret + repo_url=$(git config --get remote.origin.url) + branch_name=$(git rev-parse --abbrev-ref HEAD) + codebundle=$(basename "$(dirname "$PWD")") + + # Fetch individual cluster details from Terraform state + # pushd terraform > /dev/null + # cluster1_name=$(terraform show -json terraform.tfstate | jq -r ' + # .values.outputs.cluster_1_name.value') + + # popd > /dev/null + + # # Check if any of the required cluster variables are empty + # if [ -z "$cluster1_name" ] || [ -z "$cluster1_server" ] || [ -z "$cluster1_resource_group" ]; then + # echo "Error: Missing cluster details. Ensure Terraform plan has been applied." + # exit 1 + # fi + + # Generate workspaceInfo.yaml with fetched cluster details + cat < workspaceInfo.yaml + workspaceName: "$RW_WORKSPACE" + workspaceOwnerEmail: authors@runwhen.com + defaultLocation: location-01-us-west1 + defaultLOD: detailed + cloudConfig: + aws: + awsAccessKeyId: "$AWS_ACCESS_KEY_ID" + awsSecretAccessKey: "$AWS_SECRET_ACCESS_KEY" + codeCollections: + - repoURL: "$repo_url" + branch: "$branch_name" + codeBundles: ["$codebundle"] + custom: + aws_access_key_id: AWS_ACCESS_KEY_ID + aws_secret_access_key: AWS_SECRET_ACCESS_KEY + EOF + silent: true + + run-rwl-discovery: + desc: "Run RunWhen Local Discovery on test infrastructure" + cmds: + - | + CONTAINER_NAME="RunWhenLocal" + if docker ps -q --filter "name=$CONTAINER_NAME" | grep -q .; then + echo "Stopping and removing existing container $CONTAINER_NAME..." + docker stop $CONTAINER_NAME && docker rm $CONTAINER_NAME + elif docker ps -a -q --filter "name=$CONTAINER_NAME" | grep -q .; then + echo "Removing existing stopped container $CONTAINER_NAME..." + docker rm $CONTAINER_NAME + else + echo "No existing container named $CONTAINER_NAME found." + fi + + echo "Cleaning up output directory..." + sudo rm -rf output || { echo "Failed to remove output directory"; exit 1; } + mkdir output && chmod 777 output || { echo "Failed to set permissions"; exit 1; } + + echo "Starting new container $CONTAINER_NAME..." + + docker run --name $CONTAINER_NAME -e DEBUG_LOGGING=true -p 8081:8081 -v "$(pwd)":/shared -d ghcr.io/runwhen-contrib/runwhen-local:latest || { + echo "Failed to start container"; exit 1; + } + + echo "Running workspace builder script in container..." + docker exec -w /workspace-builder $CONTAINER_NAME ./run.sh $1 --verbose || { + echo "Error executing script in container"; exit 1; + } + + echo "Review generated config files under output/workspaces/" + silent: true + + check-terraform-infra: + desc: "Check if Terraform has any deployed infrastructure in the terraform subdirectory" + cmds: + - | + # Source Envs for Auth + source terraform/tf.secret + + # Navigate to the Terraform directory + if [ ! -d "terraform" ]; then + echo "Terraform directory not found." + exit 1 + fi + cd terraform + + # Check if Terraform state file exists + if [ ! -f "terraform.tfstate" ]; then + echo "No Terraform state file found in the terraform directory. No infrastructure is deployed." + exit 0 + fi + + # List resources in Terraform state + resources=$(terraform state list) + + # Check if any resources are listed in the state file + if [ -n "$resources" ]; then + echo "Deployed infrastructure detected." + echo "$resources" + exit 0 + else + echo "No deployed infrastructure found in Terraform state." + exit 0 + fi + silent: true + + build-terraform-infra: + desc: "Run terraform apply" + cmds: + - | + # Source Envs for Auth + source terraform/tf.secret + + + # Navigate to the Terraform directory + if [ -d "terraform" ]; then + cd terraform + else + echo "Terraform directory not found. Terraform apply aborted." + exit 1 + fi + task format-and-init-terraform + echo "Starting Terraform Build of Terraform infrastructure..." + terraform apply -auto-approve || { + echo "Failed to clean up Terraform infrastructure." + exit 1 + } + echo "Terraform infrastructure build completed." + silent: true + + check-rwp-config: + desc: Check if env vars are set for RunWhen Platform + cmds: + - | + source terraform/cb.secret + missing_vars=() + + if [ -z "$RW_WORKSPACE" ]; then + missing_vars+=("RW_WORKSPACE") + fi + + if [ -z "$RW_API_URL" ]; then + missing_vars+=("RW_API_URL") + fi + + if [ -z "$RW_PAT" ]; then + missing_vars+=("RW_PAT") + fi + + if [ ${#missing_vars[@]} -ne 0 ]; then + echo "The following required environment variables are missing: ${missing_vars[*]}" + exit 1 + fi + silent: true + + upload-slxs: + desc: "Upload SLX files to the appropriate URL" + env: + RW_WORKSPACE: "{{.RW_WORKSPACE}}" + RW_API_URL: "{{.RW_API}}" + RW_PAT: "{{.RW_PAT}}" + cmds: + - task: check-rwp-config + - | + source terraform/cb.secret + BASE_DIR="output/workspaces/${RW_WORKSPACE}/slxs" + if [ ! -d "$BASE_DIR" ]; then + echo "Directory $BASE_DIR does not exist. Upload aborted." + exit 1 + fi + + for dir in "$BASE_DIR"/*; do + if [ -d "$dir" ]; then + SLX_NAME=$(basename "$dir") + PAYLOAD=$(jq -n --arg commitMsg "Creating new SLX $SLX_NAME" '{ commitMsg: $commitMsg, files: {} }') + for file in slx.yaml runbook.yaml sli.yaml; do + if [ -f "$dir/$file" ]; then + CONTENT=$(cat "$dir/$file") + PAYLOAD=$(echo "$PAYLOAD" | jq --arg fileContent "$CONTENT" --arg fileName "$file" '.files[$fileName] = $fileContent') + fi + done + + URL="https://${RW_API_URL}/api/v3/workspaces/${RW_WORKSPACE}/branches/main/slxs/${SLX_NAME}" + echo "Uploading SLX: $SLX_NAME to $URL" + response_code=$(curl -X POST "$URL" \ + -H "Authorization: Bearer $RW_PAT" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" \ + -w "%{http_code}" -o /dev/null -s) + + if [[ "$response_code" == "200" || "$response_code" == "201" ]]; then + echo "Successfully uploaded SLX: $SLX_NAME to $URL" + elif [[ "$response_code" == "405" ]]; then + echo "Failed to upload SLX: $SLX_NAME to $URL. Method not allowed (405)." + else + echo "Failed to upload SLX: $SLX_NAME to $URL. Unexpected response code: $response_code" + fi + fi + done + silent: true + + delete-slxs: + desc: "Delete SLX objects from the appropriate URL" + env: + RW_WORKSPACE: '{{.RW_WORKSPACE | default "my-workspace"}}' + RW_API_URL: "{{.RW_API}}" + RW_PAT: "{{.RW_PAT}}" + cmds: + - task: check-rwp-config + - | + source terraform/cb.secret + BASE_DIR="output/workspaces/${RW_WORKSPACE}/slxs" + if [ ! -d "$BASE_DIR" ]; then + echo "Directory $BASE_DIR does not exist. Deletion aborted." + exit 1 + fi + + for dir in "$BASE_DIR"/*; do + if [ -d "$dir" ]; then + SLX_NAME=$(basename "$dir") + URL="https://${RW_API_URL}/api/v3/workspaces/${RW_WORKSPACE}/branches/main/slxs/${SLX_NAME}" + echo "Deleting SLX: $SLX_NAME from $URL" + response_code=$(curl -X DELETE "$URL" \ + -H "Authorization: Bearer $RW_PAT" \ + -H "Content-Type: application/json" \ + -w "%{http_code}" -o /dev/null -s) + + if [[ "$response_code" == "200" || "$response_code" == "204" ]]; then + echo "Successfully deleted SLX: $SLX_NAME from $URL" + elif [[ "$response_code" == "405" ]]; then + echo "Failed to delete SLX: $SLX_NAME from $URL. Method not allowed (405)." + else + echo "Failed to delete SLX: $SLX_NAME from $URL. Unexpected response code: $response_code" + fi + fi + done + silent: true + + cleanup-terraform-infra: + desc: "Cleanup deployed Terraform infrastructure" + cmds: + - | + # Source Envs for Auth + source terraform/tf.secret + + # Navigate to the Terraform directory + if [ -d "terraform" ]; then + cd terraform + else + echo "Terraform directory not found. Cleanup aborted." + exit 1 + fi + + echo "Starting cleanup of Terraform infrastructure..." + terraform destroy -auto-approve || { + echo "Failed to clean up Terraform infrastructure." + exit 1 + } + echo "Terraform infrastructure cleanup completed." + silent: true + + check-and-cleanup-terraform: + desc: "Check and clean up deployed Terraform infrastructure if it exists" + cmds: + - | + # Capture the output of check-terraform-infra + infra_output=$(task check-terraform-infra | tee /dev/tty) + + # Check if output contains indication of deployed infrastructure + if echo "$infra_output" | grep -q "Deployed infrastructure detected"; then + echo "Infrastructure detected; proceeding with cleanup." + task cleanup-terraform-infra + else + echo "No deployed infrastructure found; no cleanup required." + fi + silent: true + + clean-rwl-discovery: + desc: "Check and clean up RunWhen Local discovery output" + cmds: + - | + sudo rm -rf output + rm workspaceInfo.yaml + silent: true diff --git a/codebundles/aws-c7n-security-hub/.test/terraform/Taskfile.yaml b/codebundles/aws-c7n-security-hub/.test/terraform/Taskfile.yaml new file mode 100644 index 0000000..08e0e83 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.test/terraform/Taskfile.yaml @@ -0,0 +1,69 @@ +version: '3' + +env: + TERM: screen-256color + +tasks: + default: + cmds: + - task: test + + test: + desc: Run tests. + cmds: + - task: test-terraform + + clean: + desc: Clean the environment. + cmds: + - task: clean-go + - task: clean-terraform + + clean-terraform: + desc: Clean the terraform environment (remove terraform directories and files) + cmds: + - find . -type d -name .terraform -exec rm -rf {} + + - find . -type f -name .terraform.lock.hcl -delete + + format-and-init-terraform: + desc: Run Terraform fmt and init + cmds: + - | + terraform fmt + terraform init + test-terraform: + desc: Run tests for all terraform directories. + silent: true + env: + DIRECTORIES: + sh: find . -path '*/.terraform/*' -prune -o -name '*.tf' -type f -exec dirname {} \; | sort -u + cmds: + - | + BOLD=$(tput bold) + NORM=$(tput sgr0) + + CWD=$PWD + + for d in $DIRECTORIES; do + cd $d + echo "${BOLD}$PWD:${NORM}" + if ! terraform fmt -check=true -list=false -recursive=false; then + echo " ✗ terraform fmt" && exit 1 + else + echo " √ terraform fmt" + fi + + if ! terraform init -backend=false -input=false -get=true -no-color > /dev/null; then + echo " ✗ terraform init" && exit 1 + else + echo " √ terraform init" + fi + + if ! terraform validate > /dev/null; then + echo " ✗ terraform validate" && exit 1 + else + echo " √ terraform validate" + fi + + cd $CWD + done \ No newline at end of file diff --git a/codebundles/aws-c7n-security-hub/.test/terraform/main.tf b/codebundles/aws-c7n-security-hub/.test/terraform/main.tf new file mode 100644 index 0000000..f9902af --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.test/terraform/main.tf @@ -0,0 +1,14 @@ +# Create the EBS volume if it doesn't exist +resource "aws_ebs_volume" "ebs_volume" { + availability_zone = var.availability_zone + size = var.ebs_volume_size + encrypted = false + tags = { + Name = var.ebs_volume_name + } +} + +# Output +output "volume_id" { + value = aws_ebs_volume.ebs_volume.id +} \ No newline at end of file diff --git a/codebundles/aws-c7n-security-hub/.test/terraform/provider.tf b/codebundles/aws-c7n-security-hub/.test/terraform/provider.tf new file mode 100644 index 0000000..aa39e39 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.test/terraform/provider.tf @@ -0,0 +1,3 @@ +provider "aws" { + region = "us-west-2" # Replace with your desired region +} \ No newline at end of file diff --git a/codebundles/aws-c7n-security-hub/.test/terraform/vars.tf b/codebundles/aws-c7n-security-hub/.test/terraform/vars.tf new file mode 100644 index 0000000..819a450 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.test/terraform/vars.tf @@ -0,0 +1,25 @@ +# Variables +variable "ebs_volume_name" { + default = "ebs-sh" +} + +variable "ebs_volume_size" { + default = 1 +} + +variable "region" { + default = "us-west-2" +} + +variable "availability_zone" { + default = "us-west-2b" +} + + +variable "snapshot_volume_name" { + default = "ebs-snapshot-sh" +} + +variable "snapshot_volume_size" { + default = 1 +} \ No newline at end of file From 23c347b4ec7ad48f8e8a2a29fa3bb0011531c1d0 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 20 Jan 2025 14:12:44 +0530 Subject: [PATCH 37/44] sh cb: add security hub findings policy template --- codebundles/aws-c7n-security-hub/security-hub.j2 | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 codebundles/aws-c7n-security-hub/security-hub.j2 diff --git a/codebundles/aws-c7n-security-hub/security-hub.j2 b/codebundles/aws-c7n-security-hub/security-hub.j2 new file mode 100644 index 0000000..bfe3a46 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/security-hub.j2 @@ -0,0 +1,7 @@ +policies: +{%- for provider in resource_providers.split(',') %} + - name: {{ provider.strip('"').strip("'").strip() }}-findings + resource: {{ provider.strip('"').strip("'").strip() }} + filters: + - finding +{%- endfor %} \ No newline at end of file From 6b67103f86e3a66edf55a7254ee5aa0a2db4ce1e Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 20 Jan 2025 14:51:16 +0530 Subject: [PATCH 38/44] sh: add runwhen templates --- .../aws-c7n-network-health.yaml | 22 ++++++++ .../templates/aws-c7n-security-hub-sli.yaml | 51 +++++++++++++++++++ .../templates/aws-c7n-security-hub-slx.yaml | 21 ++++++++ .../aws-c7n-security-hub-taskset.yaml | 33 ++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-network-health.yaml create mode 100644 codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-sli.yaml create mode 100644 codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-slx.yaml create mode 100644 codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-taskset.yaml diff --git a/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-network-health.yaml b/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-network-health.yaml new file mode 100644 index 0000000..692cf5d --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-network-health.yaml @@ -0,0 +1,22 @@ +apiVersion: runwhen.com/v1 +kind: GenerationRules +spec: + platform: aws + generationRules: + - resourceTypes: + - aws_ec2_security_groups + matchRules: + - type: pattern + pattern: ".+" + properties: [name] + mode: substring + slxs: + - baseName: aws-c7n-security-hub + qualifiers: ["account_id"] + baseTemplateName: aws-c7n-security-hub + levelOfDetail: basic + outputItems: + - type: slx + - type: sli + - type: runbook + templateName: aws-c7n-security-hub-taskset.yaml \ No newline at end of file diff --git a/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-sli.yaml b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-sli.yaml new file mode 100644 index 0000000..d312411 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-sli.yaml @@ -0,0 +1,51 @@ +apiVersion: runwhen.com/v1 +kind: ServiceLevelIndicator +metadata: + name: {{slx_name}} + labels: + {% include "common-labels.yaml" %} + annotations: + {% include "common-annotations.yaml" %} +spec: + displayUnitsLong: OK + displayUnitsShort: ok + locations: + - {{default_location}} + description: Check for AWS Security Hub findings in AWS account {{match_resource.resource.account_id}} + codeBundle: + {% if repo_url %} + repoUrl: {{repo_url}} + {% else %} + repoUrl: https://github.com/runwhen-contrib/rw-c7n-codecollection.git + {% endif %} + {% if ref %} + ref: {{ref}} + {% else %} + ref: main + {% endif %} + pathToRobot: codebundles/aws-c7n-security-hub/sli.robot + intervalStrategy: intermezzo + intervalSeconds: 600 + configProvided: + - name: AWS_REGION + value: "{{match_resource.resource.region}}" + - name: AWS_ACCOUNT_ID + value: "{{match_resource.resource.account_id}}" + secretsProvided: + - name: AWS_ACCESS_KEY_ID + workspaceKey: {{custom.aws_access_key_id}} + - name: AWS_SECRET_ACCESS_KEY + workspaceKey: {{custom.aws_secret_access_key}} + alerts: + warning: + operator: > + threshold: '1' + for: '20m' + ticket: + operator: > + threshold: '1' + for: '40m' + page: + operator: '==' + threshold: '0' + for: '' diff --git a/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-slx.yaml b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-slx.yaml new file mode 100644 index 0000000..bfac75e --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-slx.yaml @@ -0,0 +1,21 @@ +apiVersion: runwhen.com/v1 +kind: ServiceLevelX +metadata: + name: {{slx_name}} + labels: + {% include "common-labels.yaml" %} + annotations: + {% include "common-annotations.yaml" %} +spec: + imageURL: https://PLACEHOLDER.jpg + alias: AWS Security Hub findings in AWS Account {{match_resource.resource.account_id}} + asMeasuredBy: The number of AWS Security Hub findings in AWS account {{match_resource.resource.account_id}} + configProvided: + - name: SLX_PLACEHOLDER + value: SLX_PLACEHOLDER + owners: + - {{workspace.owner_email}} + statement: List AWS Security Hub findings in the AWS account {{match_resource.resource.account_id}} + additionalContext: + region: "{{match_resource.resource.region}}" + account_id: "{{match_resource.resource.account_id}}" \ No newline at end of file diff --git a/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-taskset.yaml b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-taskset.yaml new file mode 100644 index 0000000..78ec5e4 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-taskset.yaml @@ -0,0 +1,33 @@ +apiVersion: runwhen.com/v1 +kind: Runbook +metadata: + name: {{slx_name}} + labels: + {% include "common-labels.yaml" %} + annotations: + {% include "common-annotations.yaml" %} +spec: + location: {{default_location}} + description: List Security Hub findings in the AWS account {{match_resource.resource.account_id}} + codeBundle: + {% if repo_url %} + repoUrl: {{repo_url}} + {% else %} + repoUrl: https://github.com/runwhen-contrib/rw-c7n-codecollection.git + {% endif %} + {% if ref %} + ref: {{ref}} + {% else %} + ref: main + {% endif %} + pathToRobot: codebundles/aws-c7n-security-hub/runbook.robot + configProvided: + - name: AWS_REGION + value: "{{match_resource.resource.region}}" + - name: AWS_ACCOUNT_ID + value: "{{match_resource.resource.account_id}}" + secretsProvided: + - name: AWS_ACCESS_KEY_ID + workspaceKey: {{custom.aws_access_key_id}} + - name: AWS_SECRET_ACCESS_KEY + workspaceKey: {{custom.aws_secret_access_key}} From f454c54f99a4cacaa3476203ca909e57496b1264 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 20 Jan 2025 14:51:34 +0530 Subject: [PATCH 39/44] sh cb: add sli.robot --- codebundles/aws-c7n-security-hub/sli.robot | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 codebundles/aws-c7n-security-hub/sli.robot diff --git a/codebundles/aws-c7n-security-hub/sli.robot b/codebundles/aws-c7n-security-hub/sli.robot new file mode 100644 index 0000000..be00a1b --- /dev/null +++ b/codebundles/aws-c7n-security-hub/sli.robot @@ -0,0 +1,84 @@ +*** Settings *** +Metadata Author saurabh3460 +Metadata Supports AWS Tag CloudCustodian +Metadata Display Name AWS Security Hub +Documentation Check for aws security hub findings +Force Tags Tag AWS security-hub + +Library RW.Core +Library RW.CLI +Library CloudCustodian.Core + +Suite Setup Suite Initialization + +*** Tasks *** +Check for security hub findings in AWS account `${AWS_ACCOUNT_ID}` + [Documentation] Check for security hub findings + [Tags] aws security-hub + CloudCustodian.Core.Generate Policy + ... ${CURDIR}/security-hub.j2 + ... resource_providers=${AWS_RESOURCE_PROVIDERS} + ${total_count}= Set Variable 0 + FOR ${region} IN @{AWS_ENABLED_REGIONS} + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${region} --output-dir ${OUTPUT_DIR}/aws-c7n-security-hub/${region} ${CURDIR}/security-hub.yaml --cache-period 0 + ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ... timeout_seconds=120 + ${dirs}= RW.CLI.Run Cli + ... cmd=find ${OUTPUT_DIR}/aws-c7n-security-hub/${region} -mindepth 1 -maxdepth 1 -type d | jq -R -s 'split("\n") | map(select(length > 0))'; + TRY + ${dir_list}= Evaluate json.loads(r'''${dirs.stdout}''') json + EXCEPT + Log Failed to load JSON payload, defaulting to empty list. WARN + END + + IF len(@{dir_list}) > 0 + FOR ${dir} IN @{dir_list} + ${count}= RW.CLI.Run Cli + ... cmd=cat ${dir}/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value'; + ${total_count}= Evaluate ${total_count} + int(${count.stdout.strip()}) + END + ELSE + Log No directories found to process. WARN + END + END + RW.Core.Push Metric ${total_count} + + +** Keywords *** +Suite Initialization + ${AWS_REGION}= RW.Core.Import User Variable AWS_REGION + ... type=string + ... description=AWS Region + ... pattern=\w* + ${AWS_ACCOUNT_ID}= RW.Core.Import User Variable AWS_ACCOUNT_ID + ... type=string + ... description=AWS Account ID + ... pattern=\w* + ${AWS_ACCESS_KEY_ID}= RW.Core.Import Secret AWS_ACCESS_KEY_ID + ... type=string + ... description=AWS Access Key ID + ... pattern=\w* + ${AWS_SECRET_ACCESS_KEY}= RW.Core.Import Secret AWS_SECRET_ACCESS_KEY + ... type=string + ... description=AWS Access Key Secret + ... pattern=\w* + ${AWS_RESOURCE_PROVIDERS}= RW.Core.Import User Variable AWS_RESOURCE_PROVIDERS + ... type=string + ... description=Comma separated list of AWS Resource Providers. + ... pattern=^[a-zA-Z0-9,]+$ + ... example="ec2,s3,rds,vpc" + ... default="ec2,s3,rds,vpc,ebs,iam-group,iam-policy,iam-role,iam-user" + ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-security-hub + ${AWS_ENABLED_REGIONS}= RW.CLI.Run Cli + ... cmd=aws ec2 describe-regions --region ${AWS_REGION} --query 'Regions[*].RegionName' --output json + ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ${AWS_ENABLED_REGIONS}= Evaluate json.loads(r'''${AWS_ENABLED_REGIONS.stdout}''') json + Set Suite Variable ${AWS_ENABLED_REGIONS} ${AWS_ENABLED_REGIONS} + Set Suite Variable ${AWS_REGION} ${AWS_REGION} + Set Suite Variable ${AWS_ACCOUNT_ID} ${AWS_ACCOUNT_ID} + Set Suite Variable ${AWS_ACCESS_KEY_ID} ${AWS_ACCESS_KEY_ID} + Set Suite Variable ${AWS_SECRET_ACCESS_KEY} ${AWS_SECRET_ACCESS_KEY} + Set Suite Variable ${AWS_RESOURCE_PROVIDERS} ${AWS_RESOURCE_PROVIDERS} \ No newline at end of file From e2473dab7dfe2e370788877aeebab45f936bfc00 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 20 Jan 2025 14:51:48 +0530 Subject: [PATCH 40/44] sh cb: add runbook.robot --- .../aws-c7n-security-hub/runbook.robot | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 codebundles/aws-c7n-security-hub/runbook.robot diff --git a/codebundles/aws-c7n-security-hub/runbook.robot b/codebundles/aws-c7n-security-hub/runbook.robot new file mode 100644 index 0000000..5977e83 --- /dev/null +++ b/codebundles/aws-c7n-security-hub/runbook.robot @@ -0,0 +1,115 @@ +*** Settings *** +Metadata Author saurabh3460 +Metadata Supports AWS Tag CloudCustodian +Metadata Display Name AWS Security Hub +Documentation List aws security hub findings +Force Tags Tag AWS security-hub + +Library RW.Core +Library RW.CLI +Library CloudCustodian.Core + +Suite Setup Suite Initialization + +*** Tasks *** +List aws security hub findings in AWS account `${AWS_ACCOUNT_ID}` + [Documentation] Check for security hub findings + [Tags] aws security-hub + CloudCustodian.Core.Generate Policy + ... ${CURDIR}/security-hub.j2 + ... resource_providers=${AWS_RESOURCE_PROVIDERS} + ${total_count}= Set Variable 0 + FOR ${region} IN @{AWS_ENABLED_REGIONS} + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${region} --output-dir ${OUTPUT_DIR}/aws-c7n-security-findings/${region} ${CURDIR}/security-hub.yaml --cache-period 0 + ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ... timeout_seconds=120 + ${dirs}= RW.CLI.Run Cli + ... cmd=find ${OUTPUT_DIR}/aws-c7n-security-findings/${region} -mindepth 1 -maxdepth 1 -type d | jq -R -s 'split("\n") | map(select(length > 0))'; + TRY + ${dir_list}= Evaluate json.loads(r'''${dirs.stdout}''') json + EXCEPT + Log Failed to load JSON payload, defaulting to empty list. WARN + END + + IF len(@{dir_list}) > 0 + FOR ${dir} IN @{dir_list} + ${report_data}= RW.CLI.Run Cli + ... cmd=cat ${dir}/resources.json + + TRY + ${resource_list}= Evaluate json.loads(r'''${report_data.stdout}''') json + EXCEPT + Log Failed to load JSON payload, defaulting to empty list. WARN + ${resource_list}= Create List + END + + IF len(@{resource_list}) > 0 + # Generate and format report + ${report}= RW.CLI.Run Cli + ... cmd=echo ${resource_list} | jq '.[] | { Findings: ( .["c7n:finding-filter"][] | { Title: .Title, ProductName: .ProductName, Description: .Description, Resources: ( .Resources[] | { Type: .Type, Id: .Id, Region: .Region } ) })}' + RW.Core.Add Pre To Report ${report.stdout} + + FOR ${item} IN @{resource_list} + ${pretty_item}= Evaluate pprint.pformat(${item}) modules=pprint + FOR ${finding} IN @{item['c7n:finding-filter']} + ${severity_label}= Set Variable ${finding['Severity']['Label']} + ${severity}= Evaluate 1 if '${severity_label}' == 'CRITICAL' else 2 if '${severity_label}' == 'HIGH' else 3 if '${severity_label}' == 'MEDIUM' else 4 + FOR ${resource} IN @{finding['Resources']} + RW.Core.Add Issue + ... severity=${severity} + ... expected=${resource['Type']} ${resource['Id']} in AWS Region `${region}` in AWS Account `${AWS_ACCOUNT_ID}` should follow `${finding['Title']}` + ... actual=AWS Security Hub detected an issue with the rule `${finding['Title']}` for `${resource['Type']}` `${resource['Id']}` in AWS Region `${region}` and AWS Account `${AWS_ACCOUNT_ID}` + ... title=Security issue detected: Rule `${finding['Title']}` violated by `${resource['Type']}` `${resource['Id']}` in AWS Region `${region}` and AWS Account `${AWS_ACCOUNT_ID}` + ... reproduce_hint=${c7n_output.cmd} + ... details=${pretty_item} + ... next_steps=Review security hub findings in report related to rule `${finding['Title']}` on resource `${resource['Type']}` `${resource['Id']}` in AWS Region `${region}` and AWS Account `${AWS_ACCOUNT_ID}` + END + END + END + ELSE + RW.Core.Add Pre To Report "No Security Hub Findings in AWS region ${region}" + END + END + ELSE + RW.Core.Add Pre To Report "No directories found to process" + END + END + +** Keywords *** +Suite Initialization + ${AWS_REGION}= RW.Core.Import User Variable AWS_REGION + ... type=string + ... description=AWS Region + ... pattern=\w* + ${AWS_ACCOUNT_ID}= RW.Core.Import User Variable AWS_ACCOUNT_ID + ... type=string + ... description=AWS Account ID + ... pattern=\w* + ${AWS_ACCESS_KEY_ID}= RW.Core.Import Secret AWS_ACCESS_KEY_ID + ... type=string + ... description=AWS Access Key ID + ... pattern=\w* + ${AWS_SECRET_ACCESS_KEY}= RW.Core.Import Secret AWS_SECRET_ACCESS_KEY + ... type=string + ... description=AWS Access Key Secret + ... pattern=\w* + ${AWS_RESOURCE_PROVIDERS}= RW.Core.Import User Variable AWS_RESOURCE_PROVIDERS + ... type=string + ... description=Comma separated list of AWS Resource Providers. + ... pattern=^[a-zA-Z0-9,]+$ + ... example="ec2,s3,rds,vpc" + ... default="ec2,s3,rds,vpc,ebs,iam-group,iam-policy,iam-role,iam-user" + ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-security-findings + ${AWS_ENABLED_REGIONS}= RW.CLI.Run Cli + ... cmd=aws ec2 describe-regions --region ${AWS_REGION} --query 'Regions[*].RegionName' --output json + ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} + ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ${AWS_ENABLED_REGIONS}= Evaluate json.loads(r'''${AWS_ENABLED_REGIONS.stdout}''') json + Set Suite Variable ${AWS_ENABLED_REGIONS} ${AWS_ENABLED_REGIONS} + Set Suite Variable ${AWS_REGION} ${AWS_REGION} + Set Suite Variable ${AWS_ACCOUNT_ID} ${AWS_ACCOUNT_ID} + Set Suite Variable ${AWS_ACCESS_KEY_ID} ${AWS_ACCESS_KEY_ID} + Set Suite Variable ${AWS_SECRET_ACCESS_KEY} ${AWS_SECRET_ACCESS_KEY} + Set Suite Variable ${AWS_RESOURCE_PROVIDERS} ${AWS_RESOURCE_PROVIDERS} \ No newline at end of file From 7b8599c2fdd6e399fd09535c80583497dc01044e Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 20 Jan 2025 15:21:15 +0530 Subject: [PATCH 41/44] sh cb: fix alerts opeartor in sli template --- .../.runwhen/templates/aws-c7n-security-hub-sli.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-sli.yaml b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-sli.yaml index d312411..0b28a09 100644 --- a/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-sli.yaml +++ b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-sli.yaml @@ -38,11 +38,11 @@ spec: workspaceKey: {{custom.aws_secret_access_key}} alerts: warning: - operator: > + operator: '>' threshold: '1' for: '20m' ticket: - operator: > + operator: '>' threshold: '1' for: '40m' page: From ef9a06f14c91d228366ee3c578cbfc5c1ea36c18 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Tue, 21 Jan 2025 23:24:34 +0530 Subject: [PATCH 42/44] sh cb: generation rule file name fix --- .../{aws-c7n-network-health.yaml => aws-c7n-security-hub.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename codebundles/aws-c7n-security-hub/.runwhen/generation-rules/{aws-c7n-network-health.yaml => aws-c7n-security-hub.yaml} (100%) diff --git a/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-network-health.yaml b/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-security-hub.yaml similarity index 100% rename from codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-network-health.yaml rename to codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-security-hub.yaml From 826ddfeadc6033559f3c0191be0fc66348220b38 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 22 Jan 2025 00:05:19 +0530 Subject: [PATCH 43/44] sh cb: fix report generation in runbook.robot --- codebundles/aws-c7n-security-hub/runbook.robot | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/codebundles/aws-c7n-security-hub/runbook.robot b/codebundles/aws-c7n-security-hub/runbook.robot index 5977e83..c1120fd 100644 --- a/codebundles/aws-c7n-security-hub/runbook.robot +++ b/codebundles/aws-c7n-security-hub/runbook.robot @@ -45,15 +45,15 @@ List aws security hub findings in AWS account `${AWS_ACCOUNT_ID}` ${resource_list}= Create List END + ${report}= RW.CLI.Run Cli + ... cmd=jq '.[] | { Findings: ( .["c7n:finding-filter"][] | { Title: .Title, ProductName: .ProductName, Description: .Description, Resources: ( .Resources[] | { Type: .Type, Id: .Id, Region: .Region } ) })}' ${dir}/resources.json + RW.Core.Add Pre To Report ${report.stdout} + IF len(@{resource_list}) > 0 # Generate and format report - ${report}= RW.CLI.Run Cli - ... cmd=echo ${resource_list} | jq '.[] | { Findings: ( .["c7n:finding-filter"][] | { Title: .Title, ProductName: .ProductName, Description: .Description, Resources: ( .Resources[] | { Type: .Type, Id: .Id, Region: .Region } ) })}' - RW.Core.Add Pre To Report ${report.stdout} - FOR ${item} IN @{resource_list} - ${pretty_item}= Evaluate pprint.pformat(${item}) modules=pprint FOR ${finding} IN @{item['c7n:finding-filter']} + ${pretty_finding}= Evaluate pprint.pformat(${finding}) modules=pprint ${severity_label}= Set Variable ${finding['Severity']['Label']} ${severity}= Evaluate 1 if '${severity_label}' == 'CRITICAL' else 2 if '${severity_label}' == 'HIGH' else 3 if '${severity_label}' == 'MEDIUM' else 4 FOR ${resource} IN @{finding['Resources']} @@ -63,7 +63,7 @@ List aws security hub findings in AWS account `${AWS_ACCOUNT_ID}` ... actual=AWS Security Hub detected an issue with the rule `${finding['Title']}` for `${resource['Type']}` `${resource['Id']}` in AWS Region `${region}` and AWS Account `${AWS_ACCOUNT_ID}` ... title=Security issue detected: Rule `${finding['Title']}` violated by `${resource['Type']}` `${resource['Id']}` in AWS Region `${region}` and AWS Account `${AWS_ACCOUNT_ID}` ... reproduce_hint=${c7n_output.cmd} - ... details=${pretty_item} + ... details=${pretty_finding} ... next_steps=Review security hub findings in report related to rule `${finding['Title']}` on resource `${resource['Type']}` `${resource['Id']}` in AWS Region `${region}` and AWS Account `${AWS_ACCOUNT_ID}` END END From 2cc9f2940f0c2f2d7e6b5d645e8e8498a8965103 Mon Sep 17 00:00:00 2001 From: Shea Stewart Date: Thu, 23 Jan 2025 01:30:05 +0000 Subject: [PATCH 44/44] minor tweaks --- .../generation-rules/aws-c7n-security-hub.yaml | 2 +- .../templates/aws-c7n-security-hub-slx.yaml | 2 +- codebundles/aws-c7n-security-hub/runbook.robot | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-security-hub.yaml b/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-security-hub.yaml index 692cf5d..ebe2ed7 100644 --- a/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-security-hub.yaml +++ b/codebundles/aws-c7n-security-hub/.runwhen/generation-rules/aws-c7n-security-hub.yaml @@ -4,7 +4,7 @@ spec: platform: aws generationRules: - resourceTypes: - - aws_ec2_security_groups + - aws_securityhub_hubs matchRules: - type: pattern pattern: ".+" diff --git a/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-slx.yaml b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-slx.yaml index bfac75e..626006f 100644 --- a/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-slx.yaml +++ b/codebundles/aws-c7n-security-hub/.runwhen/templates/aws-c7n-security-hub-slx.yaml @@ -7,7 +7,7 @@ metadata: annotations: {% include "common-annotations.yaml" %} spec: - imageURL: https://PLACEHOLDER.jpg + imageURL: https://storage.googleapis.com/runwhen-nonprod-shared-images/icons/aws/Resource-Icons_06072024/Res_Security-Identity-Compliance/Res_AWS-Security-Hub_Finding_48.svg alias: AWS Security Hub findings in AWS Account {{match_resource.resource.account_id}} asMeasuredBy: The number of AWS Security Hub findings in AWS account {{match_resource.resource.account_id}} configProvided: diff --git a/codebundles/aws-c7n-security-hub/runbook.robot b/codebundles/aws-c7n-security-hub/runbook.robot index c1120fd..d4aee52 100644 --- a/codebundles/aws-c7n-security-hub/runbook.robot +++ b/codebundles/aws-c7n-security-hub/runbook.robot @@ -35,6 +35,8 @@ List aws security hub findings in AWS account `${AWS_ACCOUNT_ID}` IF len(@{dir_list}) > 0 FOR ${dir} IN @{dir_list} + ${product}= Evaluate "${dir}".rstrip('/').split('/')[-1].removesuffix('-findings') + ${report_data}= RW.CLI.Run Cli ... cmd=cat ${dir}/resources.json @@ -47,8 +49,13 @@ List aws security hub findings in AWS account `${AWS_ACCOUNT_ID}` ${report}= RW.CLI.Run Cli ... cmd=jq '.[] | { Findings: ( .["c7n:finding-filter"][] | { Title: .Title, ProductName: .ProductName, Description: .Description, Resources: ( .Resources[] | { Type: .Type, Id: .Id, Region: .Region } ) })}' ${dir}/resources.json - RW.Core.Add Pre To Report ${report.stdout} - + Log ${report.stdout} + IF $report.stdout != "" + RW.Core.Add Pre To Report ${report.stdout} + ELSE + RW.Core.Add Pre To Report "No Security Hub Findings in AWS region ${region} for ${product}" + END + IF len(@{resource_list}) > 0 # Generate and format report FOR ${item} IN @{resource_list} @@ -68,8 +75,6 @@ List aws security hub findings in AWS account `${AWS_ACCOUNT_ID}` END END END - ELSE - RW.Core.Add Pre To Report "No Security Hub Findings in AWS region ${region}" END END ELSE