Skip to content

Cloud

Automated AWS Audits That Run Themselves and Land in S3

TL;DR: I built an AWS Lambda function that audits your infrastructure on a schedule and exports detailed compliance reports to S3 -- no manual spreadsheet wrangling required.

The Problem

AWS audits are one of those tasks everyone knows they should do but nobody enjoys. Security teams ask for a list of public S3 buckets, unencrypted volumes, overly permissive IAM policies, and unused resources. The typical response is someone spending half a day clicking through the console, pasting screenshots into a spreadsheet, and emailing it around. By the time the report is finished, the infrastructure has already drifted. I needed something that runs automatically and produces a consistent, timestamped report.

What I Built

aws-audit-reporter is a Python Lambda function triggered by EventBridge on a configurable schedule. It queries AWS APIs across multiple services, evaluates resources against a set of compliance checks, and writes structured JSON and human-readable HTML reports to an S3 bucket.

  • Multi-service coverage -- audits EC2, S3, IAM, RDS, VPC, Lambda, EBS, and CloudTrail configurations
  • Compliance checks -- flags public buckets, unencrypted volumes, root account usage, security groups with 0.0.0.0/0, unused Elastic IPs, and more
  • Scheduled execution -- EventBridge cron triggers the Lambda daily, weekly, or on-demand
  • S3 report storage -- reports are versioned and organized by date, making it trivial to track compliance over time
  • SNS notifications -- sends a summary alert when critical findings are detected
  • Lightweight deployment -- a single Lambda function with no infrastructure beyond the function itself, an S3 bucket, and an EventBridge rule

Tech Stack

Python, AWS Lambda, Boto3, AWS EventBridge, S3, SNS, IAM, CloudFormation

Usage

# Deploy with CloudFormation
aws cloudformation deploy \
  --template-file template.yml \
  --stack-name aws-audit-reporter \
  --capabilities CAPABILITY_IAM

# Trigger manually
aws lambda invoke --function-name aws-audit-reporter output.json

# Reports appear in s3://your-audit-bucket/reports/YYYY-MM-DD/

Results

The first automated run flagged three S3 buckets with public ACLs and two security groups allowing SSH from anywhere -- findings that had been sitting in the account for months. Weekly reports now land in S3 without anyone lifting a finger, and the SNS integration means critical findings trigger a Slack alert within minutes. Audit prep that used to take a full day now takes zero.


GitHub: https://github.com/basel5001/aws-audit-reporter

A Lambda-Based AWS Cost Anomaly Detector with Slack Alerts

TL;DR: I built a Lambda function that checks AWS Cost Explorer daily, detects spending anomalies by comparing against rolling averages, forecasts monthly spend, and sends a formatted Slack report every morning.

The Problem

AWS bills are full of surprises. A forgotten NAT Gateway running at $32/day, an accidentally provisioned large RDS instance, a Lambda function in a retry loop -- these things silently drain budget for days before anyone notices. The native AWS Cost Anomaly Detection service exists but is limited in customization and alerting flexibility. I wanted daily cost intelligence in Slack, with anomaly detection tuned to my thresholds.

What I Built

aws-cost-optimizer is a Python Lambda function triggered daily by EventBridge at 8 AM UTC. It pulls the last 7 days of spending from AWS Cost Explorer (grouped by service), computes a rolling average per service, flags any service that exceeds its average by a configurable threshold, forecasts the current month's total, and posts a detailed report to Slack.

  • Daily cost breakdown -- top services by spend over the trailing 7-day window, formatted as a clean Slack message
  • Anomaly detection -- compares each service's latest daily cost against its rolling average; configurable percentage threshold (default: 30%)
  • Monthly forecasting -- uses Cost Explorer's forecast API to project end-of-month spend and compares it against a budget target
  • Slack alerts -- formatted messages with service-by-service breakdown, flagged anomalies, forecast vs. budget status, and trend indicators
  • Fully IaC -- Terraform deploys the Lambda, EventBridge rule, IAM role, and CloudWatch log group

Tech Stack

Python, AWS Lambda, AWS Cost Explorer, EventBridge, Slack Webhooks, Terraform, CloudWatch

