Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
de44e4d
Migrated APT emulations
ehayushpathak Jun 20, 2026
2bcf77d
fix: correct technique_count and phase metadata in k8s MANIFESTs
ehayushpathak Jun 28, 2026
66e023d
fix: add startup health-check polling and extend attack chains in k8s…
ehayushpathak Jun 28, 2026
eef2053
fix: remove unused json import from rbac_impersonation attack.py
ehayushpathak Jun 28, 2026
135e962
feat: complete k8s_pvc_psa_bypass attack chain with pod creation and …
ehayushpathak Jun 28, 2026
d8d2793
feat: complete k8s_external_ips_mitm attack chain with traffic interc…
ehayushpathak Jun 28, 2026
9f4e8ec
feat: complete k8s_pod_status_mitm attack chain with traffic redirect…
ehayushpathak Jun 28, 2026
5c7109f
feat: add T1069 and T1548 detection rules for k8s_rbac_impersonation
ehayushpathak Jun 28, 2026
88e7d23
feat: add T1609 and T1611 detection rules for k8s_writable_log_escape
ehayushpathak Jun 28, 2026
281970a
feat: add T1211 and T1611 detection rules for k8s_pvc_psa_bypass
ehayushpathak Jun 28, 2026
2073194
feat: add T1557 detection rules for k8s_external_ips_mitm
ehayushpathak Jun 28, 2026
602e28f
feat: add T1557 detection rules for k8s_pod_status_mitm
ehayushpathak Jun 28, 2026
9b4fffb
fix: sync phase_count and attack_path with extended attack chains in …
ehayushpathak Jun 28, 2026
0733b51
feat: add k8s emulations with infra, detection rules, and attack chains
ehayushpathak Jun 29, 2026
031ea2d
fix(k8s): bump to schema_version 3 — add services, readiness, referen…
ehayushpathak Jun 29, 2026
451e3c8
fix(k8s): add missing __init__.py and PLAYBOOK.md to all 5 k8s emulat…
ehayushpathak Jun 29, 2026
23465a9
fix(registry): skip dot-directories and demote no-MANIFEST warning to…
ehayushpathak Jul 2, 2026
01730d4
docs: add k8s RBAC impersonation walkthrough
ehayushpathak Jul 3, 2026
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
59 changes: 59 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# MayaTrail Step1 — Agent Orientation

AWS security simulation platform: Django REST API + React/TypeScript SPA + Celery workers + Pulumi IaC.
**This platform creates intentionally vulnerable AWS resources. Only run in isolated test accounts.**

## Repository Layout

```
backend/ Django REST API (Gunicorn/Celery)
frontend/UI/ React + TypeScript SPA (Vite → Nginx)
emulations/ Attack emulation plugin packages
src/ Legacy standalone Pulumi IaC
simulations/ Legacy standalone boto3 scripts (not active)
docker-compose.yml
```

## Commands

```bash
# Full stack
docker-compose up --build

# Backend only
cd backend && pip install -r requirements.txt
python manage.py makemigrations users infrastructure emulations logs && python manage.py migrate
python manage.py runserver

# Frontend only
cd frontend/UI && npm install && npm run dev

# Celery worker
cd backend && celery -A config worker --queues=enterprise --loglevel=info
```

## Code Style

- Backend: Python 3.12, Django 5, DRF. Follow existing patterns in `backend/apps/`.
- Frontend: TypeScript strict mode, React functional components, Tailwind CSS.
- No `type: ignore` or `any` in new TypeScript code without a comment explaining why.
- New Django apps go in `backend/apps/<name>/` with the standard structure (models, views, serializers, urls, tasks).

## Key Files

- `emulations/registry.py` — plugin auto-discovery logic
- `emulations/<name>/MANIFEST.py` — emulation metadata schema
- `backend/config/settings/base.py` — Django settings (all secrets via `python-decouple`)
- `backend/apps/emulations/tasks.py` — Celery tasks that drive deploy/attack/destroy lifecycle
- `docker-compose.yml` — authoritative service topology

## Environment

Copy `backend/.env.example` to `backend/.env`. Critical vars:
- `PULUMI_CONFIG_PASSPHRASE` — never change after stacks are created
- `EMULATIONS_BASE_DIR` — set to `/opt/emulations` in Docker, absolute path to `emulations/` locally
- `STATE_BUCKET` — S3 bucket for Pulumi state (`mayatrail-state-bucket`, region `ap-south-1`)

