#!/usr/bin/env node // ═══════════════════════════════════════════════════════════════════════════ // Infrastructure Drift Detection Engine // Jämför Terraform-konfiguration med verklig AWS-miljö // ═══════════════════════════════════════════════════════════════════════════ import { readFileSync } from 'fs'; class DriftDetectionEngine { constructor(terraformDir, discoveryDir) { this.terraformDir = terraformDir; this.discoveryDir = discoveryDir; this.findings = []; } /** * Ladda Terraform-resurser från .tf-filer */ loadTerraformResources() { // TODO: Parsa .tf-filer (kräver HCL-parser) return { aws_instance: ['aamos-ledger'], aws_iam_role: ['ledger_ec2'], aws_security_group: ['ledger'], aws_secretsmanager_secret: ['ledger_jwt', 'ledger_internal', 'ledger_db', 'ledger_redis'] }; } /** * Ladda AWS-resurser från discovery JSON */ loadAWSResources() { const resources = {}; try { const ec2 = JSON.parse(readFileSync(`${this.discoveryDir}/ec2-instances.json`, 'utf8')); resources.ec2 = ec2.Reservations?.flatMap(r => r.Instances?.map(i => ({ id: i.InstanceId, type: i.InstanceType, state: i.State?.Name, tags: i.Tags }))) || []; } catch { resources.ec2 = []; } try { const rds = JSON.parse(readFileSync(`${this.discoveryDir}/rds-instances.json`, 'utf8')); resources.rds = rds.DBInstances?.map(db => ({ id: db.DBInstanceIdentifier, engine: db.Engine, status: db.DBInstanceStatus })) || []; } catch { resources.rds = []; } try { const sg = JSON.parse(readFileSync(`${this.discoveryDir}/security-groups.json`, 'utf8')); resources.security_groups = sg.SecurityGroups?.map(sg => ({ id: sg.GroupId, name: sg.GroupName, description: sg.Description })) || []; } catch { resources.security_groups = []; } try { const secrets = JSON.parse(readFileSync(`${this.discoveryDir}/secrets.json`, 'utf8')); resources.secrets = secrets.SecretList?.map(s => ({ name: s.Name, rotation: s.RotationEnabled })) || []; } catch { resources.secrets = []; } return resources; } /** * Detektera drift */ detect() { const terraform = this.loadTerraformResources(); const aws = this.loadAWSResources(); // 1. Resurser i AWS som inte finns i Terraform for (const ec2 of aws.ec2) { const inTerraform = terraform.aws_instance?.some(name => ec2.tags?.some(t => t.Key === 'Name' && t.Value.includes(name)) ); if (!inTerraform) { this.findings.push({ type: 'DRIFT', severity: 'HIGH', category: 'Orphaned Resource', description: `EC2 ${ec2.id} finns i AWS men inte i Terraform`, terraform: null, aws: ec2, action: 'Import or remove' }); } } // 2. Resurser i Terraform som inte finns i AWS for (const sg of terraform.aws_security_group || []) { const inAWS = aws.security_groups?.some(a => a.name?.includes(sg) ); if (!inAWS) { this.findings.push({ type: 'DRIFT', severity: 'MEDIUM', category: 'Missing Resource', description: `Security Group ${sg} finns i Terraform men inte i AWS`, terraform: sg, aws: null, action: 'Create or update Terraform' }); } } // 3. Secrets utan rotation for (const secret of aws.secrets || []) { if (!secret.rotation) { this.findings.push({ type: 'DRIFT', severity: 'CRITICAL', category: 'Security', description: `Secret ${secret.name} har ingen rotation`, terraform: null, aws: secret, action: 'Enable rotation (STOP-013)' }); } } return this.findings; } /** * Generera rapport */ report() { const findings = this.detect(); return { summary: { total: findings.length, critical: findings.filter(f => f.severity === 'CRITICAL').length, high: findings.filter(f => f.severity === 'HIGH').length, medium: findings.filter(f => f.severity === 'MEDIUM').length, low: findings.filter(f => f.severity === 'LOW').length }, findings, generatedAt: new Date().toISOString() }; } } // CLI if (process.argv[1] === new URL(import.meta.url).pathname) { const engine = new DriftDetectionEngine( '/home/bernt/.openclaw/workspace/terraform/aamos-ledger', '/home/bernt/.openclaw/workspace/terraform/discovery' ); console.log(JSON.stringify(engine.report(), null, 2)); } export { DriftDetectionEngine };