CT-OpsCT-Ops
Home
Getting Started
Architecture
Features
Deployment
GitHub
GitHub
Home
Getting Started
Architecture
Features
Deployment
GitHub
GitHub
  • Introduction
  • Getting Started

    • Installation
    • Configuration
    • Offline Agent Install Bundle
  • Architecture

    • Architecture Overview
    • Agent Architecture
    • Ingest Service
    • Deployment Profiles
  • Features

    • Hosts & Inventory
    • Host Groups
    • Networks
    • Monitoring
    • Certificate Management
    • SSL Certificate Checker
    • Alerts
    • Notifications
    • Reports
    • Operations Calendar
    • Terminal
    • Service Accounts & Identity
    • Directory User Lookup
    • Tasks & Runbooks
    • Scheduled Tasks
    • Tags
    • Notes
    • Feature Flags
  • Deployment

    • Docker Compose Deployment
    • Air-Gap Deployment
    • Load Testing
  • Development

    • Local Dev Stack
    • End-to-end testing
  • Licensing
  • Security

    • Security & Vulnerability Disclosure
    • mTLS: agent ↔ server authentication

Configuration

All CT-Ops configuration is via environment variables. There are no config files for the web app or ingest service in production — just set env vars in your .env file or compose config.


Web Application

🔒 = security-critical. Treat changes to these as you would changes to a password.