Usage

# Deploy
cat > terraform/terraform.tfvars <<EOF
slack_webhook_url     = "https://hooks.slack.com/services/XXX/YYY/ZZZ"
monthly_budget        = 500
anomaly_threshold_pct = 30
EOF

cd terraform && terraform init && terraform apply

The Lambda runs automatically each morning. To test manually:

aws lambda invoke --function-name aws-cost-optimizer \
  --payload '{}' response.json
cat response.json

Results

The first week caught a $28/day NAT Gateway that had been running unused for two months. Monthly forecasting flagged a projected 40% budget overrun mid-month, giving enough lead time to right-size resources. The daily Slack report became the team's morning cost standup -- a two-second glance that replaces logging into the AWS console and navigating to Cost Explorer.


GitHub: https://github.com/basel5001/aws-cost-optimizer

Python Scripts That Automate the AWS Tasks I Used to Do by Hand

TL;DR: I built a collection of Python scripts that automate common AWS operations -- backups, cost tracking, CloudFormation stack management, and resource cleanup -- so I stop repeating the same console clicks every week.

The Problem

AWS administration involves a surprising amount of repetitive manual work. Every week I was doing the same things: checking which EC2 instances were running, verifying backup status, looking up CloudFormation stack outputs, reviewing cost trends, and hunting for orphaned resources. Each task individually takes five minutes in the console, but collectively they eat an hour or more per week. Worse, manual processes are error-prone -- I once forgot to stop a dev instance that ran for an entire weekend.

What I Built

aws-scripts is a collection of standalone Python scripts using Boto3 that automate the AWS tasks I found myself doing repeatedly. Each script is self-contained, well-documented, and designed to run locally, in a Lambda function, or as a cron job.

  • Backup automation -- creates EBS snapshots and RDS snapshots on a schedule, tags them with retention policies, and cleans up expired snapshots automatically
  • Cost tracker -- pulls daily spend from AWS Cost Explorer, groups by service, and outputs a summary with week-over-week comparison. Optionally sends a Slack notification if spend exceeds a threshold
  • CloudFormation utilities -- list all stacks with their status, export stack outputs to a JSON file, detect stacks stuck in ROLLBACK_COMPLETE, and bulk-delete stacks matching a pattern
  • Resource inventory -- enumerates running EC2 instances, RDS databases, Lambda functions, and S3 buckets with key metadata (region, size, state, cost tags)
  • Cleanup scripts -- find and optionally terminate stopped EC2 instances older than N days, release unattached Elastic IPs, and delete unused security groups
  • Off-hours scheduler -- stops tagged dev/staging instances at 7 PM and starts them at 8 AM on weekdays

Tech Stack

Python, Boto3, AWS (EC2, RDS, S3, CloudFormation, Cost Explorer, Lambda, CloudWatch), Click CLI

Usage

# Install dependencies
pip install -r requirements.txt

# Run cost summary
python cost_tracker.py --days 7 --slack-webhook $SLACK_URL

# Backup all tagged volumes
python backup.py --tag Environment:production

# Clean up old snapshots
python cleanup_snapshots.py --older-than 30

Results

The off-hours scheduler alone saves roughly $200/month by stopping 6 dev instances during nights and weekends. The cost tracker caught a misconfigured NAT Gateway that was generating $45/day in unexpected data transfer charges. Automating snapshot cleanup freed 2.3 TB of stale EBS snapshots in the first run. What used to be an hour of weekly console clicking now runs unattended.


GitHub: https://github.com/basel5001/AWS_Scripts

Deploy a Production VPN on AWS in Under 10 Minutes with Terraform

TL;DR: I built a Terraform module that provisions a fully configured OpenVPN or WireGuard server on AWS -- complete with certificates, firewall rules, and client configs -- in a single terraform apply.

The Problem

Setting up a VPN sounds simple until you actually do it. You need an EC2 instance, security groups, elastic IP, key pairs, DNS records, and then the VPN software itself -- certificates for OpenVPN or key pairs for WireGuard. Most tutorials walk you through 30+ manual steps, and the result is a snowflake server that nobody can reproduce. When the instance dies, you start from scratch. I wanted a VPN I could destroy and recreate in minutes.

