Initial commit: Cloudflare infrastructure with WAF Intelligence

- Complete Cloudflare Terraform configuration (DNS, WAF, tunnels, access)
- WAF Intelligence MCP server with threat analysis and ML classification
- GitOps automation with PR workflows and drift detection
- Observatory monitoring stack with Prometheus/Grafana
- IDE operator rules for governed development
- Security playbooks and compliance frameworks
- Autonomous remediation and state reconciliation
This commit is contained in:
Vault Sovereign
2025-12-16 18:31:53 +00:00
commit 37a867c485
123 changed files with 25407 additions and 0 deletions

358
gitops/ci_plan_comment.py Normal file
View File

@@ -0,0 +1,358 @@
#!/usr/bin/env python3
"""
CI Plan Comment Bot for Cloudflare GitOps
Phase 6 - PR Workflows
Posts Terraform plan summaries as comments on Merge Requests.
Designed to run in GitLab CI/CD pipelines.
"""
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any, Dict, Optional
try:
import requests
import yaml
except ImportError:
print("ERROR: pip install requests pyyaml", file=sys.stderr)
sys.exit(1)
HERE = Path(__file__).resolve().parent
CONFIG_PATH = HERE / "config.yml"
def load_config() -> Dict[str, Any]:
"""Load gitops configuration with env expansion"""
with open(CONFIG_PATH) as f:
config = yaml.safe_load(f)
def expand_env(obj):
if isinstance(obj, str):
if obj.startswith("${") and "}" in obj:
inner = obj[2:obj.index("}")]
default = None
var = inner
if ":-" in inner:
var, default = inner.split(":-", 1)
return os.environ.get(var, default)
return obj
elif isinstance(obj, dict):
return {k: expand_env(v) for k, v in obj.items()}
elif isinstance(obj, list):
return [expand_env(i) for i in obj]
return obj
return expand_env(config)
def get_plan_summary() -> tuple[str, Dict]:
"""Run plan_summarizer and get both formats"""
# Markdown for comment
result = subprocess.run(
["python3", "plan_summarizer.py", "--format", "markdown"],
cwd=HERE,
capture_output=True,
text=True,
check=True,
)
markdown = result.stdout
# JSON for processing
result = subprocess.run(
["python3", "plan_summarizer.py", "--format", "json"],
cwd=HERE,
capture_output=True,
text=True,
check=True,
)
summary_json = json.loads(result.stdout)
return markdown, summary_json
class GitLabCI:
"""GitLab CI integration"""
def __init__(self, token: str):
self.base_url = os.environ.get("CI_API_V4_URL", "https://gitlab.com/api/v4")
self.project_id = os.environ.get("CI_PROJECT_ID")
self.mr_iid = os.environ.get("CI_MERGE_REQUEST_IID")
self.commit_sha = os.environ.get("CI_COMMIT_SHA", "")[:8]
self.pipeline_url = os.environ.get("CI_PIPELINE_URL", "")
self.job_name = os.environ.get("CI_JOB_NAME", "terraform-plan")
self.token = token
self.headers = {"PRIVATE-TOKEN": token}
@property
def is_mr_pipeline(self) -> bool:
return bool(self.mr_iid)
def get_existing_comments(self) -> list:
"""Get existing MR comments"""
url = f"{self.base_url}/projects/{self.project_id}/merge_requests/{self.mr_iid}/notes"
resp = requests.get(url, headers=self.headers)
resp.raise_for_status()
return resp.json()
def find_bot_comment(self, marker: str) -> Optional[Dict]:
"""Find existing bot comment by marker"""
comments = self.get_existing_comments()
for comment in comments:
if marker in comment.get("body", ""):
return comment
return None
def post_comment(self, body: str) -> Dict:
"""Post a new comment on the MR"""
url = f"{self.base_url}/projects/{self.project_id}/merge_requests/{self.mr_iid}/notes"
resp = requests.post(url, headers=self.headers, data={"body": body})
resp.raise_for_status()
return resp.json()
def update_comment(self, note_id: int, body: str) -> Dict:
"""Update an existing comment"""
url = f"{self.base_url}/projects/{self.project_id}/merge_requests/{self.mr_iid}/notes/{note_id}"
resp = requests.put(url, headers=self.headers, data={"body": body})
resp.raise_for_status()
return resp.json()
def delete_comment(self, note_id: int):
"""Delete a comment"""
url = f"{self.base_url}/projects/{self.project_id}/merge_requests/{self.mr_iid}/notes/{note_id}"
resp = requests.delete(url, headers=self.headers)
resp.raise_for_status()
class GitHubActions:
"""GitHub Actions integration"""
def __init__(self, token: str):
self.base_url = "https://api.github.com"
self.repo = os.environ.get("GITHUB_REPOSITORY", "")
self.pr_number = self._get_pr_number()
self.commit_sha = os.environ.get("GITHUB_SHA", "")[:8]
self.run_url = f"https://github.com/{self.repo}/actions/runs/{os.environ.get('GITHUB_RUN_ID', '')}"
self.token = token
self.headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json",
}
def _get_pr_number(self) -> Optional[str]:
"""Extract PR number from GitHub event"""
event_path = os.environ.get("GITHUB_EVENT_PATH")
if event_path and os.path.exists(event_path):
with open(event_path) as f:
event = json.load(f)
pr = event.get("pull_request", {})
return str(pr.get("number", "")) if pr else None
return None
@property
def is_pr_pipeline(self) -> bool:
return bool(self.pr_number)
def find_bot_comment(self, marker: str) -> Optional[Dict]:
"""Find existing bot comment"""
url = f"{self.base_url}/repos/{self.repo}/issues/{self.pr_number}/comments"
resp = requests.get(url, headers=self.headers)
resp.raise_for_status()
for comment in resp.json():
if marker in comment.get("body", ""):
return comment
return None
def post_comment(self, body: str) -> Dict:
"""Post a new comment"""
url = f"{self.base_url}/repos/{self.repo}/issues/{self.pr_number}/comments"
resp = requests.post(url, headers=self.headers, json={"body": body})
resp.raise_for_status()
return resp.json()
def update_comment(self, comment_id: int, body: str) -> Dict:
"""Update existing comment"""
url = f"{self.base_url}/repos/{self.repo}/issues/comments/{comment_id}"
resp = requests.patch(url, headers=self.headers, json={"body": body})
resp.raise_for_status()
return resp.json()
def build_comment_body(
cfg: Dict[str, Any],
summary_md: str,
summary_json: Dict,
ci_info: Dict,
) -> str:
"""Build the full comment body"""
ci_cfg = cfg.get("ci", {})
header = ci_cfg.get("comment_header", "Terraform Plan Summary")
# Risk indicator
risk = summary_json.get("overall_risk", "UNKNOWN")
risk_emoji = {
"LOW": "🟢",
"MEDIUM": "🟡",
"HIGH": "🟠",
"CRITICAL": "🔴",
}.get(risk, "")
# Marker for finding/updating this comment
marker = "<!-- gitops-plan-comment -->"
changes = summary_json.get("total_changes", 0)
compliance = summary_json.get("compliance_violations", [])
# Build body
lines = [
marker,
f"# {risk_emoji} {header}",
"",
f"**Commit:** `{ci_info.get('commit_sha', 'N/A')}`",
f"**Pipeline:** [{ci_info.get('job_name', 'terraform-plan')}]({ci_info.get('pipeline_url', '#')})",
"",
]
# Compliance warning banner
if compliance:
frameworks = ", ".join(compliance)
lines.extend([
f"> ⚠️ **Compliance Impact:** {frameworks}",
"",
])
# No changes case
if changes == 0:
lines.extend([
"✅ **No changes detected.**",
"",
"Terraform state matches the current configuration.",
])
else:
# Add summary
lines.append(summary_md)
# Add approval reminder for high risk
if risk in ("HIGH", "CRITICAL"):
lines.extend([
"",
"---",
f"⚠️ **{risk} risk changes detected.** Additional review recommended.",
])
lines.extend([
"",
"---",
f"*Last updated: {ci_info.get('timestamp', 'N/A')} • Phase 6 GitOps*",
])
return "\n".join(lines)
def main():
"""Main entry point"""
import argparse
from datetime import datetime
parser = argparse.ArgumentParser(
description="Post terraform plan comment on MR"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Print comment but don't post",
)
parser.add_argument(
"--update",
action="store_true",
default=True,
help="Update existing comment instead of creating new one",
)
args = parser.parse_args()
# Load config
cfg = load_config()
# Detect CI platform
token = os.environ.get("GITLAB_TOKEN") or os.environ.get("GITHUB_TOKEN")
if not token:
print("ERROR: GITLAB_TOKEN or GITHUB_TOKEN required", file=sys.stderr)
sys.exit(1)
# Determine platform
if os.environ.get("GITLAB_CI"):
ci = GitLabCI(token)
platform = "gitlab"
elif os.environ.get("GITHUB_ACTIONS"):
ci = GitHubActions(token)
platform = "github"
else:
print("ERROR: Must run in GitLab CI or GitHub Actions", file=sys.stderr)
sys.exit(1)
# Check if this is an MR/PR pipeline
if not ci.is_mr_pipeline and not ci.is_pr_pipeline:
print("Not an MR/PR pipeline. Skipping comment.")
return
# Get plan summary
print("Getting plan summary...")
summary_md, summary_json = get_plan_summary()
# Build CI info
ci_info = {
"commit_sha": getattr(ci, "commit_sha", ""),
"pipeline_url": getattr(ci, "pipeline_url", "") or getattr(ci, "run_url", ""),
"job_name": getattr(ci, "job_name", "terraform-plan"),
"timestamp": datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC"),
}
# Build comment
body = build_comment_body(cfg, summary_md, summary_json, ci_info)
if args.dry_run:
print("\n" + "=" * 60)
print("[DRY RUN] Would post comment:")
print("=" * 60)
print(body)
return
# Find existing comment to update
marker = "<!-- gitops-plan-comment -->"
existing = ci.find_bot_comment(marker)
if existing and args.update:
print(f"Updating existing comment {existing.get('id') or existing.get('note_id')}...")
note_id = existing.get("id") or existing.get("note_id")
ci.update_comment(note_id, body)
print("Comment updated.")
else:
print("Posting new comment...")
result = ci.post_comment(body)
print(f"Comment posted: {result.get('id') or result.get('html_url')}")
# Output for CI
risk = summary_json.get("overall_risk", "UNKNOWN")
changes = summary_json.get("total_changes", 0)
print(f"\nSummary: {changes} changes, {risk} risk")
# Set CI output variables (for use in subsequent jobs)
if os.environ.get("GITHUB_OUTPUT"):
with open(os.environ["GITHUB_OUTPUT"], "a") as f:
f.write(f"risk_level={risk}\n")
f.write(f"change_count={changes}\n")
elif os.environ.get("GITLAB_CI"):
# GitLab: write to dotenv artifact
with open("plan_output.env", "w") as f:
f.write(f"PLAN_RISK_LEVEL={risk}\n")
f.write(f"PLAN_CHANGE_COUNT={changes}\n")
if __name__ == "__main__":
main()