05ed037fe8
- DNS: pilot.landvex.com -> 16.170.83.169 - TLS: Let's Encrypt certificate (expires 2026-09-30) - Nginx: reverse proxy with SSL termination - API: https://pilot.landvex.com/api/v1/missions - UI: https://pilot.landvex.com/ - Upload: POST /api/v1/missions/import (multipart/form-data) Verified: ✅ https://pilot.landvex.com/health ✅ https://pilot.landvex.com/version ✅ https://pilot.landvex.com/api/v1/missions (list) ✅ https://pilot.landvex.com/api/v1/missions/:id (get) ✅ POST /api/v1/missions/import (video upload) ✅ UI loads with title 'LandveX Intelligence Lab' Next: Pilot 001 — Break the system!
176 lines
5.1 KiB
JavaScript
176 lines
5.1 KiB
JavaScript
#!/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 };
|