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
26 changes: 26 additions & 0 deletions .github/workflows/secret-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Detect committed secrets (tokens, keys, passwords) on main and PRs targeting main.
name: Secret scan

on:
push:
branches: [main, master]
pull_request:
branches: [main, master]
workflow_dispatch:

permissions:
contents: read

jobs:
gitleaks:
name: gitleaks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Run gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 changes: 15 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# gitleaks config — https://github.com/gitleaks/gitleaks
title = "ReaperC2 gitleaks"

[extend]
useDefault = true

# Template/example credentials are placeholders, not live secrets.
[[allowlists]]
description = "k3s deployment credential templates"
paths = [
'''deployments/k3s/mongo-secret\.yaml''',
'''deployments/k3s/admin-bootstrap-secret\.yaml''',
'''deployments/k3s/config\.example\.env''',
'''\.env\.example''',
]
14 changes: 11 additions & 3 deletions deployments/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,17 @@ REGISTRY_PASSWORD='your-password-here'

| Variable | Purpose |
|----------|---------|
| `MONGO_NFS_SERVER` / `MONGO_NFS_PATH` | NFS export for MongoDB PV |
| `MONGO_NFS_SERVER` / `MONGO_NFS_PATH` | NFS export for MongoDB PV (e.g. `192.168.1.100:/export/reaperc2-metric`) |
| `MONGO_DATABASE` | Mongo database name (default `reaperc2-metric`) |
| `MONGO_NODE_HOSTNAME` | Node for Mongo StatefulSet (e.g. `control-plane`; mongo:7 may need ARMv8.2+) |
| `REGISTRY_SERVER` / `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` | Private registry push/pull |
| `REGISTRY_SECRET_NAME` | Kubernetes pull secret name (default `registry-credentials`) |
| `REAPERC2_IMAGE` / `REAPERC2_IMAGE_TAG` | Container image to deploy |
| `OLLAMA_PULL_MODELS` | Comma-separated tags pulled on first Ollama start (e.g. `llama3.2:latest,gpt-oss:latest`) |
| `INGRESS_HOST` | Public beacon hostname (default `beacons.example.com`) |
| `CERT_MANAGER_ISSUER` | ClusterIssuer for beacon TLS (default `letsencrypt-prod`) |
| `OLLAMA_NFS_SERVER` / `OLLAMA_NFS_PATH` | NFS export for Ollama model weights (e.g. `/export/ollama-models`) |
| `OLLAMA_PULL_MODELS` | Comma-separated tags pulled on first Ollama start |
| `OLLAMA_STORAGE_CLASS` | Fallback PVC class only if `OLLAMA_NFS_*` is unset |
| `REAPERC2_NAMESPACE` | Default `reaperc2-ns` |
| `KUBECONFIG` | Optional kubeconfig path |

Expand All @@ -43,7 +49,9 @@ REGISTRY_PASSWORD='your-password-here'

## Mongo secrets (OnPrem `k3s/`)

Edit [`k3s/mongo-secret.yaml`](k3s/mongo-secret.yaml) before first deploy (`root_*`, `app_*`). Do not change passwords after Mongo has initialized the NFS volume without resetting data.
Edit [`k3s/mongo-secret.yaml`](k3s/mongo-secret.yaml) before first deploy (`root_*`, `app_*`). Database name is **`reaperc2-metric`** (`MONGO_DATABASE`). Do not change passwords after Mongo has initialized the NFS volume without resetting data.

**Ingress:** beacons only on `INGRESS_HOST` (default `beacons.example.com`). Admin UI uses `kubectl port-forward … 8443:8443` — not exposed on ingress (same as [`k8s/reaperc2/`](k8s/reaperc2/)).

---

Expand Down
39 changes: 31 additions & 8 deletions deployments/k3s/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,71 @@ Deploy ReaperC2 on **k3s** with **NFS MongoDB**, a **private registry** image, a

> **Not** the DocumentDB path. For k3s + **Amazon DocumentDB**, use [`../k8s/reaperc2/`](../k8s/reaperc2/) with `REAPER_CLUSTER=k3s` ([`../k8s/k3s/README.md`](../k8s/k3s/README.md)).

## Hostnames

