DocsGuidesSecurity

Security Guide

Secure your TopGun deployment with TLS encryption for all network communications.

This guide covers:

  • TLS/WSS configuration for client connections
  • mTLS (mutual TLS) for cluster communication
  • Environment variables reference
  • Certificate generation and management
  • Client-side encryption for data at rest

Production Requirement

Always enable TLS in production environments. Without TLS, all data including authentication tokens is transmitted in plaintext and can be intercepted by attackers.

TLS/WSS Configuration

Enable HTTPS and secure WebSocket (WSS) for client connections.

When TLS is enabled, TopGun automatically:

  • Creates an HTTPS server instead of HTTP
  • Upgrades WebSocket connections to WSS
  • Logs a warning if TLS is disabled in production
Server Configuration
import { ServerCoordinator } from '@topgunbuild/server';

const server = new ServerCoordinator({
  port: 443,
  clusterPort: 9443,
  nodeId: 'prod-node-1',

  // Client-facing TLS (HTTPS + WSS)
  tls: {
    enabled: true,
    certPath: '/etc/topgun/tls/server.crt',
    keyPath: '/etc/topgun/tls/server.key',
    minVersion: 'TLSv1.3'
  },

  // Cluster mTLS
  clusterTls: {
    enabled: true,
    certPath: '/etc/topgun/tls/cluster.crt',
    keyPath: '/etc/topgun/tls/cluster.key',
    caCertPath: '/etc/topgun/tls/ca.crt',
    requireClientCert: true
  }
});

Client Connection

Clients automatically use secure connections when connecting to a wss:// URL:

Client
import { TopGunClient } from '@topgunbuild/client';

// Production: Use wss:// (WebSocket Secure)
const client = new TopGunClient({
  serverUrl: 'wss://topgun.example.com',
  token: 'your-jwt-token'
});

// Development only: ws:// (unencrypted)
const devClient = new TopGunClient({
  serverUrl: 'ws://localhost:8080'
});

Often Already Configured

If you’re using a deployment platform or reverse proxy, TLS is likely already handled for you. In this case, you don’t need to configure certificates in TopGun server.

Many deployment platforms and reverse proxies automatically provide TLS termination:

Platform / ProxyTLS HandlingTopGun Server Config
DokployTraefik terminates TLSUse ws:// internally, clients connect via wss://
Vercel / NetlifyAutomatic HTTPSNot applicable (edge functions)
Railway / RenderAutomatic TLSUse ws:// on server port
Kubernetes IngressTLS at ingressConfigure TLS on Ingress, not TopGun
CloudflareEdge TLSUse ws:// to origin
Traefik / nginx / CaddyProxy terminates TLSUse ws:// backend

How it works:

