diff --git a/.dockerignore b/.dockerignore index 294cce4..ada9413 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,8 @@ .git .github -.cursor +.cursor/* +!.cursor/skills/ +!.cursor/skills/** **/.git **/.DS_Store .env @@ -14,4 +16,6 @@ terminals agent-transcripts Screenshots *.md +!SKILLS.md +!**/*_skills.md !third_party/** diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index bd24875..932e1d3 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -16,6 +16,14 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + # Dockerfile prefers vendor/ when present; populate for offline-friendly builds. + - name: Vendor Go modules + run: go mod vendor + - uses: docker/setup-buildx-action@v3 - name: Build image (load into Docker) @@ -37,3 +45,9 @@ jobs: exit-code: "1" format: table vuln-type: os,library + # Debian bookworm packages often have HIGH with no fixed package yet. + ignore-unfixed: true + # Runtime needs Go + Scythe sources; skip toolchain/mod cache noise and Scythe's + # on-disk go.mod (compile-time deps). Application Go vulns are still covered via + # govulncheck and the linked binary (Gobinary). + skip-dirs: /usr/local/go,/go,/root/third_party diff --git a/.gitignore b/.gitignore index ba1c059..0a7c689 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,8 @@ # Local Go build artifacts (modules resolve via go.mod; vendor only if you explicitly vendor) /vendor/ -/bin/ \ No newline at end of file +/bin/ + +# k3s / OnPrem deploy local config (see deployments/README.md) +deployments/k3s/config.env +deployments/config.env diff --git a/Dockerfile b/Dockerfile index b33865c..8f4649e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,11 @@ # syntax=docker/dockerfile:1.7 -# Compile inside Docker using vendored modules (run `make vendor` on the host first). +# Compile inside Docker. Prefer a host-prepared vendor/ tree (`make vendor`) for offline +# builds; if vendor/ is missing (typical CI), go mod download runs during the image build. # On Apple Silicon, prefer `make build` (cross-compiles on the host, then Dockerfile.pack). -FROM golang:1.24-bookworm AS builder +# +# Runtime stays on golang so Scythe.embedded can `go build` at request time, but we only +# ship the binary + Scythe sources + Operator AI skill files (not the full vendor/ source tree). +FROM golang:1.25.12-bookworm AS builder ARG TARGETOS=linux ARG TARGETARCH @@ -13,9 +17,11 @@ ENV CGO_ENABLED=0 \ WORKDIR /src -COPY go.mod go.sum ./ -COPY vendor/ vendor/ +RUN apt-get update \ + && apt-get install -y --no-install-recommends git ca-certificates \ + && rm -rf /var/lib/apt/lists/* +COPY go.mod go.sum ./ COPY . . RUN set -eux; \ @@ -28,18 +34,34 @@ RUN set -eux; \ WORKDIR /src/cmd RUN --mount=type=cache,target=/root/.cache/go-build \ - GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" \ - go build -mod=vendor -trimpath -ldflags="-s -w" -o /out/ReaperC2 . + --mount=type=cache,target=/go/pkg/mod \ + set -eux; \ + if [ -d /src/vendor ] && [ -f /src/vendor/modules.txt ]; then \ + GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" \ + go build -mod=vendor -trimpath -ldflags="-s -w" -o /out/ReaperC2 .; \ + else \ + (cd /src && go mod download); \ + GOOS="${TARGETOS}" GOARCH="${TARGETARCH}" \ + go build -mod=mod -trimpath -ldflags="-s -w" -o /out/ReaperC2 .; \ + fi -FROM golang:1.24-bookworm +FROM golang:1.25.12-bookworm -COPY --from=builder /src /root -COPY --from=builder /out/ReaperC2 /root/cmd/ReaperC2 +# Refresh OS packages to pick up Debian security updates in the golang base image. +RUN apt-get update \ + && apt-get upgrade -y --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* WORKDIR /root +COPY --from=builder /out/ReaperC2 /root/cmd/ReaperC2 +COPY --from=builder /src/third_party/Scythe /root/third_party/Scythe +COPY --from=builder /src/SKILLS.md /root/SKILLS.md +COPY --from=builder /src/.cursor/skills/reaper-red-team-operator /root/.cursor/skills/reaper-red-team-operator + ARG DEPLOY_ENV=ONPREM -ENV DEPLOY_ENV=${DEPLOY_ENV} +ENV DEPLOY_ENV=${DEPLOY_ENV} \ + REAPERC2_ROOT=/root EXPOSE 8080 8443 ENTRYPOINT ["/root/cmd/ReaperC2"] diff --git a/Dockerfile.pack b/Dockerfile.pack index bc1bd35..8da3359 100644 --- a/Dockerfile.pack +++ b/Dockerfile.pack @@ -1,27 +1,45 @@ # syntax=docker/dockerfile:1.7 # Package a host-built ReaperC2 binary (see `make build-binaries`). No `go build` in Docker. -FROM golang:1.24-bookworm +FROM golang:1.25.12-bookworm AS pack ARG TARGETARCH ARG SCYTHE_GIT_REF=main ARG SCYTHE_REPO=https://github.com/BuildAndDestroy/Scythe.git -WORKDIR /root +RUN apt-get update \ + && apt-get install -y --no-install-recommends git ca-certificates \ + && apt-get upgrade -y --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* -COPY . /root -COPY bin/linux-${TARGETARCH}/ReaperC2 /root/cmd/ReaperC2 +WORKDIR /src +COPY . /src +COPY bin/linux-${TARGETARCH}/ReaperC2 /out/ReaperC2 RUN set -eux; \ - test -f /root/cmd/ReaperC2; \ - test -f third_party/Scythe/go.mod || { \ + test -f /out/ReaperC2; \ + if [ ! -f third_party/Scythe/go.mod ]; then \ mkdir -p third_party; \ rm -rf third_party/Scythe; \ git clone --depth 1 --branch "${SCYTHE_GIT_REF}" "${SCYTHE_REPO}" third_party/Scythe; \ - }; \ + fi; \ test -f third_party/Scythe/go.mod +FROM golang:1.25.12-bookworm + +RUN apt-get update \ + && apt-get upgrade -y --no-install-recommends \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /root + +COPY --from=pack /out/ReaperC2 /root/cmd/ReaperC2 +COPY --from=pack /src/third_party/Scythe /root/third_party/Scythe +COPY --from=pack /src/SKILLS.md /root/SKILLS.md +COPY --from=pack /src/.cursor/skills/reaper-red-team-operator /root/.cursor/skills/reaper-red-team-operator + ARG DEPLOY_ENV=ONPREM -ENV DEPLOY_ENV=${DEPLOY_ENV} +ENV DEPLOY_ENV=${DEPLOY_ENV} \ + REAPERC2_ROOT=/root EXPOSE 8080 8443 ENTRYPOINT ["/root/cmd/ReaperC2"] diff --git a/deployments/README.md b/deployments/README.md new file mode 100644 index 0000000..78d68c2 --- /dev/null +++ b/deployments/README.md @@ -0,0 +1,67 @@ +# Deployment layouts + +Two supported Kubernetes paths — pick one per cluster: + +| Path | Use when | Entry | +|------|----------|--------| +| **[`k8s/reaperc2/`](k8s/reaperc2/)** | **DocumentDB** on EKS or k3s (Traefik, cert-manager, Bedrock Operator AI) | [`k8s/DEPLOY.md`](k8s/DEPLOY.md) | +| **[`k3s/`](k3s/)** | **OnPrem** homelab / Pi: NFS MongoDB, in-cluster **Ollama**, private registry | [`k3s/README.md`](k3s/README.md) | + +**k3s + DocumentDB** uses the **reaperc2** path (`REAPER_CLUSTER=k3s`), not `deployments/k3s/`. See [`k8s/k3s/README.md`](k8s/k3s/README.md). + +Legacy samples: [`k8s/full-deployment.yaml`](k8s/full-deployment.yaml), [`k8s/OnPrem/full-deployment.yaml`](k8s/OnPrem/full-deployment.yaml). + +--- + +## `config.env` (OnPrem `deployments/k3s/` only) + +```bash +cp deployments/k3s/config.example.env deployments/k3s/config.env +``` + +Or one shared file: `deployments/config.env` (scripts check `k3s/config.env` first, then `deployments/config.env`). + +**Special characters:** quote passwords with `#` or `!`: + +```env +REGISTRY_PASSWORD='your-password-here' +``` + +| Variable | Purpose | +|----------|---------| +| `MONGO_NFS_SERVER` / `MONGO_NFS_PATH` | NFS export for MongoDB PV | +| `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`) | +| `REAPERC2_NAMESPACE` | Default `reaperc2-ns` | +| `KUBECONFIG` | Optional kubeconfig path | + +**DocumentDB / ECR builds** use [`k8s/reaperc2/build-push-image.sh`](k8s/reaperc2/build-push-image.sh) and the Makefile (`AWS_ACCOUNT_ID`, `AWS_REGION`, etc.) — not `config.env`. + +--- + +## 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. + +--- + +## Operator AI + +[`k8s/operator-ai.yaml`](k8s/operator-ai.yaml) — ConfigMap + Secret template. Copy to `operator-ai.local.yaml` (gitignored) for real keys. + +| Concern | Variable / file | +|---------|-----------------| +| Dropdown models (comma-separated per provider) | `REAPER_AI_OPENAI_MODELS`, `REAPER_AI_ANTHROPIC_MODELS`, `REAPER_AI_OLLAMA_MODELS`, … | +| In-cluster Ollama (default) | Applied via `k8s/ollama.yaml`; `REAPER_AI_OLLAMA_*` enabled in `operator-ai.yaml` | +| Pull models into Ollama | `OLLAMA_PULL_MODELS` in `ollama.yaml` ConfigMap (or `k3s/config.env`) | + +Full variable list: [docs/operator-guide-ai.md](../docs/operator-guide-ai.md). + +--- + +## See also + +- [docs/kubernetes.md](../docs/kubernetes.md) +- [k8s/reaperc2/README.md](k8s/reaperc2/README.md) diff --git a/deployments/k3s/README.md b/deployments/k3s/README.md new file mode 100644 index 0000000..d3de562 --- /dev/null +++ b/deployments/k3s/README.md @@ -0,0 +1,59 @@ +# k3s OnPrem (NFS Mongo + Ollama) + +Deploy ReaperC2 on **k3s** with **NFS MongoDB**, a **private registry** image, and in-cluster **Ollama** for Operator AI. + +> **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)). + +## Prerequisites + +- k3s (`curl -sfL https://get.k3s.io | sh -`) +- `kubectl` or `sudo k3s kubectl` +- NFS export for MongoDB; `nfs-common` on nodes +- Go on the build machine (for `build-push-image.sh`) +- ≥ 4 GiB RAM recommended for Ollama + +## 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) + +./deployments/k3s/scripts/build-push-image.sh # arm64 default (Pi) +./deployments/k3s/scripts/deploy.sh + +kubectl port-forward -n reaperc2-ns deployment/reaperc2-deployment 8443:8443 +# http://127.0.0.1:8443/ai +``` + +## 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/undeploy.sh` | Tear down (`--delete-pvc`) | + +```bash +./deployments/k3s/scripts/deploy.sh --operator-ai-only +./deployments/k3s/scripts/reroll.sh --with-operator-ai +``` + +## Shared manifests + +| File | Purpose | +|------|---------| +| [`../k8s/ollama.yaml`](../k8s/ollama.yaml) | In-cluster Ollama Deployment | +| [`../k8s/operator-ai.yaml`](../k8s/operator-ai.yaml) | Operator AI ConfigMap + Secret | + +## Config reference + +See [`../README.md`](../README.md) for `config.env` variables. + +## Undeploy + +```bash +./deployments/k3s/scripts/undeploy.sh +./deployments/k3s/scripts/undeploy.sh --delete-pvc # destructive +``` diff --git a/deployments/k3s/config.example.env b/deployments/k3s/config.example.env new file mode 100644 index 0000000..cc4e238 --- /dev/null +++ b/deployments/k3s/config.example.env @@ -0,0 +1,24 @@ +# Copy to k3s/config.env and edit. Used by k3s/scripts/*.sh + +# MongoDB NFS (static PV in mongodb-nfs-pv.yaml) +MONGO_NFS_SERVER=192.168.1.100 +MONGO_NFS_PATH=/volume1/reaperc2db + +# Private registry (build-push-image.sh, create-registry-secret.sh) +REGISTRY_SERVER=registry.example.com +REGISTRY_USERNAME= +REGISTRY_PASSWORD= +REGISTRY_SECRET_NAME=registry-credentials + +REAPERC2_IMAGE=registry.example.com/reaperc2 +REAPERC2_IMAGE_TAG=latest + +# Ollama model tags pulled on first start (ConfigMap ollama-pull-models) +OLLAMA_PULL_MODELS=llama3.2:latest,gpt-oss:latest,deepseek-r1:latest + +# Operator AI: set cloud keys here or patch deployments/k8s/operator-ai.yaml Secret +# REAPER_AI_OPENAI_API_KEY= +# REAPER_AI_ANTHROPIC_API_KEY= + +# kubectl context (optional) +# KUBECONFIG=/path/to/k3s.yaml diff --git a/deployments/k3s/deployment.yaml b/deployments/k3s/deployment.yaml new file mode 100644 index 0000000..c97fb38 --- /dev/null +++ b/deployments/k3s/deployment.yaml @@ -0,0 +1,76 @@ +# ReaperC2 for k3s (ARM64/x86). Image from k3s/config.env — see scripts/build-push-image.sh +apiVersion: apps/v1 +kind: Deployment +metadata: + name: reaperc2-deployment + namespace: reaperc2-ns + labels: + app: reaperc2-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: reaperc2-deployment + template: + metadata: + labels: + app: reaperc2-deployment + spec: + imagePullSecrets: + - name: registry-credentials + containers: + - name: reaperc2 + image: registry.example.com/reaperc2:latest + imagePullPolicy: Always + ports: + - containerPort: 8080 + name: beacon + - containerPort: 8443 + name: admin + env: + - name: DEPLOY_ENV + value: "ONPREM" + - name: MONGO_HOST + value: "mongodb-service.reaperc2-ns.svc.cluster.local" + - name: MONGO_PORT + value: "27017" + - name: MONGO_USERNAME + valueFrom: + secretKeyRef: + name: reaperc2-mongodb-secrets + key: app_username + - name: MONGO_PASSWORD + valueFrom: + secretKeyRef: + name: reaperc2-mongodb-secrets + key: app_password + - name: MONGO_DATABASE + value: "api_db" + - name: REAPERC2_ROOT + value: "/root" + envFrom: + - configMapRef: + name: reaperc2-ai-config + optional: true + - secretRef: + name: reaperc2-ai-secrets + optional: true + readinessProbe: + httpGet: + path: /health + port: 8443 + initialDelaySeconds: 15 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /health + port: 8443 + initialDelaySeconds: 45 + periodSeconds: 30 + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "2Gi" + cpu: "1000m" diff --git a/deployments/k3s/mongo-secret.yaml b/deployments/k3s/mongo-secret.yaml new file mode 100644 index 0000000..10e27cf --- /dev/null +++ b/deployments/k3s/mongo-secret.yaml @@ -0,0 +1,14 @@ +# MongoDB credentials for in-cluster mongo (k3s/mongodb.yaml). +# 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 +kind: Secret +metadata: + name: reaperc2-mongodb-secrets + namespace: reaperc2-ns +type: Opaque +stringData: + root_username: mongo-root + root_password: k3s-change-me-root-password + app_username: api_user + app_password: k3s-change-me-app-password diff --git a/deployments/k3s/mongodb-nfs-pv.yaml b/deployments/k3s/mongodb-nfs-pv.yaml new file mode 100644 index 0000000..e03acd5 --- /dev/null +++ b/deployments/k3s/mongodb-nfs-pv.yaml @@ -0,0 +1,18 @@ +# Static NFS volume for MongoDB — edit server/path to match your NAS export. +# Pi nodes need: sudo apt install -y nfs-common +apiVersion: v1 +kind: PersistentVolume +metadata: + name: reaperc2-mongo-nfs-pv + labels: + app: mongo +spec: + capacity: + storage: 10Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + nfs: + server: 192.168.1.100 + path: /volume1/reaperc2db diff --git a/deployments/k3s/mongodb.yaml b/deployments/k3s/mongodb.yaml new file mode 100644 index 0000000..56a10ad --- /dev/null +++ b/deployments/k3s/mongodb.yaml @@ -0,0 +1,93 @@ +# MongoDB on NFS (see mongodb-nfs-pv.yaml). Seed api_db after first boot: +# ./deployments/k3s/scripts/seed-mongo.sh +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: mongo-pvc + namespace: reaperc2-ns +spec: + accessModes: + - ReadWriteOnce + storageClassName: "" + volumeName: reaperc2-mongo-nfs-pv + resources: + requests: + storage: 10Gi +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: mongo + namespace: reaperc2-ns +spec: + serviceName: mongodb-service + replicas: 1 + selector: + matchLabels: + app: mongo + template: + metadata: + labels: + app: mongo + spec: + containers: + - name: mongo + image: mongo:7 + ports: + - containerPort: 27017 + env: + - name: MONGO_INITDB_ROOT_USERNAME + valueFrom: + secretKeyRef: + name: reaperc2-mongodb-secrets + key: root_username + - name: MONGO_INITDB_ROOT_PASSWORD + valueFrom: + secretKeyRef: + name: reaperc2-mongodb-secrets + key: root_password + volumeMounts: + - name: data + mountPath: /data/db + readinessProbe: + exec: + command: + - mongosh + - --quiet + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: 5 + periodSeconds: 5 + livenessProbe: + exec: + command: + - mongosh + - --quiet + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: 30 + periodSeconds: 10 + resources: + requests: + memory: "256Mi" + cpu: "100m" + limits: + memory: "1Gi" + cpu: "1000m" + volumes: + - name: data + persistentVolumeClaim: + claimName: mongo-pvc +--- +apiVersion: v1 +kind: Service +metadata: + name: mongodb-service + namespace: reaperc2-ns +spec: + selector: + app: mongo + ports: + - port: 27017 + targetPort: 27017 + clusterIP: None diff --git a/deployments/k3s/namespace.yaml b/deployments/k3s/namespace.yaml new file mode 100644 index 0000000..a922c81 --- /dev/null +++ b/deployments/k3s/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: reaperc2-ns diff --git a/deployments/k3s/scripts/build-push-image.sh b/deployments/k3s/scripts/build-push-image.sh new file mode 100755 index 0000000..a54c058 --- /dev/null +++ b/deployments/k3s/scripts/build-push-image.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Build linux/arm64 (Pi) or amd64 image and push to your registry. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib.sh +source "${SCRIPT_DIR}/lib.sh" +load_config + +ARCH="${ARCH:-arm64}" +IMPORT_LOCAL=false +PUSH=true + +usage() { + cat <<'EOF' +Usage: deployments/k3s/scripts/build-push-image.sh [options] + +Options: + --arch arm64|amd64 Target platform (default: arm64 for Raspberry Pi k3s) + --tag TAG Image tag (default: from config.env REAPERC2_IMAGE_TAG) + --import-local Also import tarball into local k3s (offline fallback) + --load Build for local docker only (no push; single platform) + -h, --help Show help + +Requires REGISTRY_* and REAPERC2_IMAGE in k3s/config.env (unless --load). +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --arch) ARCH="$2"; shift 2 ;; + --tag) REAPERC2_IMAGE_TAG="$2"; shift 2 ;; + --import-local) IMPORT_LOCAL=true; shift ;; + --load) PUSH=false; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage; exit 1 ;; + esac +done + +IMAGE="$(reaperc2_image_ref)" +PLATFORM="linux/${ARCH}" + +if [[ "$PUSH" == true ]]; then + require_registry_credentials + docker_build_push "$REPO_ROOT" "$IMAGE" "$PLATFORM" "$IMPORT_LOCAL" + echo "Pushed ${IMAGE}" +else + echo "Building ${IMAGE} for ${PLATFORM} (load locally)..." + DOCKER_BUILD_LOAD=1 docker_build_push "$REPO_ROOT" "$IMAGE" "$PLATFORM" "$IMPORT_LOCAL" + echo "Loaded ${IMAGE} into local Docker" +fi diff --git a/deployments/k3s/scripts/common.sh b/deployments/k3s/scripts/common.sh new file mode 100755 index 0000000..3e0ffc0 --- /dev/null +++ b/deployments/k3s/scripts/common.sh @@ -0,0 +1,192 @@ +#!/usr/bin/env bash +# Shared helpers for deployments/k3s/scripts (OnPrem NFS + Ollama). + +set -euo pipefail + +kubectl_cmd() { + if command -v kubectl >/dev/null 2>&1; then + kubectl "$@" + elif command -v k3s >/dev/null 2>&1; then + sudo k3s kubectl "$@" + else + echo "kubectl or k3s not found" >&2 + exit 1 + fi +} + +require_file() { + local f="$1" + local hint="$2" + if [[ -f "$f" ]]; then + return 0 + fi + echo "Missing required file: $f" >&2 + echo "$hint" >&2 + exit 1 +} + +# Load the first existing config.env from candidate paths. +# Sets DEPLOY_CONFIG_FILE to the file sourced (empty if none found). +source_deploy_config() { + DEPLOY_CONFIG_FILE="" + DEPLOY_CONFIG_CANDIDATES=("$@") + local f + for f in "${DEPLOY_CONFIG_CANDIDATES[@]}"; do + if [[ -f "$f" ]]; then + # shellcheck disable=SC1090 + set -a + source "$f" + set +a + DEPLOY_CONFIG_FILE="$f" + if [[ "$f" != "${DEPLOY_CONFIG_CANDIDATES[0]}" ]]; then + echo "Note: using ${f} (create ${DEPLOY_CONFIG_CANDIDATES[0]} to override)" >&2 + fi + return 0 + fi + done + return 1 +} + +require_registry_credentials() { + if [[ -n "${REGISTRY_USERNAME:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then + return 0 + fi + echo "Set REGISTRY_USERNAME and REGISTRY_PASSWORD in config.env." >&2 + if [[ -n "${DEPLOY_CONFIG_FILE:-}" ]]; then + echo "Loaded ${DEPLOY_CONFIG_FILE} but registry credentials are missing or empty." >&2 + echo "Quote values that contain # or ! — e.g. REGISTRY_PASSWORD='your-pass'" >&2 + else + echo "No config.env found. Create one of:" >&2 + local f + for f in "${DEPLOY_CONFIG_CANDIDATES[@]}"; do + echo " $f" >&2 + done + echo "Copy from config.example.env in the same directory." >&2 + fi + exit 1 +} + +apply_ollama_pull_models() { + if [[ -z "${OLLAMA_PULL_MODELS:-}" ]]; then + return 0 + fi + kubectl_cmd create configmap ollama-pull-models \ + --from-literal="OLLAMA_PULL_MODELS=${OLLAMA_PULL_MODELS}" \ + -n ollama-ns \ + --dry-run=client -o yaml | kubectl_cmd apply -f - +} + +patch_mongo_nfs_pv() { + local pv_file="$1" + local server="$2" + local path="$3" + if [[ ! -f "$pv_file" ]]; then + echo "PV file not found: $pv_file" >&2 + exit 1 + fi + sed -e "s|server: 192.168.1.100|server: ${server}|" \ + -e "s|path: /volume1/reaperc2db|path: ${path}|" \ + "$pv_file" | kubectl_cmd apply -f - +} + +prepare_reaperc2_build() { + local repo_root="$1" + local arch="$2" + + if ! command -v go >/dev/null 2>&1; then + echo "Go is required on the host to cross-compile ReaperC2. Install Go or use: make build-binaries" >&2 + exit 1 + fi + + echo "Preparing source (submodule + vendor + linux/${arch} binary)..." + ( + cd "$repo_root" + git submodule update --init --recursive + if [[ ! -d vendor ]] || [[ go.mod -nt vendor ]]; then + echo "Running go mod vendor..." + go mod vendor + fi + mkdir -p "bin/linux-${arch}" + CGO_ENABLED=0 GOOS=linux GOARCH="${arch}" \ + go build -mod=vendor -trimpath -ldflags="-s -w" \ + -o "bin/linux-${arch}/ReaperC2" ./cmd + ) +} + +docker_build_push() { + local repo_root="$1" + local image="$2" + local platform="$3" + local import_local="${4:-false}" + local arch="${platform#linux/}" + local scythe_ref="${SCYTHE_GIT_REF:-main}" + local load_flag=() + local push_flag=(--push) + + if [[ "${DOCKER_BUILD_LOAD:-}" == "1" ]]; then + load_flag=(--load) + push_flag=() + fi + + prepare_reaperc2_build "$repo_root" "$arch" + + echo "Building ${image} for ${platform}..." + docker buildx build \ + --platform "${platform}" \ + -t "${image}" \ + -f "${repo_root}/Dockerfile.pack" \ + --build-arg "TARGETARCH=${arch}" \ + --build-arg "SCYTHE_GIT_REF=${scythe_ref}" \ + "${push_flag[@]}" \ + "${load_flag[@]}" \ + "${repo_root}" + + if [[ "$import_local" == true ]]; then + local tar + tar="$(mktemp /tmp/reaperc2-XXXXXX.tar)" + trap 'rm -f "$tar"' RETURN + docker pull "${image}" + docker save "${image}" -o "$tar" + if command -v k3s >/dev/null 2>&1; then + sudo k3s ctr images import "$tar" + else + echo "k3s not found; image pushed to registry only." >&2 + fi + fi +} + +create_registry_pull_secret() { + local namespace="$1" + local secret_name="$2" + local namespace_file="$3" + + kubectl_cmd apply -f "$namespace_file" + kubectl_cmd create secret docker-registry "$secret_name" \ + --docker-server="${REGISTRY_SERVER}" \ + --docker-username="${REGISTRY_USERNAME}" \ + --docker-password="${REGISTRY_PASSWORD}" \ + -n "$namespace" \ + --dry-run=client -o yaml | kubectl_cmd apply -f - +} + +apply_ollama_stack() { + local k8s_root="$1" + echo "Applying Ollama (Operator AI backend)..." + kubectl_cmd apply -f "${k8s_root}/ollama.yaml" + apply_ollama_pull_models + echo "Waiting for Ollama PVC..." + kubectl_cmd -n ollama-ns wait --for=jsonpath='{.status.phase}'=Bound pvc/ollama-data --timeout=180s || true + kubectl_cmd -n ollama-ns rollout status deploy/ollama --timeout=900s || true +} + +apply_operator_ai() { + local k8s_root="$1" + echo "Applying Operator AI config..." + kubectl_cmd apply -f "${k8s_root}/operator-ai.yaml" +} + +rollout_reaperc2() { + local namespace="$1" + kubectl_cmd -n "$namespace" rollout restart deployment/reaperc2-deployment + kubectl_cmd -n "$namespace" rollout status deployment/reaperc2-deployment --timeout=300s +} diff --git a/deployments/k3s/scripts/create-registry-secret.sh b/deployments/k3s/scripts/create-registry-secret.sh new file mode 100755 index 0000000..01ed819 --- /dev/null +++ b/deployments/k3s/scripts/create-registry-secret.sh @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib.sh +source "${SCRIPT_DIR}/lib.sh" +load_config +require_registry_credentials + +create_registry_pull_secret \ + "$REAPERC2_NAMESPACE" \ + "$REGISTRY_SECRET_NAME" \ + "${K3S_ROOT}/namespace.yaml" + +echo "Registry pull secret ${REGISTRY_SECRET_NAME} applied in ${REAPERC2_NAMESPACE}." diff --git a/deployments/k3s/scripts/deploy.sh b/deployments/k3s/scripts/deploy.sh new file mode 100755 index 0000000..0a764e9 --- /dev/null +++ b/deployments/k3s/scripts/deploy.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib.sh +source "${SCRIPT_DIR}/lib.sh" +load_config + +WITH_OLLAMA=true +WITH_OPERATOR_AI=true +SKIP_REGISTRY_SECRET=false +SKIP_MONGO_SEED=false +OPERATOR_AI_ONLY=false + +usage() { + cat <<'EOF' +Usage: deployments/k3s/scripts/deploy.sh [options] + +Deploy ReaperC2 on k3s with optional in-cluster Ollama for Operator AI. + +Options: + --no-ollama Skip Ollama Deployment (use external Ollama URL in operator-ai.yaml) + --no-operator-ai Skip Operator AI ConfigMap/Secret + --skip-registry-secret Do not create registry pull secret + --skip-mongo-seed Do not run test/setup_mongo.sh after Mongo is ready + --operator-ai-only Apply Ollama + operator-ai.yaml and restart ReaperC2 only + -h, --help Show help + +Prerequisites: + - k3s with kubectl (or sudo k3s kubectl) + - NFS export matching MONGO_NFS_* in k3s/config.env (or edit mongodb-nfs-pv.yaml) + - k3s/mongo-secret.yaml credentials set + - Image built/pushed: ./deployments/k3s/scripts/build-push-image.sh +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --no-ollama) WITH_OLLAMA=false; shift ;; + --no-operator-ai) WITH_OPERATOR_AI=false; shift ;; + --skip-registry-secret) SKIP_REGISTRY_SECRET=true; shift ;; + --skip-mongo-seed) SKIP_MONGO_SEED=true; shift ;; + --operator-ai-only) OPERATOR_AI_ONLY=true; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage; exit 1 ;; + esac +done + +if [[ "$OPERATOR_AI_ONLY" == true ]]; then + if [[ "$WITH_OLLAMA" == true ]]; then + apply_ollama_stack "$K8S_ROOT" + fi + if [[ "$WITH_OPERATOR_AI" == true ]]; then + apply_operator_ai "$K8S_ROOT" + fi + rollout_reaperc2 "$REAPERC2_NAMESPACE" + echo "Operator AI stack updated." + exit 0 +fi + +require_file "${K3S_ROOT}/mongo-secret.yaml" "Edit deployments/k3s/mongo-secret.yaml before deploy" + +if [[ "$SKIP_REGISTRY_SECRET" != true ]]; then + if [[ -n "${REGISTRY_USERNAME:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then + "${SCRIPT_DIR}/create-registry-secret.sh" + else + echo "Warning: REGISTRY_USERNAME/PASSWORD not set in k3s/config.env — ensure pull secret exists." >&2 + fi +fi + +echo "Applying namespaces..." +kubectl_cmd apply -f "${K3S_ROOT}/namespace.yaml" + +if [[ "$WITH_OLLAMA" == true ]]; then + apply_ollama_stack "$K8S_ROOT" +fi + +if [[ "$WITH_OPERATOR_AI" == true ]]; then + apply_operator_ai "$K8S_ROOT" +fi + +echo "Applying MongoDB..." +kubectl_cmd apply -f "${K3S_ROOT}/mongo-secret.yaml" +apply_k3s_mongo_nfs_pv +kubectl_cmd apply -f "${K3S_ROOT}/mongodb.yaml" + +echo "Waiting for MongoDB PVC..." +kubectl_cmd -n reaperc2-ns wait --for=jsonpath='{.status.phase}'=Bound pvc/mongo-pvc --timeout=180s +kubectl_cmd -n reaperc2-ns rollout status statefulset/mongo --timeout=600s + +if [[ "$SKIP_MONGO_SEED" != true ]]; then + echo "Seeding api_db (test/setup_mongo.sh)..." + "${SCRIPT_DIR}/seed-mongo.sh" +fi + +echo "Applying ReaperC2..." +kubectl_cmd apply -f "${K3S_ROOT}/deployment.yaml" +kubectl_cmd apply -f "${K3S_ROOT}/service.yaml" +kubectl_cmd -n reaperc2-ns set image deployment/reaperc2-deployment \ + reaperc2="$(reaperc2_image_ref)" + +kubectl_cmd -n reaperc2-ns rollout status deployment/reaperc2-deployment --timeout=300s + +echo "" +echo "Deploy complete." +kubectl_cmd -n reaperc2-ns get pods,svc,pvc +if [[ "$WITH_OLLAMA" == true ]]; then + kubectl_cmd -n ollama-ns get pods,svc,pvc +fi +echo "" +echo "Admin UI: kubectl port-forward -n reaperc2-ns deployment/reaperc2-deployment 8443:8443" +echo "Operator AI: open /ai after port-forward (Ollama provider should appear when Ollama pod is ready)." diff --git a/deployments/k3s/scripts/lib.sh b/deployments/k3s/scripts/lib.sh new file mode 100755 index 0000000..5bb6964 --- /dev/null +++ b/deployments/k3s/scripts/lib.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +K3S_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +REPO_ROOT="$(cd "${K3S_ROOT}/../.." && pwd)" +K8S_ROOT="$(cd "${K3S_ROOT}/../k8s" && pwd)" +COMMON="${SCRIPT_DIR}/common.sh" + +# shellcheck source=common.sh +source "$COMMON" + +load_config() { + source_deploy_config \ + "${K3S_ROOT}/config.env" \ + "${K3S_ROOT}/../config.env" \ + || true + MONGO_NFS_SERVER="${MONGO_NFS_SERVER:-192.168.1.100}" + MONGO_NFS_PATH="${MONGO_NFS_PATH:-/volume1/reaperc2db}" + REGISTRY_SERVER="${REGISTRY_SERVER:-registry.example.com}" + REGISTRY_SECRET_NAME="${REGISTRY_SECRET_NAME:-registry-credentials}" + REAPERC2_IMAGE="${REAPERC2_IMAGE:-registry.example.com/reaperc2}" + REAPERC2_IMAGE_TAG="${REAPERC2_IMAGE_TAG:-latest}" + OLLAMA_PULL_MODELS="${OLLAMA_PULL_MODELS:-llama3.2:latest}" + REAPERC2_NAMESPACE="${REAPERC2_NAMESPACE:-reaperc2-ns}" +} + +reaperc2_image_ref() { + echo "${REAPERC2_IMAGE}:${REAPERC2_IMAGE_TAG}" +} + +apply_k3s_mongo_nfs_pv() { + patch_mongo_nfs_pv "${K3S_ROOT}/mongodb-nfs-pv.yaml" "$MONGO_NFS_SERVER" "$MONGO_NFS_PATH" +} diff --git a/deployments/k3s/scripts/reroll.sh b/deployments/k3s/scripts/reroll.sh new file mode 100755 index 0000000..e4c5710 --- /dev/null +++ b/deployments/k3s/scripts/reroll.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# Rebuild image, redeploy ReaperC2 on k3s, optionally refresh Operator AI stack. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib.sh +source "${SCRIPT_DIR}/lib.sh" +load_config + +WITH_OPERATOR_AI=false +FRESH_MONGO=false +BUILD_ARGS=() + +usage() { + cat <<'EOF' +Usage: deployments/k3s/scripts/reroll.sh [options] + +Rebuild/push image and roll out ReaperC2 (and optionally Operator AI). + +Options: + --with-operator-ai Apply Ollama + operator-ai.yaml before restart + --fresh-mongo Undeploy with PVC delete and full redeploy (destructive) + -h, --help Show help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --with-operator-ai) WITH_OPERATOR_AI=true; shift ;; + --fresh-mongo) FRESH_MONGO=true; shift ;; + --build-arg) + BUILD_ARGS+=("$2") + shift 2 + ;; + -h|--help) usage; exit 0 ;; + *) BUILD_ARGS+=("$1"); shift ;; + esac +done + +if [[ "$FRESH_MONGO" == true ]]; then + "${SCRIPT_DIR}/undeploy.sh" --delete-pvc + echo "" + echo "Clear the NFS export (MONGO_NFS_PATH in k3s/config.env) before continuing." + read -r -p "NFS folder cleared? [y/N] " confirm + if [[ "${confirm,,}" != "y" ]]; then + echo "Aborted." + exit 1 + fi + deploy_args=() + "${SCRIPT_DIR}/deploy.sh" "${deploy_args[@]}" + exit 0 +fi + +"${SCRIPT_DIR}/build-push-image.sh" "${BUILD_ARGS[@]}" + +kubectl_cmd -n "$REAPERC2_NAMESPACE" set image deployment/reaperc2-deployment \ + reaperc2="$(reaperc2_image_ref)" + +if [[ "$WITH_OPERATOR_AI" == true ]]; then + apply_ollama_stack "$K8S_ROOT" + apply_operator_ai "$K8S_ROOT" +fi + +rollout_reaperc2 "$REAPERC2_NAMESPACE" + +echo "Reroll complete." +kubectl_cmd -n "$REAPERC2_NAMESPACE" get pods diff --git a/deployments/k3s/scripts/seed-mongo.sh b/deployments/k3s/scripts/seed-mongo.sh new file mode 100755 index 0000000..184da19 --- /dev/null +++ b/deployments/k3s/scripts/seed-mongo.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib.sh +source "${SCRIPT_DIR}/lib.sh" +load_config + +MONGO_SECRET="${K3S_ROOT}/mongo-secret.yaml" +require_file "$MONGO_SECRET" "Edit deployments/k3s/mongo-secret.yaml" + +root_user="$(kubectl_cmd get secret reaperc2-mongodb-secrets -n reaperc2-ns -o jsonpath='{.data.root_username}' | base64 -d)" +root_pass="$(kubectl_cmd get secret reaperc2-mongodb-secrets -n reaperc2-ns -o jsonpath='{.data.root_password}' | base64 -d)" +app_user="$(kubectl_cmd get secret reaperc2-mongodb-secrets -n reaperc2-ns -o jsonpath='{.data.app_username}' | base64 -d)" +app_pass="$(kubectl_cmd get secret reaperc2-mongodb-secrets -n reaperc2-ns -o jsonpath='{.data.app_password}' | base64 -d)" + +export MONGO_ADMIN_USER="$root_user" +export MONGO_ADMIN_PASSWORD="$root_pass" +export MONGO_API_USER="$app_user" +export MONGO_API_PASSWORD="$app_pass" +export MONGO_HOST="127.0.0.1" +export MONGO_PORT="27017" +export IMPORT_DATA_JSON="${IMPORT_DATA_JSON:-0}" + +echo "Port-forwarding MongoDB..." +kubectl_cmd -n reaperc2-ns port-forward svc/mongodb-service 27017:27017 & +pf_pid=$! +trap 'kill "$pf_pid" 2>/dev/null || true' EXIT +sleep 2 + +"${REPO_ROOT}/test/setup_mongo.sh" +echo "Mongo seed complete." diff --git a/deployments/k3s/scripts/undeploy.sh b/deployments/k3s/scripts/undeploy.sh new file mode 100755 index 0000000..e7a448a --- /dev/null +++ b/deployments/k3s/scripts/undeploy.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib.sh +source "${SCRIPT_DIR}/lib.sh" +load_config + +DELETE_PVC=false + +usage() { + cat <<'EOF' +Usage: deployments/k3s/scripts/undeploy.sh [options] + +Options: + --delete-pvc Also delete Mongo and Ollama PVCs (destructive) + -h, --help Show help +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --delete-pvc) DELETE_PVC=true; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1" >&2; usage; exit 1 ;; + esac +done + +kubectl_cmd delete -f "${K3S_ROOT}/service.yaml" --ignore-not-found +kubectl_cmd delete -f "${K3S_ROOT}/deployment.yaml" --ignore-not-found +kubectl_cmd delete -f "${K3S_ROOT}/mongodb.yaml" --ignore-not-found +kubectl_cmd delete -f "${K3S_ROOT}/mongo-secret.yaml" --ignore-not-found +kubectl_cmd delete -f "${K8S_ROOT}/operator-ai.yaml" --ignore-not-found +kubectl_cmd delete -f "${K8S_ROOT}/ollama.yaml" --ignore-not-found +kubectl_cmd delete -f "${K3S_ROOT}/namespace.yaml" --ignore-not-found + +if [[ "$DELETE_PVC" == true ]]; then + kubectl_cmd delete pvc mongo-pvc -n reaperc2-ns --ignore-not-found + kubectl_cmd delete pvc ollama-data -n ollama-ns --ignore-not-found + kubectl_cmd delete pv reaperc2-mongo-nfs-pv --ignore-not-found +fi + +echo "Undeploy complete." diff --git a/deployments/k3s/service.yaml b/deployments/k3s/service.yaml new file mode 100644 index 0000000..6c63d5c --- /dev/null +++ b/deployments/k3s/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: reaperc2-service + namespace: reaperc2-ns +spec: + type: ClusterIP + selector: + app: reaperc2-deployment + ports: + - name: beacon + port: 8080 + targetPort: 8080 + - name: admin + port: 8443 + targetPort: 8443 diff --git a/deployments/k8s/DEPLOY.md b/deployments/k8s/DEPLOY.md index 56a945b..d54212e 100644 --- a/deployments/k8s/DEPLOY.md +++ b/deployments/k8s/DEPLOY.md @@ -1,8 +1,21 @@ # Deploy ReaperC2 on Kubernetes (quick pointer) -The supported path is **`deployments/k8s/reaperc2/`** with **DocumentDB**, Traefik, and cert-manager. +## Choose a layout -## Read this first +| Layout | Directory | +|--------|-----------| +| **DocumentDB** (EKS or k3s) | [`reaperc2/`](reaperc2/) | +| **OnPrem** NFS Mongo + Ollama (k3s homelab / Pi) | [`../k3s/`](../k3s/) | + +See [`../README.md`](../README.md) for the full comparison. + +--- + +## DocumentDB (`reaperc2/`) + +Supported path: **DocumentDB**, Traefik, cert-manager, Bedrock Operator AI. + +## Read this first (DocumentDB) - **[reaperc2/README.md](reaperc2/README.md)** — full checklist, DocumentDB pitfalls, ingress order, troubleshooting. - **[reaperc2/examples/README.md](reaperc2/examples/README.md)** — copying secret templates to `*.local.yaml`. diff --git a/deployments/k8s/k3s/README.md b/deployments/k8s/k3s/README.md index 8682173..3fdbbd6 100644 --- a/deployments/k8s/k3s/README.md +++ b/deployments/k8s/k3s/README.md @@ -1,6 +1,8 @@ -# k3s +# k3s / OnPrem NFS + Ollama -ReaperC2 on **k3s** uses the same manifests and Traefik/cert-manager flow as EKS — see **[`../reaperc2/README.md`](../reaperc2/README.md)** (section **k3s notes**). +For **OnPrem** k3s (NFS MongoDB, in-cluster Ollama, private registry), use **[`../../k3s/`](../../k3s/)** — not this file. + +This entry is for **k3s + Amazon DocumentDB** (same as EKS): ```bash cd ../reaperc2 diff --git a/deployments/k8s/ollama.yaml b/deployments/k8s/ollama.yaml new file mode 100644 index 0000000..f9549a6 --- /dev/null +++ b/deployments/k8s/ollama.yaml @@ -0,0 +1,152 @@ +# In-cluster Ollama for Operator AI (k8s and k3s). +# ReaperC2 uses the OpenAI-compatible API at /v1 on port 11434. +# +# Apply before operator-ai.yaml (or together): +# kubectl apply -f deployments/k8s/ollama.yaml +# kubectl apply -f deployments/k8s/operator-ai.yaml +# +# k3s: default StorageClass is local-path (ReadWriteOnce). On multi-node clusters, +# ensure the Ollama pod stays on the node that holds the PVC. +# Pull models: edit ConfigMap ollama-pull-models, or exec: +# kubectl exec -n ollama-ns deploy/ollama -- ollama pull llama3.2:latest +apiVersion: v1 +kind: Namespace +metadata: + name: ollama-ns +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ollama-pull-models + namespace: ollama-ns +data: + # Comma-separated tags pulled on first pod start (empty = skip init pull). + # Keep in sync with REAPER_AI_OLLAMA_MODELS in operator-ai.yaml when possible. + OLLAMA_PULL_MODELS: "llama3.2:latest,gpt-oss:latest,deepseek-r1:latest" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: ollama-data + namespace: ollama-ns + labels: + app: ollama +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 20Gi + # Omit to use the cluster default (k3s: local-path). Set explicitly if needed: + # storageClassName: local-path +--- +apiVersion: v1 +kind: Service +metadata: + name: ollama + namespace: ollama-ns + labels: + app: ollama +spec: + type: ClusterIP + selector: + app: ollama + ports: + - name: http + port: 11434 + targetPort: 11434 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ollama + namespace: ollama-ns + labels: + app: ollama +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app: ollama + template: + metadata: + labels: + app: ollama + spec: + initContainers: + - name: pull-models + image: ollama/ollama:latest + envFrom: + - configMapRef: + name: ollama-pull-models + command: + - /bin/sh + - -c + - | + set -e + models="$(echo "${OLLAMA_PULL_MODELS:-}" | tr ',' ' ')" + if [ -z "$(echo "$models" | tr -d '[:space:]')" ]; then + echo "No OLLAMA_PULL_MODELS configured; skipping init pull." + exit 0 + fi + ollama serve & + pid=$! + trap 'kill "$pid" 2>/dev/null || true' EXIT + for i in $(seq 1 90); do + ollama list >/dev/null 2>&1 && break + sleep 2 + done + for model in $models; do + model="$(echo "$model" | xargs)" + [ -n "$model" ] || continue + echo "Pulling $model..." + ollama pull "$model" + done + volumeMounts: + - name: ollama-data + mountPath: /root/.ollama + resources: + requests: + cpu: "250m" + memory: "1Gi" + limits: + cpu: "4" + memory: "8Gi" + containers: + - name: ollama + image: ollama/ollama:latest + ports: + - containerPort: 11434 + name: http + env: + # Listen on all interfaces inside the pod (ClusterIP Service only). + - name: OLLAMA_HOST + value: "0.0.0.0:11434" + volumeMounts: + - name: ollama-data + mountPath: /root/.ollama + readinessProbe: + httpGet: + path: / + port: 11434 + initialDelaySeconds: 5 + periodSeconds: 10 + livenessProbe: + httpGet: + path: / + port: 11434 + initialDelaySeconds: 30 + periodSeconds: 30 + resources: + requests: + cpu: "250m" + memory: "2Gi" + limits: + cpu: "4" + memory: "8Gi" + volumes: + - name: ollama-data + persistentVolumeClaim: + claimName: ollama-data diff --git a/deployments/k8s/operator-ai.yaml b/deployments/k8s/operator-ai.yaml index c7958f4..99d9a55 100644 --- a/deployments/k8s/operator-ai.yaml +++ b/deployments/k8s/operator-ai.yaml @@ -14,6 +14,10 @@ # # Deployment must reference reaperc2-ai-config and reaperc2-ai-secrets via envFrom # (see deployments/k8s/reaperc2/base/deployment.yaml). +# +# In-cluster Ollama is enabled by default (see REAPER_AI_OLLAMA_* below). Deploy the Service with: +# kubectl apply -f deployments/k8s/ollama.yaml +# Or use deploy-cluster.sh apply-ollama / all (and deployments/k3s/scripts/deploy.sh for OnPrem NFS). apiVersion: v1 kind: ConfigMap @@ -24,10 +28,10 @@ data: REAPER_AI_ENABLED: "1" REAPER_AI_DEFAULT_MODEL: "auto" REAPER_AI_DEFAULT_PROVIDER: "bedrock" - REAPER_AI_MAX_TOKENS: "2048" + REAPER_AI_MAX_TOKENS: "8192" # Optional unified catalog (foundry:/bedrock:/openai: prefixes). Discovery still adds live models. - # REAPER_AI_MODELS: "bedrock:global.anthropic.claude-fable-5,foundry:YOUR_DEPLOYMENT,openai:gpt-4o-mini" + # REAPER_AI_MODELS: "bedrock:global.anthropic.claude-fable-5,foundry:YOUR_DEPLOYMENT,openai:gpt-4o-mini,ollama:llama3.2:latest" # --- OpenAI --- # REAPER_AI_OPENAI_API_URL: "https://api.openai.com/v1" @@ -55,11 +59,14 @@ data: REAPER_AI_BEDROCK_MODELS: "global.anthropic.claude-fable-5,us.anthropic.claude-opus-4-8,us.anthropic.claude-opus-4-7,us.anthropic.claude-sonnet-4-6,amazon.nova-lite-v1:0" # REAPER_AI_BEDROCK_MODEL: "us.anthropic.claude-opus-4-7" - # --- Ollama (optional in-cluster or host) --- - # REAPER_AI_OLLAMA_ENABLED: "1" - # REAPER_AI_OLLAMA_DISCOVER: "1" + # --- Ollama (in-cluster by default; apply deployments/k8s/ollama.yaml) --- + REAPER_AI_OLLAMA_ENABLED: "1" + REAPER_AI_OLLAMA_DISCOVER: "1" + REAPER_AI_OLLAMA_API_URL: "http://ollama.ollama-ns.svc.cluster.local:11434/v1" + # Comma-separated extras merged with live discovery from GET /api/tags: + REAPER_AI_OLLAMA_MODELS: "llama3.2:latest,gpt-oss:latest,deepseek-r1:latest" + # Host / Docker Compose only (replace API URL above): # REAPER_AI_OLLAMA_API_URL: "http://host.docker.internal:11434/v1" - # REAPER_AI_OLLAMA_MODELS: "llama3.2:latest" --- apiVersion: v1 kind: Secret diff --git a/deployments/k8s/reaperc2/README.md b/deployments/k8s/reaperc2/README.md index e218142..2fb4c0b 100644 --- a/deployments/k8s/reaperc2/README.md +++ b/deployments/k8s/reaperc2/README.md @@ -23,7 +23,7 @@ These caused most deploy pain — avoid them up front: | **`docdb-init` Job errors** | Run `./deploy-cluster.sh apply-core` first (refreshes SCRAM-SHA-1 scripts), then re-apply the Job. | | **App still uses wrong auth DB** | `base/deployment.yaml` reads `auth_source` from the secret. After editing the secret: `kubectl apply -f base/deployment.yaml` and rollout restart. | -ReaperC2 only needs DocumentDB for data — no Kubernetes PVC for the app. Operator AI uses Bedrock (see [Bedrock credentials](#bedrock-credentials-rotation)), not in-cluster Ollama. +ReaperC2 only needs DocumentDB for data — no Kubernetes PVC for the app. Operator AI includes **in-cluster Ollama** by default ([`../ollama.yaml`](../ollama.yaml); applied by `./deploy-cluster.sh apply-ollama` / `all`) plus optional Bedrock and other cloud providers (see [Bedrock credentials](#bedrock-credentials-rotation)). Skip Ollama with `SKIP_OLLAMA=1` if the node cannot host models. ## Quick install (script) @@ -240,7 +240,7 @@ Use the [checklist](#run-from-scratch-checklist) order. Notes: `./deploy-cluster.sh apply-core` applies the ReaperC2 Deployment, ServiceAccount, Service, and DocumentDB-related ConfigMaps (CA bundle + init scripts). It does **not** apply `ingress.yaml` / `ingressroute.yaml` — use [`./deploy-cluster.sh apply-ingress`](deploy-cluster.sh) or `kubectl apply -f ingress.yaml -f ingressroute.yaml -n reaperc2-ns` after Traefik (and cert-manager, if you use ACME annotations) are ready. -**Operator AI:** copy [`../operator-ai.yaml`](../operator-ai.yaml) → `../operator-ai.local.yaml`, set ConfigMap (Bedrock region, Foundry URL, **Azure deployment names**) and Secret (API keys). Apply the **`.local`** file only: +**Operator AI:** copy [`../operator-ai.yaml`](../operator-ai.yaml) → `../operator-ai.local.yaml`, set ConfigMap (Bedrock region, Foundry URL, **Azure deployment names**) and Secret (API keys). Ollama is **enabled by default** in the template and talks to `http://ollama.ollama-ns.svc.cluster.local:11434/v1` after `./deploy-cluster.sh apply-ollama` (or `all`). Apply the **`.local`** file only: ```bash kubectl apply -f ../operator-ai.local.yaml @@ -304,10 +304,11 @@ Step-by-step IRSA setup: [`examples/bedrock-irsa.md`](examples/bedrock-irsa.md). **Troubleshooting — `AccessDeniedException` on `bedrock:InvokeModel` with the EKS *node group* role in the error:** the pod is using the **node instance profile**, not IRSA. Fix: create the Bedrock policy + `eksctl create iamserviceaccount` (or annotate `serviceaccount.yaml` with `eks.amazonaws.com/role-arn`), ensure `REAPER_AI_BEDROCK_USE_IAM=1`, rollout restart, and confirm `AWS_ROLE_ARN` is set in the pod. Alternatively, put a Bedrock API key in `reaperc2-ai-secrets` (see [`operator-ai.yaml`](../operator-ai.yaml)). -If you previously deployed in-cluster Ollama, remove leftovers: +To remove a legacy Ollama Deployment that lived in `reaperc2-ns` (current stack uses `ollama-ns` via `../ollama.yaml`): ```bash kubectl delete deployment/ollama service/ollama pvc/ollama-data -n reaperc2-ns --ignore-not-found +kubectl delete -f ../ollama.yaml --ignore-not-found ``` ### 5. Verify @@ -338,7 +339,8 @@ Open `http://127.0.0.1:8443` locally. | `overlays/k3s/` | Kustomize overlay: `base` only (no registry secret patch) | | `base/kustomization.yaml` | `kubectl apply -k` entrypoint for **base** (included by overlays; **no** ingress) | | `base/namespace.yaml` | `reaperc2-ns` | -| [`../operator-ai.yaml`](../operator-ai.yaml) | Operator AI template (ConfigMap + Secret); apply `operator-ai.local.yaml` | +| [`../operator-ai.yaml`](../operator-ai.yaml) | Operator AI template (ConfigMap + Secret; Ollama enabled by default); apply `operator-ai.local.yaml` | +| [`../ollama.yaml`](../ollama.yaml) | In-cluster Ollama (`ollama-ns`); `./deploy-cluster.sh apply-ollama` | | `base/deployment.yaml` | ReaperC2 + DocumentDB env + CA volume | | `base/service.yaml` | ClusterIP :8080 (beacon) | | `ingress.yaml` | Standard Ingress for Traefik / cert-manager (**apply after** Traefik + cert-manager) | @@ -413,7 +415,7 @@ If you used cert-manager ACME on that ingress, also `kubectl get certificate,cer ## Teardown (app only) -Removes the ReaperC2 workload, ingress, DocumentDB app secret (if the `.local.yaml` file exists), init Jobs, and legacy Ollama objects. It does **not** delete the namespace, DocumentDB itself, admin/bootstrap secrets, ECR pull secret, or Operator AI — remove those with `kubectl delete` if you want a clean namespace. +Removes the ReaperC2 workload, ingress, DocumentDB app secret (if the `.local.yaml` file exists), init Jobs, and in-cluster Ollama (`../ollama.yaml`). It does **not** delete the namespace, DocumentDB itself, admin/bootstrap secrets, ECR pull secret, or Operator AI — remove those with `kubectl delete` if you want a clean namespace. ```bash cd deployments/k8s/reaperc2 @@ -428,5 +430,6 @@ kubectl delete -k overlays/aws-ecr --ignore-not-found kubectl delete -f ingress.yaml -f ingressroute.yaml -n reaperc2-ns --ignore-not-found kubectl delete -f examples/documentdb-secret.local.yaml --ignore-not-found kubectl delete -f base/docdb-init-job.yaml -f base/docdb-init-user-job.yaml --ignore-not-found +kubectl delete -f ../ollama.yaml --ignore-not-found kubectl delete deployment/ollama service/ollama pvc/ollama-data -n reaperc2-ns --ignore-not-found ``` diff --git a/deployments/k8s/reaperc2/deploy-cluster.sh b/deployments/k8s/reaperc2/deploy-cluster.sh index 01ece89..7d29199 100755 --- a/deployments/k8s/reaperc2/deploy-cluster.sh +++ b/deployments/k8s/reaperc2/deploy-cluster.sh @@ -41,6 +41,7 @@ Commands: preflight Check kubectl context, local files (warnings), Traefik IngressClass, optional CRDs. fetch-ca Run base/fetch-docdb-ca-bundle.sh (required before apply-core). Does not require *.local.yaml. apply-secrets Apply namespace + all three examples/*.local.yaml + ../operator-ai.local.yaml (if present). Validates required files first (see check-local). + apply-ollama Apply ../ollama.yaml (in-cluster Ollama for Operator AI). Skipped if SKIP_OLLAMA=1. ecr-secret Create/update reaperc2-myregistrykey (aws profile; validates required files first). apply-core kubectl apply -k (app + SA + service; no ingress). Validates required files first. apply-ingress Apply ingress.yaml + ingressroute.yaml (validates required files first). @@ -49,8 +50,8 @@ Commands: job-docdb-user Re-run docdb-init-user job (validates required files first). job-docdb-init Re-run docdb-init job (validates required files first). status Pods, deployment, services. - all Require local prereqs, then preflight, fetch-ca, apply-secrets, ecr-secret (aws only unless forced), apply-core, rollout (then prints next steps). - teardown Remove app from cluster (same as README Teardown): kustomize stack, ingress, jobs, sample secret apply, ollama leftovers. Does not delete the namespace or DocumentDB. + all Require local prereqs, then preflight, fetch-ca, apply-ollama, apply-secrets, ecr-secret (aws only unless forced), apply-core, rollout (then prints next steps). + teardown Remove app from cluster (same as README Teardown): kustomize stack, ingress, jobs, sample secret apply, ollama stack. Does not delete the namespace or DocumentDB. Environment: REAPER_CLUSTER aws (default) or k3s — picks Kustomize overlay and ECR checks. @@ -59,6 +60,7 @@ Environment: AWS_REGION ECR region for aws ecr get-login-password (optional if derivable from deployment image) SKIP_ECR_SECRET Set to 1 to skip ecr-secret in "all" (aws). On k3s, ecr-secret is skipped unless REAPER_ECR_SECRET=1. REAPER_ECR_SECRET Set to 1 on k3s to run ecr-secret in "all" (same as aws flow). + SKIP_OLLAMA Set to 1 to skip in-cluster Ollama on apply-ollama / all (Operator AI can still use cloud providers). Note: teardown does not remove Operator AI objects; use kubectl delete -f ../operator-ai.local.yaml if needed. EOF @@ -110,6 +112,21 @@ cmd_fetch_ca() { (cd "$BASE" && ./fetch-docdb-ca-bundle.sh) } +cmd_apply_ollama() { + if [[ "${SKIP_OLLAMA:-0}" == "1" ]]; then + info "Skipping Ollama (SKIP_OLLAMA=1)" + return 0 + fi + local ollama_yaml="$HERE/../ollama.yaml" + [[ -f "$ollama_yaml" ]] || die "missing $ollama_yaml" + info "Applying in-cluster Ollama ($ollama_yaml)" + "$KUBECTL" apply -f "$ollama_yaml" + "$KUBECTL" -n ollama-ns wait --for=jsonpath='{.status.phase}'=Bound pvc/ollama-data --timeout=180s || \ + echo "warn: ollama-data PVC not Bound yet (check StorageClass / capacity)" + "$KUBECTL" -n ollama-ns rollout status deploy/ollama --timeout=900s || \ + echo "warn: Ollama Deployment not Ready yet (first model pull can take several minutes)" +} + cmd_apply_secrets() { require_deploy_prereqs "$KUBECTL" apply -f "$BASE/namespace.yaml" @@ -124,7 +141,8 @@ cmd_apply_secrets() { info "Applying ../operator-ai.local.yaml" "$KUBECTL" apply -f "$HERE/../operator-ai.local.yaml" else - echo "warn: ../operator-ai.local.yaml not found — Operator AI env not updated (copy from ../operator-ai.yaml)." + echo "warn: ../operator-ai.local.yaml not found — applying ../operator-ai.yaml (template; Ollama enabled; replace CHANGE_ME keys)." + "$KUBECTL" apply -f "$HERE/../operator-ai.yaml" fi } @@ -257,6 +275,7 @@ cmd_job_docdb_init() { cmd_status() { "$KUBECTL" get pods,svc,deploy -n "$REAPER_NS" "$KUBECTL" get ingress,ingressroute -n "$REAPER_NS" 2>/dev/null || true + "$KUBECTL" get pods,svc,pvc -n ollama-ns 2>/dev/null || true } cmd_teardown_ingress() { @@ -302,6 +321,9 @@ cmd_teardown() { echo "warn: $HERE/examples/documentdb-secret.local.yaml missing — skip deleting that Secret (delete manually if applied)" fi "$KUBECTL" delete -f "$BASE/docdb-init-job.yaml" -f "$BASE/docdb-init-user-job.yaml" --ignore-not-found + if [[ -f "$HERE/../ollama.yaml" ]]; then + "$KUBECTL" delete -f "$HERE/../ollama.yaml" --ignore-not-found + fi "$KUBECTL" delete deployment/ollama service/ollama pvc/ollama-data -n "$REAPER_NS" --ignore-not-found info "Done. Namespace $REAPER_NS and other Secrets (admin bootstrap, ECR pull, Operator AI) may still exist — delete manually if needed." } @@ -310,6 +332,7 @@ cmd_all() { require_deploy_prereqs cmd_preflight cmd_fetch_ca + cmd_apply_ollama cmd_apply_secrets local skip_ecr="${SKIP_ECR_SECRET:-0}" if [[ "${REAPER_CLUSTER}" == "k3s" && "${REAPER_ECR_SECRET:-0}" != "1" ]]; then @@ -329,6 +352,7 @@ Next steps (see README): 2) Collections / indexes (optional): REAPER_CLUSTER=${REAPER_CLUSTER} ./deploy-cluster.sh job-docdb-init 3) When Traefik + cert-manager + IngressRoute CRD are ready: REAPER_CLUSTER=${REAPER_CLUSTER} ./deploy-cluster.sh apply-ingress 4) Admin UI: kubectl port-forward -n $REAPER_NS deployment/reaperc2-deployment 8443:8443 + 5) Ollama: kubectl -n ollama-ns get pods (models pull on first start; Operator AI uses http://ollama.ollama-ns.svc.cluster.local:11434/v1) EOF } @@ -341,6 +365,7 @@ main() { preflight) cmd_preflight ;; fetch-ca) cmd_fetch_ca ;; apply-secrets) cmd_apply_secrets ;; + apply-ollama) cmd_apply_ollama ;; ecr-secret) cmd_ecr_secret ;; apply-core) cmd_apply_core ;; apply-ingress) cmd_apply_ingress ;; diff --git a/docs/kubernetes.md b/docs/kubernetes.md index 7a927ac..b009051 100644 --- a/docs/kubernetes.md +++ b/docs/kubernetes.md @@ -1,23 +1,34 @@ # Kubernetes -Example and environment-specific manifests live under [`deployments/k8s/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/), including: +Deployment layouts live under [`deployments/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/). See **[`deployments/README.md`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/README.md)** for which path to use. -- [`full-deployment.yaml`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/full-deployment.yaml) — namespace, MongoDB, ReaperC2, Traefik **IngressRoute**, beacon-facing service (sample placeholders for secrets and NFS). -- [`OnPrem/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/OnPrem/) — in-cluster MongoDB + ReaperC2. -- [`reaperc2/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/reaperc2/) — ReaperC2 on **EKS or k3s** + **DocumentDB** + Traefik/cert-manager; use [`deploy-cluster.sh`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/reaperc2/deploy-cluster.sh), the thin wrappers [`deploy.sh`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/reaperc2/deploy.sh) (optional **`--with-egress`** / **`--no-egress`**) and [`reroll.sh`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/reaperc2/reroll.sh) (restart + optional secret/ECR refresh), or `kubectl apply -k deployments/k8s/AWS` (**legacy shim** → `aws-ecr` overlay) or `kubectl apply -k deployments/k8s/reaperc2/overlays/k3s` for the **core** stack, then apply ingress when Traefik/cert-manager are ready (see [`reaperc2/README.md`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/reaperc2/README.md)). -- [`k3s/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/k3s/) — short entrypoint for **k3s** (`REAPER_CLUSTER=k3s` + [`deploy-cluster.sh`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/reaperc2/deploy-cluster.sh)). +| Path | Use case | +|------|----------| +| [`deployments/k8s/reaperc2/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/reaperc2/) | **DocumentDB** on EKS or k3s — [`DEPLOY.md`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/DEPLOY.md) | +| [`deployments/k3s/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k3s/) | **OnPrem** k3s: NFS MongoDB + in-cluster **Ollama** | +| [`deployments/k8s/k3s/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/k3s/) | Short pointer: k3s + DocumentDB → `reaperc2/` | -Always review and replace **placeholders** (registry pull secrets, Mongo credentials, storage class, hostnames, TLS issuers) before applying to a real cluster. +Legacy samples: [`full-deployment.yaml`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/full-deployment.yaml), [`OnPrem/full-deployment.yaml`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/OnPrem/full-deployment.yaml). + +Always review placeholders (registry secrets, Mongo credentials, hostnames, TLS) before applying. ## Build and push the image +**DocumentDB (EKS / k3s):** + +```bash +cd deployments/k8s/reaperc2 +./build-push-image.sh --arch amd64 # or arm64 for Pi nodes +``` + +**OnPrem NFS + Ollama:** + ```bash -git submodule update --init --recursive # recommended -docker build -t /reaperc2: . -docker push /reaperc2: +cp deployments/k3s/config.example.env deployments/k3s/config.env +./deployments/k3s/scripts/build-push-image.sh ``` -Point the Deployment image to your pushed tag. If CI cannot init the submodule, pass `--build-arg SCYTHE_GIT_REF=` so the Dockerfile clone matches the Scythe revision you intend to ship. +Requires **Go** on the build machine for the OnPrem script (host cross-compile + `Dockerfile.pack`). ## Two listeners: beacon vs admin @@ -26,68 +37,35 @@ The binary listens on **8080** (beacon) and **8443** (admin) by default. - **Expose 8080** through Ingress / LoadBalancer for implant traffic. Configure `BEACON_PUBLIC_BASE_URL` (and per-beacon base URL in the UI) to that **public** HTTPS origin. - **Do not** publish **8443** on a public Ingress for routine operation. Use **`kubectl port-forward`** (or an SSH tunnel via a bastion) from a trusted workstation to reach the admin UI at `http://127.0.0.1:8443` on that machine. -Example: - ```bash kubectl port-forward -n reaperc2-ns deployment/reaperc2-deployment 8443:8443 ``` -Adjust namespace and resource names to match your manifests. - ## Apply manifests -After editing YAML for your cluster: +**DocumentDB:** ```bash -kubectl apply -f deployments/k8s/full-deployment.yaml +cd deployments/k8s/reaperc2 +REAPER_CLUSTER=k3s ./deploy-cluster.sh all # or default aws for EKS +./deploy-cluster.sh apply-ingress ``` -Traefik **IngressRoute** in the sample routes **beacon** traffic. If you use another ingress controller, adapt routes and TLS the same way: **only** the beacon service port should be on the public edge unless you deliberately expose the admin UI. - -## MongoDB vs DocumentDB +**OnPrem NFS + Ollama:** -- **Root / OnPrem** [`full-deployment.yaml`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/full-deployment.yaml) includes an in-cluster MongoDB Deployment and PVC. -- **AWS DocumentDB** (any Kubernetes distro): copy and edit [`deployments/k8s/reaperc2/examples/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/reaperc2/examples/) templates to `*.local.yaml`, fetch the RDS CA bundle (`reaperc2/base/fetch-docdb-ca-bundle.sh` or `./deploy-cluster.sh fetch-ca`), then `kubectl apply -k deployments/k8s/reaperc2/overlays/aws-ecr` (or `.../k3s`) and **`./deploy-cluster.sh apply-ingress`** (or manual `ingress.yaml` / `ingressroute.yaml`) when Traefik is installed. With `DEPLOY_ENV=AWS`, the app adds DocumentDB TLS query parameters automatically ([`pkg/dbconnections/mongoconnections.go`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/pkg/dbconnections/mongoconnections.go)). +```bash +./deployments/k3s/scripts/deploy.sh +``` -## Seeding the database +## MongoDB vs DocumentDB -- **OnPrem / in-cluster Mongo**: [`test/setup_mongo.sh`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/test/setup_mongo.sh) with `MONGO_HOST` set to the Mongo Service DNS name. -- **AWS DocumentDB**: run `base/docdb-init-job.yaml` and `base/docdb-init-user-job.yaml` from [`deployments/k8s/reaperc2/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/reaperc2); use your infra repo or a host with the RDS CA bundle for ad-hoc `mongosh`. +- **OnPrem / in-cluster Mongo**: `deployments/k3s/scripts/deploy.sh` or [`test/setup_mongo.sh`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/test/setup_mongo.sh). +- **DocumentDB**: [`deployments/k8s/reaperc2/README.md`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/reaperc2/README.md). ## Operator AI (multi-model) -The same env vars as Docker Compose / `.env.example` apply in Kubernetes. Sample manifests: - -- [`deployments/k8s/operator-ai.yaml`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/operator-ai.yaml) — **ConfigMap** (model catalog, defaults) and **Secret** (API keys). -- ReaperC2 Deployments under `deployments/k8s/**/full-deployment.yaml` load them with `envFrom` (`optional: true` so the app starts before you enable AI). - -**Enable Operator AI:** - -1. Copy the template (gitignored local file): - - ```bash - cp deployments/k8s/operator-ai.yaml deployments/k8s/operator-ai.local.yaml - ``` - -2. Edit `operator-ai.local.yaml`: ConfigMap (providers, model/deployment names, Bedrock region) and Secret (API keys). Same variables as `.env.example`. For **Azure Foundry**, use your resource URL (`https://YOUR_RESOURCE.openai.azure.com`) and **deployment names** from `az cognitiveservices account deployment list` — not catalog names like `gpt-5.5` unless that is the deployment name. - -3. Apply the **local** file (not the template): - - ```bash - kubectl apply -f deployments/k8s/operator-ai.local.yaml - kubectl rollout restart deployment/reaperc2-deployment -n reaperc2-ns - ``` - -4. Port-forward admin (`8443`) and open **Operator AI** — choose **Auto** or a specific model from the dropdown. - -| Source | Variables | -|--------|-----------| -| ConfigMap `reaperc2-ai-config` | `REAPER_AI_ENABLED`, `REAPER_AI_DEFAULT_*`, `REAPER_AI_*_MODELS`, `REAPER_AI_FOUNDRY_*`, `REAPER_AI_BEDROCK_*`, `REAPER_AI_OLLAMA_*` | -| Secret `reaperc2-ai-secrets` | `REAPER_AI_OPENAI_API_KEY`, `REAPER_AI_ANTHROPIC_API_KEY`, `REAPER_AI_FOUNDRY_API_KEY`, `REAPER_AI_BEDROCK_*` keys (or Bedrock via IRSA — see operator guide) | - -**AWS EKS / k3s + DocumentDB:** `kubectl apply -k deployments/k8s/reaperc2/overlays/aws-ecr` (or `.../k3s`) applies the core Deployment/Service and DocumentDB ConfigMaps only — **not** Operator AI or Ingress. Use `operator-ai.local.yaml` and [`deploy-cluster.sh`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/reaperc2/deploy-cluster.sh) (or the manual steps in [`deployments/k8s/reaperc2/README.md`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/reaperc2/README.md)). The [`AWS/`](https://github.com/BuildAndDestroy/ReaperC2/tree/main/deployments/k8s/AWS/) directory remains a one-line `kubectl apply -k deployments/k8s/AWS` shim to `aws-ecr`. - -**Ollama in K8s** is optional. Most deployments use cloud APIs only. If you run Ollama as its own Deployment/Service, set `REAPER_AI_OLLAMA_ENABLED=1` and `REAPER_AI_OLLAMA_API_URL` to the in-cluster URL (for example `http://ollama.ollama-ns.svc.cluster.local:11434/v1`) in the ConfigMap — not `host.docker.internal`. +- [`deployments/k8s/operator-ai.yaml`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/operator-ai.yaml) — template with **Ollama enabled by default**; copy to `operator-ai.local.yaml` for secrets. +- **In-cluster Ollama:** [`deployments/k8s/ollama.yaml`](https://github.com/BuildAndDestroy/ReaperC2/blob/main/deployments/k8s/ollama.yaml) — applied by `./deploy-cluster.sh apply-ollama` / `all`, or `deployments/k3s/scripts/deploy.sh`. Skip with `SKIP_OLLAMA=1`. See [Operator AI](/documentation/operator-guide-ai) for variable details. diff --git a/go.mod b/go.mod index 85cf0f8..4cf3c0a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module ReaperC2 -go 1.24 +go 1.25.0 + +toolchain go1.25.12 require ( github.com/aws/aws-sdk-go-v2 v1.41.7 @@ -11,9 +13,9 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/pquerna/otp v1.5.0 - github.com/yuin/goldmark v1.7.8 + github.com/yuin/goldmark v1.7.17 go.mongodb.org/mongo-driver v1.17.3 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.54.0 ) require ( @@ -37,7 +39,7 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect ) diff --git a/go.sum b/go.sum index 7c3cd0f..0b00c9d 100644 --- a/go.sum +++ b/go.sum @@ -65,37 +65,37 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= -github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.17 h1:p36OVWwRb246iHxA/U4p8OPEpOTESm4n+g+8t0EE5uA= +github.com/yuin/goldmark v1.7.17/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= go.mongodb.org/mongo-driver v1.17.3 h1:TQyXhnsWfWtgAhMtOgtYHMTkZIfBTpMTsMnd9ZBeHxQ= go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=