Skip to main content

Air-Gap Deployment Guide

This guide explains how to deploy Guardimesh in air-gapped (disconnected) Kubernetes clusters where nodes have no access to the public internet. This deployment model is available on the Enterprise tier.

Overview

Air-gapped deployments are common in:

  • Government and defense installations
  • Regulated industries (finance, healthcare)
  • Isolated production environments with strict network policies
  • Environments behind strict egress firewalls

Guardimesh supports fully disconnected operation through:

  1. Enterprise Operator — Deploys the full platform stack in-cluster using the GuardimeshPlatform custom resource
  2. Internal Signature Server — Serves ClamAV databases from a local PVC (no internet required)
  3. Local PostgreSQL — Replaces BigQuery for scan result storage
  4. Self-contained Web Console — UI and API run locally within the cluster

Architecture

┌─────────────────────────────────────────────────────────────┐
│ Air-Gapped Kubernetes Cluster │
│ │
│ ┌────────────────────┐ ┌──────────────────────┐ │
│ │ Signature Server │ │ PostgreSQL │ │
│ │ (Deployment + PVC) │ │ (StatefulSet + PVC) │ │
│ │ │ │ │ │
│ │ Serves ClamAV │ │ Scan results │ │
│ │ signatures via HTTP │ │ User data │ │
│ └────────────────────┘ └──────────────────────┘ │
│ ↓ ↑ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Scanner DaemonSet (one pod per node) │ │
│ │ │ │
│ │ init-puller → antivirus, scanner, inspector, │ │
│ │ puller (updates every 12h) │ │
│ └────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ backend-api (Scan Data Ingestion) │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ web-console (UI + Session API) │ │
│ └────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Admin access via kubectl / Ingress

Prerequisites

Container Images

Pre-load all required images into your private registry:

ImagePurpose
quay.io/guardimesh/guardimesh-operator-enterprise:latestEnterprise operator
quay.io/guardimesh/web-console:latestWeb console backend + UI
quay.io/guardimesh/backend-api:latestScan data ingest API
quay.io/guardimesh/storage-access-airgap:latestInternal signature server
quay.io/guardimesh/guardimesh-scanner:latestScanner
quay.io/guardimesh/guardimesh-antivirus:latestClamAV daemon
quay.io/guardimesh/guardimesh-inspector:latestSystem info collector
quay.io/guardimesh/guardimesh-puller:latestSignature puller
postgres:15-alpinePostgreSQL database

Transfer images using your preferred air-gap method (e.g., skopeo copy, docker save/load, or image mirroring tools).

ClamAV Signature Files

Download signature files from an internet-connected machine:

mkdir -p ./clamav-signatures && cd ./clamav-signatures

# Official ClamAV signatures
wget http://database.clamav.net/main.cvd
wget http://database.clamav.net/daily.cvd
wget http://database.clamav.net/bytecode.cvd

# Third-party signatures (optional, recommended)
wget https://sanesecurity.com/signatures/sanesecurity_signature_db.ldb.gz
gunzip sanesecurity_signature_db.ldb.gz

cd ..
tar czf clamav-signatures.tar.gz clamav-signatures/

Transfer the tarball to your air-gapped environment.

Cluster Requirements

  • Kubernetes 1.24+ or OpenShift 4.10+
  • Storage provisioner (local-path, hostpath, or enterprise storage)
  • At least 100 Gi available storage
  • Cluster networking configured (CNI plugin installed)
  • kubectl access with cluster-admin permissions

Installation

Step 1: Install the Enterprise Operator

# From pre-downloaded manifests
kubectl apply -f guardimesh-operator-enterprise-manifests.yaml

# Verify
kubectl get pods -n guardimesh-system

Step 2: Create Namespace and Secrets

kubectl create namespace guardimesh

# Admin password
kubectl create secret generic guardimesh-admin-secret \
--from-literal=password='your-secure-admin-password' \
-n guardimesh

# License key (provided by Guardimesh sales)
kubectl create secret generic guardimesh-license \
--from-file=license.key=/path/to/license.key \
-n guardimesh

# API key for scanner authentication
kubectl create secret generic guardimesh-api-key \
--from-literal=api-key='generate-a-random-key-here' \
-n guardimesh

Step 3: Load ClamAV Signatures

tar xzf clamav-signatures.tar.gz

kubectl create configmap clamav-signatures \
--from-file=clamav-signatures/ \
-n guardimesh

Step 4: Deploy GuardimeshPlatform

Create the custom resource:

# guardimesh-airgap.yaml
apiVersion: enterprise.guardimesh.io/v1alpha1
kind: GuardimeshPlatform
metadata:
name: guardimesh
namespace: guardimesh
spec:
console:
replicas: 2
image: your-registry.internal/guardimesh/web-console:latest
resources:
requests:
memory: "512Mi"
cpu: "200m"

api:
replicas: 1
image: your-registry.internal/guardimesh/backend-api:latest
resources:
requests:
memory: "256Mi"
cpu: "100m"

database:
managed: true
storage: 50Gi
storageClass: local-path
resources:
requests:
memory: "1Gi"
cpu: "500m"
backup:
enabled: true
schedule: "0 3 * * *"
retentionDays: 14
storage:
type: pvc
size: 100Gi

ingress:
enabled: false

auth:
adminEmail: admin@internal.local
adminPasswordSecret: guardimesh-admin-secret

