Skip to content

2026

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

An Interactive Bash Script for Linux User and Group Management

TL;DR: I built an interactive bash script that handles the most common Linux user and group administration tasks -- creating users, managing groups, setting permissions, and bulk operations -- through a menu-driven interface.

The Problem

Linux user management is straightforward if you have the commands memorized. But for junior admins, students, or anyone who does not manage Linux systems daily, remembering the exact syntax for useradd, usermod, groupadd, passwd, and their various flags is a constant trip to the man pages. One wrong flag and you have locked out a user or assigned the wrong shell. I wanted a tool that makes these operations safe and accessible while still being educational about what is happening under the hood.

What I Built

bash-scripting is an interactive menu-driven bash script for managing Linux users and groups. It wraps the standard Linux user management commands in a guided interface that validates input, confirms destructive actions, and logs everything it does.

  • User creation -- creates users with configurable home directories, shells, and group memberships. Validates that usernames follow naming conventions and prompts for password setup
  • User modification -- change shells, lock/unlock accounts, update group memberships, set password expiration policies, and modify home directories
  • User deletion -- removes users with optional home directory cleanup, confirms before executing, and logs the removal
  • Group management -- create, delete, and rename groups. Add or remove users from groups interactively
  • Bulk operations -- import a CSV of users and create them all in one run, or export the current user list to CSV for auditing
  • Password management -- force password resets, set expiration policies, and check password status across users
  • Audit logging -- every action is logged to /var/log/user-management.log with timestamps and the admin who ran it
  • Dry-run mode -- preview exactly what commands will execute before running them

Tech Stack

Bash, Linux (useradd, usermod, groupadd, passwd, chage), AWK, CSV parsing

Usage

# Run interactively
sudo ./user-manager.sh

# Bulk create users from CSV
sudo ./user-manager.sh --bulk-create users.csv

# Dry run
sudo ./user-manager.sh --dry-run

# CSV format: username,fullname,shell,groups
# john.doe,John Doe,/bin/bash,developers;sudo

Results

This script has been used as a teaching tool in a Linux administration course where students practice user management without risking misconfigured commands. The dry-run mode catches roughly one mistake per session that would have required manual cleanup. Bulk user creation from CSV reduced onboarding time for a 15-person lab setup from 45 minutes of manual work to under 2 minutes. The audit log has proven useful for tracking who made what changes during group lab exercises.


GitHub: https://github.com/basel5001/Bash-Scripting

A Kubernetes Chaos Engineering Toolkit with AI-Powered Analysis

TL;DR: I built a chaos engineering toolkit that runs five experiment types against Kubernetes clusters and uses AWS Bedrock to analyze the results and recommend resilience improvements.

The Problem

"Our system is resilient" is a claim, not a fact, until you've tested it. Most teams discover their failure modes during actual incidents -- the worst possible time. I needed a way to systematically inject failures into Kubernetes workloads, observe how the system responds, and get actionable recommendations, not just pass/fail results.

What I Built

chaos-engineering is a CLI-driven toolkit that defines experiments as YAML manifests (CRD-style), executes them against a target Kubernetes cluster, collects metrics during the blast radius, and feeds everything into AWS Bedrock for AI-powered analysis. The AI doesn't just say "the pod restarted" -- it explains whether the recovery was fast enough, what the impact radius was, and what to harden next.

  • 5 experiment types -- pod kill (random pod termination), network latency (tc netem injection), CPU stress (stress-ng on target nodes), disk fill (hostPath exhaustion), and DNS failure (CoreDNS SERVFAIL injection)
  • Declarative experiment plans -- define target namespace, label selectors, duration, and success criteria in YAML
  • AI-powered analysis -- Bedrock correlates experiment timeline with cluster metrics and provides a risk assessment with specific remediation steps
  • Dry-run mode -- validate experiments and see what would be affected without injecting any faults
  • HTML results dashboard -- a self-contained visualization of experiment timelines, impact metrics, and AI recommendations

Tech Stack

Python, Kubernetes (kubectl), AWS Bedrock, Docker, stress-ng, tc/netem, Terraform, GitHub Actions

Usage

# manifests/experiment-pod-kill.yml
apiVersion: chaos.xops.io/v1alpha1
kind: ChaosExperiment
metadata:
  name: kill-frontend-pods
spec:
  type: pod-kill
  target:
    namespace: production
    labelSelector:
      matchLabels:
        app: frontend
  schedule:
    duration: 60s
    count: 3
# Dry-run to preview impact
chaos --manifest manifests/experiment-pod-kill.yml --dry-run

# Execute with AI analysis
chaos --manifest manifests/experiment-pod-kill.yml --analyze

# Export results
chaos --manifest manifests/experiment-network.yml --output json --outfile results.json

