Airgap Service Architecture
The Airgap Service is a standalone service that combines Mock Engine and Protocol Server functionality into a single binary for air-gapped environments. It uses SQLite for storage and removes all authentication and multi-tenancy requirements.
Overview
The Airgap Service enables SureStage to run in environments without internet connectivity or external dependencies:
- Single binary: No microservice dependencies
- SQLite backend: Embedded database, no PostgreSQL required
- No authentication: All requests use a synthetic admin user
- No multi-tenancy: Single tenant mode
- Rate limited: 120 requests/minute per IP
- Portable: Configurable SQLite database path
Architecture
Key Differences from Cloud Services
| Feature | Cloud Services | Airgap Service |
|---|---|---|
| Database | PostgreSQL | SQLite (configurable path) |
| Authentication | JWT + OAuth | NoopAuthGuard (no auth) |
| Multi-tenancy | TenantGuard + tenant_id | Single tenant mode |
| User Context | Real users from JWT | Synthetic admin user |
| Rate Limiting | Tier-based (10-1000/min) | 120 req/min per IP |
| External Services | AI, Identity, Organization | Self-contained |
| Deployment | Kubernetes pods | Single binary |
SQLite Backend
AirgapDatabaseModule
The Airgap Service uses a custom database module that replaces TypeORM's PostgreSQL connection with SQLite:
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: process.env.AIRGAP_DB_PATH || './airgap.db',
entities: [Instance, Route, Response, MockState, ProtocolConfig],
synchronize: true, // Auto-migrate schema
logging: false
})
]
})
export class AirgapDatabaseModule {}
Configuration:
AIRGAP_DB_PATH: Path to SQLite database file (default:./airgap.db)- Auto-creates database if it doesn't exist
- Schema migrations run automatically on startup
Schema Differences
PostgreSQL-specific features are removed or adapted:
| Feature | PostgreSQL | SQLite |
|---|---|---|
| UUIDs | uuid_generate_v4() | GUID() or application-generated |
| JSONB | Native JSONB | Text column + JSON parse |
| Arrays | Native arrays | JSON-serialized strings |
| Full-text search | tsvector | LIKE queries |
| Transactions | Full ACID | Limited (no nested transactions) |
Authentication & Authorization
NoopAuthGuard
Replaces JwtAuthGuard and TenantGuard with a no-op implementation:
@Injectable()
export class NoopAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
return true; // Always allow
}
}
Applied globally in main.ts:
app.useGlobalGuards(new NoopAuthGuard());
AirgapUserMiddleware
Injects a synthetic user into every request:
@Injectable()
export class AirgapUserMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
req.user = {
userId: 'airgap_admin',
email: 'admin@airgap.local',
tenantId: 'airgap_tenant',
roles: ['admin']
};
next();
}
}
All services expecting req.user from JWT now receive this synthetic user.
Mock Engine Modules
The Airgap Service imports all Mock Engine modules:
Simulations Module
- Create, start, stop, delete Sandbox simulations
- Manage simulation lifecycle
- No tenant isolation (single tenant)
Routes Module
- Define routes for HTTP, GraphQL, gRPC, WebSocket
- Path and method matching
- Route priority and fallback handling
Responses Module
- Response templates with dynamic placeholders
- Status code, headers, body configuration
- Template rendering (Handlebars)
Mock State Module
- Stateful mock scenarios
- Key-value state storage per instance
- State reset and initialization
Mock Engine Module
- Core request matching and response resolution
- Protocol-agnostic request handling
- State evaluation and transformation
Flows Module
- Multi-step test flows
- Request chaining and variable passing
- Flow execution and result tracking
Protocol Server Modules
The Airgap Service imports all Protocol Server modules:
Protocol Configs Module
- HTTP, GraphQL, gRPC, WebSocket configurations
- Protocol-specific settings (CORS, rate limits, timeouts)
Port Allocation Module
- Dynamic port assignment for simulations
- Port pool management
- Conflict detection
Protocol Server Module
- Protocol adapter instantiation (HTTP, GraphQL, gRPC, WebSocket)
- Request routing to Mock Engine
- Protocol-specific request/response transformation
Protocol Health Module
- Health check endpoints per protocol
- Simulation readiness checks
- Liveness probes
AirgapSharedProtocolModule
Replaces PostgreSQL-specific queries in Protocol Server with SQLite-compatible versions:
Example - Port Allocation:
PostgreSQL:
SELECT port FROM port_allocation WHERE instance_id = $1 FOR UPDATE;
SQLite (via AirgapSharedProtocolModule):
SELECT port FROM port_allocation WHERE instance_id = ?;
-- Manual locking via application logic
Changes:
- Replace
FOR UPDATEwith application-level locking - Replace
RETURNING *with separateSELECTafterINSERT - Replace array queries with JSON-serialized columns
Rate Limiting
Global Rate Limit
120 requests per minute per IP address:
@Injectable()
export class AirgapRateLimitGuard implements CanActivate {
private requests = new Map<string, number[]>();
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
const ip = req.ip;
const now = Date.now();
const windowStart = now - 60000; // 1 minute
// Get requests from this IP in the last minute
const ipRequests = this.requests.get(ip) || [];
const recentRequests = ipRequests.filter(t => t > windowStart);
if (recentRequests.length >= 120) {
throw new HttpException('Rate limit exceeded', 429);
}
recentRequests.push(now);
this.requests.set(ip, recentRequests);
return true;
}
}
Applied globally in main.ts:
app.useGlobalGuards(new AirgapRateLimitGuard());
Configuration
Environment Variables
| Variable | Description | Default |
|---|---|---|
AIRGAP_DB_PATH | Path to SQLite database file | ./airgap.db |
AIRGAP_PORT | HTTP port | 3000 |
AIRGAP_RATE_LIMIT | Requests per minute | 120 |
AIRGAP_LOG_LEVEL | Log verbosity | info |
Startup
# Default configuration
./airgap-service
# Custom database path
AIRGAP_DB_PATH=/data/airgap.db ./airgap-service
# Custom port and rate limit
AIRGAP_PORT=8080 AIRGAP_RATE_LIMIT=200 ./airgap-service
Deployment
Binary Distribution
The Airgap Service is distributed as a single binary:
# Linux
./airgap-service-linux-amd64
# macOS
./airgap-service-darwin-arm64
# Windows
airgap-service-windows-amd64.exe
Docker
FROM node:20-alpine
WORKDIR /app
COPY airgap-service /app/
VOLUME ["/data"]
ENV AIRGAP_DB_PATH=/data/airgap.db
EXPOSE 3000
CMD ["./airgap-service"]
Run:
docker run -v ./data:/data -p 3000:3000 surestage/airgap-service
Systemd Service
[Unit]
Description=SureStage Airgap Service
After=network.target
[Service]
Type=simple
User=surestage
WorkingDirectory=/opt/surestage
ExecStart=/opt/surestage/airgap-service
Environment="AIRGAP_DB_PATH=/var/lib/surestage/airgap.db"
Restart=on-failure
[Install]
WantedBy=multi-user.target
Data Persistence
Database Backups
SQLite database can be backed up with a simple file copy:
# Stop service
systemctl stop airgap-service
# Copy database
cp /var/lib/surestage/airgap.db /backup/airgap-$(date +%Y%m%d).db
# Start service
systemctl start airgap-service
Export/Import
Export simulations and routes to JSON:
curl http://localhost:3000/instances/export > instances.json
Import on another Airgap instance:
curl -X POST http://localhost:3000/instances/import \
-H "Content-Type: application/json" \
-d @instances.json
Limitations
Not Supported
The following features are not available in Airgap mode:
- User authentication: No login, no JWT tokens
- Multi-tenancy: Single tenant only
- AI features: No AI Service integration
- OAuth providers: No external auth
- SSO: No SAML or OIDC
- SCIM provisioning: No user sync
- Audit logs: No audit trail (can enable file logging)
- Feature flags: No dynamic flag management
- Analytics: No usage metrics
- External webhooks: No outbound HTTP to external services
Workarounds
- Authentication: Use network-level access controls (VPN, firewall)
- Multi-tenancy: Run multiple Airgap services with different database files
- AI features: Pre-generate responses in cloud, export to Airgap
- Audit logs: Enable file-based logging with log rotation
Security Considerations
No Built-in Auth
Since Airgap Service has no authentication, you must secure access via:
- Firewall rules: Restrict to internal network
- VPN: Require VPN connection
- Reverse proxy: Use nginx/Apache with basic auth
- Network segmentation: Deploy in isolated network
SQLite Permissions
Secure the SQLite database file:
chmod 600 /var/lib/surestage/airgap.db
chown surestage:surestage /var/lib/surestage/airgap.db
Rate Limiting
The 120 req/min limit protects against accidental DoS but is not sufficient for hostile environments. Use additional rate limiting at the network level if exposed to untrusted networks.
Troubleshooting
Database Locked
SQLite locks the entire database file during writes. If you see "database is locked" errors:
- Reduce concurrency (limit parallel requests)
- Increase SQLite timeout:
PRAGMA busy_timeout = 5000; - Use WAL mode:
PRAGMA journal_mode = WAL;
Port Conflicts
If Airgap Service fails to start due to port conflicts:
# Check what's using port 3000
lsof -i :3000
# Use different port
AIRGAP_PORT=8080 ./airgap-service
Database Corruption
If SQLite database is corrupted:
# Attempt recovery
sqlite3 airgap.db ".recover" | sqlite3 airgap-recovered.db
# Or restore from backup
cp /backup/airgap-20260320.db /var/lib/surestage/airgap.db
Related
- Mock Engine Service - Source of Simulations, Routes, Responses modules
- Protocol Server Service - Source of Protocol modules
- Deployment - Cloud deployment architecture