Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 0 additions & 27 deletions .eslintrc.json

This file was deleted.

14 changes: 7 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
cache: 'npm'
cache-dependency-path: |
api-service/package-lock.json
lambdas/risk-engine/package-lock.json
packages/risk-engine/package-lock.json
lambdas/collector/package-lock.json
lambdas/incremental-collector/package-lock.json
lambdas/list-accounts/package-lock.json
Expand All @@ -56,7 +56,7 @@ jobs:
run: npm ci

- name: Install risk-engine deps
working-directory: lambdas/risk-engine
working-directory: packages/risk-engine
run: npm ci

- name: Install collector deps
Expand Down Expand Up @@ -98,7 +98,7 @@ jobs:
run: npm run build

- name: Build Risk Engine
working-directory: lambdas/risk-engine
working-directory: packages/risk-engine
run: npm run build

- name: Build Collector
Expand Down Expand Up @@ -142,10 +142,10 @@ jobs:
tar -C dist -czf "dist/api-service_${version}.tar.gz" api-service

# Risk Engine Lambda
mkdir -p dist/lambdas/risk-engine
cp -r lambdas/risk-engine/dist dist/lambdas/risk-engine/
cp lambdas/risk-engine/package.json lambdas/risk-engine/package-lock.json dist/lambdas/risk-engine/
tar -C dist -czf "dist/risk-engine_${version}.tar.gz" lambdas/risk-engine
mkdir -p dist/packages/risk-engine
cp -r packages/risk-engine/dist dist/packages/risk-engine/
cp packages/risk-engine/package.json packages/risk-engine/package-lock.json dist/packages/risk-engine/
tar -C dist -czf "dist/risk-engine_${version}.tar.gz" packages/risk-engine

# Collector Lambda
mkdir -p dist/lambdas/collector
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,14 @@ temp/
# CloudWatch
awslogs/

# Local planning
plan.md

# Compiled JS — keep source .ts, ignore .js (but allow config)
*.js
*.d.ts
*.d.ts.map
*.js.map
!jest.config.js
!jest.setup.js
!prettier.config.js
Expand Down
148 changes: 79 additions & 69 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,62 @@
## Data Flow

```
AWS Org (multi-account)
┌──────────────────────────────────────────────┐
│ Step Functions State Machine (every 2h) │
│ + EventBridge → SQS → Incremental Updates │
└─────────────┬──────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Collector Lambda (per account, per region) │
│ - STS AssumeRole → SecurityGraphCollectorRole│
│ - 25+ AWS service APIs │
│ - Tag ingestion via ResourceGroupsTaggingAPI │
│ - Internet exposure derivation │
│ - Cross-account trust analysis │
└─────────────┬──────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Graph Writer Lambda │
│ → Neptune (Gremlin) │
└─────────────┬──────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Neptune Security Graph │
│ - 50+ vertex labels │
│ - 30+ edge labels │
│ - Tag-derived properties (env, data_class) │
└────────┬─────────────────────┬───────────────┘
▼ ▼
┌──────────────────────┐ ┌────────────────────────┐
│ Risk Engine Lambda │ │ Compliance Evaluators │
│ (every 1h) │ │ (CIS/SOC2/ISO27001) │
│ - 10 Gremlin rules │ │ - 124 controls │
│ - Risk scoring │ │ - DynamoDB persistence │
└──────────┬───────────┘ └────────────┬───────────┘
▼ ▼
┌──────────────────────┐ ┌────────────────────────┐
│ DynamoDB: │ │ DynamoDB: │
│ SecurityIssues │ │ ComplianceEvidence │
│ │ │ ComplianceReports │
└──────────┬───────────┘ └────────────┬───────────┘
▼ ▼
┌─────────────────────────────────────────────────┐
│ api-service (Express on EKS) │
│ - JWT/Cognito auth (jose) │
│ - RBAC: Viewer/Analyst/Admin via Cognito groups │
│ - Gremlin query parameterization │
└─────────────┬───────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ ui (Next.js) │
│ - /issues, /attack-paths, /compliance/[framework]│
│ - Cognito OIDC bearer tokens │
└─────────────────────────────────────────────────┘
AWS Org (multi-account)
┌──────────────────────────────────────────────────────┐
│ Step Functions State Machine (every 2h) │
│ + EventBridge → SQS → Incremental Updates │
└───────────────────────────┬──────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ Collector Lambda (per account, per region) │
│ - STS AssumeRole → SecurityGraphCollectorRole │
│ - 30+ AWS service APIs │
│ - Tag ingestion via ResourceGroupsTaggingAPI │
│ - Internet exposure derivation │
│ - Cross-account trust analysis │
└───────────────────────────┬──────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ Graph Writer Lambda │
│ → Neptune (Gremlin) │
└───────────────────────────┬──────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ Neptune Security Graph │
│ - 50+ vertex labels │
│ - 30+ edge labels │
│ - Tag-derived properties (env, data_class) │
└────────────┬────────────────────────────┬────────────┘
▼ ▼
┌────────────────────────┐ ┌──────────────────────────┐
│ Risk Engine Lambda │ │ Compliance Evaluators │
│ (every 1h) │ │ (CIS/SOC2/ISO27001) │
│ - 10 Gremlin rules │ │ - 117 controls │
│ - Risk scoring │ │ - DynamoDB persistence │
└────────────┬───────────┘ └─────────────┬────────────┘
▼ ▼
┌────────────────────────┐ ┌──────────────────────────┐
│ DynamoDB: │ │ DynamoDB: │
│ SecurityIssues │ │ ComplianceEvidence │
│ │ │ ComplianceReports │
└────────────────────────┘ └──────────────────────────┘
│ │
└─────────────┬──────────────┘
┌──────────────────────────────────────────────────────┐
│ api-service (Express on EKS) │
│ - JWT/Cognito auth (jose) │
│ - RBAC: Viewer/Analyst/Admin via Cognito groups │
│ - Gremlin query parameterization │
└──────────────────────────┬───────────────────────────┘
┌──────────────────────────────────────────────────────┐
│ ui (Next.js) │
│ - /issues, /attack-paths, /compliance/[framework] │
│ - Cognito OIDC bearer tokens │
└──────────────────────────────────────────────────────┘
```