Results

Running weekly chaos experiments surfaced three critical resilience gaps: a service without a PodDisruptionBudget that went fully unavailable during pod kills, a DNS-dependent service with no caching that cascaded failures during DNS experiments, and a CPU-bound workload with no horizontal autoscaler. Each was caught and fixed before a real incident.


GitHub: https://github.com/basel5001/chaos-engineering

Building an Internal Developer Platform with an AI Advisor

TL;DR: I built a self-hosted developer portal where teams register services, provision AWS environments with one click, track costs, and get AI-powered infrastructure recommendations -- all backed by FastAPI and Terraform.

The Problem

Developers shouldn't need to write Terraform to spin up a staging environment. They also shouldn't wait three days for an ops ticket to get resolved. At the same time, giving everyone raw AWS console access is a recipe for cost blowouts and security incidents. I needed a paved road: self-service with guardrails.

What I Built

Cloud Platform is an Internal Developer Portal that wraps Terraform behind a REST API and a dark-themed single-page dashboard. Developers register their services, click "Create Environment," and the platform runs terraform plan && terraform apply under the hood -- provisioning VPCs, ALBs, ECS services, or Lambda functions.

  • Service Catalog -- register and track microservices by type (web, API, worker) with ownership metadata
  • Self-Service Provisioning -- create dev/staging/prod environments in one click; Terraform runs behind the scenes
  • AI Advisor -- ask AWS Bedrock (Claude Haiku) for infrastructure recommendations, Terraform code generation, and cost explanations
  • Cost Tracking -- per-service cost breakdown pulled from AWS Cost Explorer daily
  • Zero-Build Dashboard -- a single-page HTML app with Tailwind CSS; no Node build step required

Tech Stack

Python, FastAPI, SQLite, Terraform, AWS Bedrock (Claude), Tailwind CSS, AWS (ECS, Lambda, VPC, ALB, Cost Explorer)

Usage

# Start the platform
make dev
# Open http://localhost:8000

# Register a service via API
curl -X POST http://localhost:8000/api/services \
  -H "Content-Type: application/json" \
  -d '{"name": "payment-api", "type": "api", "owner": "platform-team"}'

# Ask the AI advisor
curl -X POST http://localhost:8000/api/ai/suggest \
  -d '{"question": "What instance type should I use for a Node.js API handling 500 RPS?"}'

Results

Environment provisioning dropped from a multi-day ticket-based process to under five minutes. The AI advisor handles the most common "how should I architect this?" questions that used to land in the platform team's Slack channel. Cost visibility per service caught two orphaned environments in the first week, saving roughly $400/month.


GitHub: https://github.com/basel5001/cloud-platform

One GitHub Action to Scan Every Container You Ship

TL;DR: I built a composite GitHub Action that runs Trivy, Grype, and Syft against your container images in a single step -- producing vulnerability reports and a full SBOM without any pipeline boilerplate.

The Problem

Container security scanning is non-negotiable, but configuring it properly is tedious. Most teams bolt on a single scanner and call it done. The problem? No single tool catches everything. Trivy excels at OS-level CVEs, Grype is strong on application dependencies, and Syft generates the SBOM you need for compliance. Running all three means maintaining three separate workflow steps, three sets of configs, and three output formats. Multiply that by a dozen repositories and you have a maintenance nightmare.

What I Built

container-security-scanner is a reusable GitHub Action that wraps Trivy, Grype, and Syft into a single, configurable step. Point it at an image (or a Dockerfile), and it runs all three scanners in sequence, merges the results, and fails the build if any vulnerability exceeds your severity threshold.

  • Triple-scanner coverage -- Trivy for OS and language vulnerabilities, Grype for dependency-level scanning, Syft for SBOM generation in SPDX and CycloneDX formats
  • Configurable severity gates -- set your threshold to CRITICAL, HIGH, or MEDIUM; the action exits non-zero if anything exceeds it
  • Unified SARIF output -- results are uploaded to GitHub's Security tab via code scanning, so vulnerabilities appear alongside your code
  • SBOM as an artifact -- the generated Software Bill of Materials is attached to the workflow run for download or forwarding to your compliance tooling
  • Minimal config -- add four lines to your workflow and you are scanning

Tech Stack

GitHub Actions (composite action), Trivy, Grype, Syft, SARIF, Docker

Usage

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: basel5001/container-security-scanner@main
        with:
          image: myapp:latest
          severity-threshold: HIGH
          generate-sbom: true

Results

Integrating this action across my repositories caught 14 high-severity CVEs in base images during the first week -- vulnerabilities that had been sitting in production undetected. SBOM generation now runs automatically on every release, and the compliance team pulls artifacts directly from GitHub without asking engineering. The unified SARIF view means developers see container vulnerabilities in the same place they see CodeQL findings, reducing context switching.