license:
mode: local
licenseSecret: guardimesh-license

scanner:
enabled: true
image: your-registry.internal/guardimesh/guardimesh-scanner:latest
antivirusImage: your-registry.internal/guardimesh/guardimesh-antivirus:latest
inspectorImage: your-registry.internal/guardimesh/guardimesh-inspector:latest
pullerImage: your-registry.internal/guardimesh/pull:latest

signatureStorage:
mode: internal
updateInterval: 43200
internalServer:
enabled: true
image: your-registry.internal/guardimesh/storage-access-airgap:latest
storage: 10Gi
storageClass: local-path
replicas: 1
resources:
requests:
memory: "128Mi"
cpu: "100m"
initialSignatures:
configMapName: clamav-signatures

resources:
requests:
memory: "512Mi"
cpu: "200m"
tolerations:
- effect: NoSchedule
operator: Exists

Apply:

kubectl apply -f guardimesh-airgap.yaml

Step 5: Verify Deployment

# Watch platform status
kubectl get guardimeshplatforms -n guardimesh -w

# Expected output after ~5 minutes:
# NAME PHASE CONSOLE API DATABASE AGE
# guardimesh Ready true true true 5m

# Check all pods
kubectl get pods -n guardimesh

# Check scanner DaemonSet
kubectl get daemonset -n guardimesh

Step 6: Access the Console

# Port-forward (if ingress is disabled)
kubectl port-forward -n guardimesh svc/guardimesh-console 8080:80

Open http://localhost:8080 and log in with the admin email and password from Step 2.


Signature Updates

ClamAV signatures should be updated periodically (recommended: monthly or when new threats are identified).

Update Procedure

  1. On an internet-connected machine, download the latest signatures
  2. Transfer the files to the air-gapped environment
  3. Update the ConfigMap or PVC:
# Update ConfigMap method
kubectl create configmap clamav-signatures \
--from-file=clamav-signatures/ \
-n guardimesh \
--dry-run=client -o yaml | kubectl apply -f -

# Restart signature server to pick up changes
kubectl rollout restart deployment/signature-server -n guardimesh

Scanner pods will pull the updated signatures on their next update cycle (within 12 hours) or on restart.

Verifying Signature Updates

# Check signature server has the files
kubectl exec -n guardimesh deployment/signature-server -- ls -lh /signatures/

# Check a scanner pod's ClamAV version and signature dates
SCANNER_POD=$(kubectl get pods -n guardimesh -l app.kubernetes.io/component=guardimesh-scanner -o jsonpath='{.items[0].metadata.name}')
kubectl exec -n guardimesh $SCANNER_POD -c guardimesh-antivirus -- clamdscan --version

Database Backups

Automated backups run according to the schedule in your CR. Manual backup:

kubectl create job -n guardimesh manual-backup-$(date +%Y%m%d) \
--from=cronjob/guardimesh-backup

Upgrading

  1. Load new container image versions into your private registry
  2. Update the GuardimeshPlatform CR with new image tags:
kubectl edit guardimeshplatform guardimesh -n guardimesh
  1. Monitor the rollout:
kubectl rollout status deployment/guardimesh-console -n guardimesh
kubectl rollout status deployment/guardimesh-backend-api -n guardimesh
kubectl rollout status daemonset/guardimesh-guardimesh-scanner -n guardimesh

Troubleshooting

Signature Server Not Starting

kubectl logs -n guardimesh deployment/signature-server

# Check PVC is bound
kubectl get pvc -n guardimesh signature-storage

# Check ConfigMap has files
kubectl get configmap clamav-signatures -n guardimesh -o jsonpath='{.data}' | head -c 200

Scanner Pods Not Pulling Signatures

# Check puller sidecar logs
kubectl logs -n guardimesh $SCANNER_POD -c guardimesh-signatures

# Verify signature server Service is accessible
kubectl exec -n guardimesh $SCANNER_POD -c guardimesh-scanner -- \
curl -s http://signature-server:8080/health

ClamAV Not Starting

ClamAV requires at least 2 GB RAM for large signature databases. If pods are OOMKilled:

kubectl describe pod $SCANNER_POD -n guardimesh | grep -A5 "Last State"

Increase antivirus container memory limits in the CR.

Database Connection Issues

kubectl get statefulset -n guardimesh
kubectl logs -n guardimesh guardimesh-postgresql-0

Advanced Configuration

Ingress

Enable ingress for browser access without port-forwarding:

spec:
ingress:
enabled: true
hostname: guardimesh.internal.example.com
tls:
enabled: true
secretName: guardimesh-tls-cert

Multiple Signature Server Replicas

For large clusters, run multiple replicas for availability:

spec:
scanner:
signatureStorage:
internalServer:
replicas: 3

Custom Storage Classes

spec:
database:
storageClass: fast-ssd
scanner:
signatureStorage:
internalServer:
storageClass: standard-hdd

Network Requirements (Intra-Cluster Only)

No external internet access is required. All communication is within the cluster:

SourceDestinationPortProtocol
Scanner podsSignature server8080HTTP
Scanner podsbackend-api8080HTTP
Console podsPostgreSQL5432TCP
backend-apiPostgreSQL5432TCP
AdminConsole (via Ingress/port-forward)80/443HTTP(S)

Next Steps