## Graph Schema
Expand All @@ -71,7 +73,7 @@
| `Ec2Instance` | EC2 | `arn`, `account_id`, `instance_type`, `state`, `public_ip` |
| `NetworkInterface` | EC2 | `arn`, `subnet_id`, `vpc_id` |
| `SecurityGroup` | EC2 | `arn`, `vpc_id`, `is_world_open` |
| `SecurityGroupRule` | EC2 | `protocol`, `port_range`, `cidr_block` |
| `SecurityGroupRule` | EC2 | `protocol`, `port_from`, `port_to`, `port_range`, `cidr_block` |
| `Vpc` | EC2 | `arn`, `cidr_block`, `flow_logs_enabled` |
| `Subnet` | EC2 | `arn`, `vpc_id`, `is_public` |
| `InternetGateway` | EC2 | `arn` |
Expand All @@ -83,9 +85,11 @@
| `IamUser` | IAM | `arn`, `password_enabled`, `mfa_enabled` |
| `IamRole` | IAM | `arn`, `has_cross_account_trust`, `is_admin_role` |
| `IamPolicy` | IAM | `arn` |
| `IamPolicyDocument` | IAM | `arn`, `policy_arn`, `document_json`, `policy_type` |
| `IamPolicyStatement` | IAM | `arn`, `effect`, `actions`, `resources`, `has_wildcard_resource`, `has_wildcard_action` |
| `AccountPasswordPolicy` | IAM | `min_password_length`, etc. |
| `KmsKey` | KMS | `arn`, `key_state` |
| `RdsInstance` | RDS | `arn`, `publicly_accessible`, `storage_encrypted` |
| `RdsInstance` | RDS | `arn`, `publicly_accessible`, `is_publicly_accessible`, `storage_encrypted` |
| `EksCluster` | EKS | `arn`, `version` |
| `EcrRepository` | ECR | `arn`, `name` |
| `ContainerImage` | ECR | `arn`, `digest`, `tag` |
Expand All @@ -96,7 +100,7 @@
| `ConfigRule` | Config | `arn` |
| `GuardDutyDetector` | GuardDuty | `arn`, `status` |
| `AccessAnalyzer` | IAM Access Analyzer | `arn` |
| `LambdaFunction` | Lambda | `arn`, `runtime`, `role` |
| `LambdaFunction` | Lambda | `arn`, `runtime`, `role`, `is_in_vpc`, `has_internet_access` |
| `LambdaAlias` | Lambda | `arn` |
| `ApiGateway` | API Gateway | `arn`, `endpoint_type` |
| `ApiGatewayStage` | API Gateway | `arn` |
Expand All @@ -117,35 +121,41 @@
| Edge | Meaning |
|------|---------|
| `EXPOSES` | Internet → IGW/LB/ExternalAccount |
| `CONTAINS` | VPC → Subnet/Instance; Account → Resource |
| `ATTACHED_TO` | ENI → Instance; IGW → VPC |
| `CONTAINS` | VPC → Subnet/Instance; Account → Resource; Policy → Statement |
| `ATTACHED_TO` | ENI → Instance; IGW → VPC; IAMPrincipal → Policy |
| `PROTECTS` | SecurityGroup → NetworkInterface |
| `PART_OF` | Rule → SecurityGroup |
| `ALLOWS_INGRESS` | SG rule → SG (legacy compatibility) |
| `HAS_IAM_ROLE` | Instance → IAMRole |
| `ALLOWS_ACCESS_TO` | IAMRole → Resource |
| `CONTAINS` | (also: Policy → Statement) |
| `TRUSTS` | IAMRole → ExternalAccount |
| `PART_OF` | SecurityGroupRule → SecurityGroup |
| `HAS_IAM_ROLE` | EC2/Lambda → IAMRole |
| `GRANTS` | IamPolicyStatement → Resource (non-wildcard) |
| `TRUSTS` | IAMRole → ExternalAccount/Principal |
| `OWNS` | Account root → Resource |
| `HAS_FINDING` | Account → SecurityHub finding |
| `HAS_FINDING` | Account → SecurityHub/AccessAnalyzer finding |
| `HAS_STATUS` | CloudTrail → CloudTrailStatus |
| `IN_SUBNET` | LB/NatGW → Subnet |
| `HAS_ENDPOINT` | VPC → VPC Endpoint |
| `HAS_NACL` / `HAS_ROUTE_TABLE` | VPC → NACL/RouteTable |
| `MEMBER_OF` | IamUser → IamGroup |
| `BELONGS_TO` | ContainerImage → EcrRepository |
| `RUNS_ON` | ContainerImage → workload (Phase 1) |
| `RUNS_ON` | ContainerImage → workload (not yet created) |
| `HAS_CVE` | ContainerImage → Vulnerability (not yet created) |
| `HAS_ALIAS` / `HAS_STAGE` | Function → Alias/Stage |