What I Built

aws-vpn-provisioner is a Terraform project that automates the entire VPN deployment lifecycle on AWS. Choose OpenVPN or WireGuard, set a few variables, and Terraform handles everything from infrastructure to software configuration via cloud-init user data scripts.

  • Dual protocol support -- deploy OpenVPN (with Easy-RSA PKI) or WireGuard (with auto-generated peer keys) using the same Terraform codebase
  • Full infrastructure as code -- VPC (or existing VPC), subnet, security groups, EC2 instance, Elastic IP, and Route53 DNS record
  • Automated server configuration -- cloud-init scripts install and configure the VPN software on first boot, no SSH required
  • Client config generation -- .ovpn files for OpenVPN or WireGuard conf files are generated and stored in S3 for secure retrieval
  • Cost-conscious defaults -- uses t3.micro with spot pricing by default, keeping monthly costs under $5
  • Tear down and rebuild -- terraform destroy cleans up everything; terraform apply rebuilds identically

Tech Stack

Terraform, AWS (EC2, VPC, S3, Route53, IAM), OpenVPN, WireGuard, cloud-init, Bash

Usage

# Clone and configure
git clone https://github.com/basel5001/aws-vpn-provisioner.git
cd aws-vpn-provisioner

# Deploy WireGuard
terraform init
terraform apply -var="vpn_type=wireguard" -var="domain=vpn.example.com"

# Download client config from S3
aws s3 cp s3://vpn-configs-bucket/client.conf ./wireguard-client.conf

Results

Total deployment time from clone to connected VPN: under 8 minutes. Destroying and redeploying takes 4 minutes. Monthly cost with a t3.micro spot instance sits around $3.50. I have used this to spin up temporary VPN servers for travel, tear them down on return, and avoid paying for idle infrastructure. The WireGuard option consistently delivers 400+ Mbps throughput -- more than enough for any remote work scenario.


GitHub: https://github.com/basel5001/aws-vpn-provisioner

Automating AWS Disaster Recovery with AI-Generated Runbooks

TL;DR: I built a Terraform-managed AWS disaster recovery system with cross-region replication, automated failover, scheduled DR drills that measure RTO/RPO, and AI-generated runbooks via Bedrock.

The Problem

Every team says they have a DR plan. Few have tested it. Fewer still can tell you their actual RTO and RPO numbers. Manual failover runbooks go stale the moment the infrastructure changes. I needed DR that tests itself, measures itself, and keeps its own documentation current.

What I Built

disaster-recovery-aws is a complete DR automation platform spanning two AWS regions. The primary region (us-east-1) replicates data to a DR region (us-west-2) across S3, RDS, and DynamoDB. Lambda functions continuously monitor health, execute failover when thresholds breach, and run scheduled DR drills that produce hard RTO/RPO measurements.

  • Cross-region replication -- S3 CRR, RDS read replicas with automatic promotion, DynamoDB global tables
  • Automated failover -- Lambda detects health check failures and executes DNS switchover, RDS promotion, and ECS scaling without human intervention
  • Scheduled DR drills -- EventBridge triggers simulated failures; each drill measures actual recovery time and data loss, then reports via SNS
  • AI runbook generation -- AWS Bedrock generates step-by-step runbooks tailored to the current infrastructure state, not a static wiki page
  • Full observability -- CloudWatch alarms, a unified dashboard, and Synthetics canaries for external endpoint monitoring

Tech Stack

Terraform, Python, AWS (Lambda, RDS, S3, DynamoDB, Route53, ECS, ALB, CloudWatch, EventBridge, SNS, Bedrock)

Usage

# Deploy the full DR stack
cd terraform
cp backend.tf.example backend.tf
terraform init && terraform plan

# Run a manual DR drill
aws lambda invoke --function-name dr-drill-runner \
  --payload '{"scenario": "rds-failover"}' response.json

# Generate a runbook for a specific scenario
aws lambda invoke --function-name dr-runbook-generator \
  --payload '{"scenario": "full-region-failure"}' runbook.json

