☁️ Cloud Security
Cloud Misconfiguration Reference
AWS, Azure, and GCP misconfiguration patterns — how attackers exploit them, how to detect them, and how to fix them. With copy-paste CLI commands.
CRITICALS3 Bucket Publicly AccessibleS3StorageData ExposureT1530▼
What it is
An S3 bucket has public access enabled — either via bucket ACL, bucket policy, or the account-level Public Access Block not configured. Any data in the bucket is readable (and sometimes writeable) by anyone on the internet.
How attackers exploit it
Attacker uses tools like S3Scanner, GrayhatWarfare, or Shodan S3 searches to enumerate buckets. Common bucket naming patterns (company-name-backup, company-logs, company-prod-data) are easily guessable. Data exfiltration is silent — no authentication, no logging required.
Detection
# Check if Public Access Block is enabled on account
aws s3control get-public-access-block --account-id $(aws sts get-caller-identity --query Account --output text)
# Check specific bucket public access settings
aws s3api get-bucket-acl --bucket BUCKET_NAME
aws s3api get-bucket-policy --bucket BUCKET_NAME
aws s3api get-public-access-block --bucket BUCKET_NAME
# List all buckets and their public access status (AWS CLI)
for bucket in $(aws s3 ls | awk '{print $3}'); do
echo -n "$bucket: "
aws s3api get-public-access-block --bucket $bucket 2>/dev/null | \
python3 -c "import sys,json; d=json.load(sys.stdin)['PublicAccessBlockConfiguration']; print('PROTECTED' if all(d.values()) else 'EXPOSED')" 2>/dev/null || echo "No block config"
done
Remediation
# Enable account-level Public Access Block (blocks all public access)
aws s3control put-public-access-block \
--account-id $(aws sts get-caller-identity --query Account --output text) \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# Enable on individual bucket
aws s3api put-public-access-block --bucket BUCKET_NAME \
--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true
# Enable S3 server access logging
aws s3api put-bucket-logging --bucket BUCKET_NAME \
--bucket-logging-status '{"LoggingEnabled":{"TargetBucket":"your-log-bucket","TargetPrefix":"s3-logs/"}}'
CRITICALIAM User with AdministratorAccess or *:* PolicyIAMPrivilegeLateral MovementT1078.004▼
What it is
An IAM user, role, or group is attached to a policy that grants wildcard (*) actions on wildcard (*) resources — effectively giving full AWS account control. Common in developer accounts that were never tightened.
How attackers exploit it
Attacker who compromises credentials (via phishing, code repo leak, SSRF, or supply chain) immediately has full account takeover — create new IAM users, exfiltrate all data, spin up crypto mining, delete all resources. No further privilege escalation needed.
Detection
# Find all IAM entities with AdministratorAccess
aws iam list-entities-for-policy \
--policy-arn arn:aws:iam::aws:policy/AdministratorAccess
# Find inline policies with wildcard permissions
aws iam get-account-authorization-details --filter User Role Group LocalManagedPolicy | \
python3 -c "
import sys,json
data=json.load(sys.stdin)
for user in data.get('UserDetailList',[]):
for pol in user.get('UserPolicyList',[]):
doc=pol['PolicyDocument']
for s in doc['Statement']:
if s.get('Effect')=='Allow' and s.get('Action')=='*':
print(f'WILDCARD: {user[\"UserName\"]} - {pol[\"PolicyName\"]}')
"
# AWS Access Analyser — detects overly permissive policies
aws accessanalyzer list-analyzers
aws accessanalyzer list-findings --analyzer-arn ARN
Remediation
# Apply least privilege — replace AdministratorAccess with specific permissions
# Use AWS IAM Access Advisor to see what permissions are actually used
aws iam generate-service-last-accessed-details --arn USER_ARN
# (wait ~30s)
aws iam get-service-last-accessed-details --job-id JOB_ID
# Enable IAM Access Analyser
aws accessanalyzer create-analyzer --analyzer-name account-analyser --type ACCOUNT
# Enforce MFA for console access
aws iam create-policy --policy-name EnforceMFA --policy-document file://enforce-mfa.json
# enforce-mfa.json: Deny all actions unless MFA present (aws.amazon.com/premiumsupport/knowledge-center/mfa-iam-user-cli/)
CRITICALIMDSv1 Enabled — SSRF to Credential TheftEC2SSRFCredential AccessT1552.005▼
What it is
EC2 Instance Metadata Service version 1 (IMDSv1) allows any HTTP request from the instance (including from web applications running on it) to retrieve instance credentials by hitting http://169.254.169.254/. A Server-Side Request Forgery (SSRF) vulnerability in a web app can be exploited to steal these credentials.
How attackers exploit it
Attacker finds an SSRF in a web application (common in Indian fintech apps). They redirect the server-side request to http://169.254.169.254/latest/meta-data/iam/security-credentials/. The instance role credentials are returned in plaintext. Attacker now has AWS credentials with whatever permissions the instance role has.
Detection
# Check if IMDSv2 is required (Token Required = IMDSv2 only)
aws ec2 describe-instances --query \
"Reservations[*].Instances[*].[InstanceId,MetadataOptions.HttpTokens,MetadataOptions.HttpEndpoint]" \
--output table
# Find instances still using IMDSv1 (optional)
aws ec2 describe-instances \
--filters "Name=metadata-options.http-tokens,Values=optional" \
--query "Reservations[*].Instances[*].InstanceId" \
--output text
# SSRF payload to demonstrate (test only on your own systems)
# curl "http://YOUR_APP_SSRF_ENDPOINT?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
Remediation
# Enforce IMDSv2 on existing instances
aws ec2 modify-instance-metadata-options \
--instance-id INSTANCE_ID \
--http-tokens required \
--http-put-response-hop-limit 1
# Enforce IMDSv2 for ALL new instances (account default)
aws ec2 modify-instance-metadata-defaults \
--http-tokens required
# If IMDSv1 not needed at all, disable metadata endpoint
aws ec2 modify-instance-metadata-options \
--instance-id INSTANCE_ID \
--http-endpoint disabled
HIGHSecurity Groups with 0.0.0.0/0 Inbound on SSH/RDPEC2NetworkInitial AccessT1190▼
What it is
An EC2 security group has inbound rules allowing SSH (port 22) or RDP (port 3389) from 0.0.0.0/0 — any IP address on the internet. Exposes instances to brute force, credential stuffing, and exploitation of SSH/RDP vulnerabilities.
How attackers exploit it
Automated scanners find exposed SSH/RDP within minutes of deployment. Attackers attempt default credentials, leaked credential databases, or CVE-based exploits. Once in, full host access. Common finding in Indian startup environments where developers opened SSH for convenience during development.
Detection
# Find security groups with SSH/RDP open to world
aws ec2 describe-security-groups \
--filters "Name=ip-permission.from-port,Values=22" \
"Name=ip-permission.cidr,Values=0.0.0.0/0" \
--query "SecurityGroups[*].[GroupId,GroupName]" --output table
# Same for RDP (3389)
aws ec2 describe-security-groups \
--filters "Name=ip-permission.from-port,Values=3389" \
"Name=ip-permission.cidr,Values=0.0.0.0/0" \
--query "SecurityGroups[*].[GroupId,GroupName]" --output table
# Find which instances use these security groups
aws ec2 describe-instances \
--filters "Name=instance.group-id,Values=sg-XXXXXXXX" \
--query "Reservations[*].Instances[*].[InstanceId,PublicIpAddress]" --output table
Remediation
# Remove world-open SSH rule
aws ec2 revoke-security-group-ingress \
--group-id sg-XXXXXXXX \
--protocol tcp --port 22 --cidr 0.0.0.0/0
# Add restricted rule (your office IP only)
aws ec2 authorize-security-group-ingress \
--group-id sg-XXXXXXXX \
--protocol tcp --port 22 --cidr YOUR_OFFICE_IP/32
# Better: use AWS Systems Manager Session Manager (no SSH port needed)
# Enable SSM agent on instance, remove all SSH security group rules entirely
HIGHCloudTrail Disabled or Missing in RegionsLoggingDetection GapCloudTrailT1562.008▼
What it is
AWS CloudTrail is not enabled in all regions, or has been disabled in one or more regions. CloudTrail logs all API calls — without it, attacker activity is undetectable and forensic investigation is impossible.
How attackers exploit it
Attacker who gains initial access first checks for and disables CloudTrail (StopLogging API call) or deletes the S3 log bucket. Without logs, all subsequent actions — data exfiltration, IAM changes, new resource creation — leave no audit trail.
Detection
# Check CloudTrail status in all regions
aws cloudtrail get-trail-status --name default
# List all trails
aws cloudtrail describe-trails --include-shadow-trails false
# Check if multi-region trail exists
aws cloudtrail describe-trails --query "trailList[?IsMultiRegionTrail==\`true\`].[Name,S3BucketName,IsLogging]" --output table
# GuardDuty finding: check for StopLogging events
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=StopLogging \
--start-time 2024-01-01
Remediation
# Enable multi-region CloudTrail
aws cloudtrail create-trail \
--name org-trail \
--s3-bucket-name YOUR_LOG_BUCKET \
--is-multi-region-trail \
--enable-log-file-validation \
--include-global-service-events
aws cloudtrail start-logging --name org-trail
# Protect CloudTrail logs — S3 Object Lock
aws s3api put-object-lock-configuration \
--bucket YOUR_LOG_BUCKET \
--object-lock-configuration '{"ObjectLockEnabled":"Enabled","Rule":{"DefaultRetention":{"Mode":"COMPLIANCE","Days":90}}}'
# Alert on StopLogging via CloudWatch alarm
# Create metric filter on CloudTrail → CloudWatch → SNS alert
HIGHRDS Publicly AccessibleRDSDatabaseData ExposureT1190▼
What it is
An RDS database instance has PubliclyAccessible=true, making it reachable from the internet. Combined with weak credentials or known vulnerabilities, this is a direct path to database compromise.
How attackers exploit it
Automated scanners identify public RDS endpoints. Attackers attempt default credentials (admin/admin, root/root, postgres/postgres) or exploit database-specific vulnerabilities. MySQL, PostgreSQL, and MSSQL are all exposed. Indian companies frequently expose dev/staging databases this way.
Detection
# Find publicly accessible RDS instances
aws rds describe-db-instances \
--query "DBInstances[?PubliclyAccessible==\`true\`].[DBInstanceIdentifier,Endpoint.Address,DBInstanceClass,Engine]" \
--output table
# Check security groups attached to RDS
aws rds describe-db-instances \
--query "DBInstances[*].[DBInstanceIdentifier,VpcSecurityGroups[*].VpcSecurityGroupId]"
Remediation
# Disable public accessibility
aws rds modify-db-instance \
--db-instance-identifier YOUR_DB_ID \
--no-publicly-accessible \
--apply-immediately
# Move to private subnet — requires modifying subnet group
# Access via bastion host or VPN only
# Enable RDS encryption at rest
aws rds modify-db-instance \
--db-instance-identifier YOUR_DB_ID \
--storage-encrypted # (requires snapshot + restore if not already encrypted)
CRITICALAzure Blob Container with Public AccessBlob StorageData ExposureT1530▼
What it is
An Azure Storage blob container has public access set to "Blob" or "Container" level, making files accessible without authentication. Azure now allows disabling this at account level but many legacy accounts still have it enabled.
How attackers exploit it
Same as S3 public exposure — automated tools enumerate storage account names (company.blob.core.windows.net) and brute-force container names. Azure storage account names are globally unique and guessable from company names. Grey Hat Warfare specifically indexes Azure blobs.
Detection
# Check if public access is disabled at storage account level
az storage account list --query "[].{Name:name, AllowBlobPublicAccess:allowBlobPublicAccess}" --output table
# Check specific container public access
az storage container show --name CONTAINER_NAME --account-name ACCOUNT_NAME --query "properties.publicAccess"
# List all containers with public access
az storage container list --account-name ACCOUNT_NAME \
--query "[?properties.publicAccess!='None'].[name,properties.publicAccess]" --output table
Remediation
# Disable public blob access at account level
az storage account update --name ACCOUNT_NAME --resource-group RG_NAME --allow-blob-public-access false
# Enable Azure Defender for Storage
az security pricing create --name StorageAccounts --tier Standard
# Enable diagnostic logging to Log Analytics
az monitor diagnostic-settings create --name blob-diag \
--resource /subscriptions/SUB/resourceGroups/RG/providers/Microsoft.Storage/storageAccounts/ACCOUNT \
--workspace LOG_ANALYTICS_WORKSPACE_ID \
--logs '[{"category":"StorageRead","enabled":true},{"category":"StorageWrite","enabled":true}]'
CRITICALAzure AD — No MFA for Global AdministratorsAzure ADIdentityPrivilegeT1078.004▼
What it is
Global Administrator accounts in Azure AD do not have Multi-Factor Authentication enforced. A single compromised password gives full tenant control — all subscriptions, all data, all resources.
How attackers exploit it
Attacker uses password spray, phishing, or credential stuffing against Global Admin accounts. Without MFA, a valid password is sufficient for complete tenant takeover. Azure AD Global Admin can read all mail, access all SharePoint, and control all Azure subscriptions.
Detection
# Check MFA status for Global Admins
# Via Azure Portal: Azure AD → Users → Per-user MFA
# Via PowerShell (MSOnline module)
Get-MsolUser -All | Where-Object {$_.StrongAuthenticationRequirements.Count -eq 0} | Select-Object DisplayName,UserPrincipalName
# Check Conditional Access policies requiring MFA
az ad policy list
# Via Microsoft Graph (modern approach)
# GET /reports/authenticationMethods/userRegistrationDetails
# Filter: isAdmin=true AND isMfaRegistered=false
Remediation
# Enable Security Defaults (enables MFA for all, free tier)
az rest --method PATCH \
--url "https://graph.microsoft.com/v1.0/policies/identitySecurityDefaultsEnforcementPolicy" \
--body '{"isEnabled":true}'
# Better: Create Conditional Access policy requiring MFA for admins
# Azure Portal → Azure AD → Security → Conditional Access → New Policy
# Assignments: Users → Directory roles → Global Administrator
# Grant: Require MFA
# Enforce Privileged Identity Management (PIM) for admin roles
# Requires Azure AD P2 — admins must activate roles with MFA + justification
HIGHNSG Allowing Any Inbound on Management PortsNSGNetworkInitial AccessT1190▼
What it is
A Network Security Group (NSG) has an inbound rule with Source=Any and Destination Port=22, 3389, 5985 (WinRM), or 1433 (SQL). Management protocols are exposed to the internet.
How attackers exploit it
Same as AWS security groups — automated scanners find exposed management ports. Azure VMs are frequently targeted for RDP brute force. Azure has seen mass credential stuffing campaigns specifically targeting Azure VMs.
Detection
# Find NSGs with overly permissive inbound rules
az network nsg list --query \
"[*].{NSG:name,RG:resourceGroup,Rules:securityRules[?access=='Allow' && direction=='Inbound' && sourceAddressPrefix=='*' && contains('22 3389 5985 1433',destinationPortRange)]}" \
--output table
# Or use Azure Policy compliance view
az policy state list --resource-group RG_NAME
Remediation
# Remove internet-facing RDP
az network nsg rule update --nsg-name NSG_NAME --resource-group RG_NAME \
--name RDP-Rule --source-address-prefix YOUR_VPN_IP/32
# Better: Use Azure Bastion (no public ports needed)
az network bastion create --name MyBastion --public-ip-address MyBastionIP \
--resource-group RG_NAME --vnet-name MY_VNET --location eastus
# Enable Just-in-Time VM Access (Microsoft Defender for Cloud)
az security jit-policy create --resource-group RG_NAME --location eastus \
--name default --virtual-machines ID
HIGHNo Diagnostic Logging on Key Azure ServicesLoggingAzure MonitorDetection GapT1562▼
What it is
Diagnostic settings not configured on Azure Key Vault, Azure AD, Azure Storage, and NSGs. Without logs sent to Log Analytics, security incidents cannot be detected or investigated.
How attackers exploit it
Any attacker action on these resources leaves no audit trail. Key Vault secret access (credential theft), Azure AD sign-in anomalies, and storage account data access all become invisible.
Detection
# Check Key Vault diagnostic settings
az monitor diagnostic-settings list \
--resource $(az keyvault show --name KV_NAME --query id --output tsv)
# Check Azure AD audit logs are being sent somewhere
az monitor diagnostic-settings list --resource /tenants/TENANT_ID/providers/microsoft.aadiam
# List all resources with NO diagnostic settings
az resource list --query "[*].id" --output tsv | while read id; do
count=$(az monitor diagnostic-settings list --resource "$id" --query "length(@)" --output tsv 2>/dev/null)
[ "$count" = "0" ] && echo "NO LOGGING: $id"
done
Remediation
# Enable Key Vault diagnostic logging
az monitor diagnostic-settings create \
--name keyvault-diag \
--resource $(az keyvault show --name KV_NAME --query id --output tsv) \
--workspace LOG_ANALYTICS_ID \
--logs '[{"category":"AuditEvent","enabled":true,"retentionPolicy":{"enabled":true,"days":90}}]'
# Use Azure Policy to enforce diagnostic settings org-wide
# Built-in policy: "Deploy Diagnostic Settings for Key Vault to Log Analytics workspace"
az policy assignment create \
--name "enforce-kv-diag" \
--policy "/providers/Microsoft.Authorization/policyDefinitions/KV_POLICY_DEF_ID" \
--scope /subscriptions/SUB_ID
MEDIUMAzure Subscription Without Privileged Identity ManagementPIMPrivilegeIdentityT1078▼
What it is
Azure Privileged Identity Management (PIM) is not configured. Privileged roles (Owner, Contributor, Global Admin) are permanently assigned rather than just-in-time activated. Compromised admin accounts have perpetual elevated access.
How attackers exploit it
A compromised Owner account with permanent access can immediately destroy resources, exfiltrate data, and establish persistence. With PIM, a compromised account that has not activated its role is significantly less dangerous.
Detection
# Check PIM status
az rest --method GET \
--url "https://management.azure.com/subscriptions/SUB_ID/providers/Microsoft.Authorization/roleEligibilityScheduleInstances?api-version=2020-10-01"
# List permanent role assignments (should be minimal)
az role assignment list --subscription SUB_ID \
--query "[?roleDefinitionName=='Owner' || roleDefinitionName=='Global Administrator'].[principalName,roleDefinitionName,scope]" --output table
Remediation
# Requires Azure AD P2
# Enable PIM for a role assignment
az rest --method PUT \
--url "https://management.azure.com/subscriptions/SUB_ID/providers/Microsoft.Authorization/roleEligibilitySchedules/SCHEDULE_ID?api-version=2020-10-01" \
--body @pim-schedule.json
# Remove permanent Owner assignments, replace with eligible
# Azure Portal → PIM → Azure Resources → Roles → Owner → Add assignments → Eligible
CRITICALGCS Bucket with allUsers or allAuthenticatedUsersGCSStorageData ExposureT1530▼
What it is
A Google Cloud Storage bucket has an IAM binding granting access to "allUsers" (anonymous internet users) or "allAuthenticatedUsers" (any Google account). Either setting exposes bucket data publicly.
How attackers exploit it
GCS buckets are discoverable via Google dorking (site:storage.googleapis.com company-name), GrayhatWarfare, and bucket enumeration tools. allAuthenticatedUsers means ANY Google account holder — 3 billion people — can access the data.
Detection
# Check bucket IAM policies for public access
gsutil iam get gs://BUCKET_NAME
# Find all buckets with public access in project
for bucket in $(gsutil ls); do
echo -n "$bucket: "
gsutil iam get $bucket | python3 -c "
import sys,json
d=json.load(sys.stdin)
public=[b for b in d.get('bindings',[]) if any(m in ['allUsers','allAuthenticatedUsers'] for m in b.get('members',[]))]
print('PUBLIC: '+str(public) if public else 'OK')
" 2>/dev/null
done
# Using gcloud
gcloud projects get-iam-policy PROJECT_ID \
--flatten="bindings[].members" \
--filter="bindings.members:(allUsers OR allAuthenticatedUsers)"
Remediation
# Remove public access from bucket
gsutil iam ch -d allUsers gs://BUCKET_NAME
gsutil iam ch -d allAuthenticatedUsers gs://BUCKET_NAME
# Enable uniform bucket-level access (disables per-object ACLs)
gsutil uniformbucketlevelaccess set on gs://BUCKET_NAME
# Enable public access prevention (org policy)
gcloud resource-manager org-policies set-policy \
--organization=ORG_ID public-access-prevention.json
# policy: storage.publicAccessPrevention = enforced
CRITICALService Account Key File Exposed / Long-Lived KeysIAMService AccountCredentialT1552.001▼
What it is
GCP service account key files (.json) have been downloaded and stored in source code, CI/CD systems, or public repositories. Service account keys do not expire by default — a leaked key may have been valid for years.
How attackers exploit it
Service account key files found in GitHub repos, Docker images, or npm packages provide persistent, MFA-bypass access to GCP resources. Unlike user accounts, service account keys cannot be protected with MFA. GCP security incidents involving key leaks are extremely common in Indian startup environments.
Detection
# List all service account keys and their age
gcloud iam service-accounts list --format="value(email)" | while read sa; do
gcloud iam service-accounts keys list --iam-account="$sa" \
--format="table(name.basename(),validAfterTime,validBeforeTime,keyType)"
echo "---"
done
# Find keys older than 90 days
python3 -c "
import subprocess, json
from datetime import datetime, timezone, timedelta
threshold = datetime.now(timezone.utc) - timedelta(days=90)
# Run: gcloud iam service-accounts keys list --iam-account=SA --format=json
# Check validAfterTime against threshold
print('Keys older than 90 days should be rotated')
"
Remediation
# Delete unused service account keys
gcloud iam service-accounts keys delete KEY_ID --iam-account=SA_EMAIL
# Create new key (rotate)
gcloud iam service-accounts keys create new-key.json --iam-account=SA_EMAIL
# Better: use Workload Identity Federation instead of key files
# For GKE: use Workload Identity
gcloud container clusters update CLUSTER_NAME \
--workload-pool=PROJECT_ID.svc.id.goog
# Set org policy to disable service account key creation
gcloud resource-manager org-policies set-policy \
--organization=ORG_ID no-sa-keys-policy.json
HIGHCompute Instance with Default Service Account and Full API AccessComputeIAMPrivilegeT1078.004▼
What it is
A Compute Engine VM was created with the default service account and "Allow full access to all Cloud APIs" scope. The default service account often has broad project editor permissions. Any workload on the VM can access GCP APIs as an editor.
How attackers exploit it
SSRF on a web app or code execution on the VM allows accessing the metadata server (metadata.google.internal) to get the service account token. With Editor permissions, attacker can read all data, create new VMs, and establish persistence.
Detection
# List VMs with their service accounts and scopes
gcloud compute instances list \
--format="table(name,serviceAccounts[0].email,serviceAccounts[0].scopes)"
# Find instances using default SA with full cloud-platform scope
gcloud compute instances list \
--format=json | python3 -c "
import sys,json
instances=json.load(sys.stdin)
for i in instances:
for sa in i.get('serviceAccounts',[]):
if 'default' in sa.get('email',''):
scopes=sa.get('scopes',[])
if any('cloud-platform' in s for s in scopes):
print(f'RISKY: {i[\"name\"]} - {sa[\"email\"]} - full scope')
"
Remediation
# Create dedicated service account with minimal permissions
gcloud iam service-accounts create app-sa --display-name "App Service Account"
gcloud projects add-iam-policy-binding PROJECT_ID \
--member=serviceAccount:app-sa@PROJECT_ID.iam.gserviceaccount.com \
--role=roles/logging.logWriter # Only the roles actually needed
# Recreate VM with minimal scope
gcloud compute instances create VM_NAME \
--service-account=app-sa@PROJECT_ID.iam.gserviceaccount.com \
--scopes=logging-write,monitoring-write # Minimal scopes
# Disable default SA usage via org policy
gcloud resource-manager org-policies set-policy \
--organization=ORG_ID no-default-sa-policy.json
HIGHCloud Audit Logs DisabledLoggingAuditDetection GapT1562▼
What it is
Cloud Audit Logs (Admin Activity, Data Access, System Events) are not enabled or are not being exported to Cloud Logging / SIEM. Without audit logs, attacker actions within GCP leave no trace.
How attackers exploit it
Admin Activity logs are always enabled and cannot be disabled — but Data Access logs (who read/modified data) are disabled by default due to volume. Attackers who exfiltrate data from GCS or BigQuery leave no trace without Data Access logging.
Detection
# Check current audit log configuration
gcloud projects get-iam-policy PROJECT_ID \
--flatten="auditConfigs[].service" \
--format="table(auditConfigs.service,auditConfigs.auditLogConfigs)"
# Check if Data Access logs are enabled for all services
gcloud projects get-iam-policy PROJECT_ID | grep -A5 "auditLogConfigs"
Remediation
# Enable Data Access audit logs for all services
gcloud projects set-iam-policy PROJECT_ID - << 'EOF'
{
"auditConfigs": [{
"service": "allServices",
"auditLogConfigs": [
{"logType": "ADMIN_READ"},
{"logType": "DATA_READ"},
{"logType": "DATA_WRITE"}
]
}]
}
EOF
# Export logs to Cloud Storage for long-term retention
gcloud logging sinks create audit-export-sink \
storage.googleapis.com/YOUR_AUDIT_BUCKET \
--log-filter='logName=~"cloudaudit.googleapis.com"'