Lab 08 — Cloud Security Configuration Auditor
Mission Information
Section titled “Mission Information”Difficulty: Advanced
Estimated Time: 150–210 minutes
Primary Language: Python
Security Domain: Cloud Security / CSPM / Security Automation / Governance
Cloud Coverage: AWS / Microsoft Azure / Google Cloud
Environment: Local controlled lab with synthetic cloud configuration data
Automation Type: Read-Only Cloud Security Assessment
Mission
Section titled “Mission”Your task is to build a reusable cloud security configuration auditor.
Instead of manually checking individual cloud resources, your Python application will process configuration data representing:
AWS
MICROSOFT AZURE
GOOGLE CLOUDand evaluate selected defensive security controls.
You will review:
IDENTITY
LOGGING
ENCRYPTION
PUBLIC EXPOSURE
NETWORK SECURITY
STORAGE SECURITY
SECURITY SERVICES
RESOURCE OWNERSHIP
SECURITY CONFIGURATIONThe final workflow will be:
CLOUD CONFIGURATION ↓INGEST ↓VALIDATE ↓NORMALIZE ↓SECURITY CHECKS ↓FINDINGS ↓PRIORITIZE ↓REPORTWhy This Lab Matters
Section titled “Why This Lab Matters”Cloud environments are dynamic.
An enterprise may have:
10 ACCOUNTSor:
500 SUBSCRIPTIONSor:
THOUSANDS OF PROJECTSand potentially millions of cloud resources.
Manual inspection does not scale.
Cloud security teams therefore rely on automated configuration assessment to answer questions such as:
IS AUDIT LOGGING ENABLED?
IS STORAGE PUBLIC?
IS ENCRYPTION ENABLED?
ARE PRIVILEGED IDENTITIES PROTECTED?
ARE SECURITY SERVICES ENABLED?
ARE MANAGEMENT PORTS EXPOSED?
DO CRITICAL RESOURCES HAVE OWNERS?
ARE NETWORK CONTROLS TOO PERMISSIVE?This lab introduces the core engineering pattern behind:
CLOUD SECURITY POSTURE MANAGEMENTor:
CSPMLearning Objectives
Section titled “Learning Objectives”By completing this lab, you should be able to:
MODEL CLOUD SECURITY DATA
PROCESS MULTI-CLOUD CONFIGURATION
NORMALIZE PROVIDER-SPECIFIC DATA
BUILD READ-ONLY SECURITY CHECKS
REVIEW CLOUD IDENTITY
REVIEW AUDIT LOGGING
REVIEW STORAGE EXPOSURE
REVIEW ENCRYPTION
REVIEW NETWORK EXPOSURE
REVIEW SECURITY SERVICES
IDENTIFY MISSING OWNERS
BUILD A COMMON FINDING SCHEMA
ASSIGN REVIEW PRIORITIES
EXPORT CSV
EXPORT JSON
GENERATE MARKDOWN REPORTS
DESIGN FOR FUTURE CLOUD API INTEGRATIONFinal Architecture
Section titled “Final Architecture” CLOUD ENVIRONMENTS ↓ ┌──────────────┼──────────────┐ ↓ ↓ ↓ AWS AZURE GCP ↓ ↓ ↓ └──────────────┼──────────────┘ ↓ CONFIGURATION DATA ↓ VALIDATION ↓ NORMALIZATION ↓ SECURITY CHECK ENGINE ┌───────────┼───────────┐ ↓ ↓ ↓ IDENTITY LOGGING STORAGE ↓ ↓ ↓ NETWORK ENCRYPTION SERVICES └───────────┼───────────┘ ↓ FINDINGS ↓ PRIORITIZE ↓ ┌───────────┼───────────┐ ↓ ↓ ↓ CSV JSON MARKDOWNAuthorization and Safety
Section titled “Authorization and Safety”This lab is:
READ ONLYand uses:
SYNTHETIC CONFIGURATION DATADo not perform cloud assessment activities against accounts, subscriptions, projects, or resources unless you own them or have explicit authorization.
The auditor should:
READ
ASSESS
REPORTIt should not automatically:
DELETE RESOURCES
CHANGE IAM
DISABLE USERS
MODIFY FIREWALLS
ROTATE KEYS
BLOCK NETWORK TRAFFIC
SHUT DOWN WORKLOADS01 — Create the Lab Workspace
Section titled “01 — Create the Lab Workspace”Create:
cloud-security-auditor/|+-- data/|+-- reports/|+-- src/|+-- tests/|+-- README.mdLinux/macOS:
mkdir -p cloud-security-auditor/{data,reports,src,tests}cd cloud-security-auditorPowerShell:
mkdir cloud-security-auditor
cd cloud-security-auditor
mkdir datamkdir reportsmkdir srcmkdir tests02 — Verify Python
Section titled “02 — Verify Python”Run:
python --versionRecommended:
Python 3.10+03 — Define the Multi-Cloud Security Model
Section titled “03 — Define the Multi-Cloud Security Model”For this lab, your auditor will evaluate six control families:
IDENTITY
LOGGING
ENCRYPTION
STORAGE
NETWORK
SECURITY SERVICES04 — Understand Provider Differences
Section titled “04 — Understand Provider Differences”The providers use different terminology.
Example:
| Security Concept | AWS | Azure | Google Cloud |
|---|---|---|---|
| Account boundary | Account | Subscription | Project |
| Identity service | IAM | Entra ID / RBAC | Cloud IAM |
| Audit logging | CloudTrail | Activity Log | Cloud Audit Logs |
| Object storage | S3 | Blob Storage | Cloud Storage |
| Security posture service | Security Hub | Defender for Cloud | Security Command Center |
Your internal security model should remain:
CONSISTENTeven when provider terminology differs.
05 — Create Synthetic AWS Data
Section titled “05 — Create Synthetic AWS Data”Create:
data/aws.jsonAdd:
{ "provider": "aws", "account_id": "111122223333", "account_name": "production", "owner": "Cloud Platform Team", "logging": { "cloudtrail_enabled": true, "multi_region": true, "log_validation": true }, "security_services": { "security_hub_enabled": true, "guardduty_enabled": true }, "identities": [ { "name": "cloud-admin", "type": "user", "privileged": true, "mfa_enabled": true, "access_key_age_days": 24 }, { "name": "legacy-admin", "type": "user", "privileged": true, "mfa_enabled": false, "access_key_age_days": 180 }, { "name": "app-role", "type": "role", "privileged": false, "mfa_enabled": null, "access_key_age_days": null } ], "storage": [ { "name": "prod-application-data", "type": "s3", "public": false, "encrypted": true, "criticality": "critical", "owner": "Application Team" }, { "name": "legacy-public-files", "type": "s3", "public": true, "encrypted": false, "criticality": "high", "owner": "" } ], "network_rules": [ { "resource": "web-sg", "protocol": "tcp", "port": 443, "source": "0.0.0.0/0", "purpose": "public-web" }, { "resource": "admin-sg", "protocol": "tcp", "port": 22, "source": "0.0.0.0/0", "purpose": "administration" } ]}06 — Review the AWS Scenario
Section titled “06 — Review the AWS Scenario”The synthetic AWS account contains:
GOOD CLOUDTRAIL CONFIGURATION
SECURITY HUB ENABLED
GUARDDUTY ENABLED
ONE PRIVILEGED USER WITHOUT MFA
ONE OLD ACCESS KEY
ONE PUBLIC S3-LIKE BUCKET
ONE UNENCRYPTED STORAGE RESOURCE
ONE MANAGEMENT PORT OPEN TO THE INTERNET
ONE RESOURCE WITHOUT OWNER07 — Create Synthetic Azure Data
Section titled “07 — Create Synthetic Azure Data”Create:
data/azure.jsonAdd:
{ "provider": "azure", "subscription_id": "00000000-1111-2222-3333-444444444444", "subscription_name": "enterprise-prod", "owner": "Azure Platform Team", "logging": { "activity_log_enabled": true, "diagnostic_settings_enabled": false }, "security_services": { "defender_for_cloud_enabled": true, "sentinel_connected": true }, "identities": [ { "name": "Global Admin 01", "type": "user", "privileged": true, "mfa_enabled": true }, { "name": "Legacy Cloud Admin", "type": "user", "privileged": true, "mfa_enabled": false }, { "name": "app-managed-identity", "type": "managed_identity", "privileged": false, "mfa_enabled": null } ], "storage": [ { "name": "prodstorage01", "type": "blob", "public": false, "encrypted": true, "criticality": "critical", "owner": "Application Team" }, { "name": "publicarchive01", "type": "blob", "public": true, "encrypted": true, "criticality": "medium", "owner": "Archive Team" } ], "network_rules": [ { "resource": "web-nsg", "protocol": "tcp", "port": 443, "source": "Internet", "purpose": "public-web" }, { "resource": "management-nsg", "protocol": "tcp", "port": 3389, "source": "Internet", "purpose": "administration" } ]}08 — Review the Azure Scenario
Section titled “08 — Review the Azure Scenario”The Azure subscription contains:
ACTIVITY LOG ENABLED
MISSING DIAGNOSTIC SETTINGS
DEFENDER FOR CLOUD ENABLED
SENTINEL CONNECTED
PRIVILEGED USER WITHOUT MFA
PUBLIC BLOB STORAGE
RDP EXPOSED TO INTERNET09 — Create Synthetic Google Cloud Data
Section titled “09 — Create Synthetic Google Cloud Data”Create:
data/gcp.jsonAdd:
{ "provider": "gcp", "project_id": "ghc-production-001", "project_name": "production-platform", "owner": "", "logging": { "audit_logs_enabled": true, "data_access_logs_enabled": false }, "security_services": { "security_command_center_enabled": true }, "identities": [ { "name": "cloud-admin@example.com", "type": "user", "privileged": true, "mfa_enabled": true }, { "name": "legacy-admin@example.com", "type": "user", "privileged": true, "mfa_enabled": false }, { "name": "app-service-account", "type": "service_account", "privileged": false, "mfa_enabled": null } ], "storage": [ { "name": "prod-secure-data", "type": "cloud-storage", "public": false, "encrypted": true, "criticality": "critical", "owner": "Data Platform Team" }, { "name": "public-training-content", "type": "cloud-storage", "public": true, "encrypted": true, "criticality": "low", "owner": "Training Team" } ], "network_rules": [ { "resource": "public-web-rule", "protocol": "tcp", "port": 443, "source": "0.0.0.0/0", "purpose": "public-web" }, { "resource": "ssh-admin-rule", "protocol": "tcp", "port": 22, "source": "0.0.0.0/0", "purpose": "administration" } ]}10 — Review the Google Cloud Scenario
Section titled “10 — Review the Google Cloud Scenario”The project includes:
AUDIT LOGGING ENABLED
DATA ACCESS LOGGING DISABLED
SECURITY COMMAND CENTER ENABLED
PROJECT OWNER MISSING
PRIVILEGED USER WITHOUT MFA
PUBLIC STORAGE
SSH OPEN TO INTERNET11 — Create the Python Auditor
Section titled “11 — Create the Python Auditor”Create:
src/cloud_security_auditor.pyStart with:
from pathlib import Pathfrom collections import Counterimport csvimport jsonimport logging12 — Define Project Paths
Section titled “12 — Define Project Paths”Add:
BASE_DIR = Path(__file__).resolve().parent.parent
DATA_DIR = BASE_DIR / "data"
REPORT_DIR = BASE_DIR / "reports"
REPORT_DIR.mkdir( parents=True, exist_ok=True)13 — Configure Logging
Section titled “13 — Configure Logging”Add:
logging.basicConfig( level=logging.INFO, format=( "%(asctime)s " "%(levelname)s " "%(message)s" ))14 — Define Provider Files
Section titled “14 — Define Provider Files”Add:
PROVIDER_FILES = [ DATA_DIR / "aws.json", DATA_DIR / "azure.json", DATA_DIR / "gcp.json"]15 — Create a Common Finding Schema
Section titled “15 — Create a Common Finding Schema”Every finding should eventually look like:
{ "provider": "aws", "scope": "production", "resource_type": "identity", "resource": "legacy-admin", "control": "privileged-mfa", "status": "fail", "severity": "high", "title": "Privileged identity does not use MFA", "evidence": "mfa_enabled=false", "recommendation": "Review and enforce approved strong authentication controls."}16 — Why Use a Common Finding Schema?
Section titled “16 — Why Use a Common Finding Schema?”Without normalization:
AWS FINDING
AZURE FINDING
GCP FINDINGmay all look different.
With normalization:
MULTI-CLOUD DATA ↓COMMON FINDING MODEL ↓ONE REPORTING ENGINE17 — Create Finding Builder
Section titled “17 — Create Finding Builder”Add:
def create_finding( provider, scope, resource_type, resource, control, status, severity, title, evidence, recommendation): return { "provider": provider,
"scope": scope,
"resource_type": resource_type,
"resource": resource,
"control": control,
"status": status,
"severity": severity,
"title": title,
"evidence": evidence,
"recommendation": recommendation }18 — Load JSON Safely
Section titled “18 — Load JSON Safely”Create:
def load_json_file( path): with path.open( "r", encoding="utf-8" ) as file:
return json.load( file )19 — Handle Invalid JSON
Section titled “19 — Handle Invalid JSON”Improve:
def load_json_file( path): try: with path.open( "r", encoding="utf-8" ) as file:
return json.load( file )
except FileNotFoundError: logging.error( "Missing file: %s", path )
return None
except json.JSONDecodeError as error: logging.error( "Invalid JSON in %s: %s", path, error )
return None20 — Load All Cloud Data
Section titled “20 — Load All Cloud Data”Create:
def load_cloud_data(): records = []
for path in PROVIDER_FILES: data = load_json_file( path )
if data: records.append( data )
return records21 — Validate Provider
Section titled “21 — Validate Provider”Supported values:
SUPPORTED_PROVIDERS = { "aws", "azure", "gcp"}Create:
def validate_provider( data): provider = str( data.get( "provider", "" ) ).strip().lower()
return ( provider in SUPPORTED_PROVIDERS )22 — Determine Cloud Scope
Section titled “22 — Determine Cloud Scope”AWS uses:
account_nameAzure:
subscription_nameGoogle Cloud:
project_nameCreate:
def get_scope_name( data): provider = data[ "provider" ]
if provider == "aws": return data.get( "account_name", "unknown" )
if provider == "azure": return data.get( "subscription_name", "unknown" )
if provider == "gcp": return data.get( "project_name", "unknown" )
return "unknown"23 — Security Control 01: Scope Ownership
Section titled “23 — Security Control 01: Scope Ownership”Cloud accounts should have clear ownership.
Create:
def check_scope_owner( data): provider = data[ "provider" ]
scope = get_scope_name( data )
owner = str( data.get( "owner", "" ) ).strip()
if owner: return create_finding( provider, scope, "cloud-scope", scope, "scope-owner", "pass", "informational", "Cloud scope has documented owner", f"owner={owner}", "Maintain current ownership information." )
return create_finding( provider, scope, "cloud-scope", scope, "scope-owner", "fail", "high", "Cloud scope has no documented owner", "owner is empty", "Assign an accountable business or technical owner." )24 — Why Ownership Matters
Section titled “24 — Why Ownership Matters”Without clear ownership:
WHO APPROVES ACCESS?
WHO RESPONDS TO INCIDENTS?
WHO REVIEWS FINDINGS?
WHO ACCEPTS RISK?
WHO OWNS REMEDIATION?25 — Security Control 02: Privileged MFA
Section titled “25 — Security Control 02: Privileged MFA”Create:
def check_privileged_mfa( data): findings = []
provider = data[ "provider" ]
scope = get_scope_name( data )
for identity in data.get( "identities", [] ):
if not identity.get( "privileged" ): continue
mfa = identity.get( "mfa_enabled" )
if mfa is True: status = "pass" severity = "informational"
title = ( "Privileged identity uses MFA" )
recommendation = ( "Maintain strong authentication " "and periodically review privilege." )
else: status = "fail" severity = "high"
title = ( "Privileged identity does not use MFA" )
recommendation = ( "Review the identity and enforce " "approved strong authentication controls " "where applicable." )
findings.append( create_finding( provider, scope, "identity", identity.get( "name", "unknown" ), "privileged-mfa", status, severity, title, f"mfa_enabled={mfa}", recommendation ) )
return findings26 — Important Identity Context
Section titled “26 — Important Identity Context”MFA generally applies to:
HUMAN IDENTITIESIt may not apply directly to:
WORKLOAD IDENTITIES
SERVICE ACCOUNTS
MANAGED IDENTITIES
IAM ROLESThose should instead use controls such as:
SHORT-LIVED CREDENTIALS
WORKLOAD IDENTITY
LEAST PRIVILEGE
KEY ROTATION
NO STATIC SECRETS27 — Security Control 03: AWS Access Key Age
Section titled “27 — Security Control 03: AWS Access Key Age”For the synthetic AWS data, evaluate long-lived human access keys.
Create:
def check_aws_access_key_age( data): findings = []
if data[ "provider" ] != "aws": return findings
scope = get_scope_name( data )
for identity in data.get( "identities", [] ):
age = identity.get( "access_key_age_days" )
if age is None: continue
if age > 90: status = "fail" severity = "medium"
title = ( "Long-lived access key requires review" )
recommendation = ( "Review whether the access key is still " "required and follow the approved credential " "rotation or workload-identity standard." )
else: status = "pass" severity = "informational"
title = ( "Access key age is within lab threshold" )
recommendation = ( "Continue credential lifecycle monitoring." )
findings.append( create_finding( "aws", scope, "identity", identity[ "name" ], "access-key-age", status, severity, title, f"age_days={age}", recommendation ) )
return findings28 — Threshold Warning
Section titled “28 — Threshold Warning”The:
90 DAYSvalue is a training threshold.
Use your organization’s:
IDENTITY STANDARD
CLOUD POLICY
REGULATORY REQUIREMENTSfor production decisions.
29 — Security Control 04: AWS CloudTrail
Section titled “29 — Security Control 04: AWS CloudTrail”Create:
def check_aws_logging( data): findings = []
if data[ "provider" ] != "aws": return findings
scope = get_scope_name( data )
logging_config = data.get( "logging", {} )
checks = [ ( "cloudtrail_enabled", "cloudtrail-enabled", "AWS CloudTrail is enabled" ), ( "multi_region", "cloudtrail-multi-region", "CloudTrail multi-region coverage is enabled" ), ( "log_validation", "cloudtrail-log-validation", "CloudTrail log validation is enabled" ) ]
for field, control, title in checks: value = logging_config.get( field )
findings.append( create_finding( "aws", scope, "logging", "CloudTrail", control, ( "pass" if value else "fail" ), ( "informational" if value else "high" ), title, f"{field}={value}", ( "Maintain current configuration." if value else "Enable and validate required audit logging." ) ) )
return findings30 — Security Control 05: Azure Logging
Section titled “30 — Security Control 05: Azure Logging”Create:
def check_azure_logging( data): findings = []
if data[ "provider" ] != "azure": return findings
scope = get_scope_name( data )
config = data.get( "logging", {} )
activity = config.get( "activity_log_enabled" )
findings.append( create_finding( "azure", scope, "logging", "Activity Log", "activity-log-enabled", ( "pass" if activity else "fail" ), ( "informational" if activity else "high" ), "Azure Activity Log configuration", f"enabled={activity}", ( "Maintain current logging." if activity else "Enable required activity logging." ) ) )
diagnostics = config.get( "diagnostic_settings_enabled" )
findings.append( create_finding( "azure", scope, "logging", "Diagnostic Settings", "diagnostic-settings", ( "pass" if diagnostics else "fail" ), ( "informational" if diagnostics else "medium" ), "Azure diagnostic settings configuration", f"enabled={diagnostics}", ( "Maintain required diagnostic routing." if diagnostics else "Review diagnostic settings and route " "required logs to approved destinations." ) ) )
return findings31 — Security Control 06: Google Cloud Logging
Section titled “31 — Security Control 06: Google Cloud Logging”Create:
def check_gcp_logging( data): findings = []
if data[ "provider" ] != "gcp": return findings
scope = get_scope_name( data )
config = data.get( "logging", {} )
audit = config.get( "audit_logs_enabled" )
findings.append( create_finding( "gcp", scope, "logging", "Cloud Audit Logs", "audit-logs-enabled", ( "pass" if audit else "fail" ), ( "informational" if audit else "high" ), "Google Cloud audit logging configuration", f"enabled={audit}", ( "Maintain audit logging." if audit else "Enable required Cloud Audit Logs." ) ) )
data_access = config.get( "data_access_logs_enabled" )
findings.append( create_finding( "gcp", scope, "logging", "Data Access Logs", "data-access-logging", ( "pass" if data_access else "fail" ), ( "informational" if data_access else "medium" ), "Google Cloud Data Access logging configuration", f"enabled={data_access}", ( "Maintain required logging." if data_access else "Review whether Data Access logs are " "required for the project and data sensitivity." ) ) )
return findings32 — Why Logging Matters
Section titled “32 — Why Logging Matters”Cloud audit logs help answer:
WHO?
DID WHAT?
TO WHICH RESOURCE?
WHEN?
FROM WHERE?Without adequate logs:
INVESTIGATIONbecomes significantly harder.
33 — Security Control 07: Public Storage
Section titled “33 — Security Control 07: Public Storage”Create:
def check_public_storage( data): findings = []
provider = data[ "provider" ]
scope = get_scope_name( data )
for storage in data.get( "storage", [] ):
public = storage.get( "public" )
if public: status = "fail"
severity = ( "high" if storage.get( "criticality" ) in { "critical", "high" } else "medium" )
title = ( "Storage resource is publicly accessible" )
recommendation = ( "Validate whether public access is " "business-required. Remove unnecessary " "public exposure through approved change control." )
else: status = "pass" severity = "informational"
title = ( "Storage resource is not publicly accessible" )
recommendation = ( "Maintain least-exposure configuration." )
findings.append( create_finding( provider, scope, "storage", storage[ "name" ], "storage-public-access", status, severity, title, f"public={public}", recommendation ) )
return findings34 — Public Does Not Automatically Mean Wrong
Section titled “34 — Public Does Not Automatically Mean Wrong”Some resources intentionally host:
PUBLIC WEBSITE CONTENT
DOCUMENTATION
SOFTWARE DOWNLOADSTherefore:
PUBLIC ACCESSmeans:
REVIEWnot automatically:
SECURITY INCIDENT35 — Security Control 08: Storage Encryption
Section titled “35 — Security Control 08: Storage Encryption”Create:
def check_storage_encryption( data): findings = []
provider = data[ "provider" ]
scope = get_scope_name( data )
for storage in data.get( "storage", [] ):
encrypted = storage.get( "encrypted" )
findings.append( create_finding( provider, scope, "storage", storage[ "name" ], "storage-encryption", ( "pass" if encrypted else "fail" ), ( "informational" if encrypted else "high" ), ( "Storage encryption enabled" if encrypted else "Storage encryption is not enabled" ), f"encrypted={encrypted}", ( "Maintain encryption controls." if encrypted else "Enable approved encryption for stored data." ) ) )
return findings36 — Encryption Context
Section titled “36 — Encryption Context”Cloud encryption review should eventually include:
PROVIDER-MANAGED KEYS
CUSTOMER-MANAGED KEYS
KEY ROTATION
KEY ACCESS
KEY OWNERSHIP
BACKUP ENCRYPTION
DATA CLASSIFICATIONFor this lab, use only:
ENCRYPTED = TRUE / FALSE37 — Security Control 09: Storage Ownership
Section titled “37 — Security Control 09: Storage Ownership”Create:
def check_storage_owner( data): findings = []
provider = data[ "provider" ]
scope = get_scope_name( data )
for storage in data.get( "storage", [] ):
owner = str( storage.get( "owner", "" ) ).strip()
findings.append( create_finding( provider, scope, "storage", storage[ "name" ], "storage-owner", ( "pass" if owner else "fail" ), ( "informational" if owner else "medium" ), ( "Storage resource has documented owner" if owner else "Storage resource has no documented owner" ), ( f"owner={owner}" if owner else "owner is empty" ), ( "Maintain ownership information." if owner else "Assign an accountable owner." ) ) )
return findings38 — Security Control 10: Internet Management Ports
Section titled “38 — Security Control 10: Internet Management Ports”Management services such as:
SSH
RDPshould not generally be broadly exposed without a justified and secured architecture.
Define:
MANAGEMENT_PORTS = { 22: "SSH", 3389: "RDP"}39 — Broad Internet Sources
Section titled “39 — Broad Internet Sources”Create:
BROAD_SOURCES = { "0.0.0.0/0", "::/0", "internet", "Internet"}40 — Check Management Exposure
Section titled “40 — Check Management Exposure”Create:
def check_management_exposure( data): findings = []
provider = data[ "provider" ]
scope = get_scope_name( data )
for rule in data.get( "network_rules", [] ):
port = rule.get( "port" )
source = rule.get( "source" )
if ( port not in MANAGEMENT_PORTS ): continue
exposed = ( source in BROAD_SOURCES )
service = ( MANAGEMENT_PORTS[ port ] )
findings.append( create_finding( provider, scope, "network-rule", rule[ "resource" ], "management-port-exposure", ( "fail" if exposed else "pass" ), ( "high" if exposed else "informational" ), ( f"{service} management port is broadly exposed" if exposed else f"{service} management port is restricted" ), ( f"port={port}; " f"source={source}" ), ( "Restrict administrative access through " "approved management paths, trusted sources, " "or identity-aware access controls." if exposed else "Maintain restricted administration paths." ) ) )
return findings41 — Why Port 443 Is Different
Section titled “41 — Why Port 443 Is Different”Your synthetic cloud data also contains:
443 FROM INTERNETfor public web services.
That may be fully expected.
Therefore do not blindly treat:
0.0.0.0/0as a vulnerability.
Interpret:
SOURCE+PORT+RESOURCE PURPOSE42 — Security Control 11: AWS Security Services
Section titled “42 — Security Control 11: AWS Security Services”Create:
def check_aws_security_services( data): findings = []
if data[ "provider" ] != "aws": return findings
scope = get_scope_name( data )
services = data.get( "security_services", {} )
checks = [ ( "security_hub_enabled", "Security Hub", "security-hub-enabled" ), ( "guardduty_enabled", "GuardDuty", "guardduty-enabled" ) ]
for field, resource, control in checks: enabled = services.get( field )
findings.append( create_finding( "aws", scope, "security-service", resource, control, ( "pass" if enabled else "fail" ), ( "informational" if enabled else "medium" ), ( f"{resource} is enabled" if enabled else f"{resource} is not enabled" ), f"enabled={enabled}", ( "Maintain current security monitoring." if enabled else "Review whether the service is required " "by the approved AWS security baseline." ) ) )
return findings43 — Security Control 12: Azure Security Services
Section titled “43 — Security Control 12: Azure Security Services”Create:
def check_azure_security_services( data): findings = []
if data[ "provider" ] != "azure": return findings
scope = get_scope_name( data )
services = data.get( "security_services", {} )
checks = [ ( "defender_for_cloud_enabled", "Defender for Cloud", "defender-for-cloud" ), ( "sentinel_connected", "Microsoft Sentinel", "sentinel-connected" ) ]
for field, resource, control in checks: enabled = services.get( field )
findings.append( create_finding( "azure", scope, "security-service", resource, control, ( "pass" if enabled else "fail" ), ( "informational" if enabled else "medium" ), ( f"{resource} is enabled" if enabled else f"{resource} requires review" ), f"enabled={enabled}", ( "Maintain current configuration." if enabled else "Review against the approved Azure " "security monitoring baseline." ) ) )
return findings44 — Security Control 13: Google Cloud Security Services
Section titled “44 — Security Control 13: Google Cloud Security Services”Create:
def check_gcp_security_services( data): findings = []
if data[ "provider" ] != "gcp": return findings
scope = get_scope_name( data )
services = data.get( "security_services", {} )
enabled = services.get( "security_command_center_enabled" )
findings.append( create_finding( "gcp", scope, "security-service", "Security Command Center", "security-command-center", ( "pass" if enabled else "fail" ), ( "informational" if enabled else "medium" ), ( "Security Command Center is enabled" if enabled else "Security Command Center requires review" ), f"enabled={enabled}", ( "Maintain current security posture monitoring." if enabled else "Review against the approved Google Cloud " "security baseline." ) ) )
return findings45 — Build Provider Assessment
Section titled “45 — Build Provider Assessment”Create:
def assess_cloud_scope( data): findings = []
if not validate_provider( data ): return findings
findings.append( check_scope_owner( data ) )
findings.extend( check_privileged_mfa( data ) )
findings.extend( check_public_storage( data ) )
findings.extend( check_storage_encryption( data ) )
findings.extend( check_storage_owner( data ) )
findings.extend( check_management_exposure( data ) )
provider = data[ "provider" ]
if provider == "aws": findings.extend( check_aws_access_key_age( data ) )
findings.extend( check_aws_logging( data ) )
findings.extend( check_aws_security_services( data ) )
elif provider == "azure": findings.extend( check_azure_logging( data ) )
findings.extend( check_azure_security_services( data ) )
elif provider == "gcp": findings.extend( check_gcp_logging( data ) )
findings.extend( check_gcp_security_services( data ) )
return findings46 — Assess All Cloud Environments
Section titled “46 — Assess All Cloud Environments”Create:
def run_assessment(): all_findings = []
for cloud_data in load_cloud_data(): provider = cloud_data.get( "provider", "unknown" )
logging.info( "Assessing provider: %s", provider )
all_findings.extend( assess_cloud_scope( cloud_data ) )
return all_findings47 — First Complete Run
Section titled “47 — First Complete Run”Temporarily:
findings = run_assessment()
print( json.dumps( findings, indent=2 ))Run:
python src/cloud_security_auditor.py48 — Review Findings
Section titled “48 — Review Findings”You should identify findings around:
AWS LEGACY ADMIN MFA
AWS OLD ACCESS KEY
AWS PUBLIC STORAGE
AWS UNENCRYPTED STORAGE
AWS SSH INTERNET EXPOSURE
AZURE PRIVILEGED MFA
AZURE DIAGNOSTIC SETTINGS
AZURE PUBLIC STORAGE
AZURE RDP INTERNET EXPOSURE
GCP PROJECT OWNER
GCP PRIVILEGED MFA
GCP DATA ACCESS LOGGING
GCP PUBLIC STORAGE
GCP SSH INTERNET EXPOSURE49 — Filter Failed Findings
Section titled “49 — Filter Failed Findings”Create:
def get_failed_findings( findings): return [ item for item in findings if item[ "status" ] == "fail" ]50 — Count by Provider
Section titled “50 — Count by Provider”Create:
def count_failures_by_provider( findings): return Counter( item[ "provider" ] for item in get_failed_findings( findings ) )51 — Count by Severity
Section titled “51 — Count by Severity”Create:
def count_failures_by_severity( findings): return Counter( item[ "severity" ] for item in get_failed_findings( findings ) )52 — Count by Control Family
Section titled “52 — Count by Control Family”Use:
resource_typeCreate:
def count_failures_by_type( findings): return Counter( item[ "resource_type" ] for item in get_failed_findings( findings ) )53 — Priority Weights
Section titled “53 — Priority Weights”Create:
SEVERITY_SCORE = { "critical": 100, "high": 75, "medium": 50, "low": 25, "informational": 0}54 — Add Priority Score
Section titled “54 — Add Priority Score”Create:
def add_priority_scores( findings): results = []
for finding in findings: item = finding.copy()
item[ "priority_score" ] = ( SEVERITY_SCORE.get( item[ "severity" ], 0 ) if item[ "status" ] == "fail" else 0 )
results.append( item )
return results55 — Priority Limitation
Section titled “55 — Priority Limitation”This is intentionally simple.
Production prioritization should consider:
RESOURCE CRITICALITY
DATA CLASSIFICATION
INTERNET EXPOSURE
BUSINESS SERVICE
KNOWN ATTACK PATHS
THREAT ACTIVITY
COMPENSATING CONTROLS56 — Build Summary
Section titled “56 — Build Summary”Create:
def build_summary( findings): failures = get_failed_findings( findings )
return { "total_checks": len( findings ),
"passed_checks": len([ item for item in findings if item[ "status" ] == "pass" ]),
"failed_checks": len( failures ),
"failures_by_provider": dict( count_failures_by_provider( findings ) ),
"failures_by_severity": dict( count_failures_by_severity( findings ) ),
"failures_by_resource_type": dict( count_failures_by_type( findings ) ) }57 — Export All Findings CSV
Section titled “57 — Export All Findings CSV”Create:
def export_findings_csv( findings): output = ( REPORT_DIR / "cloud-security-findings.csv" )
fields = [ "provider", "scope", "resource_type", "resource", "control", "status", "severity", "priority_score", "title", "evidence", "recommendation" ]
with output.open( "w", encoding="utf-8", newline="" ) as file:
writer = csv.DictWriter( file, fieldnames=fields )
writer.writeheader()
writer.writerows( findings )58 — Export Failed Findings Only
Section titled “58 — Export Failed Findings Only”Create:
def export_failed_findings( findings): output = ( REPORT_DIR / "cloud-security-review-queue.csv" )
failures = sorted( get_failed_findings( findings ), key=lambda item: item[ "priority_score" ], reverse=True )
fields = [ "provider", "scope", "resource_type", "resource", "control", "severity", "priority_score", "title", "evidence", "recommendation" ]
with output.open( "w", encoding="utf-8", newline="" ) as file:
writer = csv.DictWriter( file, fieldnames=fields, extrasaction="ignore" )
writer.writeheader()
writer.writerows( failures )59 — Export JSON
Section titled “59 — Export JSON”Create:
def export_json( findings, summary): output = ( REPORT_DIR / "cloud-security-assessment.json" )
with output.open( "w", encoding="utf-8" ) as file:
json.dump( { "summary": summary,
"findings": findings }, file, indent=2 )60 — Generate Markdown Report
Section titled “60 — Generate Markdown Report”Create:
def generate_markdown_report( findings, summary): failures = sorted( get_failed_findings( findings ), key=lambda item: item[ "priority_score" ], reverse=True )
lines = []
lines.append( "# Cloud Security Configuration Assessment" )
lines.append("")
lines.append( "## Executive Summary" )
lines.append("")
lines.append( f"- Total checks: " f"{summary['total_checks']}" )
lines.append( f"- Passed checks: " f"{summary['passed_checks']}" )
lines.append( f"- Failed checks: " f"{summary['failed_checks']}" )
lines.append("")
lines.append( "## Findings Requiring Review" )
lines.append("")
for item in failures: lines.append( f"### {item['severity'].upper()} — " f"{item['title']}" )
lines.append("")
lines.append( f"- Provider: {item['provider']}" )
lines.append( f"- Scope: {item['scope']}" )
lines.append( f"- Resource: {item['resource']}" )
lines.append( f"- Control: {item['control']}" )
lines.append( f"- Evidence: {item['evidence']}" )
lines.append( f"- Recommendation: " f"{item['recommendation']}" )
lines.append("")
lines.append( "## Assessment Guidance" )
lines.append("")
lines.append( "This lab identifies configuration states " "that require review. A failed check does not " "automatically prove compromise or business risk. " "Validate resource purpose, approved exceptions, " "data classification, exposure, compensating controls, " "and organizational cloud-security standards before " "making remediation decisions." )
output = ( REPORT_DIR / "cloud-security-assessment.md" )
output.write_text( "\n".join( lines ), encoding="utf-8" )61 — Build main()
Section titled “61 — Build main()”Create:
def main(): logging.info( "Cloud security assessment started" )
findings = run_assessment()
findings = add_priority_scores( findings )
summary = build_summary( findings )
export_findings_csv( findings )
export_failed_findings( findings )
export_json( findings, summary )
generate_markdown_report( findings, summary )
logging.info( "Cloud security assessment completed" )
print( f"Reports saved to: " f"{REPORT_DIR}" )62 — Add Entry Point
Section titled “62 — Add Entry Point”Add:
if __name__ == "__main__": main()63 — Run the Complete Auditor
Section titled “63 — Run the Complete Auditor”Run:
python src/cloud_security_auditor.pyExpected:
Cloud security assessment started
Assessing provider: aws
Assessing provider: azure
Assessing provider: gcp
Cloud security assessment completed64 — Review Report Files
Section titled “64 — Review Report Files”Expected:
reports/|+-- cloud-security-findings.csv|+-- cloud-security-review-queue.csv|+-- cloud-security-assessment.json|+-- cloud-security-assessment.md65 — Review the Review Queue
Section titled “65 — Review the Review Queue”The review queue should prioritize failed checks such as:
PRIVILEGED USER WITHOUT MFA
MANAGEMENT PORT INTERNET EXPOSURE
UNENCRYPTED STORAGE
PUBLIC HIGH-CRITICALITY STORAGE
MISSING CLOUD OWNER66 — Build Provider Summary
Section titled “66 — Build Provider Summary”Example:
AWSPassed: XFailed: X
AZUREPassed: XFailed: X
GCPPassed: XFailed: XCreate:
def provider_summary( findings): summary = {}
for provider in ( "aws", "azure", "gcp" ):
provider_findings = [ item for item in findings if item[ "provider" ] == provider ]
summary[ provider ] = { "total": len( provider_findings ),
"passed": len([ item for item in provider_findings if item[ "status" ] == "pass" ]),
"failed": len([ item for item in provider_findings if item[ "status" ] == "fail" ]) }
return summary67 — Compliance Percentage
Section titled “67 — Compliance Percentage”Create:
def calculate_pass_rate( findings): if not findings: return 0.0
passed = len([ item for item in findings if item[ "status" ] == "pass" ])
return round( 100.0 * passed / len(findings), 2 )68 — Be Careful With Compliance Percentages
Section titled “68 — Be Careful With Compliance Percentages”A result such as:
90% PASSdoes not automatically mean:
LOW RISKThe remaining 10% could contain:
PUBLIC ADMINISTRATIVE ACCESS
NO AUDIT LOGGING
CRITICAL PUBLIC STORAGEAlways review the actual findings.
69 — Baseline vs Finding
Section titled “69 — Baseline vs Finding”Your code currently says:
IF CONDITIONTHEN FINDINGA more mature architecture uses:
POLICY BASELINE ↓EXPECTED STATE ↓CURRENT STATE ↓COMPARE ↓FINDING70 — Create a Baseline File
Section titled “70 — Create a Baseline File”Create:
data/baseline.jsonAdd:
{ "privileged_mfa_required": true, "public_management_ports_allowed": false, "storage_encryption_required": true, "resource_owner_required": true, "cloud_audit_logging_required": true}71 — Why Externalize Policy?
Section titled “71 — Why Externalize Policy?”Without a baseline:
SECURITY POLICYIS HARD-CODEDINSIDE PYTHONWith a baseline:
POLICY ↓CONFIGURATION ↓AUDITORThis is easier to:
REVIEW
VERSION
APPROVE
CHANGE72 — Policy-as-Code Mental Model
Section titled “72 — Policy-as-Code Mental Model”SECURITY REQUIREMENT ↓MACHINE-READABLE POLICY ↓AUTOMATED CHECK ↓EVIDENCE ↓FINDING73 — Add Control IDs
Section titled “73 — Add Control IDs”A professional finding should have identifiers such as:
IAM-001
LOG-001
STO-001
NET-001Example:
"control_id": "IAM-001"74 — Why Control IDs Matter
Section titled “74 — Why Control IDs Matter”They enable:
TRACKING
MAPPING
REPORTING
EXCEPTIONS
RETESTING75 — Add Framework Mapping
Section titled “75 — Add Framework Mapping”Future findings may include:
{ "control_id": "LOG-001", "frameworks": { "CIS": ["example-control"], "NIST": ["AU"], "ISO27001": ["logging-related-control"] }}Do not invent certification compliance simply because a technical check passes.
76 — Technical Check vs Compliance
Section titled “76 — Technical Check vs Compliance”Remember:
TECHNICAL CONTROL CHECK≠FULL COMPLIANCECompliance may require:
POLICY
PROCESS
EVIDENCE
APPROVAL
OWNERSHIP
MONITORING
REVIEW
DOCUMENTATION77 — Add Exception Handling
Section titled “77 — Add Exception Handling”Real enterprises need exceptions.
Example:
{ "control": "storage-public-access", "resource": "public-training-content", "approved": true, "reason": "Public training content", "expires": "2026-12-31"}78 — Exception Workflow
Section titled “78 — Exception Workflow”FINDING ↓BUSINESS REQUIREMENT? ↓EXCEPTION REQUEST ↓RISK REVIEW ↓APPROVAL ↓EXPIRY ↓PERIODIC REVIEW79 — Never Make Exceptions Permanent by Default
Section titled “79 — Never Make Exceptions Permanent by Default”Exceptions should have:
OWNER
APPROVER
JUSTIFICATION
EXPIRY DATE
COMPENSATING CONTROLS80 — Add Resource Criticality to Findings
Section titled “80 — Add Resource Criticality to Findings”Storage already includes:
criticalityFuture finding schema could include:
resource_criticalityThen your prioritization can distinguish:
PUBLIC LOW-RISK CONTENTfrom:
PUBLIC CRITICAL DATABASE BACKUP81 — Add Exposure Weight
Section titled “81 — Add Exposure Weight”Example training model:
HIGH SEVERITY+INTERNET EXPOSED=HIGHER REVIEW PRIORITY82 — Cloud Security Risk Model
Section titled “82 — Cloud Security Risk Model”A mature model can consider:
CONTROL FAILURE
RESOURCE CRITICALITY
PUBLIC EXPOSURE
DATA SENSITIVITY
PRIVILEGE
THREAT CONTEXT
BUSINESS SERVICE
EXCEPTION STATUS83 — Identity Analysis Challenge
Section titled “83 — Identity Analysis Challenge”Count privileged identities by provider.
Create:
def count_privileged_identities( cloud_data): return len([ identity for identity in cloud_data.get( "identities", [] ) if identity.get( "privileged" ) ])84 — Privileged Identity Ratio
Section titled “84 — Privileged Identity Ratio”A useful metric:
PRIVILEGED IDENTITIES/TOTAL HUMAN IDENTITIESA high ratio can indicate excessive privilege or poor role design.
Do not use the metric alone as proof of a security problem.
85 — Workload Identity Review
Section titled “85 — Workload Identity Review”Future checks could identify:
STATIC SERVICE ACCOUNT KEYS
LONG-LIVED ACCESS KEYS
OVER-PRIVILEGED ROLES
UNUSED SERVICE ACCOUNTS
CROSS-ACCOUNT TRUST86 — Least Privilege Mental Model
Section titled “86 — Least Privilege Mental Model”IDENTITY ↓REQUIRED JOB ↓REQUIRED ACTIONS ↓REQUIRED RESOURCES ↓MINIMUM PERMISSION87 — Network Security Expansion
Section titled “87 — Network Security Expansion”Future network checks could evaluate:
ANY-ANY RULES
ALL PORTS
MANAGEMENT PORTS
PUBLIC DATABASE PORTS
UNRESTRICTED EGRESS
UNUSED SECURITY GROUPS
NETWORK LOGGING88 — Public Database Port Review
Section titled “88 — Public Database Port Review”Examples that may require review when broadly exposed include:
1433
1521
3306
5432
27017Do not use the presence of a port alone to conclude a vulnerability.
Validate:
SERVICE
SOURCE RANGE
FIREWALL
AUTHENTICATION
BUSINESS PURPOSE89 — Logging Expansion
Section titled “89 — Logging Expansion”Future checks could evaluate:
LOG RETENTION
CENTRAL LOG ROUTING
IMMUTABLE STORAGE
LOG ENCRYPTION
ADMINISTRATIVE EVENT COVERAGE
DATA ACCESS EVENT COVERAGE90 — Encryption Expansion
Section titled “90 — Encryption Expansion”Future checks could review:
DISK ENCRYPTION
DATABASE ENCRYPTION
BACKUP ENCRYPTION
QUEUE ENCRYPTION
SECRET ENCRYPTION
CUSTOMER-MANAGED KEYS91 — Security Service Expansion
Section titled “91 — Security Service Expansion”Future AWS checks:
SECURITY HUB
GUARDDUTY
CONFIG
INSPECTOR
MACIEFuture Azure checks:
DEFENDER FOR CLOUD
SENTINEL
ENTRA IDENTITY PROTECTION
POLICYFuture Google Cloud checks:
SECURITY COMMAND CENTER
EVENT THREAT DETECTION
ORG POLICY
CLOUD ASSET INVENTORY92 — Test Invalid Provider
Section titled “92 — Test Invalid Provider”Create:
data/invalid.jsonwith:
{ "provider": "unknown-cloud"}Confirm:
NO ASSESSMENTis generated for unsupported providers.
93 — Test Missing Logging Section
Section titled “93 — Test Missing Logging Section”Remove:
loggingfrom a copy of one provider file.
Your code should:
FAIL THE RELEVANT CHECKSORMARK DATA UNAVAILABLErather than crash.
94 — Missing Data vs Failed Control
Section titled “94 — Missing Data vs Failed Control”This distinction matters.
logging_enabled = falsemeans:
CONTROL DISABLEDbut:
logging field missingmay mean:
COLLECTION FAILEDor:
DATA UNAVAILABLEThese are different.
95 — Add Unknown Status
Section titled “95 — Add Unknown Status”A mature finding model should support:
PASS
FAIL
UNKNOWN
NOT_APPLICABLE96 — Why UNKNOWN Matters
Section titled “96 — Why UNKNOWN Matters”Do not convert:
WE COULD NOT CHECK ITinto:
IT IS SECURE97 — Why NOT_APPLICABLE Matters
Section titled “97 — Why NOT_APPLICABLE Matters”Example:
MFA CHECKfor:
MANAGED IDENTITYmay be:
NOT APPLICABLErather than:
PASSor:
FAIL98 — Update Finding Schema
Section titled “98 — Update Finding Schema”Future schema:
{ "status": "pass|fail|unknown|not_applicable"}This makes the assessment more accurate.
99 — Add Evidence Timestamp
Section titled “99 — Add Evidence Timestamp”A cloud finding should eventually record:
COLLECTION TIMECreate:
from datetime import datetime, timezoneThen:
COLLECTION_TIME = ( datetime.now( timezone.utc ).isoformat())100 — Why Evidence Time Matters
Section titled “100 — Why Evidence Time Matters”Cloud configuration can change quickly.
A finding from:
MONDAYmay no longer exist:
FRIDAYAlways know:
WHEN WAS THIS STATE OBSERVED?101 — Add Evidence Source
Section titled “101 — Add Evidence Source”Future finding schema should include:
sourceExamples:
aws-api
azure-api
gcp-api
configuration-export102 — Raw Evidence Preservation
Section titled “102 — Raw Evidence Preservation”Consider preserving:
RAW API RESPONSEalong with:
NORMALIZED FINDINGfor troubleshooting and audit purposes.
103 — Evidence Integrity
Section titled “103 — Evidence Integrity”For important assessment artifacts, create:
SHA-256 HASHto support integrity checking.
Example:
import hashlib104 — Hash Report File
Section titled “104 — Hash Report File”Create:
def sha256_file( path): digest = hashlib.sha256()
with path.open( "rb" ) as file:
for block in iter( lambda: file.read( 8192 ), b"" ): digest.update( block )
return digest.hexdigest()105 — Cloud API Integration Architecture
Section titled “105 — Cloud API Integration Architecture”Your synthetic JSON files represent what would eventually come from:
AWS APIs
AZURE RESOURCE MANAGER / MICROSOFT GRAPH
GOOGLE CLOUD APIsArchitecture:
CLOUD API ↓COLLECTOR ↓NORMALIZER ↓CHECK ENGINE ↓FINDINGS106 — Read-Only Cloud Credentials
Section titled “106 — Read-Only Cloud Credentials”When moving to real cloud APIs, create credentials with:
READ-ONLY SECURITY INVENTORYpermissions where possible.
Do not give a posture-auditing script:
ADMINISTRATORsimply because it is convenient.
107 — AWS Future Integration
Section titled “107 — AWS Future Integration”Conceptually:
AWS API ↓IAM
CLOUDTRAIL
S3
EC2
SECURITY HUB
GUARDDUTYThe collector should:
DESCRIBE
LIST
GETrather than:
DELETE
MODIFY
STOP108 — Azure Future Integration
Section titled “108 — Azure Future Integration”Conceptually:
AZURE APIs ↓SUBSCRIPTIONS
RBAC
STORAGE
NETWORK
ACTIVITY LOG
DEFENDER FOR CLOUDUse:
READERor another appropriately scoped read role rather than broad contributor permissions.
109 — Google Cloud Future Integration
Section titled “109 — Google Cloud Future Integration”Conceptually:
GOOGLE CLOUD APIs ↓IAM POLICY
LOGGING
CLOUD STORAGE
VPC FIREWALL
SECURITY COMMAND CENTERUse minimum viewer-style roles required for the assessment.
110 — Avoid Secret Keys When Possible
Section titled “110 — Avoid Secret Keys When Possible”Prefer:
WORKLOAD IDENTITY
MANAGED IDENTITY
SHORT-LIVED CREDENTIALS
FEDERATED ACCESSover long-lived:
STATIC CLOUD ACCESS KEYS111 — Multi-Account Architecture
Section titled “111 — Multi-Account Architecture”A mature enterprise auditor may handle:
100 AWS ACCOUNTS
50 AZURE SUBSCRIPTIONS
80 GCP PROJECTSArchitecture:
ORGANIZATION INVENTORY ↓ACCOUNT / SUBSCRIPTION / PROJECT LIST ↓READ-ONLY COLLECTION ↓NORMALIZATION ↓CENTRAL FINDINGS112 — Rate Limiting
Section titled “112 — Rate Limiting”Cloud APIs have:
RATE LIMITS
THROTTLING
PAGINATIONReuse lessons from:
Lab 07 — Security API Integration113 — Retry Strategy
Section titled “113 — Retry Strategy”Use retries only for:
TRANSIENT FAILURES
THROTTLING
SERVER ERRORSnot:
ACCESS DENIED
BAD REQUEST
INVALID CONFIGURATION114 — Access Denied Is Important Evidence
Section titled “114 — Access Denied Is Important Evidence”If an auditor receives:
403
ACCESS DENIEDdo not silently treat the resource as:
SECURERecord:
COLLECTION GAP115 — Coverage Metrics
Section titled “115 — Coverage Metrics”Track:
EXPECTED CLOUD SCOPES
SUCCESSFULLY ASSESSED
FAILED COLLECTIONS
PARTIAL COLLECTIONS116 — Coverage Example
Section titled “116 — Coverage Example”AWS Accounts Expected: 10
Assessed: 9
Failed: 1A:
90% assessment coveragemust not be represented as:
100% cloud security visibility117 — Security Drift
Section titled “117 — Security Drift”Run the auditor regularly and compare:
YESTERDAYwith:
TODAYIdentify:
NEW PUBLIC STORAGE
NEW ADMIN
LOGGING DISABLED
NEW INTERNET RULE
SECURITY SERVICE DISABLED118 — Drift Mental Model
Section titled “118 — Drift Mental Model”KNOWN STATE ↓TIME ↓NEW STATE ↓COMPARE ↓SECURITY DRIFT119 — Baseline Comparison
Section titled “119 — Baseline Comparison”You can store:
approved-baseline.jsonand compare each cloud scope against it.
Example:
EXPECTED:CloudTrail = Enabled
CURRENT:CloudTrail = Disabled
RESULT:FAIL120 — Configuration Change Detection
Section titled “120 — Configuration Change Detection”Your auditor could later identify:
CONTROL PASSED YESTERDAY
CONTROL FAILED TODAYThis should usually receive higher attention than a long-standing known exception.
121 — Add Finding Fingerprint
Section titled “121 — Add Finding Fingerprint”To track the same finding over time:
provider+scope+control+resourceCreate:
def finding_fingerprint( finding): return "|".join([ finding[ "provider" ], finding[ "scope" ], finding[ "control" ], finding[ "resource" ] ])122 — Why Fingerprints Matter
Section titled “122 — Why Fingerprints Matter”They help distinguish:
NEW FINDING
EXISTING FINDING
RESOLVED FINDING
REOPENED FINDING123 — State Lifecycle
Section titled “123 — State Lifecycle”A future CSPM-like workflow:
NEW ↓OPEN ↓ACKNOWLEDGED ↓REMEDIATION ↓RESOLVED ↓VERIFIED124 — Remediation Verification
Section titled “124 — Remediation Verification”Never assume:
TICKET CLOSED=CLOUD FINDING FIXEDInstead:
RE-RUN CHECK ↓CURRENT CONFIGURATION ↓PASS? ↓VERIFIED125 — Human Approval
Section titled “125 — Human Approval”For this lab:
AUDITOR ↓FINDING ↓ANALYSTFor future remediation:
FINDING ↓VALIDATE ↓OWNER ↓CHANGE APPROVAL ↓REMEDIATE ↓VERIFY126 — Do Not Build Automatic Destructive Remediation Yet
Section titled “126 — Do Not Build Automatic Destructive Remediation Yet”Avoid:
PUBLIC STORAGE FOUND ↓AUTOMATICALLY DELETE BUCKETor:
ADMIN PORT FOUND ↓DELETE FIREWALL RULEThese actions can cause:
OUTAGES
DATA LOSS
BROKEN APPLICATIONS
LOCKOUT127 — Safe Future Automation
Section titled “127 — Safe Future Automation”Lower-risk actions can include:
CREATE TICKET
SEND REVIEW NOTIFICATION
ADD REPORT TAG
REQUEST OWNER REVIEWrather than changing cloud configuration directly.
128 — Test Public Storage Check
Section titled “128 — Test Public Storage Check”Create a synthetic storage object:
{ "name": "test-bucket", "public": true, "encrypted": true, "criticality": "critical", "owner": "Security Team"}Expected:
PUBLIC ACCESS→ FAIL129 — Test Encryption Check
Section titled “129 — Test Encryption Check”Set:
encrypted = falseExpected:
STORAGE ENCRYPTION→ FAIL130 — Test Ownership Check
Section titled “130 — Test Ownership Check”Set:
owner = ""Expected:
OWNER CONTROL→ FAIL131 — Test MFA Check
Section titled “131 — Test MFA Check”Create:
privileged = true
mfa_enabled = falseExpected:
PRIVILEGED MFA→ FAIL132 — Test Non-Privileged Identity
Section titled “132 — Test Non-Privileged Identity”Create:
privileged = false
mfa_enabled = falseYour current control should not generate a privileged-MFA failure.
133 — Test Management Exposure
Section titled “133 — Test Management Exposure”Create:
port = 22
source = 0.0.0.0/0Expected:
HIGH REVIEW FINDING134 — Test Restricted SSH
Section titled “134 — Test Restricted SSH”Change:
source = 10.10.0.0/16Expected:
PASSunder the simple training check.
A production check should still validate whether the internal range is appropriate.
135 — Test Public Web
Section titled “135 — Test Public Web”Create:
port = 443
source = 0.0.0.0/0Your management-port check should:
NOT FLAG ITsimply because it is public.
136 — Test Missing Provider File
Section titled “136 — Test Missing Provider File”Rename:
gcp.jsonRun the script.
Expected:
ERROR LOGGED
AWS AND AZURE STILL ASSESSED137 — Test Invalid JSON
Section titled “137 — Test Invalid JSON”Break one JSON file intentionally.
Verify:
INVALID JSON LOGGEDand the remaining providers continue.
Then restore the file.
138 — Create Unit Test File
Section titled “138 — Create Unit Test File”Create:
tests/test_cloud_security_auditor.py139 — Test Provider Validation
Section titled “139 — Test Provider Validation”def test_provider_validation(): assert validate_provider( { "provider": "aws" } )140 — Test Invalid Provider
Section titled “140 — Test Invalid Provider”def test_invalid_provider(): assert not validate_provider( { "provider": "unknown" } )141 — Test Missing Owner
Section titled “141 — Test Missing Owner”def test_missing_owner(): data = { "provider": "gcp", "project_name": "training", "owner": "" }
finding = ( check_scope_owner( data ) )
assert ( finding[ "status" ] == "fail" )142 — Test Management Port
Section titled “142 — Test Management Port”Build a minimal cloud object with:
SSH
0.0.0.0/0and verify:
FAIL143 — Test Storage Encryption
Section titled “143 — Test Storage Encryption”Create:
encrypted = falseand verify:
FAIL144 — Test Public Storage
Section titled “144 — Test Public Storage”Create:
public = trueand confirm:
FAIL145 — Challenge 01 — Add Database Checks
Section titled “145 — Challenge 01 — Add Database Checks”Extend the cloud dataset with:
databasesEvaluate:
PUBLIC EXPOSURE
ENCRYPTION
BACKUP
OWNER146 — Challenge 02 — Add Compute Checks
Section titled “146 — Challenge 02 — Add Compute Checks”Add:
virtual_machinesor:
instancesReview:
PUBLIC IP
DISK ENCRYPTION
OWNER
CRITICALITY
SECURITY AGENT147 — Challenge 03 — Add Secret Management
Section titled “147 — Challenge 03 — Add Secret Management”Add:
secretsReview:
ROTATION
AGE
OWNER
ACCESS MODELDo not include actual secret values in your dataset or reports.
148 — Challenge 04 — Add Key Management
Section titled “148 — Challenge 04 — Add Key Management”Model:
KMS KEYS
KEY VAULT KEYS
CLOUD KMS KEYSReview:
ROTATION
ENABLED STATUS
OWNER
ACCESS149 — Challenge 05 — Add Database Public Exposure
Section titled “149 — Challenge 05 — Add Database Public Exposure”Create a check for synthetic database resources where:
public = trueand criticality is:
highor:
critical150 — Challenge 06 — Add Resource Tags
Section titled “150 — Challenge 06 — Add Resource Tags”Evaluate tags such as:
owner
environment
criticality
cost_center
data_classification151 — Challenge 07 — Add Environment Awareness
Section titled “151 — Challenge 07 — Add Environment Awareness”A security finding on:
Productionmay be prioritized differently from:
SandboxAdd:
environmentto the finding schema.
152 — Challenge 08 — Add Data Classification
Section titled “152 — Challenge 08 — Add Data Classification”Storage could contain:
Public
Internal
Confidential
RestrictedThen prioritize public exposure accordingly.
153 — Challenge 09 — Add Exceptions
Section titled “153 — Challenge 09 — Add Exceptions”Create:
data/exceptions.jsonand suppress or annotate only:
APPROVED
NON-EXPIREDexceptions.
154 — Challenge 10 — Add Baseline Version
Section titled “154 — Challenge 10 — Add Baseline Version”Add:
baseline_versionto every report.
Example:
Cloud Security Baseline v1.2155 — Challenge 11 — Add HTML Report
Section titled “155 — Challenge 11 — Add HTML Report”Generate:
cloud-security-assessment.htmlwith sections for:
EXECUTIVE SUMMARY
AWS
AZURE
GCP
HIGH FINDINGS
MEDIUM FINDINGS
PASSED CONTROLS156 — Challenge 12 — Add SQLite
Section titled “156 — Challenge 12 — Add SQLite”Store findings in:
cloud-security.dbThen run:
SQL ANALYTICSacross cloud providers.
Architecture:
AWSAZUREGCP ↓NORMALIZED FINDINGS ↓SQLITE ↓SECURITY ANALYTICS157 — Challenge 13 — Add API Collectors
Section titled “157 — Challenge 13 — Add API Collectors”Replace static files gradually.
Start:
STATIC JSONThen:
AUTHORIZED READ-ONLY CLOUD APIKeep the:
CHECK ENGINEunchanged.
158 — Collector Architecture
Section titled “158 — Collector Architecture”AWS COLLECTOR ↓NORMALIZED CLOUD MODEL
AZURE COLLECTOR ↓NORMALIZED CLOUD MODEL
GCP COLLECTOR ↓NORMALIZED CLOUD MODEL
↓ CHECK ENGINE159 — Separation of Concerns
Section titled “159 — Separation of Concerns”Keep:
COLLECTION
NORMALIZATION
ASSESSMENT
REPORTINGseparate.
Do not create one giant function that does everything.
160 — Future Project Structure
Section titled “160 — Future Project Structure”A mature version could look like:
cloud-security-auditor/|+-- collectors/| +-- aws.py| +-- azure.py| +-- gcp.py|+-- checks/| +-- identity.py| +-- logging.py| +-- network.py| +-- storage.py| +-- encryption.py|+-- reporting/| +-- csv_report.py| +-- json_report.py| +-- markdown_report.py|+-- policies/| +-- baseline.json|+-- src/| +-- main.py161 — Why Modular Architecture Matters
Section titled “161 — Why Modular Architecture Matters”As your auditor grows from:
10 CHECKSto:
500 CHECKSmaintainability becomes critical.
162 — Naming Security Checks
Section titled “162 — Naming Security Checks”Use clear names such as:
IAM-001 — Privileged MFA
LOG-001 — Audit Logging
STO-001 — Public Storage
ENC-001 — Storage Encryption
NET-001 — Public Management Port
GOV-001 — Resource Ownership163 — Finding Evidence
Section titled “163 — Finding Evidence”Every failed check should answer:
WHAT DID YOU OBSERVE?Example:
resource=legacy-admin
privileged=true
mfa_enabled=false164 — Finding Recommendation
Section titled “164 — Finding Recommendation”Recommendations should be:
ACTIONABLEbut not blindly destructive.
Prefer:
Review the administrative access path andrestrict broad management exposure throughthe approved cloud change process.instead of:
DELETE RULE NOW165 — Cloud Security Review Questions
Section titled “165 — Cloud Security Review Questions”When reviewing a finding, ask:
IS THE CONFIGURATION REAL?
IS THE RESOURCE STILL ACTIVE?
IS THERE AN APPROVED EXCEPTION?
WHAT BUSINESS SERVICE DEPENDS ON IT?
WHAT DATA DOES IT HANDLE?
IS IT INTERNET EXPOSED?
WHAT COMPENSATING CONTROLS EXIST?
WHO OWNS REMEDIATION?166 — Multi-Cloud Mental Model
Section titled “166 — Multi-Cloud Mental Model”Do not memorize only provider product names.
Think:
IDENTITY
LOGGING
NETWORK
STORAGE
ENCRYPTION
MONITORING
OWNERSHIPThen map each cloud provider to those control families.
167 — Cloud Shared Responsibility
Section titled “167 — Cloud Shared Responsibility”Your cloud provider secures:
THE CLOUD PLATFORMbut customers remain responsible for many areas including:
IDENTITY CONFIGURATION
RESOURCE PERMISSIONS
DATA ACCESS
NETWORK CONFIGURATION
WORKLOAD SECURITY
LOGGING CONFIGURATIONdepending on the service model.
168 — Cloud Misconfiguration Is Contextual
Section titled “168 — Cloud Misconfiguration Is Contextual”Examples:
PUBLIC OBJECT STORAGEmay be:
INTENTIONALfor a public website.
But:
PUBLIC OBJECT STORAGE+RESTRICTED CUSTOMER DATAwould be a much more serious scenario.
169 — Security Context Equation
Section titled “169 — Security Context Equation”CONFIGURATION +RESOURCE PURPOSE +DATA CLASSIFICATION +EXPOSURE +IDENTITY +BUSINESS CRITICALITY =SECURITY RISK CONTEXT170 — Common Cloud Auditor Mistakes
Section titled “170 — Common Cloud Auditor Mistakes”Avoid:
USING ADMIN CREDENTIALS FOR READ-ONLY AUDITS
HARDCODING CLOUD KEYS
NO PAGINATION
NO RATE-LIMIT HANDLING
TREATING MISSING DATA AS PASS
TREATING EVERY PUBLIC RESOURCE AS VULNERABLE
NO EXCEPTION PROCESS
NO RESOURCE OWNERSHIP
NO COLLECTION TIMESTAMP
NO BASELINE VERSION
NO EVIDENCE
AUTOMATICALLY CHANGING CLOUD RESOURCES
NO REMEDIATION VERIFICATION171 — Create README
Section titled “171 — Create README”Create:
README.mdInclude:
PROJECT OVERVIEW
SECURITY PURPOSE
AUTHORIZED USE
MULTI-CLOUD MODEL
SUPPORTED PROVIDERS
SUPPORTED CONTROLS
INPUT DATA
HOW TO RUN
OUTPUT REPORTS
PRIORITY MODEL
SECURITY CONSIDERATIONS
LIMITATIONS
FUTURE API INTEGRATION172 — README Security Statement
Section titled “172 — README Security Statement”Include:
This project performs defensive, read-onlycloud configuration assessment.
The baseline lab uses synthetic configurationdata and does not modify cloud resources.
Any future integration with real cloud APIsmust use explicitly authorized, least-privilege,read-only credentials wherever possible.173 — Document Current Controls
Section titled “173 — Document Current Controls”Your README should list:
GOV-001Cloud scope ownership
IAM-001Privileged MFA
IAM-002AWS access key age
LOG-001Audit logging
LOG-002Enhanced diagnostic / data access logging
STO-001Public storage
ENC-001Storage encryption
GOV-002Storage ownership
NET-001Broad management-port exposure
MON-001Cloud security service enablement174 — Document Limitations
Section titled “174 — Document Limitations”The lab does not provide:
COMPLETE CIS BENCHMARK COVERAGE
FULL CLOUD API INTEGRATION
FULL ORGANIZATION HIERARCHY
KUBERNETES SECURITY
CONTAINER SECURITY
SERVERLESS SECURITY
DATABASE CONFIGURATION ASSESSMENT
SECRET SCANNING
VULNERABILITY SCANNING
ATTACK PATH ANALYSIS
FULL COMPLIANCE CERTIFICATION175 — Portfolio Deliverables
Section titled “175 — Portfolio Deliverables”Your final project should contain:
AWS SYNTHETIC CONFIGURATION
AZURE SYNTHETIC CONFIGURATION
GCP SYNTHETIC CONFIGURATION
PYTHON AUDITOR
NORMALIZED FINDINGS
REVIEW QUEUE
JSON REPORT
MARKDOWN ASSESSMENT
UNIT TESTS
README
ARCHITECTURE DIAGRAM176 — Final Project Structure
Section titled “176 — Final Project Structure”cloud-security-auditor/|+-- data/| +-- aws.json| +-- azure.json| +-- gcp.json| +-- baseline.json|+-- src/| +-- cloud_security_auditor.py|+-- tests/| +-- test_cloud_security_auditor.py|+-- reports/| +-- cloud-security-findings.csv| +-- cloud-security-review-queue.csv| +-- cloud-security-assessment.json| +-- cloud-security-assessment.md|+-- README.md|+-- architecture.md177 — Mission Validation Checklist
Section titled “177 — Mission Validation Checklist”Confirm:
- Python environment prepared
- Lab directory created
- AWS dataset created
- Azure dataset created
- Google Cloud dataset created
- Multi-cloud model understood
- JSON loading implemented
- Invalid JSON handled
- Provider validation implemented
- Common finding schema created
- Scope ownership reviewed
- Privileged MFA reviewed
- AWS access-key age reviewed
- AWS CloudTrail reviewed
- Azure Activity Log reviewed
- Azure diagnostic settings reviewed
- Google Cloud Audit Logs reviewed
- Google Cloud Data Access logs reviewed
- Public storage reviewed
- Storage encryption reviewed
- Storage ownership reviewed
- Management-port exposure reviewed
- AWS Security Hub reviewed
- AWS GuardDuty reviewed
- Azure Defender for Cloud reviewed
- Microsoft Sentinel connection reviewed
- Google Security Command Center reviewed
- Findings normalized
- Failures counted by provider
- Failures counted by severity
- Priority scores added
- CSV report generated
- Review queue generated
- JSON report generated
- Markdown report generated
- Missing-data risks understood
- No cloud resources modified
- No real cloud credentials embedded
- Least-privilege design documented
- Limitations documented
Mission Review
Section titled “Mission Review”You started with:
AWS CONFIGURATION
AZURE CONFIGURATION
GOOGLE CLOUD CONFIGURATIONEach provider had different:
TERMINOLOGY
SERVICES
RESOURCE TYPESYou transformed them into:
MULTI-CLOUD DATA ↓NORMALIZATION ↓COMMON CONTROL MODEL ↓SECURITY CHECKS ↓COMMON FINDINGS ↓PRIORITIZED REVIEW ↓REPORTWhat You Built
Section titled “What You Built”You now have the foundation of a multi-cloud security posture assessment tool capable of reviewing:
CLOUD OWNERSHIP
PRIVILEGED IDENTITY
MFA
ACCESS-KEY AGE
AUDIT LOGGING
DIAGNOSTIC LOGGING
STORAGE EXPOSURE
STORAGE ENCRYPTION
NETWORK MANAGEMENT EXPOSURE
CLOUD SECURITY SERVICESacross:
AWS
MICROSOFT AZURE
GOOGLE CLOUDKey Security Lesson
Section titled “Key Security Lesson”The central lesson from this lab is:
CLOUD SECURITY AUTOMATIONSHOULD NORMALIZESECURITY CONCEPTS,NOT JUST PROVIDER PRODUCTSAWS may call a service:
CloudTrailAzure may call one:
Activity LogGoogle Cloud may call one:
Cloud Audit LogsBut the security question is:
DO WE HAVERELIABLE AUDIT LOGGING?Final Mental Model
Section titled “Final Mental Model”Whenever you assess cloud configuration, think:
WHAT CLOUD SCOPE IS THIS? ↓WHO OWNS IT? ↓WHO HAS PRIVILEGE? ↓HOW ARE IDENTITIES PROTECTED? ↓IS ACTIVITY LOGGED? ↓IS DATA ENCRYPTED? ↓WHAT IS PUBLIC? ↓WHAT MANAGEMENT ACCESS IS EXPOSED? ↓ARE SECURITY SERVICES ENABLED? ↓WHAT DATA IS MISSING? ↓WHAT REQUIRES HUMAN REVIEW?The goal is not:
CREATE HUNDREDSOF CLOUD FINDINGSThe goal is:
TURN CLOUD CONFIGURATIONINTO CONSISTENT,PRIORITIZED,ACTIONABLESECURITY CONTEXTWhat’s Next?
Section titled “What’s Next?”➡️ Lab 09 — SOC Alert Triage Automation
The next lab moves from cloud posture assessment into SOC security operations.
You will build:
RAW SOC ALERT ↓VALIDATE ↓NORMALIZE ↓IDENTITY CONTEXT ↓ASSET CONTEXT ↓IOC CONTEXT ↓SEVERITY ↓RISK SCORE ↓TRIAGE RECOMMENDATION ↓ANALYST REVIEWYou will combine skills from:
Lab 01Security Log Analysis
Lab 02IOC Processing
Lab 05SQL Security Analytics
Lab 06Risk Prioritization
Lab 07Security API Integration
Lab 08Cloud Security Configuration Auditingto build a controlled SOC alert-triage pipeline that helps analysts prioritize investigations without automatically taking disruptive response actions.