## Testing

No automated test suite currently. Validate changes by running the stack locally and exercising the affected API endpoints or UI flows.
24 changes: 20 additions & 4 deletions backend/apps/emulations/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,19 +94,35 @@ def _assume_user_role(user) -> dict[str, str]:
Credentials are never stored in the database — they are generated per-task
invocation and discarded once the task completes.

If aws_role_arn is not set on the user, falls back to the platform-level
credentials already present in the environment (dev / direct-cred scenario).

Args:
user: Authenticated User instance with a valid aws_role_arn.
user: Authenticated User instance with an optional aws_role_arn.

Returns:
Dict with keys: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
AWS_SESSION_TOKEN.
Dict with keys: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and
optionally AWS_SESSION_TOKEN.

Raises:
botocore.exceptions.ClientError if the role cannot be assumed.
"""
role_arn = getattr(user, "aws_role_arn", None)
if not role_arn:
# No role configured — use the platform credentials directly.
creds: dict[str, str] = {
"AWS_ACCESS_KEY_ID": os.environ.get("AWS_ACCESS_KEY_ID", ""),
"AWS_SECRET_ACCESS_KEY": os.environ.get("AWS_SECRET_ACCESS_KEY", ""),
}
session_token = os.environ.get("AWS_SESSION_TOKEN", "")
if session_token:
creds["AWS_SESSION_TOKEN"] = session_token
logger.info("No aws_role_arn for user %s — using platform credentials", user.id)
return creds

sts = boto3.client("sts")
assumed = sts.assume_role(
RoleArn=user.aws_role_arn,
RoleArn=role_arn,
RoleSessionName=f"mayatrail-emulation-{user.id}",
DurationSeconds=3600,
)
Expand Down
173 changes: 173 additions & 0 deletions docs/k8s_rbac_impersonation-walkthrough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# k8s_rbac_impersonation — End-to-End Test Walkthrough

Date: 2026-07-02
Branch: `k8s_emulation`
Stack deployed to: `ap-south-1`

---

## 1. Start the stack

```bash
docker-compose up --build
```

Wait until the backend prints `Booting worker with pid: ...`.

---

## 2. Create an enterprise user

```bash
docker-compose exec backend python manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); u = User.objects.create_user(username='testadmin1', email='test@test1.com', password='testpass123', is_active=True); u.is_enterprise = True; u.save(); print('Created:', u.username, '| enterprise:', u.is_enterprise)"
```

**Output:**
```
Created: testadmin1 | enterprise: True
```

Then mark the user as verified (required by `IsEnterpriseUser` permission):

```bash
docker-compose exec backend python manage.py shell -c "from django.contrib.auth import get_user_model; User = get_user_model(); u = User.objects.get(username='testadmin1'); u.is_verified = True; u.save(); print('is_verified:', u.is_verified)"
```

**Output:**
```
is_verified: True
```

---

## 3. Get a JWT access token

```powershell
$resp = Invoke-RestMethod -Method Post -Uri "http://localhost:8000/api/auth/login/" -ContentType "application/json" -Body '{"username": "testadmin1", "password": "testpass123"}'
$TOKEN = $resp.access
$TOKEN
```

**Output:**
```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNz
gyOTk1OTcyLCJpYXQiOjE3ODI5OTIzNzIsImp0aSI6IjMyM2I5MmNmN2Q4YTQ0MGFiNTY4YjIzNGI0
ZGJjYmE4IiwidXNlcl9pZCI6MTAsInVzZXJuYW1lIjoidGVzdGFkbWluMSIsImlzX3ZlcmlmaWVkIjp
mYWxzZSwiaXNfZGVtbyI6ZmFsc2UsImRlbW9fdXNlZCI6ZmFsc2UsImRlbW9fZXhwaXJlc19hdCI6bn
VsbCwiYXV0aF9tZXRob2QiOiJjcmVkZW50aWFscyJ9.4m3etem2az3YaMdXvwqyJUWHtwaeXMnNqa14
M5MJsqc
```

---

## 4. Deploy the emulation stack

```powershell
$deploy = Invoke-RestMethod -Method Post -Uri "http://localhost:8000/api/emulations/deploy/" -ContentType "application/json" -Headers @{Authorization="Bearer $TOKEN"} -Body '{"emulation_type": "k8s_rbac_impersonation", "stack_name": "k8s-rbac-test1"}'
$STACK_ID = $deploy.stackId
$deploy
```

**Output:**
```
stackId stackName
------- ---------
2a6c19db-bcb7-4066-9090-2c39f5b7044b k8s-rbac-test1
```

Pulumi provisions: VPC, subnet, internet gateway, route table, security group, EC2 (Amazon Linux 2023, t3.micro), Flask simulator on port 8080.

---

## 5. Poll until ready

```powershell
Invoke-RestMethod -Method Get -Uri "http://localhost:8000/api/stacks/$STACK_ID/" -Headers @{Authorization="Bearer $TOKEN"}
```

**Output (when ready, ~3 min):**
```
id : 2a6c19db-bcb7-4066-9090-2c39f5b7044b
name : k8s-rbac-test1
region : ap-south-1
status : ready_for_attack
outputs : @{vuln_instance_ip=13.201.122.179}
owner : testadmin1
emulation_type : k8s_rbac_impersonation
expires_at : 2026-07-02T13:42:21.998321Z
```

Status transitions: `deploying` → `ec2_booting` → `ready_for_attack`

---

## 6. Trigger the attack

```powershell
$run = Invoke-RestMethod -Method Post -Uri "http://localhost:8000/api/emulations/$STACK_ID/attack/" -Headers @{Authorization="Bearer $TOKEN"} -ContentType "application/json"
$RUN_ID = $run.runId
$run
```

**Output:**
```
runId stackId
----- -------
caa85015-26f4-4af4-92e4-8e8d499bb7e2 2a6c19db-bcb7-4066-9090-2c39f5b7044b
```

---

## 7. Poll the run result

```powershell
Invoke-RestMethod -Method Get -Uri "http://localhost:8000/api/emulations/$RUN_ID/" -Headers @{Authorization="Bearer $TOKEN"}
```

**Output:**
```
id : caa85015-26f4-4af4-92e4-8e8d499bb7e2
stack : 2a6c19db-bcb7-4066-9090-2c39f5b7044b
emulation_type : k8s_rbac_impersonation
status : completed
phase_current : 0
phase_total : 2
stdout : [*] Waiting for simulator to become ready...
[+] Simulator is ready.
[*] Phase 1: Self Subject Rules Review (Permission Enumeration)
[+] Successfully queried SelfSubjectRulesReviews!
[+] Permissions returned: [{'apiGroups': ['*'], 'resources': ['serviceaccounts'], 'verbs': ['impersonate']}, {'apiGroups':
['authorization.k8s.io'], 'resources': ['selfsubjectrulesreviews'], 'verbs': ['create']}]
[*] Phase 2: Attempting Privilege Escalation via Impersonation
[+] Privilege Escalation Successful!
[+] Retrieved Secret: {'items': [{'data': {'password': 'c3VwZXItc2VjcmV0LWszcw=='}, 'metadata': {'name': 'db-credential'}}]}
stderr :
started_at : 2026-07-02T11:46:10.182236Z
completed_at : 2026-07-02T11:46:10.419976Z
```

### Attack phases

| Phase | Technique | Result |
|-------|-----------|--------|
| 1 | T1069 — Permission Groups Discovery via `SelfSubjectRulesReview` | Found `impersonate` verb on service accounts |
| 2 | T1548 — Abuse Elevation Control via `Impersonate-User: admin-sa` + `Impersonate-Group: system:masters` headers | Retrieved secret `db-credential` (`super-secret-k3s`) |

---

## 8. Destroy the stack

```powershell
Invoke-RestMethod -Method Post -Uri "http://localhost:8000/api/emulations/$STACK_ID/destroy/" -Headers @{Authorization="Bearer $TOKEN"} -ContentType "application/json"
```

Pulumi tears down all 7 resources. Stack DB record is deleted on success.

---

## Notes

- `IsEnterpriseUser` permission checks `is_verified=True` (not `is_enterprise`) — both flags must be set when creating test users via shell.
- The Flask simulator on the EC2 instance mocks the Kubernetes API; no real cluster is deployed.
- The secret password base64-decodes to `super-secret-k3s`.
- Total wall time deploy→attack→destroy: ~5 minutes. AWS cost per run: ~$0.01.
Loading