Results

DR drills now run weekly on a schedule. The first automated drill revealed an RTO of 12 minutes -- twice what we assumed. After tuning, we consistently hit under 5 minutes. AI-generated runbooks stay current because they query live infrastructure state each time. The biggest win: confidence. When an actual us-east-1 degradation occurred, the system detected it and began failover within 90 seconds.


GitHub: https://github.com/basel5001/disaster-recovery-aws

Bootstrapping an AWS Organization with Terraform

TL;DR: I built a Terraform-managed AWS landing zone that provisions an entire Organization -- OUs, accounts, SCPs, SSO, GuardDuty, Security Hub, and an account vending machine -- from a single terraform apply.

The Problem

Starting a new AWS Organization the right way involves dozens of manual steps: create OUs, attach SCPs, enable security services in every account, set up SSO, configure networking, establish CloudTrail. Do it by hand and you'll forget something. Do it with ClickOps and there's no audit trail. Every account you add later needs the same baseline applied, and drift is silent.

What I Built

multi-cloud-landing-zone is a Terraform codebase that bootstraps an AWS Organization with enterprise-grade security guardrails. It creates the OU hierarchy, attaches service control policies, enables security services across all accounts, configures IAM Identity Center (SSO), provisions shared networking, and includes an account vending machine that creates new accounts with a full baseline.

  • Service Control Policies -- region restriction (us-east-1 + eu-west-1), root account denial in member accounts, mandatory encryption for S3/EBS/RDS
  • Security services everywhere -- GuardDuty, Security Hub (CIS benchmarks), AWS Config, IAM Access Analyzer, and Macie enabled across all accounts with centralized findings
  • IAM Identity Center (SSO) -- four permission sets (Admin, ReadOnly, Developer, SecurityAudit) federated across all accounts
  • Account vending machine -- add an account definition to terraform.tfvars and the pipeline creates it with baseline IAM roles, CloudTrail, VPC, and Config rules
  • Shared networking -- Transit Gateway, centralized VPC flow logs, and private Route53 hosted zones

Tech Stack

Terraform, AWS Organizations, AWS IAM Identity Center, GuardDuty, Security Hub, AWS Config, SCPs, Transit Gateway, Python (policy tests)

Usage

# terraform.tfvars -- add a new account
vended_accounts = {
  team-alpha-dev = {
    email = "aws+alpha-dev@company.com"
    ou    = "Workloads"
    tags  = { Team = "alpha", Environment = "dev" }
  }
}
# Bootstrap the entire organization
cp .env.example .env && source .env
make init && make plan && make apply

# Validate SCPs
make test-policies

Results

A new AWS account goes from zero to fully baselined in under 15 minutes -- CloudTrail enabled, GuardDuty active, VPC provisioned, SSO configured. SCPs enforce encryption and region restrictions at the organizational level, so individual teams can't bypass them. The entire org structure is version-controlled: every change goes through a PR, gets reviewed, and is applied via CI.


GitHub: https://github.com/basel5001/multi-cloud-landing-zone

Deploying HashiCorp Vault as Code on AWS

TL;DR: I deployed a production-grade HashiCorp Vault cluster on AWS ECS with KMS auto-unseal, DynamoDB HA backend, dynamic secrets, and Kubernetes CSI integration -- entirely managed as Terraform.

The Problem

Secrets management is one of those things that's easy to get wrong and painful to fix. Environment variables in .env files, hardcoded API keys in config, shared credentials in a team password manager -- I've seen all of it. Setting up Vault properly involves dozens of moving parts: storage backend, unseal mechanism, TLS, audit logging, auth methods, secret engines. I wanted to codify the entire deployment so it's reproducible, auditable, and doesn't require a Vault expert to maintain.

What I Built

