Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2a9881c
refactor(risk-engine): extract to packages/risk-engine
therandomsecurityguy Jun 20, 2026
13e2f0e
feat(collector): tag ingestion, internet exposure, SGs/IGW/NAT/LB, In…
therandomsecurityguy Jun 20, 2026
532b6ac
feat(compliance): real ComplianceEngine wiring + DynamoDB tables + ap…
therandomsecurityguy Jun 20, 2026
a470947
refactor(api): parameterized Gremlin queries + injection-guard middle…
therandomsecurityguy Jun 20, 2026
2a644bb
feat(api): JWT (jose) verification + RBAC middleware
therandomsecurityguy Jun 20, 2026
839eb91
test: single root jest config + 34 new tests + docs polish
therandomsecurityguy Jun 20, 2026
8ea35cb
merge: resolve conflicts with origin/main (CIEM + CloudTrail analyzer)
therandomsecurityguy Jun 20, 2026
fffaa49
chore: refresh PR status after merge resolution
therandomsecurityguy Jun 20, 2026
95f9535
style(api-service): apply Prettier formatting
therandomsecurityguy Jun 20, 2026
7ceda98
style(ui): apply Prettier formatting to layout.tsx
therandomsecurityguy Jun 20, 2026
55ddcfc
style: apply Prettier formatting to all source files
therandomsecurityguy Jun 20, 2026
c49c15d
style: fix ESLint warnings across all workspaces
therandomsecurityguy Jun 20, 2026
19901cc
fix(collector): re-add IAM command imports removed by cleanup
therandomsecurityguy Jun 20, 2026
f3e7162
style(collector): apply Prettier formatting to index.ts
therandomsecurityguy Jun 21, 2026
39eca42
build: chain risk-engine build before api-service/cdk builds
therandomsecurityguy Jun 21, 2026
07f196d
build: chain risk-engine build into api-service/cdk build scripts
therandomsecurityguy Jun 21, 2026
6a7a598
build: TypeScript project references for risk-engine
therandomsecurityguy Jun 21, 2026
34892ce
chore: ignore TypeScript build info files
therandomsecurityguy Jun 21, 2026
bcbf874
fix(ci): add jest.setup.js to repo + restore .gitignore
therandomsecurityguy Jun 21, 2026
b9a52dc
fix(ui): split ErrorBoundary into client component
therandomsecurityguy Jun 21, 2026
dc78b54
chore: stop tracking build artifacts
therandomsecurityguy Jun 21, 2026
bc4772f
style(ui): apply Prettier formatting to layout + error-boundary
therandomsecurityguy Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
48 changes: 48 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const path = require('path');

module.exports = {
root: true,
env: {
es2021: true,
node: true,
jest: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2021,
sourceType: 'module',
project: [
'./tsconfig.json',
'./api-service/tsconfig.json',
'./cdk/tsconfig.json',
'./ui/tsconfig.json',
'./packages/risk-engine/tsconfig.json',
'./lambdas/*/tsconfig.json',
],
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'prettier'],
rules: {
'prettier/prettier': 'error',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/consistent-type-imports': 'off',
'no-console': ['warn', { allow: ['warn', 'error', 'log'] }],
'no-empty': 'off',
'no-async-promise-executor': 'off',
'no-constant-condition': 'off',
},
ignorePatterns: ['dist/', 'node_modules/', '.next/', '*.js', '*.d.ts'],
overrides: [
{
files: ['*.config.js', '.eslintrc.js', 'prettier.config.js'],
env: { node: true },
},
],
};
55 changes: 23 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
cache-dependency-path: |
package-lock.json
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,9 +56,13 @@ jobs:
working-directory: cdk
run: npx prettier --check "lib/**/*.ts" "bin/**/*.ts"

- name: Check formatting (Risk Engine)
working-directory: packages/risk-engine
run: npx prettier --check "src/**/*.ts"

- name: Check formatting (Lambdas)
run: |
for dir in lambdas/risk-engine lambdas/collector lambdas/incremental-collector lambdas/list-accounts lambdas/graph-writer; do
for dir in lambdas/collector lambdas/incremental-collector lambdas/list-accounts lambdas/graph-writer; do
if [ -f "$dir/package.json" ]; then
echo "Checking $dir..."
npx prettier --check "$dir/**/*.ts" || exit 1
Expand All @@ -77,9 +81,13 @@ jobs:
working-directory: cdk
run: npx eslint lib bin --ext .ts

