From 63adb4f0f60a2d2019c35a5d4e0a8644f6160c8c Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Wed, 20 Nov 2024 17:14:40 +0530 Subject: [PATCH 01/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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/50] 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 5e63e7cea937ed3f37d5dd006da83d365a9d5fd9 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 15:33:23 +0530 Subject: [PATCH 35/50] Update c7n version to 0.9.43 in requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c737c58..2329fcf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,5 @@ robotframework>=4.1.2 ruamel.base>=1.0.0 ruamel.yaml>=0.17.20 requests>=2 -c7n>=0.9.41 +c7n>=0.9.43 rw-cli-keywords>=0.0.19 \ No newline at end of file From 9658c05057d606aee9ce7c7d3caa3b339736a14f Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 15:33:49 +0530 Subject: [PATCH 36/50] Add README for AWS Cloud Custodian Service Usage CodeBundle --- codebundles/aws-c7n-service-usage/README.md | 27 +++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 codebundles/aws-c7n-service-usage/README.md diff --git a/codebundles/aws-c7n-service-usage/README.md b/codebundles/aws-c7n-service-usage/README.md new file mode 100644 index 0000000..362b3cf --- /dev/null +++ b/codebundles/aws-c7n-service-usage/README.md @@ -0,0 +1,27 @@ +# AWS Cloud Custodian Service Usage + +This CodeBundle evaluates the AWS Service Usage in AWS Account + +## SLI +The SLI produces a score of 0 (bad), 1(good), or a value in between. This score is generated by capturing the following: +- List AWS Service Usage Exceeding defined threshold + +The score of each check is added up and then divided by the total amount of checks. + + +## TaskSet +Similar to the SLI, but produces a report on the specific resources and raises issues for each volume 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 718a5f3b1426874d2b8e1c0d90bbb4db43d6bf78 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 15:33:58 +0530 Subject: [PATCH 37/50] Add service usage policy template for AWS service quotas --- .../aws-c7n-service-usage/service-usage.j2 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 codebundles/aws-c7n-service-usage/service-usage.j2 diff --git a/codebundles/aws-c7n-service-usage/service-usage.j2 b/codebundles/aws-c7n-service-usage/service-usage.j2 new file mode 100644 index 0000000..911ff46 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/service-usage.j2 @@ -0,0 +1,15 @@ +policies: + - name: service-usage + description: AWS services usage stats + resource: aws.service-quota + {%- if resource_providers %} + query: + - include_service_codes: + {%- for provider in resource_providers.split(',') %} + - {{ provider.strip('"').strip("'").strip() }} + {%- endfor %} + {%- endif %} + filters: + - UsageMetric: present + - type: usage-metric + limit: {{ usage_percent }} \ No newline at end of file From 20ee761ff56399ffa9fb33cad56b7c96dd6e0ad2 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 15:34:08 +0530 Subject: [PATCH 38/50] Add utility function to generate usage tables from AWS resource logs --- codebundles/aws-c7n-service-usage/Util.py | 68 +++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 codebundles/aws-c7n-service-usage/Util.py diff --git a/codebundles/aws-c7n-service-usage/Util.py b/codebundles/aws-c7n-service-usage/Util.py new file mode 100644 index 0000000..ff71bdc --- /dev/null +++ b/codebundles/aws-c7n-service-usage/Util.py @@ -0,0 +1,68 @@ +import re +import json +from tabulate import tabulate + +def usage_table(resource_file_path): + # Load JSON data from file + print(resource_file_path) + try: + # Attempt to open and read the log file + with open(resource_file_path, 'r') as file: + data = json.load(file) + except FileNotFoundError: + return f"Error: The file '{resource_file_path}' was not found." + except PermissionError: + return f"Error: Permission denied when trying to read '{resource_file}'." + except Exception as e: + return f"An unexpected error occurred while reading the file: {e}" + + # Define table headers + headers = ["ServiceName", "QuotaName", "Usage", "Quota", "UsagePercentage", "MetricName", "Period"] + + # Prepare table rows + table = [] + for item in data: + service_name = item.get("ServiceName", "N/A") + quota_name = item.get("QuotaName", "N/A") + usage = item.get("c7n:UsageMetric", {}).get("metric", 0) + quota = item.get("c7n:UsageMetric", {}).get("quota", 1) # Avoid division by zero + usage_percentage = round((usage / quota) * 100, 2) if quota else 0 + metric_name = item.get("UsageMetric", {}).get("MetricName", "N/A") + period_value = item.get("Period", {}).get("PeriodValue", "N/A") + period_unit = item.get("Period", {}).get("PeriodUnit", "N/A") + period = f"{period_value} {period_unit}" + + table.append([service_name, quota_name, usage, quota, f"{usage_percentage}%", metric_name, period]) + + # Print the table + return tabulate(table, headers=headers, tablefmt="grid") + +# e.g aws usage logs + # "2025-02-06 06:27:33,611: custodian.filters:INFO Amazon Elastic Compute Cloud (Amazon EC2) Running On-Demand Standard (A, C, D, H, I, M, R, T, Z) instances usage: 2.0/512.0", + # "2025-02-06 06:28:05,175: custodian.filters:INFO Elastic Load Balancing (ELB) Targets per Network Load Balancer usage: 0.0/3000.0", + # "2025-02-06 06:32:00,460: custodian.filters:INFO Amazon CloudWatch Rate of GetMetricStatistics requests usage: 0.37/400.0", + # "2025-02-06 06:28:01,668: custodian.filters:INFO Elastic Load Balancing (ELB) Targets per Availability Zone per Network Load Balancer usage: 0.0/500.0", + # "2025-02-06 06:28:04,540: custodian.filters:INFO Elastic Load Balancing (ELB) Classic Load Balancers per Region usage: 0.0/20.0", + # "2025-02-06 06:28:05,175: custodian.filters:INFO Elastic Load Balancing (ELB) Targets per Network Load Balancer usage: 0.0/3000.0", + # "2025-02-06 06:28:06,787: custodian.filters:INFO Elastic Load Balancing (ELB) Listeners per Network Load Balancer usage: 0.0/50.0", + # "2025-02-06 06:28:05,969: custodian.filters:INFO Elastic Load Balancing (ELB) Target Groups per Region usage: 0.0/3000.0", + # "2025-02-06 06:28:07,590: custodian.filters:INFO Elastic Load Balancing (ELB) Targets per Target Group per Region usage: 0.0/1000.0", + # "2025-02-06 06:28:08,426: custodian.filters:INFO Elastic Load Balancing (ELB) Targets per Application Load Balancer usage: 0.0/1000.0", + # "2025-02-06 06:28:09,248: custodian.filters:INFO Elastic Load Balancing (ELB) Application Load Balancers per Region usage: 0.0/50.0", + # "2025-02-06 06:28:10,064: custodian.filters:INFO Elastic Load Balancing (ELB) Network Load Balancers per Region usage: 0.0/50.0", + # "2025-02-06 06:28:10,864: custodian.filters:INFO Elastic Load Balancing (ELB) Registered Instances per Classic Load Balancer usage: 0.0/1000.0", + # "2025-02-06 06:28:43,856: custodian.filters:INFO Amazon EMR The maximum number of ListSecurityConfigurations API requests that you can make per second. usage: 0.0/5.0", + # "2025-02-06 06:28:46,315: custodian.filters:INFO Amazon EventBridge (CloudWatch Events) Invocations throttle limit in transactions per second usage: 0.05/18750.0", + # "2025-02-06 06:28:52,341: custodian.filters:INFO Amazon Kinesis Data Firehose Rate of ListDeliveryStream requests usage: 1.0/5.0", + # "2025-02-06 06:30:26,459: custodian.filters:INFO AWS Key Management Service (AWS KMS) ListKeys request rate usage: 0.0/500.0", + # "2025-02-06 06:30:40,805: custodian.filters:INFO AWS Key Management Service (AWS KMS) GetKeyPolicy request rate usage: 0.01/1000.0", + # "2025-02-06 06:30:41,863: custodian.filters:INFO AWS Key Management Service (AWS KMS) ListAliases request rate usage: 0.0/500.0", + # "2025-02-06 06:30:54,569: custodian.filters:INFO AWS Key Management Service (AWS KMS) Customer Master Keys (CMKs) usage: 2.0/100000.0", + # "2025-02-06 06:30:57,280: custodian.filters:INFO AWS Key Management Service (AWS KMS) Cryptographic operations (symmetric) request rate usage: 0.01/100000.0", + # "2025-02-06 06:31:03,602: custodian.filters:INFO AWS Key Management Service (AWS KMS) DescribeKey request rate usage: 0.01/2000.0", + # "2025-02-06 06:31:07,009: custodian.filters:INFO AWS Lambda Concurrent executions usage: 1.0/1000.0", + # "2025-02-06 06:31:17,046: custodian.filters:INFO Amazon CloudWatch Logs DescribeLogGroups throttle limit in transactions per second usage: 0.0/10.0", + # "2025-02-06 06:31:20,730: custodian.filters:INFO Amazon CloudWatch Logs CreateLogStream throttle limit in transactions per second usage: 0.01/50.0", + # "2025-02-06 06:31:29,270: custodian.filters:INFO Amazon CloudWatch Logs DescribeDestinations throttle limit in transactions per second usage: 0.0/5.0", + # "2025-02-06 06:31:29,771: custodian.filters:INFO Amazon CloudWatch Logs PutLogEvents throttle limit in transactions per second usage: 0.03/5000.0", + # "2025-02-06 06:31:30,934: custodian.filters:INFO Amazon CloudWatch Logs DescribeMetricFilters throttle limit in transactions per second usage: 0.01/5.0" From 1c689f3e71b21e9ff9ccaa0037e8af684f0be1b7 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 15:34:36 +0530 Subject: [PATCH 39/50] Add generation rules and templates for AWS service usage monitoring --- .../aws-c7n-service-usage.yaml | 22 ++++++++ .../templates/aws-c7n-service-usage-sli.yaml | 51 +++++++++++++++++++ .../templates/aws-c7n-service-usage-slx.yaml | 21 ++++++++ .../aws-c7n-service-usage-taskset.yaml | 33 ++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 codebundles/aws-c7n-service-usage/.runwhen/generation-rules/aws-c7n-service-usage.yaml create mode 100644 codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-sli.yaml create mode 100644 codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-slx.yaml create mode 100644 codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-taskset.yaml diff --git a/codebundles/aws-c7n-service-usage/.runwhen/generation-rules/aws-c7n-service-usage.yaml b/codebundles/aws-c7n-service-usage/.runwhen/generation-rules/aws-c7n-service-usage.yaml new file mode 100644 index 0000000..35e860b --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.runwhen/generation-rules/aws-c7n-service-usage.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-service-usage + qualifiers: ["account_id"] + baseTemplateName: aws-c7n-service-usage + levelOfDetail: basic + outputItems: + - type: slx + - type: sli + - type: runbook + templateName: aws-c7n-service-usage-taskset.yaml \ No newline at end of file diff --git a/codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-sli.yaml b/codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-sli.yaml new file mode 100644 index 0000000..fffd295 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-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: Count AWS Service Usage Exceeding defined threshold 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-service-usage/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-service-usage/.runwhen/templates/aws-c7n-service-usage-slx.yaml b/codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-slx.yaml new file mode 100644 index 0000000..0d5e260 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-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://storage.googleapis.com/runwhen-nonprod-shared-images/icons/aws/Resource-Icons_06072024/Res_Networking-Content-Delivery/Res_Amazon-VPC_Virtual-private-cloud-VPC_48.svg + alias: AWS Service Usage Exceeding defined threshold in AWS Account {{match_resource.resource.account_id}} + asMeasuredBy: The number of AWS Service Usage Exceeding defined threshold in AWS account {{match_resource.resource.account_id}} + configProvided: + - name: SLX_PLACEHOLDER + value: SLX_PLACEHOLDER + owners: + - {{workspace.owner_email}} + statement: List AWS Service Usage Exceeding defined threshold 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-service-usage/.runwhen/templates/aws-c7n-service-usage-taskset.yaml b/codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-taskset.yaml new file mode 100644 index 0000000..15935d5 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.runwhen/templates/aws-c7n-service-usage-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 AWS Service Usage Exceeding defined threshold 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-service-usage/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 57c73ba69d8365586a3cea582bb962092edac507 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 15:35:36 +0530 Subject: [PATCH 40/50] Add AWS Service Usage monitoring suite with threshold checks --- codebundles/aws-c7n-service-usage/sli.robot | 71 +++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 codebundles/aws-c7n-service-usage/sli.robot diff --git a/codebundles/aws-c7n-service-usage/sli.robot b/codebundles/aws-c7n-service-usage/sli.robot new file mode 100644 index 0000000..ed9c834 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/sli.robot @@ -0,0 +1,71 @@ +*** Settings *** +Metadata Author saurabh3460 +Metadata Supports AWS Tag CloudCustodian +Metadata Display Name AWS Service Usage +Documentation Count AWS Service Usage Exceeding defined threshold +Force Tags Tag AWS usage + +Library RW.Core +Library RW.CLI +Library CloudCustodian.Core + +Suite Setup Suite Initialization + + +*** Tasks *** +Check for AWS Service Usage Exceeding defined threshold in AWS Account `${AWS_ACCOUNT_ID}` + [Documentation] Check for AWS services where usage exceeds a specified usage percentage + [Tags] aws service usage + ${result}= CloudCustodian.Core.Generate Policy + ... ${CURDIR}/service-usage.j2 + ... usage_percent=${USAGE_PERCENTAGE} + ... resource_providers=${AWS_RESOURCE_PROVIDERS} + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-service-usage ${CURDIR}/service-usage.yaml --cache-period 0 + ... 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-service-usage/service-usage/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' + RW.Core.Push Metric int(${count.stdout}) + +** 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,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms + ... default=ec2,firehose + ${USAGE_PERCENTAGE}= RW.Core.Import User Variable USAGE_PERCENTAGE + ... type=number + ... description=Usage threshold percentage + ... pattern=\d* + ... example=80 + ... default=80 + ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-service-usage # Note: Clean out the cloud custoding report dir to ensure accurate data + ${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} \ No newline at end of file From cf9c6cb2098b6f076e89e1397bfe5a951741c5fa Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 15:35:41 +0530 Subject: [PATCH 41/50] Add runbook for listing AWS service usage exceeding defined thresholds --- .../aws-c7n-service-usage/runbook.robot | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 codebundles/aws-c7n-service-usage/runbook.robot diff --git a/codebundles/aws-c7n-service-usage/runbook.robot b/codebundles/aws-c7n-service-usage/runbook.robot new file mode 100644 index 0000000..b913a65 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/runbook.robot @@ -0,0 +1,98 @@ +*** Settings *** +Metadata Author saurabh3460 +Metadata Supports AWS Tag CloudCustodian +Metadata Display Name AWS Service Usage +Documentation List AWS Service Usage Exceeding defined threshold +Force Tags Tag AWS usage + +Library RW.Core +Library RW.CLI +Library CloudCustodian.Core +Library Util.py +Suite Setup Suite Initialization + + +*** Tasks *** +List AWS Service Usage Exceeding defined threshold in AWS Account ${AWS_ACCOUNT_ID} + [Documentation] List AWS services where usage exceeds a specified usage percentage + [Tags] aws service usage + ${result}= CloudCustodian.Core.Generate Policy + ... ${CURDIR}/service-usage.j2 + ... usage_percent=${USAGE_PERCENTAGE} + ... resource_providers=${AWS_RESOURCE_PROVIDERS} + ${c7n_output}= RW.CLI.Run Cli + ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-service-usage ${CURDIR}/service-usage.yaml --cache-period 0 + ... secret__aws_access_key_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-service-usage/service-usage/resources.json + + TRY + ${service_list}= Evaluate json.loads(r'''${report_data.stdout}''') json + EXCEPT + Log Failed to load JSON payload, defaulting to empty list. WARN + ${service_list}= Create List + END + + IF len(@{service_list}) > 0 + # Generate and format report + ${formatted_results}= USAGE TABLE ${OUTPUT_DIR}/aws-c7n-service-usage/service-usage/resources.json + RW.Core.Add Pre To Report ${formatted_results} + + FOR ${service} IN @{service_list} + ${usage_percentage}= Evaluate round(${service['c7n:UsageMetric']['metric']}/${service['c7n:UsageMetric']['quota']}*100, 2) + RW.Core.Add Issue + ... severity=3 + ... expected=Service `${service['ServiceName']}` usage should be below ${USAGE_PERCENTAGE}% of quota + ... actual=Service `${service['ServiceName']}` usage is at `${usage_percentage}%` of quota + ... title=Service `${service['ServiceName']}` usage exceeds threshold + ... reproduce_hint=${c7n_output.cmd} + ... details=${service} + ... next_steps=Increase the limit of `${service['ServiceName']}` + END + ELSE + RW.Core.Add Pre To Report No services found with usage exceeding `${USAGE_PERCENTAGE}%` in AWS Region `${AWS_REGION}` in AWS Account `${AWS_ACCOUNT_ID}` + 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,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms + ... default=ec2,firehose + ${USAGE_PERCENTAGE}= RW.Core.Import User Variable USAGE_PERCENTAGE + ... type=number + ... description=Usage threshold percentage + ... pattern=\d* + ... example=80 + ... default=80 + ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-service-usage # Note: Clean out the cloud custoding report dir to ensure accurate data + ${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} \ No newline at end of file From 318c9b7e601624e715e2d0089df37124513ed685 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 15:41:54 +0530 Subject: [PATCH 42/50] add placeholder for test infra --- .../aws-c7n-service-usage/.test/README.md | 91 +++++ .../aws-c7n-service-usage/.test/Taskfile.yaml | 352 ++++++++++++++++++ .../.test/terraform/Taskfile.yaml | 69 ++++ .../.test/terraform/main.tf | 1 + .../.test/terraform/provider.tf | 3 + 5 files changed, 516 insertions(+) create mode 100644 codebundles/aws-c7n-service-usage/.test/README.md create mode 100644 codebundles/aws-c7n-service-usage/.test/Taskfile.yaml create mode 100644 codebundles/aws-c7n-service-usage/.test/terraform/Taskfile.yaml create mode 100644 codebundles/aws-c7n-service-usage/.test/terraform/main.tf create mode 100644 codebundles/aws-c7n-service-usage/.test/terraform/provider.tf diff --git a/codebundles/aws-c7n-service-usage/.test/README.md b/codebundles/aws-c7n-service-usage/.test/README.md new file mode 100644 index 0000000..a568c3f --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.test/README.md @@ -0,0 +1,91 @@ +### 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", + "ec2:DescribeRegions", + "ec2:DescribeSecurityGroups", + "servicequotas:ListServices", + "servicequotas:ListAWSDefaultServiceQuotas", + "servicequotas:ListServiceQuotas", + "cloudwatch:GetMetricStatistics" + ], + "Resource": "*" + } + ] +} +``` + +**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: + **Note** WIP + +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-service-usage/.test/Taskfile.yaml b/codebundles/aws-c7n-service-usage/.test/Taskfile.yaml new file mode 100644 index 0000000..4319299 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.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-service-usage/.test/terraform/Taskfile.yaml b/codebundles/aws-c7n-service-usage/.test/terraform/Taskfile.yaml new file mode 100644 index 0000000..08e0e83 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.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-service-usage/.test/terraform/main.tf b/codebundles/aws-c7n-service-usage/.test/terraform/main.tf new file mode 100644 index 0000000..f457233 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.test/terraform/main.tf @@ -0,0 +1 @@ +# placeholder \ No newline at end of file diff --git a/codebundles/aws-c7n-service-usage/.test/terraform/provider.tf b/codebundles/aws-c7n-service-usage/.test/terraform/provider.tf new file mode 100644 index 0000000..aa39e39 --- /dev/null +++ b/codebundles/aws-c7n-service-usage/.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 From 0b2b15f7bfe370c3a74bb4210843842a55479b17 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 16:00:52 +0530 Subject: [PATCH 43/50] Add Util.py library to SLI robot suite --- codebundles/aws-c7n-service-usage/sli.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebundles/aws-c7n-service-usage/sli.robot b/codebundles/aws-c7n-service-usage/sli.robot index ed9c834..9f1a9fd 100644 --- a/codebundles/aws-c7n-service-usage/sli.robot +++ b/codebundles/aws-c7n-service-usage/sli.robot @@ -8,7 +8,7 @@ Force Tags Tag AWS usage Library RW.Core Library RW.CLI Library CloudCustodian.Core - +Library Util.py Suite Setup Suite Initialization From 50c39e9b6a36f1657210476d72bb5a077e97b7a4 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 16:27:18 +0530 Subject: [PATCH 44/50] Fix metric value conversion in AWS service usage check --- codebundles/aws-c7n-service-usage/sli.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebundles/aws-c7n-service-usage/sli.robot b/codebundles/aws-c7n-service-usage/sli.robot index 9f1a9fd..2152961 100644 --- a/codebundles/aws-c7n-service-usage/sli.robot +++ b/codebundles/aws-c7n-service-usage/sli.robot @@ -26,7 +26,7 @@ Check for AWS Service Usage Exceeding defined threshold in AWS Account `${AWS_AC ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-service-usage/service-usage/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - RW.Core.Push Metric int(${count.stdout}) + RW.Core.Push Metric Convert to Number ${count.stdout} 2 ** Keywords *** Suite Initialization From a943aacc957068525b5def9de908287449ff63f7 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 16:29:46 +0530 Subject: [PATCH 45/50] Update usage percentage pattern to enforce positive integer values --- codebundles/aws-c7n-service-usage/runbook.robot | 2 +- codebundles/aws-c7n-service-usage/sli.robot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-service-usage/runbook.robot b/codebundles/aws-c7n-service-usage/runbook.robot index b913a65..a0de2a0 100644 --- a/codebundles/aws-c7n-service-usage/runbook.robot +++ b/codebundles/aws-c7n-service-usage/runbook.robot @@ -82,7 +82,7 @@ Suite Initialization ${USAGE_PERCENTAGE}= RW.Core.Import User Variable USAGE_PERCENTAGE ... type=number ... description=Usage threshold percentage - ... pattern=\d* + ... pattern=^\d+$ ... example=80 ... default=80 ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-service-usage # Note: Clean out the cloud custoding report dir to ensure accurate data diff --git a/codebundles/aws-c7n-service-usage/sli.robot b/codebundles/aws-c7n-service-usage/sli.robot index 2152961..84a978d 100644 --- a/codebundles/aws-c7n-service-usage/sli.robot +++ b/codebundles/aws-c7n-service-usage/sli.robot @@ -55,7 +55,7 @@ Suite Initialization ${USAGE_PERCENTAGE}= RW.Core.Import User Variable USAGE_PERCENTAGE ... type=number ... description=Usage threshold percentage - ... pattern=\d* + ... pattern=^\d+$ ... example=80 ... default=80 ${clean_workding_dir}= RW.CLI.Run Cli cmd=rm -rf ${OUTPUT_DIR}/aws-c7n-service-usage # Note: Clean out the cloud custoding report dir to ensure accurate data From ca9be89d105f0fd23b944cf96290ea1548aa0c4a Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Fri, 7 Feb 2025 16:50:41 +0530 Subject: [PATCH 46/50] Update default AWS resource providers and set suite variables in runbook and SLI files --- codebundles/aws-c7n-service-usage/runbook.robot | 6 ++++-- codebundles/aws-c7n-service-usage/sli.robot | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/codebundles/aws-c7n-service-usage/runbook.robot b/codebundles/aws-c7n-service-usage/runbook.robot index a0de2a0..31322cd 100644 --- a/codebundles/aws-c7n-service-usage/runbook.robot +++ b/codebundles/aws-c7n-service-usage/runbook.robot @@ -78,7 +78,7 @@ Suite Initialization ... description=Comma-separated list of AWS Resource Providers ... pattern=^[a-zA-Z0-9,]+$ ... example=ec2,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms - ... default=ec2,firehose + ... default=ec2,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms ${USAGE_PERCENTAGE}= RW.Core.Import User Variable USAGE_PERCENTAGE ... type=number ... description=Usage threshold percentage @@ -95,4 +95,6 @@ Suite Initialization 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} \ No newline at end of file + Set Suite Variable ${AWS_SECRET_ACCESS_KEY} ${AWS_SECRET_ACCESS_KEY} + Set Suite Variable ${AWS_RESOURCE_PROVIDERS} ${AWS_RESOURCE_PROVIDERS} + Set Suite Variable ${USAGE_PERCENTAGE} ${USAGE_PERCENTAGE} \ No newline at end of file diff --git a/codebundles/aws-c7n-service-usage/sli.robot b/codebundles/aws-c7n-service-usage/sli.robot index 84a978d..dbca197 100644 --- a/codebundles/aws-c7n-service-usage/sli.robot +++ b/codebundles/aws-c7n-service-usage/sli.robot @@ -51,7 +51,7 @@ Suite Initialization ... description=Comma-separated list of AWS Resource Providers ... pattern=^[a-zA-Z0-9,]+$ ... example=ec2,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms - ... default=ec2,firehose + ... default=ec2,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms ${USAGE_PERCENTAGE}= RW.Core.Import User Variable USAGE_PERCENTAGE ... type=number ... description=Usage threshold percentage @@ -68,4 +68,6 @@ Suite Initialization 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} \ No newline at end of file + Set Suite Variable ${AWS_SECRET_ACCESS_KEY} ${AWS_SECRET_ACCESS_KEY} + Set Suite Variable ${AWS_RESOURCE_PROVIDERS} ${AWS_RESOURCE_PROVIDERS} + Set Suite Variable ${USAGE_PERCENTAGE} ${USAGE_PERCENTAGE} \ No newline at end of file From bdec9b86c09228d37aad81cc67a329cf320f1761 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Mon, 10 Feb 2025 14:54:40 +0530 Subject: [PATCH 47/50] Add timeout parameter to AWS service usage commands --- codebundles/aws-c7n-service-usage/runbook.robot | 2 +- codebundles/aws-c7n-service-usage/sli.robot | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-service-usage/runbook.robot b/codebundles/aws-c7n-service-usage/runbook.robot index 31322cd..f205552 100644 --- a/codebundles/aws-c7n-service-usage/runbook.robot +++ b/codebundles/aws-c7n-service-usage/runbook.robot @@ -24,7 +24,7 @@ List AWS Service Usage Exceeding defined threshold in AWS Account ${AWS_ACCOUNT_ ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-service-usage ${CURDIR}/service-usage.yaml --cache-period 0 ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} - + ... timeout_seconds=200 ${report_data}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-service-usage/service-usage/resources.json diff --git a/codebundles/aws-c7n-service-usage/sli.robot b/codebundles/aws-c7n-service-usage/sli.robot index dbca197..f7dad36 100644 --- a/codebundles/aws-c7n-service-usage/sli.robot +++ b/codebundles/aws-c7n-service-usage/sli.robot @@ -24,9 +24,10 @@ Check for AWS Service Usage Exceeding defined threshold in AWS Account `${AWS_AC ... cmd=custodian run -r ${AWS_REGION} --output-dir ${OUTPUT_DIR}/aws-c7n-service-usage ${CURDIR}/service-usage.yaml --cache-period 0 ... secret__aws_access_key_id=${AWS_ACCESS_KEY_ID} ... secret__aws_secret_access_key=${AWS_SECRET_ACCESS_KEY} + ... timeout_seconds=200 ${count}= RW.CLI.Run Cli ... cmd=cat ${OUTPUT_DIR}/aws-c7n-service-usage/service-usage/metadata.json | jq '.metrics[] | select(.MetricName == "ResourceCount") | .Value' - RW.Core.Push Metric Convert to Number ${count.stdout} 2 + RW.Core.Push Metric ${count.stdout} ** Keywords *** Suite Initialization From 093db1cdff82d765f018be74982b152154bbce23 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Tue, 11 Feb 2025 15:34:27 +0530 Subject: [PATCH 48/50] Improve AWS service usage reporting with detailed account context and improve variable descriptions --- codebundles/aws-c7n-service-usage/runbook.robot | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/codebundles/aws-c7n-service-usage/runbook.robot b/codebundles/aws-c7n-service-usage/runbook.robot index f205552..4d3456b 100644 --- a/codebundles/aws-c7n-service-usage/runbook.robot +++ b/codebundles/aws-c7n-service-usage/runbook.robot @@ -44,15 +44,15 @@ List AWS Service Usage Exceeding defined threshold in AWS Account ${AWS_ACCOUNT_ ${usage_percentage}= Evaluate round(${service['c7n:UsageMetric']['metric']}/${service['c7n:UsageMetric']['quota']}*100, 2) RW.Core.Add Issue ... severity=3 - ... expected=Service `${service['ServiceName']}` usage should be below ${USAGE_PERCENTAGE}% of quota - ... actual=Service `${service['ServiceName']}` usage is at `${usage_percentage}%` of quota - ... title=Service `${service['ServiceName']}` usage exceeds threshold + ... expected=Service `${service['ServiceName']}` usage should be below ${USAGE_PERCENTAGE}% of quota in AWS Account `${AWS_ACCOUNT_ID}` + ... actual=Service `${service['ServiceName']}` usage is at `${usage_percentage}%` of quota in AWS Account `${AWS_ACCOUNT_ID}` + ... title=Service `${service['ServiceName']}` usage exceeds threshold ${USAGE_PERCENTAGE}% in AWS Account `${AWS_ACCOUNT_ID}` ... reproduce_hint=${c7n_output.cmd} ... details=${service} - ... next_steps=Increase the limit of `${service['ServiceName']}` + ... next_steps=Increase the limit of AWS service `${service['ServiceName']}` in AWS Account `${AWS_ACCOUNT_ID}` END ELSE - RW.Core.Add Pre To Report No services found with usage exceeding `${USAGE_PERCENTAGE}%` in AWS Region `${AWS_REGION}` in AWS Account `${AWS_ACCOUNT_ID}` + RW.Core.Add Pre To Report No services found with usage exceeding `${USAGE_PERCENTAGE}%` in AWS Account `${AWS_ACCOUNT_ID}` END ** Keywords *** @@ -75,13 +75,13 @@ Suite Initialization ... pattern=\w* ${AWS_RESOURCE_PROVIDERS}= RW.Core.Import User Variable AWS_RESOURCE_PROVIDERS ... type=string - ... description=Comma-separated list of AWS Resource Providers + ... description=Comma separated list of AWS Resource Providers ... pattern=^[a-zA-Z0-9,]+$ ... example=ec2,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms ... default=ec2,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms ${USAGE_PERCENTAGE}= RW.Core.Import User Variable USAGE_PERCENTAGE ... type=number - ... description=Usage threshold percentage + ... description=Threshold percentage for service usage monitoring. If usage exceeds this value, an issue will be raised. ... pattern=^\d+$ ... example=80 ... default=80 From 18f8bad8c62664fb2bb52df6e4e02d18dea2df49 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Tue, 11 Feb 2025 15:34:45 +0530 Subject: [PATCH 49/50] Improve descriptions for AWS resource providers and usage percentage variables in SLI file --- codebundles/aws-c7n-service-usage/sli.robot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codebundles/aws-c7n-service-usage/sli.robot b/codebundles/aws-c7n-service-usage/sli.robot index f7dad36..f860e8f 100644 --- a/codebundles/aws-c7n-service-usage/sli.robot +++ b/codebundles/aws-c7n-service-usage/sli.robot @@ -49,13 +49,13 @@ Suite Initialization ... pattern=\w* ${AWS_RESOURCE_PROVIDERS}= RW.Core.Import User Variable AWS_RESOURCE_PROVIDERS ... type=string - ... description=Comma-separated list of AWS Resource Providers + ... description=Comma separated list of AWS Resource Providers ... pattern=^[a-zA-Z0-9,]+$ ... example=ec2,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms ... default=ec2,firehose,lambda,logs,monitoring,rds,servicequotas,ssm,fargate,kms ${USAGE_PERCENTAGE}= RW.Core.Import User Variable USAGE_PERCENTAGE ... type=number - ... description=Usage threshold percentage + ... description=Threshold percentage for service usage monitoring. If usage exceeds this value, an issue will be raised. ... pattern=^\d+$ ... example=80 ... default=80 From fc4448c8188be8b9b094f53b4665aac4dfaf5f19 Mon Sep 17 00:00:00 2001 From: saurabh3460 Date: Tue, 11 Feb 2025 15:51:24 +0530 Subject: [PATCH 50/50] Clarify SLI metrics description in README for better understanding --- codebundles/aws-c7n-service-usage/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebundles/aws-c7n-service-usage/README.md b/codebundles/aws-c7n-service-usage/README.md index 362b3cf..a89cbb8 100644 --- a/codebundles/aws-c7n-service-usage/README.md +++ b/codebundles/aws-c7n-service-usage/README.md @@ -3,7 +3,7 @@ This CodeBundle evaluates the AWS Service Usage in AWS Account ## SLI -The SLI produces a score of 0 (bad), 1(good), or a value in between. This score is generated by capturing the following: +The SLI metrics are generated by executing the following task: - List AWS Service Usage Exceeding defined threshold The score of each check is added up and then divided by the total amount of checks.