Client (wss://) → Reverse Proxy (TLS termination) → TopGun Server (ws://)
  1. Client connects to wss://your-domain.com
  2. Reverse proxy handles TLS/SSL certificates
  3. Proxy forwards to TopGun via unencrypted ws://localhost:8080
  4. All external traffic is encrypted; internal traffic stays on localhost

When to configure TLS directly in TopGun:

  • Direct server exposure without reverse proxy
  • End-to-end encryption requirements
  • mTLS for cluster communication

mTLS for Cluster Communication

Mutual TLS (mTLS) ensures that only authorized nodes can join the cluster.

With mTLS enabled:

  • Each node presents its certificate when connecting to peers
  • Nodes verify peer certificates against the CA
  • Unauthorized nodes are rejected
With mTLS
  • Encrypted inter-node traffic
  • Verified node identity
  • Protection against rogue nodes
  • Zero-trust network security
Without mTLS
  • Plaintext inter-node traffic
  • Any node can join cluster
  • Vulnerable to MITM attacks
  • Not suitable for production

Environment Variables Reference

Configure TLS using environment variables for containerized deployments.
VariableTypeDefaultDescription
TOPGUN_TLS_ENABLEDbooleanfalseEnable TLS for client connections
TOPGUN_TLS_CERT_PATHstring-Path to certificate (PEM)
TOPGUN_TLS_KEY_PATHstring-Path to private key (PEM)
TOPGUN_TLS_CA_PATHstring-Path to CA certificate
TOPGUN_TLS_MIN_VERSIONenumTLSv1.2Minimum TLS version (TLSv1.2 or TLSv1.3)
TOPGUN_TLS_PASSPHRASEstring-Key passphrase (if encrypted)
TOPGUN_CLUSTER_TLS_ENABLEDbooleanfalseEnable TLS for cluster
TOPGUN_CLUSTER_TLS_CERT_PATHstring-Cluster certificate path
TOPGUN_CLUSTER_TLS_KEY_PATHstring-Cluster key path
TOPGUN_CLUSTER_TLS_CA_PATHstring-CA for peer verification
TOPGUN_CLUSTER_MTLSbooleanfalseRequire client certificate (mTLS)
TOPGUN_CLUSTER_TLS_REJECT_UNAUTHORIZEDbooleantrueVerify peer certificates

Example: Production Environment

.env.production
# Client TLS
TOPGUN_PORT=443
TOPGUN_TLS_ENABLED=true
TOPGUN_TLS_CERT_PATH=/etc/topgun/tls/server.crt
TOPGUN_TLS_KEY_PATH=/etc/topgun/tls/server.key
TOPGUN_TLS_MIN_VERSION=TLSv1.3

# Cluster mTLS
TOPGUN_CLUSTER_PORT=9443
TOPGUN_CLUSTER_TLS_ENABLED=true
TOPGUN_CLUSTER_TLS_CERT_PATH=/etc/topgun/tls/cluster.crt
TOPGUN_CLUSTER_TLS_KEY_PATH=/etc/topgun/tls/cluster.key
TOPGUN_CLUSTER_TLS_CA_PATH=/etc/topgun/tls/ca.crt
TOPGUN_CLUSTER_MTLS=true

Certificate Generation

Generate certificates for development and testing.

Production Certificates

For production, use certificates from a trusted CA like Let’s Encrypt or your organization’s internal CA. Self-signed certificates are suitable only for development and testing.

Development Certificates Script

scripts/generate-certs.sh
#!/bin/bash
# Generate self-signed certificates for testing
# For production, use Let's Encrypt or your organization's CA

# Create output directory
mkdir -p certs && cd certs

# 1. Generate CA (Certificate Authority)
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key \
  -out ca.crt -subj "/CN=TopGun CA"

# 2. Generate Server Certificate
openssl genrsa -out server.key 2048
openssl req -new -key server.key \
  -out server.csr -subj "/CN=topgun.example.com"

# Create SAN (Subject Alternative Name) config
cat > san.cnf << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
[req_distinguished_name]
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = topgun.example.com
DNS.2 = *.topgun.example.com
DNS.3 = localhost
IP.1 = 127.0.0.1
EOF

# Sign with CA
openssl x509 -req -days 365 -in server.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out server.crt -extfile san.cnf -extensions v3_req

# 3. Generate Cluster Node Certificates (for mTLS)
for i in 1 2 3; do
  openssl genrsa -out node${i}.key 2048
  openssl req -new -key node${i}.key \
    -out node${i}.csr -subj "/CN=topgun-node-${i}"
  openssl x509 -req -days 365 -in node${i}.csr \
    -CA ca.crt -CAkey ca.key -CAcreateserial \
    -out node${i}.crt
done

echo "Certificates generated in ./certs/"

Run the script:

terminal
chmod +x scripts/generate-certs.sh
./scripts/generate-certs.sh

Kubernetes cert-manager Integration

Automate certificate management with cert-manager.

For Kubernetes deployments, use cert-manager to automatically provision and renew certificates:

k8s/cert-manager.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - http01:
        ingress:
          class: nginx
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: topgun-tls
  namespace: topgun
spec:
  secretName: topgun-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - topgun.example.com
  - "*.topgun-cluster.svc.cluster.local"

Installation

terminal
# Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml

# Apply configuration
kubectl apply -f k8s/cert-manager.yaml

Troubleshooting

Common TLS issues and solutions.

Useful OpenSSL Commands

terminal
# Verify certificate
openssl x509 -in server.crt -text -noout

# Check expiry date
openssl x509 -enddate -noout -in server.crt

# Verify key matches certificate
openssl x509 -noout -modulus -in server.crt | openssl md5
openssl rsa -noout -modulus -in server.key | openssl md5
# Both should output the same hash

# Test TLS connection
openssl s_client -connect localhost:443 -tls1_3

# View full certificate chain
openssl s_client -showcerts -connect topgun.example.com:443

Common Errors

ErrorCauseSolution
UNABLE_TO_VERIFY_LEAF_SIGNATUREMissing CA certificateAdd caCertPath configuration
CERT_HAS_EXPIREDCertificate expiredRenew the certificate
DEPTH_ZERO_SELF_SIGNED_CERTSelf-signed cert in productionUse CA-signed certificate
ERR_TLS_CERT_ALTNAME_INVALIDHostname mismatchAdd correct SAN to certificate
ENOENT on cert pathFile not foundCheck path and file permissions

Security Best Practices

Do

  • Use TLS 1.3 when possible
  • Enable mTLS for cluster
  • Rotate certificates regularly
  • Store keys in secrets/vault
  • Monitor certificate expiry

Don’t

  • Disable TLS in production
  • Use self-signed certs in prod
  • Commit certificates to git
  • Use weak cipher suites
  • Ignore certificate warnings

For maximum security, TopGun recommends using modern cipher suites:

Recommended TLS 1.3 Ciphers
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256

For TLS 1.2 compatibility:

Recommended TLS 1.2 Ciphers
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-CHACHA20-POLY1305
ECDHE-RSA-CHACHA20-POLY1305

Client-Side Encryption (Data at Rest)

Encrypt local data stored in IndexedDB or other client-side storage.

While TLS protects data in transit, client-side encryption protects data at rest on user devices. The EncryptedStorageAdapter wraps any storage adapter (like IndexedDB) with AES-256-GCM encryption using the Web Crypto API.

What It Protects

Protected Against

  • Browser DevTools inspection
  • Malicious browser extensions
  • Physical device access (disk dumps)
  • IndexedDB data export

Not Protected Against

  • XSS attacks (if attacker has JS execution)
  • Compromised application code
  • Key theft from memory

Basic Usage

Encrypted Storage
import { TopGunClient, EncryptedStorageAdapter } from '@topgunbuild/client';
import { IDBAdapter } from '@topgunbuild/adapters';

// Get encryption key (see key management strategies below)
const encryptionKey = await getEncryptionKey(); // or getDeviceKey()

// Create base adapter and wrap with encryption
const baseAdapter = new IDBAdapter();
const encryptedAdapter = new EncryptedStorageAdapter(baseAdapter, encryptionKey);

const client = new TopGunClient({
  serverUrl: 'wss://topgun.example.com',
  storage: encryptedAdapter
});

Key Management Strategies

Choose a key management strategy based on your security requirements:

Password-Derived Key (PBKDF2)

Best for user-authenticated encryption where the key is derived from a user’s password:

Password-Derived Key
async function getEncryptionKey(password: string): Promise<CryptoKey> {
  const encoder = new TextEncoder();

  // Get or create salt
  let saltString = localStorage.getItem('encryption_salt');
  let salt: Uint8Array;

  if (saltString) {
    salt = Uint8Array.from(atob(saltString), c => c.charCodeAt(0));
  } else {
    salt = crypto.getRandomValues(new Uint8Array(16));
    localStorage.setItem('encryption_salt', btoa(String.fromCharCode(...salt)));
  }

  // Derive key from password
  const keyMaterial = await crypto.subtle.importKey(
    'raw',
    encoder.encode(password),
    'PBKDF2',
    false,
    ['deriveKey']
  );

  return crypto.subtle.deriveKey(
    {
      name: 'PBKDF2',
      salt: salt,
      iterations: 100000,
      hash: 'SHA-256'
    },
    keyMaterial,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
  );
}

Device-Bound Random Key

Best for protecting data from disk dumps without requiring a user password. Data is tied to the device:

Device-Bound Key
async function getDeviceKey(): Promise<CryptoKey> {
  const storedKey = localStorage.getItem('topgun_device_key');

  if (storedKey) {
    // Import existing key
    const keyData = Uint8Array.from(atob(storedKey), c => c.charCodeAt(0));
    return crypto.subtle.importKey(
      'raw',
      keyData,
      { name: 'AES-GCM', length: 256 },
      false,
      ['encrypt', 'decrypt']
    );
  }

  // Generate new key
  const key = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true, // extractable
    ['encrypt', 'decrypt']
  );

  // Store for future use
  const exported = await crypto.subtle.exportKey('raw', key);
  localStorage.setItem(
    'topgun_device_key',
    btoa(String.fromCharCode(...new Uint8Array(exported)))
  );

  return key;
}

Key Management Warning

If the encryption key is lost, all encrypted data becomes irrecoverable. Consider implementing key backup strategies for password-derived keys, or accept data loss on device change for device-bound keys.

When to Use Client-Side Encryption

Use CaseRecommendation
Medical or financial dataRequired - regulatory compliance
Personal user dataRecommended - privacy protection
Multi-tenant applicationsRecommended - tenant isolation
Gaming or non-sensitive appsOptional - based on threat model

API Reference

For detailed API documentation, see the EncryptedStorageAdapter section in the Adapter API reference.