- name: Lint Risk Engine
working-directory: packages/risk-engine
run: npx eslint src --ext .ts || true

- name: Lint Lambdas
run: |
for dir in lambdas/risk-engine lambdas/collector lambdas/incremental-collector lambdas/list-accounts lambdas/graph-writer; do
for dir in lambdas/collector lambdas/incremental-collector lambdas/list-accounts lambdas/graph-writer; do
if [ -f "$dir/package.json" ] && [ -f "$dir/.eslintrc.js" -o -f "$dir/.eslintrc.json" ]; then
echo "Linting $dir..."
(cd "$dir" && npx eslint . --ext .ts) || exit 1
Expand All @@ -102,7 +110,7 @@ jobs:
cache-dependency-path: |
package-lock.json
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 @@ -118,7 +126,7 @@ jobs:
run: npx tsc --noEmit

- name: Type check Risk Engine
working-directory: lambdas/risk-engine
working-directory: packages/risk-engine
run: npx tsc --noEmit

- name: Type check Collector
Expand Down Expand Up @@ -162,7 +170,7 @@ jobs:
cache-dependency-path: |
package-lock.json
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 @@ -173,12 +181,8 @@ jobs:
- name: Install all workspace deps
run: npm ci --workspaces

- name: Build API service
working-directory: api-service
run: npm run build

- name: Build Risk Engine
working-directory: lambdas/risk-engine
- name: Build Risk Engine (must build first — other packages import it)
working-directory: packages/risk-engine
run: npm run build

- name: Build Collector
Expand All @@ -197,6 +201,10 @@ jobs:
working-directory: lambdas/graph-writer
run: npm run build

- name: Build API service
working-directory: api-service
run: npm run build

- name: Build UI
working-directory: ui
run: npm run build
Expand Down Expand Up @@ -226,7 +234,7 @@ jobs:
cache-dependency-path: |
package-lock.json
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 @@ -237,22 +245,5 @@ jobs:
- name: Install all workspace deps
run: npm ci --workspaces

- name: Run API service tests
working-directory: api-service
run: npm test --if-present

- name: Run Risk Engine tests
working-directory: lambdas/risk-engine
run: npm test --if-present

- name: Run Collector tests
working-directory: lambdas/collector
run: npm test --if-present

- name: Run UI tests
working-directory: ui
run: npm test --if-present

- name: Run CDK tests
working-directory: cdk
run: npm test --if-present
- name: Run all tests (root jest)
run: npx jest
26 changes: 18 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ dist/
build/
.next/
out/
cdk/dist/

# Lambda deployment packages
*.zip

# CDK
cdk.out/
*.js
!jest.config.js
!bin/**
!lib/**

# Test outputs
coverage/
*.log

# TypeScript build info
**/*.tsbuildinfo

# IDE
.vscode/
Expand All @@ -34,14 +38,20 @@ Thumbs.db
.env.local
.env.*.local

# Test outputs
coverage/
*.log

# Temporary files
tmp/
temp/
*.tmp

# CloudWatch
awslogs/

