Skip to content

Commit 35feec4

Browse files
AWS rule: aws.opensearch.domain.idle (#179)
1 parent f1d5630 commit 35feec4

15 files changed

Lines changed: 1638 additions & 9 deletions

File tree

README.fr.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Gaspillage minimum estimé : ~$25 944/mois
100100
- Détecte le gaspillage IA/ML coûteux : SageMaker, AML, Vertex AI — ressources GPU signalées comme candidats à risque plus élevé (500–23 000 $/mois)
101101
- Fonctionne sur AWS, Azure et GCP en un seul outil
102102
- S'exécute entièrement dans votre environnement — aucun agent, pas de SaaS, aucun credential stocké
103-
- 49 règles de détection sélectives et haut signal, conçues pour éviter les faux positifs en environnements IaC
103+
- 50 règles de détection sélectives et haut signal, conçues pour éviter les faux positifs en environnements IaC
104104
- Prêt pour CI/CD — codes de sortie d'application + sorties JSON/CSV/markdown
105105

106106
### Ce que CleanCloud ne fait PAS
@@ -434,13 +434,13 @@ Oui. CleanCloud n'a besoin d'accès réseau qu'aux endpoints API de votre cloud
434434

435435
## Ce que CleanCloud détecte
436436

437-
49 règles pour AWS, Azure et GCP — conservatrices, haut signal, conçues pour éviter les faux positifs en environnements IaC.
437+
50 règles pour AWS, Azure et GCP — conservatrices, haut signal, conçues pour éviter les faux positifs en environnements IaC.
438438

439439
**AWS :**
440440
- Compute : instances arrêtées 30+ jours (charges EBS continuent)
441441
- Stockage : volumes EBS non attachés (HIGH), anciens snapshots EBS, anciennes AMIs, anciens snapshots RDS 90+ jours
442442
- Réseau : Elastic IPs non attachées (HIGH), ENI détachées, NAT Gateways inactives, Load Balancers inactifs (HIGH)
443-
- Plateforme : instances RDS inactives (HIGH), clusters Redshift inactifs (zéro connexion 14+ jours)
443+
- Plateforme : instances RDS inactives (HIGH), clusters Redshift inactifs (zéro connexion 14+ jours), domaines OpenSearch inactifs (zéro requête 14+ jours)
444444
- Observabilité : logs CloudWatch à rétention infinie
445445
- Gouvernance : ressources sans tags, security groups inutilisés
446446
- IA/ML *(opt-in : `--category ai`)* : Bedrock Provisioned Throughput (Model Units) inactifs avec zéro invocation depuis 7+ jours ; endpoints SageMaker sans trafic `InvokeEndpoint` observé depuis 14+ jours ; instances Notebook SageMaker avec timestamps de contrôle inactifs depuis 14+ jours ; Domaines SageMaker sans apps en cours d'exécution sur tous les profils et espaces depuis 30+ jours (coût de stockage EFS continu) ; Studio Apps SageMaker (`KernelGateway`/`JupyterLab`/`CodeEditor`) sans signal d'activité récent exploitable depuis 7+ jours ; training jobs SageMaker toujours `InProgress` au-delà du seuil de 24h ; processing jobs SageMaker toujours `InProgress` au-delà du seuil de 24h

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ Minimum estimated waste: ~$25,944/month
100100
- Catches expensive idle AI/ML waste: SageMaker, AML, Vertex AI — GPU-backed resources flagged as higher-risk review candidates ($500–$23K/month)
101101
- Works across AWS, Azure, and GCP in one tool
102102
- Runs entirely in your environment — no agents, no SaaS, no credentials stored
103-
- 49 curated, high-signal detection rules designed to avoid false positives in IaC environments
103+
- 50 curated, high-signal detection rules designed to avoid false positives in IaC environments
104104
- CI/CD-ready — enforcement exit codes + JSON/CSV/markdown output
105105

106106
### What CleanCloud does NOT do
@@ -434,13 +434,13 @@ Yes. CleanCloud only needs network access to your cloud provider's API endpoints
434434

435435
## What CleanCloud Detects
436436

437-
49 rules across AWS, Azure, and GCP — conservative, high-signal, designed to avoid false positives in IaC environments.
437+
50 rules across AWS, Azure, and GCP — conservative, high-signal, designed to avoid false positives in IaC environments.
438438

439439
**AWS:**
440440
- Compute: stopped instances 30+ days (EBS charges continue)
441441
- Storage: unattached EBS volumes (HIGH), old EBS snapshots, old AMIs, old RDS snapshots 90+ days
442442
- Network: unattached Elastic IPs (HIGH), detached ENIs, idle NAT Gateways, idle load balancers (HIGH)
443-
- Platform: idle RDS instances (HIGH), idle Redshift clusters (zero connections 14+ days)
443+
- Platform: idle RDS instances (HIGH), idle Redshift clusters (zero connections 14+ days), idle OpenSearch domains (zero requests 14+ days)
444444
- Observability: infinite retention CloudWatch Logs
445445
- Governance: untagged resources, unused security groups
446446
- AI/ML *(opt-in: `--category ai`)*: idle Bedrock Provisioned Throughput (Model Units) with zero invocations 7+ days; idle SageMaker endpoints with no observed `InvokeEndpoint` traffic 14+ days; SageMaker Notebook Instances with stale control-plane timestamps 14+ days; SageMaker Domains with no running apps across all user profiles and spaces 30+ days (continuous EFS storage cost); SageMaker Studio apps (`KernelGateway`/`JupyterLab`/`CodeEditor`) with no usable recent activity signal 7+ days; SageMaker training jobs still `InProgress` beyond the 24h threshold; SageMaker processing jobs still `InProgress` beyond the 24h threshold

cleancloud/doctor/aws.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ def run_aws_doctor(profile: Optional[str], region: Optional[str] = None) -> None
243243
info(" rds:DescribeDBSnapshotAttributes")
244244
info(" cloudtrail:LookupEvents")
245245
info(" redshift:DescribeClusters")
246+
info(" es:ListDomainNames")
247+
info(" es:DescribeDomain")
246248
info(" elasticloadbalancing:DescribeLoadBalancers")
247249
info(" elasticloadbalancing:DescribeTargetGroups")
248250
info(" logs:DescribeLogGroups")
@@ -531,6 +533,32 @@ def run_aws_doctor(profile: Optional[str], region: Optional[str] = None) -> None
531533
permissions_failed.append(("redshift:DescribeClusters", str(e)))
532534
warn(f"redshift:DescribeClusters - {e}")
533535

536+
# Test OpenSearch permissions
537+
try:
538+
opensearch = session.client("opensearch", region_name=region)
539+
opensearch.list_domain_names()
540+
permissions_tested.append("es:ListDomainNames")
541+
success("es:ListDomainNames")
542+
except Exception as e:
543+
permissions_failed.append(("es:ListDomainNames", str(e)))
544+
warn(f"es:ListDomainNames - {e}")
545+
546+
try:
547+
# Use a non-existent domain to test permission
548+
opensearch.describe_domain(DomainName="cleancloud-perm-test-nonexistent")
549+
permissions_tested.append("es:DescribeDomain")
550+
success("es:DescribeDomain")
551+
except Exception as e:
552+
if "ResourceNotFoundException" in str(e) or "not found" in str(e).lower():
553+
permissions_tested.append("es:DescribeDomain")
554+
success("es:DescribeDomain")
555+
elif "AccessDenied" in str(e) or "not authorized" in str(e).lower():
556+
permissions_failed.append(("es:DescribeDomain", str(e)))
557+
warn(f"es:DescribeDomain - {e}")
558+
else:
559+
permissions_tested.append("es:DescribeDomain")
560+
success("es:DescribeDomain")
561+
534562
# Test ELB permissions
535563
try:
536564
elbv2 = session.client("elbv2", region_name=region)

0 commit comments

Comments
 (0)