| Surface | Access |
|---------|--------|
| **Beacons** | Public HTTPS on `INGRESS_HOST` (default `beacons.example.com`) → service `:8080` (`deploy.sh --with-ingress`) |
| **Admin UI** | **Port-forward only** (same as AWS reaperc2 path — not on public ingress) |
| **Operator AI** | `/ai` after admin port-forward |

## Prerequisites

- k3s (`curl -sfL https://get.k3s.io | sh -`)
- `kubectl` or `sudo k3s kubectl`
- NFS export for MongoDB; `nfs-common` on nodes
- NFS export for MongoDB (e.g. `/export/reaperc2-metric` on your NAS); `nfs-common` on nodes
- Pin MongoDB to a capable node if needed (`MONGO_NODE_HOSTNAME=control-plane`; mongo:7 needs ARMv8.2 on some ARM boards)
- Go on the build machine (for `build-push-image.sh`)
- ≥ 4 GiB RAM recommended for Ollama
- For public beacons: Traefik + cert-manager with ClusterIssuer `letsencrypt-prod` (or set `CERT_MANAGER_ISSUER` in `config.env`)
- DNS for `INGRESS_HOST` → cluster ingress

## Quick start

```bash
cp deployments/k3s/config.example.env deployments/k3s/config.env
# Edit config.env, mongo-secret.yaml, k8s/operator-ai.yaml (or operator-ai.local.yaml)
# Edit config.env, mongo-secret.yaml, admin-bootstrap-secret.yaml, k8s/operator-ai.yaml (or operator-ai.local.yaml)

./deployments/k3s/scripts/build-push-image.sh # arm64 default (Pi)
./deployments/k3s/scripts/deploy.sh
./deployments/k3s/scripts/deploy.sh --with-ingress

# Admin UI (never on ingress):
kubectl port-forward -n reaperc2-ns deployment/reaperc2-deployment 8443:8443
# https://127.0.0.1:8443/login — default first operator: admin / changeme (admin-bootstrap-secret.yaml)
# http://127.0.0.1:8443/ai
```

## MongoDB

- Database name: **`reaperc2-metric`** (`MONGO_DATABASE` in `config.env`)
- NFS path example: `/export/reaperc2-metric` (edit `MONGO_NFS_*` in `config.env`)
- First install requires an **empty** NFS export; app user is bootstrapped on first pod start
- Do not change `mongo-secret.yaml` passwords after Mongo has initialized without resetting NFS data

## Scripts

| Script | Purpose |
|--------|---------|
| `scripts/build-push-image.sh` | Host cross-compile + push (`--arch arm64\|amd64`, `--import-local`, `--load`) |
| `scripts/deploy.sh` | Ollama + Operator AI + Mongo + ReaperC2 |
| `scripts/seed-mongo.sh` | Seed `api_db` via port-forward |
| `scripts/reroll.sh` | Rebuild image + rollout (`--with-operator-ai`, `--fresh-mongo`) |
| `scripts/deploy.sh` | Ollama + Operator AI + Mongo + ReaperC2; `--with-ingress` for beacon TLS |
| `scripts/seed-mongo.sh` | Seed `reaperc2-metric` via port-forward |
| `scripts/reroll.sh` | Rebuild image + rollout (`--with-operator-ai`, `--fresh-mongo`, `--with-ingress`) |
| `scripts/undeploy.sh` | Tear down (`--delete-pvc`) |
| `scripts/verify-mongo-app-user.sh` | Check app user auth against `reaperc2-metric` |

```bash
./deployments/k3s/scripts/deploy.sh --operator-ai-only
./deployments/k3s/scripts/reroll.sh --with-operator-ai
./deployments/k3s/scripts/reroll.sh --fresh-mongo --with-ingress
```

## Shared manifests

| File | Purpose |
|------|---------|
| [`../k8s/ollama.yaml`](../k8s/ollama.yaml) | In-cluster Ollama Deployment |
| [`../k8s/ollama.yaml`](../k8s/ollama.yaml) | In-cluster Ollama Deployment (models on NFS via `OLLAMA_NFS_*` in `config.env`) |
| [`../k8s/operator-ai.yaml`](../k8s/operator-ai.yaml) | Operator AI ConfigMap + Secret |
| `ingress.yaml` / `ingressroute.yaml` | Beacon `:8080` only (matches AWS reaperc2 ingress pattern) |

