aboutsummaryrefslogtreecommitdiff
path: root/sections
diff options
context:
space:
mode:
Diffstat (limited to 'sections')
-rw-r--r--sections/__init__.py1
-rw-r--r--sections/acm.py41
-rw-r--r--sections/cloudfront.py37
-rw-r--r--sections/cloudwatch.py45
-rw-r--r--sections/config.py37
-rw-r--r--sections/costexplorer.py67
-rw-r--r--sections/route53.py40
-rw-r--r--sections/s3.py47
-rw-r--r--sections/securityhub.py56
9 files changed, 371 insertions, 0 deletions
diff --git a/sections/__init__.py b/sections/__init__.py
new file mode 100644
index 0000000..02276b8
--- /dev/null
+++ b/sections/__init__.py
@@ -0,0 +1 @@
+# sections/__init__.py
diff --git a/sections/acm.py b/sections/acm.py
new file mode 100644
index 0000000..3a62668
--- /dev/null
+++ b/sections/acm.py
@@ -0,0 +1,41 @@
+# acm.py
+import boto3
+from datetime import datetime, timedelta, timezone
+from tabulate import tabulate
+
+
+def get_section(config):
+ profile = config["aws"].get("profile")
+ region = config["aws"]["region"]
+ session = boto3.Session(
+ profile_name=profile if profile else None, region_name=region
+ )
+ client = session.client("acm")
+
+ today = datetime.now(timezone.utc)
+ deadline = today + timedelta(days=30)
+
+ certs = client.list_certificates(CertificateStatuses=["ISSUED"])[
+ "CertificateSummaryList"
+ ]
+ rows = []
+
+ for cert in certs:
+ detail = client.describe_certificate(CertificateArn=cert["CertificateArn"])[
+ "Certificate"
+ ]
+ not_after = detail.get("NotAfter")
+ if not_after and today <= not_after <= deadline:
+ rows.append([cert["DomainName"], not_after.strftime("%Y-%m-%d")])
+
+ if not rows:
+ return "Expiring TLS Certificates:\nNo certs expiring in the next 30 days."
+
+ table = tabulate(rows, headers=["Domain", "Expires"], tablefmt="simple_grid")
+ lines = [
+ "Expiring TLS Certificates (Next 30 Days):",
+ f"[https://{config['aws'].get('region')}.console.aws.amazon.com/acm/home#/certificates/list]",
+ table,
+ ]
+
+ return "\n".join(lines)
diff --git a/sections/cloudfront.py b/sections/cloudfront.py
new file mode 100644
index 0000000..df161e6
--- /dev/null
+++ b/sections/cloudfront.py
@@ -0,0 +1,37 @@
+# cloudfront.py
+import boto3
+from datetime import datetime, timedelta, timezone
+from tabulate import tabulate
+
+
+def get_section(config):
+ profile = config["aws"].get("profile")
+ session = boto3.Session(profile_name=profile if profile else None)
+ client = session.client("cloudfront")
+
+ now = datetime.now(timezone.utc)
+ cutoff = now - timedelta(days=2)
+
+ dists = client.list_distributions().get("DistributionList", {}).get("Items", [])
+ rows = []
+
+ for dist in dists:
+ last_mod = dist["LastModifiedTime"]
+ if last_mod >= cutoff:
+ rows.append(
+ [dist["Id"], dist["DomainName"], last_mod.strftime("%Y-%m-%d %H:%M")]
+ )
+
+ if not rows:
+ return "CloudFront Changes:\nNo distributions changed in the last 48h."
+
+ table = tabulate(
+ rows, headers=["ID", "Domain", "Last Modified"], tablefmt="simple_grid"
+ )
+ lines = [
+ "CloudFront Distribution Changes (Last 48h):",
+ f"[https://{config['aws'].get('region')}.console.aws.amazon.com/cloudfront/v4/home#/distributions]",
+ table,
+ ]
+
+ return "\n".join(lines)
diff --git a/sections/cloudwatch.py b/sections/cloudwatch.py
new file mode 100644
index 0000000..65a59d6
--- /dev/null
+++ b/sections/cloudwatch.py
@@ -0,0 +1,45 @@
+# cloudwatch.py
+import boto3
+import datetime
+from tabulate import tabulate
+
+
+def get_section(config):
+ profile = config["aws"].get("profile")
+ region = config["aws"]["region"]
+ session = boto3.Session(
+ profile_name=profile if profile else None, region_name=region
+ )
+ client = session.client("cloudwatch")
+
+ now = datetime.datetime.utcnow()
+ yesterday = now - datetime.timedelta(days=1)
+
+ alarms = client.describe_alarms(StateValue="ALARM")["MetricAlarms"]
+ rows = []
+
+ for alarm in alarms:
+ last_updated = alarm["StateUpdatedTimestamp"]
+ if last_updated >= yesterday:
+ rows.append(
+ [
+ alarm["AlarmName"],
+ alarm["MetricName"],
+ alarm["StateValue"],
+ last_updated.strftime("%Y-%m-%d %H:%M UTC"),
+ ]
+ )
+
+ if not rows:
+ return "CloudWatch Alarms:\nNo alarms triggered in the last 24h."
+
+ table = tabulate(
+ rows, headers=["Name", "Metric", "State", "Updated"], tablefmt="simple_grid"
+ )
+ lines = [
+ "CloudWatch Alarms (Last 24h):",
+ f"[https://{config['aws'].get('region')}.console.aws.amazon.com/cloudwatch/home#alarmsV2:]",
+ table,
+ ]
+
+ return "\n".join(lines)
diff --git a/sections/config.py b/sections/config.py
new file mode 100644
index 0000000..28505fa
--- /dev/null
+++ b/sections/config.py
@@ -0,0 +1,37 @@
+# config.py
+import boto3
+from tabulate import tabulate
+
+
+def get_section(config):
+ profile = config["aws"].get("profile")
+ region = config["aws"]["region"]
+ session = boto3.Session(
+ profile_name=profile if profile else None, region_name=region
+ )
+ client = session.client("config")
+
+ paginator = client.get_paginator("describe_compliance_by_resource")
+ page_iterator = paginator.paginate(ComplianceTypes=["NON_COMPLIANT"])
+
+ rows = []
+
+ for page in page_iterator:
+ for result in page.get("ComplianceByResources", []):
+ resource_type = result.get("ResourceType", "Unknown")
+ resource_id = result.get("ResourceId", "Unknown")
+ rows.append([resource_type, resource_id])
+
+ if not rows:
+ return "AWS Config Non-Compliance:\nAll resources are compliant."
+
+ table = tabulate(
+ rows, headers=["Resource Type", "Resource ID"], tablefmt="simple_grid"
+ )
+ lines = [
+ "AWS Config Non-Compliant Resources:",
+ f"[https://{config['aws'].get('region')}.console.aws.amazon.com/config/home#/resources?complianceType=NON_COMPLIANT]",
+ table,
+ ]
+
+ return "\n".join(lines)
diff --git a/sections/costexplorer.py b/sections/costexplorer.py
new file mode 100644
index 0000000..66747f0
--- /dev/null
+++ b/sections/costexplorer.py
@@ -0,0 +1,67 @@
+# costexplorer.py
+import boto3
+import datetime
+from tabulate import tabulate
+
+
+def get_section(config):
+ profile = config["aws"].get("profile")
+ region = config["aws"]["region"]
+
+ session = boto3.Session(
+ profile_name=profile if profile else None, region_name=region
+ )
+ client = session.client("ce")
+
+ today = datetime.date.today()
+ start = (today - datetime.timedelta(days=1)).strftime("%Y-%m-%d")
+ end = today.strftime("%Y-%m-%d")
+
+ response = client.get_cost_and_usage(
+ TimePeriod={"Start": start, "End": end},
+ Granularity="DAILY",
+ Metrics=["UnblendedCost"],
+ GroupBy=[{"Type": "DIMENSION", "Key": "SERVICE"}],
+ )
+
+ results = response["ResultsByTime"][0]
+ date = results["TimePeriod"]["Start"]
+ estimated = results.get("Estimated", False)
+
+ rows = []
+ groups = results.get("Groups", [])
+ if groups:
+ for group in groups:
+ service = group["Keys"][0]
+ cost = (
+ group.get("Metrics", {}).get("UnblendedCost", {}).get("Amount", "0.00")
+ )
+ rows.append([service, f"${float(cost):.2f}"])
+ else:
+ rows.append(["No service-level data available", ""])
+
+ total_cost = results.get("Total", {}).get("UnblendedCost", {}).get("Amount", None)
+ if total_cost is None:
+ total_cost = sum(
+ float(group.get("Metrics", {}).get("UnblendedCost", {}).get("Amount", 0.0))
+ for group in groups
+ )
+ rows.append(["TOTAL", f"${float(total_cost):.2f}"])
+
+ table = tabulate(
+ rows,
+ headers=["Service", "Cost"],
+ tablefmt="simple_grid",
+ colalign=("left", "right"),
+ )
+
+ lines = [
+ f"AWS Billing Report for {date}",
+ f"[https://{config['aws'].get('region')}.console.aws.amazon.com/costmanagement/]",
+ table,
+ ]
+
+ if estimated:
+ lines.append("\nNote: Costs are estimated and may change.")
+
+ return "\n".join(lines)
diff --git a/sections/route53.py b/sections/route53.py
new file mode 100644
index 0000000..d2da7cf
--- /dev/null
+++ b/sections/route53.py
@@ -0,0 +1,40 @@
+# route53.py
+import boto3
+from tabulate import tabulate
+
+
+def get_section(config):
+ profile = config["aws"].get("profile")
+ region = config["aws"]["region"] # Not used by Route53, but kept for consistency
+
+ session = boto3.Session(profile_name=profile if profile else None)
+ client = session.client("route53")
+
+ health_checks = client.list_health_checks()["HealthChecks"]
+ rows = []
+
+ for hc in health_checks:
+ hc_id = hc["Id"]
+ name = hc.get("HealthCheckConfig", {}).get(
+ "FullyQualifiedDomainName", "Unnamed"
+ )
+ status = client.get_health_check_status(HealthCheckId=hc_id)
+ status_summary = status["HealthCheckObservations"]
+ healthy = all(
+ obs["StatusReport"]["Status"].startswith("Success")
+ for obs in status_summary
+ )
+ state = "HEALTHY" if healthy else "UNHEALTHY"
+ rows.append([name, state])
+
+ if not rows:
+ return "Route 53 Health Checks:\nNo health checks configured."
+
+ table = tabulate(rows, headers=["Domain", "Status"], tablefmt="simple_grid")
+ lines = [
+ "Route 53 Health Checks:",
+ f"[https://{config['aws'].get('region')}.console.aws.amazon.com/route53/v2/healthchecks/home]",
+ table,
+ ]
+
+ return "\n".join(lines)
diff --git a/sections/s3.py b/sections/s3.py
new file mode 100644
index 0000000..c2c53e6
--- /dev/null
+++ b/sections/s3.py
@@ -0,0 +1,47 @@
+# s3.py
+import boto3
+from tabulate import tabulate
+
+
+def get_section(config):
+ profile = config["aws"].get("profile")
+ session = boto3.Session(profile_name=profile if profile else None)
+ client = session.client("s3")
+
+ buckets = client.list_buckets()["Buckets"]
+ rows = []
+
+ for bucket in buckets:
+ name = bucket["Name"]
+ public = "Unknown"
+ encrypted = "No"
+
+ try:
+ acl = client.get_bucket_acl(Bucket=name)
+ public = any(
+ grant["Grantee"].get("URI", "").endswith("AllUsers")
+ for grant in acl["Grants"]
+ )
+ except Exception:
+ public = "Error"
+
+ try:
+ enc = client.get_bucket_encryption(Bucket=name)
+ rules = enc["ServerSideEncryptionConfiguration"]["Rules"]
+ if rules:
+ encrypted = "Yes"
+ except client.exceptions.ClientError:
+ encrypted = "No"
+
+ rows.append([name, "Yes" if public else "No", encrypted])
+
+ table = tabulate(
+ rows, headers=["Bucket", "Public", "Encrypted"], tablefmt="simple_grid"
+ )
+ lines = [
+ "S3 Bucket Access Summary:",
+ f"[https://{config['aws'].get('region')}.console.aws.amazon.com/s3/home]",
+ table,
+ ]
+
+ return "\n".join(lines)
diff --git a/sections/securityhub.py b/sections/securityhub.py
new file mode 100644
index 0000000..0ccb5fe
--- /dev/null
+++ b/sections/securityhub.py
@@ -0,0 +1,56 @@
+# securityhub.py
+import boto3
+import datetime
+from tabulate import tabulate
+
+
+def get_section(config):
+ profile = config["aws"].get("profile")
+ region = config["aws"]["region"]
+
+ session = boto3.Session(
+ profile_name=profile if profile else None, region_name=region
+ )
+ client = session.client("securityhub")
+
+ findings = []
+ paginator = client.get_paginator("get_findings")
+
+ response_iterator = paginator.paginate(
+ Filters={
+ "CreatedAt": [{"DateRange": {"Value": 1, "Unit": "DAYS"}}],
+ "RecordState": [{"Value": "ACTIVE", "Comparison": "EQUALS"}],
+ "WorkflowStatus": [{"Value": "NEW", "Comparison": "EQUALS"}],
+ },
+ )
+
+ for page in response_iterator:
+ findings.extend(page.get("Findings", []))
+
+ rows = []
+ for finding in findings:
+ title = finding.get("Title", "No title")
+ severity = finding.get("Severity", {}).get("Label", "UNKNOWN")
+ product = finding.get("ProductName", "Unknown Product")
+ resource = finding.get("Resources", [{}])[0].get("Id", "Unknown Resource")
+ rows.append([severity, title[:50], product, resource[:30]])
+
+ if not rows:
+ lines = [
+ "AWS Security Hub Findings (Last 24h)",
+ "No new findings in the past 24 hours.",
+ ]
+ else:
+ table = tabulate(
+ rows,
+ headers=["Severity", "Title", "Product", "Resource"],
+ tablefmt="simple_grid",
+ colalign=("center", "left", "left", "left"),
+ )
+ lines = [
+ f"AWS Security Hub Findings (Last 24h): {len(rows)} new finding(s)",
+ f"[https://{config['aws'].get('region')}.console.aws.amazon.com/securityhub/home?region=eu-west-1#/findings]",
+ table,
+ ]
+
+ return "\n".join(lines)