secret-vault is a complete Vault deployment on AWS, defined entirely in Terraform. Vault runs on ECS Fargate behind an ALB with TLS 1.3, stores data in DynamoDB with point-in-time recovery, and auto-unseals using AWS KMS. Secret engines, auth methods, and policies are all configured as code.

  • KMS auto-unseal -- no manual unseal keys to manage; Vault recovers automatically after restarts or scaling events
  • Dynamic secrets -- AWS IAM credentials and PostgreSQL database credentials are generated on-demand with automatic TTL-based revocation
  • Kubernetes CSI integration -- pods mount secrets as files via the Vault CSI Provider; no application code changes required
  • AppRole auth -- machine-to-machine authentication for CI/CD pipelines and automated tooling
  • Automated backups -- Raft snapshots shipped to S3 with KMS encryption on a schedule

Tech Stack

Terraform, HashiCorp Vault, AWS (ECS Fargate, KMS, DynamoDB, ALB, CloudWatch, S3), Docker, Kubernetes CSI

Usage

# Local development
make dev
# Access Vault UI at http://localhost:8200/ui (token: root)

# Production deployment
cp .env.example .env && source .env
make init && make plan && make apply

After deployment, applications retrieve secrets via the API or CSI driver:

# Read a static secret
vault kv get secret/my-app/config

# Generate dynamic AWS credentials
vault read aws/creds/deploy-role

# K8s CSI: mount secrets as files
# (SecretProviderClass references Vault path)

Results

Zero hardcoded secrets in application code or CI configs. Dynamic AWS credentials rotate automatically, eliminating long-lived IAM keys. The Kubernetes CSI integration means applications consume secrets as mounted files without any Vault SDK dependency. The entire stack rebuilds from scratch with terraform apply in under 10 minutes.


GitHub: https://github.com/basel5001/secret-vault

Production-Grade Reusable Terraform Modules for AWS

TL;DR: I created a library of four battle-tested Terraform modules -- VPC, EKS, Lambda, and S3 -- with sane defaults, full input/output documentation, and ready-to-use examples.

The Problem

Every new AWS project starts the same way: copy a VPC module from the last project, tweak the CIDR blocks, fix the broken NAT gateway config, remember to enable flow logs this time. Then do the same dance for EKS, Lambda, and S3. I was spending the first day of every project rebuilding infrastructure primitives that should have been solved once.

What I Built

terraform-aws-modules is a collection of four production-ready Terraform modules designed to be referenced directly from GitHub. Each module follows a consistent interface, includes sensible defaults for production use, and exposes enough variables for customization without overwhelming the consumer. Every module has a corresponding example in the examples/ directory.

  • VPC module -- 2-AZ public/private subnets, Internet Gateway, NAT Gateways, VPC Flow Logs, fully configurable CIDRs
  • EKS module -- managed node groups, IRSA (IAM Roles for Service Accounts) via OIDC provider, CloudWatch logging, configurable instance types and scaling
  • Lambda module -- function deployment with IAM role, CloudWatch log group, optional VPC attachment, and event source mappings (SQS, API Gateway, EventBridge)
  • S3 module -- versioning, AES-256 encryption, public access block, lifecycle rules for cost management
  • Consistent interface -- every module takes project_name, environment, and tags; outputs are predictable and composable

Tech Stack

Terraform, AWS (VPC, EKS, Lambda, S3, IAM, CloudWatch), HCL

Usage

module "vpc" {
  source = "github.com/basel5001/terraform-aws-modules//modules/vpc?ref=v1.0.0"

  project_name = "my-app"
  environment  = "prod"
  vpc_cidr     = "10.0.0.0/16"
}

module "eks" {
  source = "github.com/basel5001/terraform-aws-modules//modules/eks?ref=v1.0.0"

  cluster_name  = "my-app-prod"
  environment   = "prod"
  vpc_id        = module.vpc.vpc_id
  subnet_ids    = module.vpc.private_subnet_ids
  node_min_size = 2
  node_max_size = 6
}

Results

New projects reach a working VPC + EKS cluster in under 30 minutes instead of a full day. The modules encode production best practices by default -- flow logs are on, public access blocks are set, encryption is enabled. I've used these across seven projects without modification. When I do need to update a default (e.g., bumping the EKS version), one PR to the module repo updates all consumers on their next terraform init -upgrade.


GitHub: https://github.com/basel5001/terraform-aws-modules