## Config reference

Expand All @@ -55,5 +78,5 @@ See [`../README.md`](../README.md) for `config.env` variables.

```bash
./deployments/k3s/scripts/undeploy.sh
./deployments/k3s/scripts/undeploy.sh --delete-pvc # destructive
./deployments/k3s/scripts/undeploy.sh --delete-pvc # destructive — wipe NFS export before redeploy
```
13 changes: 13 additions & 0 deletions deployments/k3s/admin-bootstrap-secret.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# First admin UI operator when the MongoDB operators collection is empty.
# Edit before first deploy (or before first ReaperC2 pod start). After bootstrap,
# change password in the UI or insert operators in MongoDB; updating this secret
# alone does not reset existing operators.
apiVersion: v1
kind: Secret
metadata:
name: reaperc2-admin-bootstrap
namespace: reaperc2-ns
type: Opaque
stringData:
username: admin
password: changeme
23 changes: 20 additions & 3 deletions deployments/k3s/config.example.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

# MongoDB NFS (static PV in mongodb-nfs-pv.yaml)
MONGO_NFS_SERVER=192.168.1.100
MONGO_NFS_PATH=/volume1/reaperc2db
MONGO_NFS_PATH=/export/reaperc2-metric
MONGO_DATABASE=reaperc2-metric
# mongo:7 needs Pi 5 (ARMv8.2) — must run on control plane, not Pi 4 workers
MONGO_NODE_HOSTNAME=control-plane

# Private registry (build-push-image.sh, create-registry-secret.sh)
REGISTRY_SERVER=registry.example.com
Expand All @@ -12,9 +15,23 @@ REGISTRY_SECRET_NAME=registry-credentials

REAPERC2_IMAGE=registry.example.com/reaperc2
REAPERC2_IMAGE_TAG=latest
# Pi k3s cluster requires linux/arm64 (default in build-push-image.sh)
REAPERC2_BUILD_ARCH=arm64

# Ollama model tags pulled on first start (ConfigMap ollama-pull-models)
OLLAMA_PULL_MODELS=llama3.2:latest,gpt-oss:latest,deepseek-r1:latest
# Public beacon hostname (admin UI is port-forward only — same as AWS reaperc2 path)
INGRESS_HOST=beacons.example.com
CERT_MANAGER_ISSUER=letsencrypt-prod

# Ollama model storage on NFS (/root/.ollama in the pod → this export).
OLLAMA_NFS_SERVER=192.168.1.100
OLLAMA_NFS_PATH=/export/ollama-models
# Ollama model tags pulled on first start (ConfigMap ollama-pull-models).
# :cloud tags (e.g. glm-5.2:cloud) proxy to Ollama Cloud — require OLLAMA_API_KEY below.
OLLAMA_PULL_MODELS=llama3.2:latest,gpt-oss:latest,deepseek-r1:latest,glm-5.2:cloud
# Fallback only when OLLAMA_NFS_* is unset (not recommended on k3s)
# OLLAMA_STORAGE_CLASS=local-path
# API key from https://ollama.com/settings/keys — required for :cloud models in-cluster
# OLLAMA_API_KEY=

