Kasten FLR UI — File Level Recovery, Malware Detection & Scheduled Scanning

Kasten FLR UI — File Level Recovery, Malware Detection & Scheduled Scanning

A full-featured, demo-grade web application for Kasten K10's native File-Level Recovery (FLR) API, extended with integrated on-demand malware scanning and automated scheduled scanning of all backup policies. Provides a guided six-step wizard to browse backup contents and recover individual files or folders — directly into running application pods, to any SSH host, or as a ZIP download to the browser.

Built with FastAPI + vanilla JS on Alpine Linux, deployed on OpenShift in its own namespace.

Written by James Tate — Kasten Senior System Engineer — 2026

https://github.com/jdtate101/kasten-flr-ui


Table of Contents


Architecture

Browser
  │
  ▼
nginx:8080  ──────────────────────────────────────────────────────────────
  │                                                                       │
  ▼                                                                       │
FastAPI:8000                                                              │
  │                                                                       │
  ├── Kubernetes API (https://kubernetes.default.svc)                     │
  │     ├── GET  /apis/config.kio.kasten.io   → Policies                 │
  │     ├── GET  /apis/apps.kio.kasten.io     → RestorePoints            │
  │     ├── POST /apis/datamover.kio.kasten.io → FileRecoverySession CR  │
  │     ├── POST /apis/actions.kio.kasten.io  → RestoreAction (scan)     │
  │     ├── GET  /api/v1/namespaces           → Namespace list           │
  │     └── GET  /api/v1/pods                 → Pod list + exec          │
  │                                                                       │
  ├── kubectl port-forward ──► FLR SFTP pod (kasten-io:2222)             │
  │     └── paramiko SFTP client ──► Browse & stage files                │
  │                                                                       │
  ├── kubectl cp ──► Running pod filesystem (pod recovery)               │
  ├── paramiko SCP ──► Remote SSH host (SSH recovery)                    │
  ├── ZIP + stream ──► Browser download                                  │
  │                                                                       │
  ├── Malware scan namespace (kasten-malware-scan-<id>)                  │
  │     ├── RestoreAction → clone PVCs into isolated namespace            │
  │     ├── Scale workloads to 0 → release PVC mounts                    │
  │     ├── YARA rules ConfigMap → injected from /data/yara-rules/       │
  │     └── Scanner Job (malware-scanner image) → YARA + ClamAV          │
  │                                                                       │
  └── asyncio background scheduler                                        │
        ├── Wakes every 60s → checks /data/scan-schedule.json            │
        ├── Iterates all eligible policies sequentially                   │
        ├── Uses snapshot RP (falls back to export) per policy            │
        └── Writes to /data/scan-history.json + optional email alert     │
                                                                          │
Static files (index.html, app.js, scan.js, schedule.js,                  │
              style.css, login.html, veeam-logo.png) ◄────────────────────┘

Why kubectl port-forward for SFTP?

Kasten's FLR NetworkPolicy only allows SFTP ingress from the application's own namespace (e.g. navidrome). Since the app pod runs in kasten-flr-ui, direct TCP to the FLR pod is blocked. kubectl port-forward tunnels through the Kubernetes API server, which is not subject to NetworkPolicy.


Features

Core Recovery

Feature Detail
Policy browser Lists all Kasten backup policies with real-time search/filter by name or namespace. Skeleton shimmer placeholders while loading. Excludes KubeVirt/VM policies and policies with no PVC data
Restore point selection Snapshot and export RPs with type badges. Paired RPs from the same backup run grouped side-by-side with green left border. Skeleton loading on Step 2
Step 3 confirmation card Policy avatar, restore point name, type badge, PVC list, mount headroom warning if capacity is low
FLR session lifecycle Creates FileRecoverySession CR, SSE heartbeat polling until Ready, SFTP connection, session expiry countdown. Click badge to get terminate dropdown
Dual-pane file browser SFTP browser with 60+ file-type-accurate SVG icons. Hover tooltips show file size, modified date and full path. Drag-and-drop or checkbox selection basket
Pod recovery kubectl cp into a running pod with an in-browser filesystem navigator to select destination path
SSH recovery SCP files to any SSH host
Browser ZIP download Download selected files as a ZIP directly to the browser
Transfer success animation Spring-animated green banner with checkmark on completion
Real-time progress SSE-streamed colour-coded log window with progress bar

Malware Scanning

Feature Detail
Pre-recovery scan Scan any restore point via "Scan for Malware" in Step 2 before committing to recovery
Isolated clone namespace RestoreAction clones PVCs into kasten-malware-scan-<id>, workloads scaled to 0 to release PVC mounts
Dual-engine scanning YARA (user-defined rules) + ClamAV (freshclam updates signatures at scan start, 60s timeout, falls back to baked-in)
All PVCs scanned Every PVC in the restore point must pass — a single threat marks the whole restore point dirty
SSE streaming Live restore progress bar, per-PVC status pills, threat hits appear in real time
Animated result banner Full-width spring-animated CLEAN (green) or DIRTY (red) banner on completion
Threat detail Scanner, rule name, file path per threat, with MITRE ATT&CK / CISA reference links for known malware families
Unified scan history All scans (manual + scheduled) in one searchable modal with Manual/Scheduled type badges
Auto-cleanup Clone namespace deleted after scan; stale namespaces cleaned on pod startup

Scheduled Malware Scanner

Feature Detail
Daily automated scanning All eligible policies (those with PVC data) scanned once per day at a configured UTC time
Snapshot-first strategy Uses the latest snapshot RP for speed (seconds). Falls back to latest export if no snapshot exists
Sequential with gap One policy at a time with a configurable inter-policy gap (default 5 min) to limit resource usage
Persistent state Schedule config and run results stored on PVC — survives pod restarts. Scheduler resumes at next daily time after restart
Enable/disable toggle Master switch disables scanning without losing config — useful during maintenance
Run Now Manual trigger from the Scheduled Scan modal, independent of the daily schedule
In-app alert banner Red banner below the header on page load if the latest run found threats. Shows timestamp, dirty policy count and names. Dismissible per run, stored on PVC
Email alerts Gmail SMTP (app password) alert email with full HTML threat table per policy. Test email button to verify config
Unified history Results appear in the Malware Scan History modal alongside manual scans, tagged "Scheduled"

UI & Advanced Features

Feature Detail
Veeam-aligned design Veeam green #00c853 throughout. Flat dark header, ghost buttons, segmented destination tabs, card-based Steps 4/5/6
Diff mode Mount two export RPs simultaneously — Diff tab shows added (green), deleted (red), modified (amber), unchanged (grey)
FLR history detail panels Click any FLR history row to expand: metadata grid (RP, policy, location profile, duration, mode) and transfer detail table
Scan history filters Filter by policy/RP/PVC text, date picker (all scans on a given day), type (Manual/Scheduled), result (Clean/Dirty)
Scan history detail panels Click any scan row to expand: metadata grid + full threat table with reference links
API call visualiser Resizable panel at page bottom. Syntax-highlighted kubectl commands — green verb, white resource, grey namespace
Health dashboard Version strip, mount headroom progress bar, FLR session count, recovery history donut chart, active sessions table
Kubernetes token auth TokenReview validation, 8-hour HMAC-signed session cookie
Dark/light theme Full theme toggle persisted in localStorage. Veeam logo uses transparent PNG corners — correct on both themes
Startup cleanup Deletes stale flr-ui-* FileRecoverySessions and kasten-malware-scan-* namespaces on pod start
Graceful shutdown Cleans FLR session, SFTP connection and staging area on SIGTERM

Prerequisites

  • OpenShift 4.x with Kasten K10 installed in kasten-io
  • Harbor registry at harbor.apps.openshift2.lab.home (update image refs if different)
  • oc CLI authenticated to the cluster
  • docker on the build machine
  • Gmail account with an App Password for scheduled scan email alerts (optional)

File Structure

kasten-flr-ui/
├── Dockerfile                    # Multi-stage Alpine (Python + kubectl + nginx)
├── README.md
├── backend/
│   ├── main.py                   # FastAPI app, all routers, startup/shutdown, scheduler init
│   ├── auth.py                   # K8s token validation, HMAC session cookies
│   ├── flr.py                    # FileRecoverySession CR lifecycle
│   ├── sftp.py                   # Paramiko SFTP client via kubectl port-forward
│   ├── transfer.py               # kubectl cp, SCP, ZIP transfer engines
│   ├── kasten.py                 # Kubernetes API client helpers
│   ├── history.py                # FLR session history (JSON on PVC)
│   ├── keys.py                   # ed25519 keypair generation
│   ├── scan_routes.py            # On-demand malware scan API + SSE stream
│   ├── yara_routes.py            # YARA rules upload/list/delete/settings
│   ├── schedule_routes.py        # Scheduled scanner, background loop, email alerts
│   └── requirements.txt
├── frontend/
│   ├── index.html                # Main app — 6-step wizard + all modals
│   ├── login.html                # Token login page + capability grid
│   ├── style.css                 # Full Veeam-aligned dark/light theme
│   ├── app.js                    # Wizard, file browser, health modal, FLR history
│   ├── scan.js                   # ScanUI — malware scan overlay, unified history + filters
│   ├── schedule.js               # ScheduleUI — scheduler modal, alert banner
│   └── veeam-logo.png            # Veeam logo, transparent corners, 24KB optimised
├── scanner-image/
│   ├── Dockerfile                # Alpine + YARA + ClamAV scanner image
│   ├── scan.sh                   # freshclam + YARA + ClamAV, structured output
│   └── rules/
│       └── kasten-starter.yar    # Starter YARA rules (37 rules, 9 categories)
├── nginx/
│   └── nginx.conf                # Proxy, SSE support, large download config
└── k8s/
    ├── namespace.yaml
    ├── serviceaccount.yaml
    ├── clusterrole.yaml
    ├── clusterrolebinding.yaml
    ├── scc.yaml
    ├── pvc.yaml                  # 1Gi PVC for all persistent state
    ├── deployment.yaml           # Recreate strategy, emptyDir staging (10Gi)
    ├── service.yaml
    └── route.yaml

Initial Deployment

1. Build and push both images

cd kasten-flr-ui

# Main FLR UI image
docker build -t harbor.apps.openshift2.lab.home/kasten-flr-ui/kasten-flr-ui:latest .
docker push harbor.apps.openshift2.lab.home/kasten-flr-ui/kasten-flr-ui:latest

# Malware scanner image
cd scanner-image/
docker build -t harbor.apps.openshift2.lab.home/kasten-flr-ui/malware-scanner:latest .
docker push harbor.apps.openshift2.lab.home/kasten-flr-ui/malware-scanner:latest
cd ..

2. Apply Kubernetes manifests

oc apply -f k8s/namespace.yaml
oc apply -f k8s/serviceaccount.yaml
oc apply -f k8s/clusterrole.yaml
oc apply -f k8s/clusterrolebinding.yaml
oc apply -f k8s/scc.yaml
oc apply -f k8s/pvc.yaml
oc apply -f k8s/deployment.yaml
oc apply -f k8s/service.yaml
oc apply -f k8s/route.yaml

3. Verify

oc rollout status deployment/kasten-flr-ui -n kasten-flr-ui
oc get route kasten-flr-ui -n kasten-flr-ui
oc exec deployment/kasten-flr-ui -n kasten-flr-ui -- df -h /data

Update & Redeploy

# Full rebuild + push + restart + archive
docker build -t harbor.apps.openshift2.lab.home/kasten-flr-ui/kasten-flr-ui:latest . && \
docker push harbor.apps.openshift2.lab.home/kasten-flr-ui/kasten-flr-ui:latest && \
oc rollout restart deployment/kasten-flr-ui -n kasten-flr-ui && \
tar -czf ~/kasten-flr-ui-$(date +%Y%m%d-%H%M%S).tar.gz -C ~ kasten-flr-ui/ && \
echo "✓ Done"

Hot-copy frontend files without a full rebuild:

POD=$(oc get pod -n kasten-flr-ui -l app=kasten-flr-ui -o jsonpath='{.items[0].metadata.name}')
oc cp frontend/app.js       kasten-flr-ui/$POD:/usr/share/nginx/html/app.js
oc cp frontend/scan.js      kasten-flr-ui/$POD:/usr/share/nginx/html/scan.js
oc cp frontend/schedule.js  kasten-flr-ui/$POD:/usr/share/nginx/html/schedule.js
oc cp frontend/style.css    kasten-flr-ui/$POD:/usr/share/nginx/html/style.css
oc cp frontend/index.html   kasten-flr-ui/$POD:/usr/share/nginx/html/index.html
oc cp frontend/login.html   kasten-flr-ui/$POD:/usr/share/nginx/html/login.html

Rebuild scanner image only:

cd scanner-image/
docker build -t harbor.apps.openshift2.lab.home/kasten-flr-ui/malware-scanner:latest .
docker push harbor.apps.openshift2.lab.home/kasten-flr-ui/malware-scanner:latest

Recovery Workflow

Step 1 — Select Policy
  Real-time search/filter by name or namespace.
  Shimmer skeleton while loading. Excludes VM and no-PVC policies.

Step 2 — Select Restore Point
  Snapshot and export RPs with type badges.
  Paired RPs (same backup run) grouped side-by-side.
  "Scan for Malware" to verify the RP before recovery.
  Diff dropdown for export RPs (select a second RP to compare).

Step 3 — Start FLR Session
  Confirmation card: policy avatar, RP name, PVC pills, headroom warning.
  Creates FileRecoverySession CR. Polls until Ready. Connects SFTP.

Step 4 — Browse & Select Files
  Left: SFTP browser with 60+ file-type SVG icons.
  Hover any file for tooltip: size, modified date, full path.
  Right: selection basket. Drag-and-drop or checkbox.
  Diff tab available in diff mode.

Step 5 — Choose Destination
  Segmented tab control: Pod/PVC · SSH Host · ZIP download.
  Pod mode: in-browser filesystem navigator to pick destination path.

Step 6 — Transfer
  Progress card + colour-coded SSE log.
  Spring-animated green success banner on completion.
  Terminate → namespace cleanup → return to Step 1.

Malware Scanning

On-demand scan

Click Scan for Malware on any restore point in Step 2. A full-screen three-phase modal opens:

Phase 1 — Restore: Creates an isolated kasten-malware-scan-<id> namespace. Kasten RestoreAction clones the restore point PVCs. Workloads are scaled to 0 to release PVC mounts. Progress bar streams in real time.

Phase 2 — Scan: freshclam updates ClamAV signatures (60s timeout, falls back to baked-in sigs if offline). YARA scans files under the configured size threshold. ClamAV scans all files. Threats appear in real time as they are detected. Per-PVC status pills show progress.

Phase 3 — Results: Animated CLEAN (green) or DIRTY (red) banner. Threat table shows scanner, rule name and file path. Known families link to MITRE ATT&CK or CISA advisories. Clone namespace deleted (or retained if you choose to keep it for investigation).

Scan the snapshot (local, seconds) → if clean, proceed with FLR using the export RP.


Scheduled Malware Scanner

Configuration

Open the Scheduled Scan modal from the header. Settings are persisted to /data/scan-schedule.json.

Setting Default Description
Enabled Off Master on/off switch
Run time (UTC) 02:00 Daily execution time
Gap between policies 5 min Pause between each policy scan
Gmail address SMTP sender (your@gmail.com)
Gmail app password 16-character Google app password
Alert recipients Comma-separated email addresses

Use Send Test Email to verify SMTP config before enabling alerts.

Execution flow

  1. Background asyncio task wakes every 60 seconds
  2. Reads config — if enabled and past scheduled time, starts a run
  3. Fetches all policies with PVC data (same filter as Step 1)
  4. For each policy: gets the latest snapshot RP; falls back to export if none
  5. Creates isolated scan namespace, restores PVCs, runs YARA + ClamAV
  6. Writes result to /data/scan-history.json with scan_type: "scheduled"
  7. Waits the configured gap, then moves to the next policy
  8. After all policies: if any DIRTY → sends alert email + sets in-app banner
  9. Computes and saves next daily run time

In-app alert banner

A red banner appears below the header on the next page load after a dirty run. It shows the run timestamp, the number of dirty policies and their names. Clicking View Details opens the unified Malware Scan History modal pre-filtered to that run's results. The banner is dismissible per run — dismissal is written to the PVC and persists across refreshes.

Alert email

An HTML email is sent via Gmail SMTP if any policy scan finds threats. It includes a table of affected policies, namespaces, threat counts and per-threat detail (scanner, rule, file path).


YARA Rules Management

Click YARA Rules in the header.

  • Upload .yar or .yara files via drag-and-drop or file picker
  • Stored on the persistent PVC at /data/yara-rules/
  • Injected into each scanner Job via a Kubernetes ConfigMap at scan time
  • Falls back to image-baked kasten-starter.yar if no user rules are present
  • Preview rule content and delete individual files from the modal
  • Max file size — files larger than this threshold are skipped by YARA (default 1MB, configurable). Stored in /data/yara-settings.json

Starter rules (kasten-starter.yar) — 37 rules, 9 categories

Category Rules
Test EICAR test file
Ransomware WannaCry, LockBit, REvil/Sodinokibi, Conti, BlackCat/ALPHV, Phobos
Web shells PHP shell, ASPX shell, JSP shell, China Chopper
Credential stealers RedLine, Raccoon, AgentTesla
RATs AsyncRAT, QuasarRAT
Offensive tools Cobalt Strike, Mimikatz, Metasploit/Meterpreter
Miners XMRig cryptocurrency miner
Loaders PowerShell downloader, Linux reverse shell
Kubernetes-specific Secret exfiltration, container escape, K8s API abuse

Snapshot vs Export Restore Points

Type Badge FLR Diff mode Malware scan Restore speed
Snapshot Green Seconds (local)
Export Blue Minutes (S3/object store)

Snapshot RPs are stored locally on the cluster. Export RPs are stored in an external location profile (S3, NFS, etc.) and must be pulled down before the restore point can be used — hence the speed difference.


Authentication

  1. Browse to the app URL — unauthenticated users are redirected to /login
  2. Obtain your token: oc whoami -t
  3. Paste the token and click Authenticate
  4. The backend validates the token via the Kubernetes TokenReview API
  5. A signed session cookie is issued (8-hour expiry, HMAC-signed with itsdangerous)
  6. Any 401 from the backend automatically redirects to /login

Session History

PVC data layout

Path Content Max records
/data/flr-history.json FLR session records 500
/data/scan-history.json All scan records (manual + scheduled, scan_type field) 500
/data/scan-schedule.json Scheduler config and last/next run state
/data/scheduled-scan-results.json Per-run summary records for the scheduler 200
/data/yara-rules/ User YARA rule files
/data/yara-settings.json YARA max file size setting

All writes are atomic: write to .tmp then os.replace(). Oldest records are pruned automatically when limits are reached.

FLR History modal

Click FLR History in the header. Click any row to expand a detail panel showing:

  • Restore point name, policy, location profile, session start, duration, mode (Standard/Diff)
  • Transfer table: destination type, host/pod, item count, outcome

Malware Scan History modal

Click Malware Scan History in the header (or View Scan History in the Scheduled Scan modal).

Filter bar controls:

  • Text search — filters by policy name, restore point name or PVC name
  • Date picker — shows only scans from a selected day (useful for reviewing all scheduled runs on a given date)
  • Type — All / Manual / Scheduled
  • Result — All / Clean / Dirty

Click any row to expand: scanner versions (YARA/ClamAV), duration, restore point date, and a full threat table with MITRE/CISA reference links.


Health Dashboard

Click Health in the header. Auto-refreshes every 30 seconds while open.

Component Description
Version strip Kasten K10 version · cluster version · platform (OpenShift / Kubernetes)
Mount Headroom Progress bar — mounts in use vs frs.maxMountsPerNamespace limit. Red <25%, amber <50%, green otherwise
FLR Sessions Cluster-wide count of FileRecoverySessions; active session indicator
Recovery History Canvas donut chart — completed (green) / terminated (amber) / failed (red)
Active sessions table All cluster-wide FLR sessions with namespace pills, state badge and expiry time

Technical Notes

Scheduler persistence — Config and run results stored on PVC. Pod restarts resume at the next configured daily time. Any in-progress scan interrupted by a restart will have its clone namespace cleaned up at next startup.

One FLR session at a time — The backend tracks a single active session. A second start attempt returns HTTP 409.

ClamAV signature updatesfreshclam runs with a 60-second timeout at the start of every scan (both on-demand and scheduled). Falls back gracefully to image-baked signatures if the network is unavailable.

Staging area — Selected files are downloaded from SFTP to /tmp/flr-stage (emptyDir, 10Gi limit) before onward transfer. Adjust the emptyDir.sizeLimit in k8s/deployment.yaml for large recoveries.

Session expiry — Kasten default is 30 minutes, configurable via Helm frs.sessionExpiryTimeInMinutes. The live countdown is shown in the header session badge. Click the badge while a session is active to get a terminate dropdown.

Deployment strategystrategy: Recreate because the PVC is ReadWriteOnce — only one pod can mount it at a time. This means a brief downtime during rolling deploys.

NetworkPolicy bypass — Kasten's FLR NetworkPolicy restricts SFTP ingress to the application namespace. kubectl port-forward tunnels through the K8s API server, which is exempt from NetworkPolicy, providing a clean bypass.

API call visualiser — The panel at the bottom of the page is resizable by dragging its top edge. Height preference is saved to localStorage.


Environment Variables

Variable Default Description
DATA_DIR /data Persistent PVC mount point for all state
SCANNER_IMAGE harbor.apps.openshift2.lab.home/kasten-flr-ui/malware-scanner:latest Container image used for malware scan Jobs

RBAC Summary

The kasten-flr-ui ClusterRole grants the service account the following permissions:

Resource API Group Verbs
restorepoints, restorepoints/details apps.kio.kasten.io get, list, watch
restorepointcontents, restorepointcontents/details apps.kio.kasten.io get, list
policies config.kio.kasten.io get, list
filerecoverysessions datamover.kio.kasten.io get, list, create, delete, watch
restoreactions actions.kio.kasten.io get, list, create, watch, delete
namespaces core get, list, create, delete
pods core get, list, watch
pods/exec core create, get
pods/portforward core create, get
pods/log core get, list, watch
persistentvolumeclaims core get, list, watch
services core get, list
configmaps core get, list, create
deployments, statefulsets, replicasets apps get, list, watch, patch
jobs batch get, list, create, watch, delete
clusterversions config.openshift.io get, list
clusterserviceversions operators.coreos.com get, list

Kasten FLR API Reference