GitHub: https://github.com/basel5001/container-security-scanner

A GitHub Action That Catches Technical Debt Before It Merges

TL;DR: I built a GitHub Action that scans pull requests for technical debt patterns -- TODOs, FIXMEs, empty catch blocks, hardcoded secrets, and oversized files -- and blocks the merge until they are addressed.

The Problem

Technical debt accumulates silently. A developer drops a TODO: fix this later comment and moves on. Someone writes an empty catch block during a quick prototype. A hardcoded API key sneaks into a config file. Individually, none of these are catastrophic. Collectively, they erode codebase quality over months until the team is spending more time fighting the code than building features. Code reviews catch some of it, but reviewers are human -- they miss things, especially under time pressure.

What I Built

debtless is a GitHub Action that runs on every pull request and automatically scans the diff for common technical debt patterns. It annotates the PR with inline comments pointing to each finding and can be configured to block the merge if the debt exceeds a threshold.

  • Pattern detection -- scans for TODO, FIXME, HACK, XXX, WORKAROUND, and custom markers
  • Empty catch blocks -- detects catch blocks with no error handling across JavaScript, TypeScript, Python, Java, and Go
  • Hardcoded secrets -- flags patterns that look like API keys, tokens, passwords, and connection strings
  • Large file detection -- warns when files exceed a configurable line count, catching monolith classes before they grow
  • Inline PR annotations -- findings appear as review comments directly on the changed lines, not buried in logs
  • Configurable thresholds -- set a maximum debt score per PR; exceed it and the check fails
  • Trend tracking -- outputs a debt score over time so you can measure whether the codebase is getting better or worse

Tech Stack

GitHub Actions, TypeScript, GitHub Checks API, Regular Expressions, YAML Configuration

Usage

# .github/workflows/debtless.yml
name: Debt Check
on: [pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: basel5001/debtless@main
        with:
          max-debt-score: 10
          fail-on-secrets: true
          ignore-paths: "vendor/,node_modules/"

Results

After enabling debtless on three active repositories, the team resolved 47 TODO comments and 8 empty catch blocks within the first two weeks -- not because anyone mandated a cleanup sprint, but because the action surfaced them during normal PR reviews. The hardcoded-secrets check caught two AWS access keys that had been committed in test fixtures. Debt score trending showed a 30% reduction in new debt items per sprint after the first month.


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

Measuring What Matters: A DORA Metrics Dashboard Built on the GitHub API

TL;DR: I built a FastAPI dashboard that pulls deployment data from the GitHub API and calculates the four DORA metrics in real time -- giving engineering teams actual numbers instead of gut feelings about delivery performance.

The Problem

Every engineering manager has been asked "how fast can we ship?" and answered with a vague "pretty fast." DORA metrics -- deployment frequency, lead time for changes, mean time to recovery (MTTR), and change failure rate -- are the industry standard for measuring software delivery performance. But most teams either pay for expensive platforms or simply never measure. I wanted a self-hosted, open-source alternative that pulls directly from where the work already happens: GitHub.

What I Built

devops-dashboard is a Python application that connects to the GitHub API, ingests deployment and pull request data, and computes all four DORA metrics with historical trends. A clean web dashboard presents the numbers alongside DORA's Elite/High/Medium/Low benchmarks so teams can see exactly where they stand.

  • Deployment Frequency -- calculated from GitHub Deployments or tagged releases, shown as daily/weekly/monthly rates
  • Lead Time for Changes -- measured from first commit on a branch to production deployment, broken down by percentile
  • Mean Time to Recovery -- tracked by correlating incident-labeled issues with the deployment that resolved them
  • Change Failure Rate -- ratio of deployments followed by a rollback or hotfix within a configurable window
  • Trend charts -- week-over-week sparklines for each metric so you can spot regressions early
  • Multi-repo aggregation -- track metrics across your entire organization, not just one repo

Tech Stack

Python, FastAPI, GitHub REST API, SQLite, Jinja2, Chart.js, Docker

Usage

# Clone and start
git clone https://github.com/basel5001/devops-dashboard.git
cd devops-dashboard
cp .env.example .env  # add GITHUB_TOKEN
docker compose up -d
# Open http://localhost:8000

Results

Within the first sprint of running the dashboard, the team discovered that lead time had ballooned to 4.2 days -- well above the "Elite" threshold of under one day. The root cause turned out to be a stale review policy requiring two approvals on trivial changes. After adjusting the policy, lead time dropped to 1.1 days. Without the numbers, that bottleneck would have stayed invisible.


GitHub: https://github.com/basel5001/devops-dashboard