### Risk Rule Properties (queried by Gremlin rules)

| Property | Type | Source |
|----------|------|--------|
| `is_internet_exposed` | boolean | Collector derivation |
| `is_internet_exposed` | boolean | Collector exposure derivation |
| `is_publicly_accessible` | boolean | S3/RDS direct fetch |
| `is_in_vpc` | boolean | Lambda VPC config |
| `has_internet_access` | boolean | Lambda VPC config (inverse) |
| `crown_jewel` | boolean | `crown_jewel` tag |
| `data_classification` | string | `data_classification` tag |
| `env` | string | `env` or `environment` tag |
| `is_publicly_accessible` | boolean | S3/RDS direct fetch |
| `has_cross_account_trust` | boolean | IAM trust analysis |
| `has_wildcard_resource` | boolean | IAM policy statement analysis |
| `has_wildcard_action` | boolean | IAM policy statement analysis |
| `port_from` / `port_to` | number | Security group rule |
| `flow_logs_enabled` | boolean | EC2 DescribeFlowLogs |

## Authentication

Expand Down
1 change: 1 addition & 0 deletions api-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"dependencies": {
"@aws-sdk/client-dynamodb": "^3.450.0",
"@aws-sdk/client-secrets-manager": "^3.1073.0",
"@aws-sdk/lib-dynamodb": "^3.450.0",
"@aws-sdk/util-dynamodb": "^3.450.0",
"@khalifa/risk-engine": "*",
Expand Down
19 changes: 19 additions & 0 deletions api-service/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,25 @@ const app = express();

app.use(express.json());

app.use((_req: Request, res: Response, next: NextFunction) => {
const allowedOrigins = (process.env.CORS_ALLOWED_ORIGINS || '')
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const origin = _req.headers.origin;
if (origin && (allowedOrigins.length === 0 || allowedOrigins.includes(origin))) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Allow-Credentials', 'true');
}
if (_req.method === 'OPTIONS') {
res.status(204).end();
return;
}
next();
});

app.get('/health', (_req: Request, res: Response) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
Expand Down
Loading
Loading