VariableRequiredDefaultDescription
DATABASE_URL✓ 🔒—PostgreSQL connection string (contains credentials)
BETTER_AUTH_SECRET✓ 🔒—Secret used to sign session cookies AND for LDAP bind-password decryption (min 32 chars, never reuse across environments)
BETTER_AUTH_URL✓ 🔒—Public URL of the web app (e.g. https://ct-ops.corp.example.com). Use https:// — http:// disables cookie Secure flag
BETTER_AUTH_TRUSTED_ORIGINS✓ 🔒—Comma-separated list of allowed origins. Auth flows from origins not in this list are rejected
REQUIRE_EMAIL_VERIFICATION— 🔒trueRequire local email/password users to verify their email before sign-in. Set to false only when email verification should be optional
CT_OPS_TRUST_PROXY_HEADERS— 🔒false (true in the bundled Docker Compose profile)Trust X-Forwarded-For / X-Real-IP for client IP rate limits only when a reverse proxy overwrites those headers and direct web-container access is blocked
CT_OPS_LOADTEST_ADMIN_KEY— 🔒—Bearer credential for /api/admin/hosts/bulk-delete. Endpoint returns 503 when unset. Set only on environments running load tests
NEXT_PUBLIC_APP_URL——Exposed to the browser — used for constructing absolute links
NODE_ENV—developmentSet to production in production
AGENT_DIST_DIR—/var/lib/ct-ops/agent-distDirectory where compiled agent binaries are stored for download. If you override or mount it, ensure it is writable by uid/gid 1001 (nextjs:nodejs)
AGENT_DOWNLOAD_BASE_URL—https://localhostPublic URL agents use to download new binaries. Must be reachable from every agent host
INGEST_WS_URL—(empty)WebSocket URL of the ingest service. Empty = same-origin via the bundled nginx (recommended). Set to an absolute wss:// URL only to bypass the bundled proxy
ANSIBLE_API_URL—http://ansible-api:8080Internal URL used by the web app to health-check the optional Ansible automation API service
WEB_TLS_CERT—/var/lib/ct-ops/server-tls/server.crtPath to the nginx-facing server cert. The enrolment bundle route reads this file and embeds it so agents can verify the HTTPS download URL
CT_CVE_SERVICE_TOKENS— 🔒(empty)JSON allow-list of signed CT-CVE service tokens that can deliver findings or call CT Ops connection health; use findings:write and connection:read scopes for the initial inbound connector
CT_CVE_INVENTORY_PUSH_TARGETS— 🔒(empty)JSON list of outbound CT-CVE inventory targets. Each entry contains name, baseUrl, and a token object with id, secret, ctOpsInstallationId, and scopes:["inventory:write"]; schedule pnpm --dir apps/web ct-cve:push-inventory to send snapshots

Licence verification

CT-Ops validates licence JWTs using an RSA public key. CT-Ops release builds fetch the current CarrTech verifier key from carrtech-dev/licence-public-keys, bake it into the web image, and package it in customer bundles at ./licence-keys/current.pem. When an admin saves a licence, CT-Ops stores the exact public key that verified that licence and keeps using it for that stored JWT, so future key rotations or image upgrades do not invalidate active licences.

For releases, the public key source of truth is carrtech-dev/licence-public-keys at ct-ops/current.pem. In an installed customer bundle, it lives at ./licence-keys/current.pem and is mounted read-only into the web container. Air-gapped installs must upgrade CT-Ops to a release built after any CarrTech key rotation before activating licences purchased after that rotation. The private signing key belongs only in CT Portal.

VariableRequiredDefaultDescription
LICENCE_PUBLIC_KEY_PATH— 🔒/var/lib/ct-ops/licence-keys/current.pem in the customer bundlePath to the current PEM-encoded RSA public key used for newly activated licence JWTs. If the mounted file is unavailable, CT-Ops falls back to the verifier key baked into the web image. Existing saved licences keep using the verifier key stored with the licence.
LICENCE_PUBLIC_KEY— 🔒(baked production key)Inline PEM override used only for internal QA/staging or emergency recovery when a file mount is not available. In production, the dev key is explicitly rejected even if supplied here.
LICENCE_REVOCATION_URLhttps://licence.carrtech.dev/.well-known/ct-ops-licence-revocations.jwtsameSigned JWT bundle listing revoked licence ids (jti). Connected installs refresh it opportunistically; offline installs fall back to expiry-only validation until the endpoint is reachable again. Set to an empty string to disable remote revocation checks.

Example .env.local (development)

DATABASE_URL=postgresql://ct-ops:ct-ops@localhost:5432/ct-ops
BETTER_AUTH_SECRET=change-me-to-something-long-and-random-in-production
BETTER_AUTH_URL=https://localhost
BETTER_AUTH_TRUSTED_ORIGINS=https://localhost
REQUIRE_EMAIL_VERIFICATION=true
AGENT_DOWNLOAD_BASE_URL=https://localhost
NEXT_PUBLIC_APP_URL=https://localhost
INGEST_WS_URL=

Ingest Service

VariableRequiredDefaultDescription
DATABASE_URL✓—PostgreSQL connection string
INGEST_TLS_CERT✓—Path to TLS certificate file (PEM)
INGEST_TLS_KEY✓—Path to TLS private key file (PEM)
INGEST_JWT_KEY_FILE—/var/lib/ct-ops/jwt_key.pemPath to RSA private key for JWT signing (auto-generated if missing)
INGEST_GRPC_PORT—9443gRPC listener port
INGEST_HTTP_PORT—8080HTTP port for JWKS endpoint and health check
INGEST_AGENT_DOWNLOAD_BASE_URL—https://localhostPublic URL of the web app — agents construct their binary download URL from this
INGEST_TERMINAL_TRUSTED_ORIGINS— 🔒(empty)Comma-separated browser origins allowed to open terminal WebSockets directly against ingest. Leave empty to require same-origin proxying
INGEST_WEB_SERVER_CERT—/etc/ct-ops/server-tls/server.crtPath to the nginx-facing server cert. Ingest reads this and pushes rotations down the heartbeat stream when an operator swaps the cert, so agents keep verifying download URLs without manual CA distribution. Empty disables the rotation RPC

JWT Key Backup

Back up INGEST_JWT_KEY_FILE. Losing it invalidates all existing agent JWTs and forces every agent to re-register.


Agent

The agent is configured via a TOML file. The path is passed with the -config flag.

[ingest]
# Address of the ingest service (host:port)
address = "ingest.corp.example.com:9443"

# Optional: path to CA cert for self-signed/corporate TLS
# Leave empty if ingest uses a publicly trusted certificate
ca_cert_file = "/etc/ct-ops/ca.crt"

[agent]
# Enrolment token from Administration → Agents → Enrolment
# Can also be set via CT_OPS_ENROLMENT_TOKEN env var
enrolment_token = "tok_..."

# Directory where agent stores its identity (keypair + JWT)
data_dir = "/var/lib/ct-ops/agent"

# Agent version string (set by build system)
version = "0.1.0"

# How often to send heartbeat (seconds)
heartbeat_interval_secs = 30

Environment variable overrides

All TOML values can be overridden via environment variables:

Environment variableTOML path
CT_OPS_INGEST_ADDRESSingest.address
CT_OPS_INGEST_CA_CERTingest.ca_cert_file
CT_OPS_ENROLMENT_TOKENagent.enrolment_token
CT_OPS_DATA_DIRagent.data_dir

Ports Summary

Public ports (bound to all interfaces):

ServicePortProtocolPurpose
nginx443HTTPSBrowser traffic — TLS termination
nginx80HTTPRedirect to :443
Ingest9443gRPC + mTLSAgent connections (bypasses nginx)

Loopback-only ports (reachable from the host over SSH tunnels only):

ServicePortProtocolPurpose
Web3000HTTPNext.js — fronted by nginx
Ingest8080HTTPJWKS, /healthz, WebSocket terminal — fronted by nginx
PostgreSQL5432TCPDatabase

Optional internal-only services:

ServicePortProtocolPurpose
Ansible API8080HTTPOptional automation provider, reachable only on the Compose network when enabled

Override the nginx port bindings with NGINX_HTTPS_PORT and NGINX_HTTP_PORT in .env if 443/80 are already in use.

Edit this page on GitHub
Last Updated: 5/11/26, 8:36 PM
Contributors: Simon Carr, Claude Sonnet 4.6, Claude Opus 4.7, Claude, Claude Opus 4.7 (1M context)
Prev
Installation
Next
Offline Agent Install Bundle