# Operator AI: set cloud keys here or patch deployments/k8s/operator-ai.yaml Secret
# REAPER_AI_OPENAI_API_KEY=
Expand Down
12 changes: 11 additions & 1 deletion deployments/k3s/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,19 @@ spec:
name: reaperc2-mongodb-secrets
key: app_password
- name: MONGO_DATABASE
value: "api_db"
value: "reaperc2-metric"
- name: REAPERC2_ROOT
value: "/root"
- name: ADMIN_BOOTSTRAP_USERNAME
valueFrom:
secretKeyRef:
name: reaperc2-admin-bootstrap
key: username
- name: ADMIN_BOOTSTRAP_PASSWORD
valueFrom:
secretKeyRef:
name: reaperc2-admin-bootstrap
key: password
envFrom:
- configMapRef:
name: reaperc2-ai-config
Expand Down
22 changes: 22 additions & 0 deletions deployments/k3s/ingress-http-redirect.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Port 80 → 301 redirect to HTTPS. cert-manager HTTP-01 uses separate challenge routes.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: reaperc2-http-redirect
namespace: reaperc2-ns
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
traefik.ingress.kubernetes.io/router.middlewares: reaperc2-ns-redirect-https@kubernetescrd
spec:
ingressClassName: traefik
rules:
- host: beacons.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: reaperc2-service
port:
number: 8080
30 changes: 30 additions & 0 deletions deployments/k3s/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Beacon traffic only (port 8080). Admin UI is not on public ingress — use port-forward :8443.
# Apply after core stack: ./deployments/k3s/scripts/deploy.sh --with-ingress
# Requires Traefik IngressClass + cert-manager ClusterIssuer (CERT_MANAGER_ISSUER in config.env).
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: reaperc2-ingress
namespace: reaperc2-ns
annotations:
acme.cert-manager.io/http01-edit-in-place: "true"
cert-manager.io/cluster-issuer: letsencrypt-prod
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: reaperc2-ns-security-headers@kubernetescrd,reaperc2-ns-per-ip-ratelimit@kubernetescrd
spec:
ingressClassName: traefik
rules:
- host: beacons.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: reaperc2-service
port:
number: 8080
tls:
- hosts:
- beacons.example.com
secretName: beacons-example-com-tls
20 changes: 20 additions & 0 deletions deployments/k3s/ingressroute.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Traefik IngressRoute for beacon HTTPS (matches TLS secret from ingress.yaml).
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: reaperc2-ingressroute
namespace: reaperc2-ns
spec:
entryPoints:
- websecure
routes:
- match: Host(`beacons.example.com`)
kind: Rule
middlewares:
- name: security-headers
- name: per-ip-ratelimit
services:
- name: reaperc2-service
port: 8080
tls:
secretName: beacons-example-com-tls
1 change: 1 addition & 0 deletions deployments/k3s/mongo-secret.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# MongoDB credentials for in-cluster mongo (k3s/mongodb.yaml).
# App user is created on database reaperc2-metric at first boot (mongo/init-app-user.js).
# Edit before first deploy. After Mongo has initialized the volume, changing root_*
# or app_* requires resetting the NFS export (see k3s/README.md).
apiVersion: v1
Expand Down
24 changes: 24 additions & 0 deletions deployments/k3s/mongo/init-app-user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Creates readWrite user on reaperc2-metric DB (runs once on empty data dir).
// Requires env: MONGO_APP_USERNAME, MONGO_APP_PASSWORD
const user = process.env.MONGO_APP_USERNAME;
const pwd = process.env.MONGO_APP_PASSWORD;
if (!user || !pwd) {
throw new Error("MONGO_APP_USERNAME and MONGO_APP_PASSWORD must be set");
}

const dbName = "reaperc2-metric";
db = db.getSiblingDB(dbName);

if (db.getUser(user)) {
db.updateUser(user, { pwd });
print(`Updated password for app user ${user} on ${dbName}`);
quit(0);
}

db.createUser({
user,
pwd,
roles: [{ role: "readWrite", db: dbName }],
});

print(`Created app user ${user} on ${dbName}`);
7 changes: 7 additions & 0 deletions deployments/k3s/mongo/init-app-user.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash
# Runs after root user is created (mongo:7 entrypoint, first init only).
set -euo pipefail

mongosh -u "${MONGO_INITDB_ROOT_USERNAME}" -p "${MONGO_INITDB_ROOT_PASSWORD}" \
--authenticationDatabase admin \
/docker-entrypoint-initdb.d/init-app-user.js
8 changes: 8 additions & 0 deletions deployments/k3s/mongo/mongo-healthcheck.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
# Probe script — password passed to mongosh as argv (safe for special characters).
set -e
mongosh --quiet \
-u "${MONGO_INITDB_ROOT_USERNAME}" \
-p "${MONGO_INITDB_ROOT_PASSWORD}" \
--authenticationDatabase admin \
--eval 'db.adminCommand("ping").ok'
2 changes: 1 addition & 1 deletion deployments/k3s/mongodb-nfs-pv.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ spec:
storageClassName: ""
nfs:
server: 192.168.1.100
path: /volume1/reaperc2db
path: /export/reaperc2-metric
Loading
Loading