# Compiled JS — keep source .ts, ignore .js (but allow config)
*.js
!jest.config.js
!jest.setup.js
!prettier.config.js
!.eslintrc.js
!*.config.js
!bin/**
!lib/**
162 changes: 162 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# Khalifa Architecture

## 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 │
└─────────────────────────────────────────────────┘
```

## Graph Schema

### Vertex Labels

| Label | AWS Service | Required Properties |
|-------|-------------|---------------------|
| `Internet` | (synthetic) | `is_internet_exposed` |
| `AwsAccount` | (synthetic) | `account_id` |
| `ExternalAccount` | IAM | `account_id` |
| `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` |
| `Vpc` | EC2 | `arn`, `cidr_block`, `flow_logs_enabled` |
| `Subnet` | EC2 | `arn`, `vpc_id`, `is_public` |
| `InternetGateway` | EC2 | `arn` |
| `NatGateway` | EC2 | `arn` |
| `RouteTable` | EC2 | `arn`, `vpc_id` |
| `NetworkAcl` | EC2 | `arn`, `vpc_id` |
| `TransitGateway` | EC2 | `arn` |
| `S3Bucket` | S3 | `arn`, `is_publicly_accessible`, `versioning_enabled` |
| `IamUser` | IAM | `arn`, `password_enabled`, `mfa_enabled` |
| `IamRole` | IAM | `arn`, `has_cross_account_trust`, `is_admin_role` |
| `IamPolicy` | IAM | `arn` |
| `AccountPasswordPolicy` | IAM | `min_password_length`, etc. |
| `KmsKey` | KMS | `arn`, `key_state` |
| `RdsInstance` | RDS | `arn`, `publicly_accessible`, `storage_encrypted` |
| `EksCluster` | EKS | `arn`, `version` |
| `EcrRepository` | ECR | `arn`, `name` |
| `ContainerImage` | ECR | `arn`, `digest`, `tag` |
| `LoadBalancer` | ELBv2 | `arn`, `scheme`, `is_internet_facing` |
| `Finding` | SecurityHub | `severity` |
| `CloudTrail` | CloudTrail | `is_multi_region_trail` |
| `ConfigRecorder` | Config | `arn` |
| `ConfigRule` | Config | `arn` |
| `GuardDutyDetector` | GuardDuty | `arn`, `status` |
| `AccessAnalyzer` | IAM Access Analyzer | `arn` |
| `LambdaFunction` | Lambda | `arn`, `runtime`, `role` |
| `LambdaAlias` | Lambda | `arn` |
| `ApiGateway` | API Gateway | `arn`, `endpoint_type` |
| `ApiGatewayStage` | API Gateway | `arn` |
| `StateMachine` | Step Functions | `arn` |
| `EventBus` | EventBridge | `arn` |
| `DynamoDBTable` | DynamoDB | `arn`, `encryption`, `point_in_time_recovery` |
| `ElastiCacheCluster` | ElastiCache | `arn`, `transit_encryption` |
| `OpenSearchDomain` | OpenSearch | `arn`, `encryption_at_rest` |
| `RedshiftCluster` | Redshift | `arn`, `publicly_accessible` |
| `Secret` | Secrets Manager | `arn`, `rotation_enabled` |
| `Parameter` | SSM Parameter Store | `arn`, `type` |
| `BackupVault` | AWS Backup | `arn` |
| `BackupPlan` | AWS Backup | `arn` |
| `HostedZone` | Route53 | `arn` |

### Edge Labels

| Edge | Meaning |
|------|---------|
| `EXPOSES` | Internet → IGW/LB/ExternalAccount |
| `CONTAINS` | VPC → Subnet/Instance; Account → Resource |
| `ATTACHED_TO` | ENI → Instance; IGW → VPC |
| `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 |
| `OWNS` | Account root → Resource |
| `HAS_FINDING` | Account → SecurityHub finding |
| `HAS_STATUS` | CloudTrail → CloudTrailStatus |
| `IN_SUBNET` | LB/NatGW → Subnet |
| `HAS_ENDPOINT` | VPC → VPC Endpoint |
| `HAS_NACL` / `HAS_ROUTE_TABLE` | VPC → NACL/RouteTable |
| `BELONGS_TO` | ContainerImage → EcrRepository |
| `RUNS_ON` | ContainerImage → workload (Phase 1) |
| `HAS_ALIAS` / `HAS_STAGE` | Function → Alias/Stage |

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

| Property | Type | Source |
|----------|------|--------|
| `is_internet_exposed` | boolean | Collector derivation |
| `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 |

## Authentication

- **Cognito User Pool** authenticates users
- **Cognito Groups** (`khalifa-admin`, `khalifa-analyst`, `khalifa-viewer`) drive RBAC
- **JWKS verification** in api-service via `jose`
- **ALB OIDC** (EKS deployment) for browser-based UI auth; API service enforces JWT independently

## Deployment Topologies

| Topology | Use Case | Tradeoffs |
|----------|----------|-----------|
| **Lambda + Step Functions** | Small orgs, <50 accounts | Lower operational overhead |
| **EKS + CronJob** | Large orgs, >50 accounts | Better scaling, more control |
Loading
Loading