# JAVV on one machine: OpenSearch, the backend and the frontend. # # ./setup.sh # makes .env with the four secrets (docs/DEPLOYING.md, step 2) # docker compose up -d # open http://:8080 # # Every backend setting is listed below with its default and what it does # (docs/CONFIGURATION.md has the long form). Each one is written ${NAME:-default}, so a line in # .env overrides it without an edit here. A test keeps these defaults equal to the defaults in # the code. Guide: docs/DEPLOYING.md. # # The backend and frontend images are the ones a release publishes under its version; each release # PR moves both tags. To run a checkout that is not a release, build them first with # development/scripts/build-app-images.sh, which tags them with the names below. # The project name prefixes every volume. A second installation on this machine sets its own # COMPOSE_PROJECT_NAME in its .env (docs/DEPLOYING.md); renaming an installation with data here # would start it on empty volumes. name: javv services: opensearch: image: opensearchproject/opensearch:3.9.0 restart: unless-stopped # The demo security setup of the image also adds six users whose password is their own name # (readall can read every index), and no setting turns them off. So, before the start script of # the image runs, the command below keeps only _meta and admin in the users file. It also adds # javv, the user that the backend signs in as, with its password hashed by the hash.sh tool of # OpenSearch. The demo setup then sets the admin password from # OPENSEARCH_INITIAL_ADMIN_PASSWORD. OpenSearch reads the users, roles and role-mapping files on # its first start with security on. The command then starts the script of the image with the # same arguments, so `compose run` still works. entrypoint: - /bin/bash - -c - >- set -e; users=config/opensearch-security/internal_users.yml; kept=$$(mktemp); trap 'rm -f "$$kept"' EXIT; awk '/^[A-Za-z_]/ { keep = ($$1 == "_meta:" || $$1 == "admin:") } /^---/ || keep' "$$users" > "$$kept"; grep -q '^admin:' "$$kept" || { echo "no admin user in $$users; not starting" >&2; exit 1; }; hash=$$(plugins/opensearch-security/tools/hash.sh -env OPENSEARCH_JAVV_PASSWORD | tail -n 1); case "$$hash" in '$$2'*) ;; *) echo "could not hash OPENSEARCH_JAVV_PASSWORD; not starting" >&2; exit 1 ;; esac; printf 'javv:\n hash: "%s"\n reserved: false\n description: "JAVV backend"\n' "$$hash" >> "$$kept"; cat "$$kept" > "$$users"; rm -f "$$kept"; exec ./opensearch-docker-entrypoint.sh "$$@" - javv-opensearch command: [opensearch] environment: discovery.type: single-node # The security plugin is on, with OpenSearch's demo certificates: a real login, but not # private TLS, since the demo key ships in the image. OpenSearch is still reachable only on # this compose network (no published port), and the backend is its only client. OpenSearch # reads both passwords one time, on its first start with security on. The admin password # stays with the store and its health check. The backend signs in with the javv password. # See docs/DEPLOYING.md. OPENSEARCH_INITIAL_ADMIN_PASSWORD: ${JAVV_OPENSEARCH_ADMIN_PASSWORD:?set JAVV_OPENSEARCH_ADMIN_PASSWORD in .env} OPENSEARCH_JAVV_PASSWORD: ${JAVV_OPENSEARCH_PASSWORD:?set JAVV_OPENSEARCH_PASSWORD in .env} OPENSEARCH_JAVA_OPTS: ${OPENSEARCH_JAVA_OPTS:--Xms1g -Xmx1g} # JAVV runs its own index lifecycle and does not use query insights; their internal # indices would otherwise hold a single node on yellow plugins.index_state_management.enabled: "false" plugins.index_state_management.history.enabled: "false" search.insights.top_queries.latency.enabled: "false" search.insights.top_queries.cpu.enabled: "false" search.insights.top_queries.memory.enabled: "false" # where Settings › Data & OpenSearch takes snapshots, inside the data volume path.repo: /usr/share/opensearch/data/snapshots # OpenSearch's own audit log is off: the demo security setup would write a new # security-auditlog- index every day, and with ISM off nothing deletes them. JAVV keeps # its own audit trail (system-audit-log). plugins.security.audit.type: noop ulimits: memlock: { soft: -1, hard: -1 } nofile: { soft: 65536, hard: 65536 } volumes: - opensearch-data:/usr/share/opensearch/data # in place of the image's demo roles and mappings: the javv role, and admin's all_access configs: - source: opensearch-roles target: /usr/share/opensearch/config/opensearch-security/roles.yml - source: opensearch-roles-mapping target: /usr/share/opensearch/config/opensearch-security/roles_mapping.yml healthcheck: # $$ gives the variable to the shell in the container. With one $, compose would put the # password in the command of the container, where docker inspect shows it. printf gives the # login to curl on its input (-K -), so the password is in the arguments of no process. curl # reads a quoted value with escapes, so sed first escapes " and \. test: - CMD-SHELL - >- printf 'user = "admin:%s"\n' "$$(printf %s "$$OPENSEARCH_INITIAL_ADMIN_PASSWORD" | sed 's/[\\"]/\\&/g')" | curl -fsSk -K - https://localhost:9200/_cluster/health || exit 1 interval: 10s timeout: 5s retries: 30 start_period: 30s backend: image: ghcr.io/danube-labs/javv-backend:0.9.6 # x-release-please-version restart: unless-stopped depends_on: opensearch: condition: service_healthy environment: # --- deployment --- # prod refuses to start on the dev secret key (the image itself defaults to dev) JAVV_ENV: ${JAVV_ENV:-prod} # debug | info | warning | error; an unknown name stops the backend at start JAVV_LOG_LEVEL: ${JAVV_LOG_LEVEL:-info} # the zone the job schedules below are read in; a container has none of its own TZ: ${TZ:-UTC} # --- OpenSearch --- # the compose service above, over https with its demo certificates JAVV_OPENSEARCH_URL: ${JAVV_OPENSEARCH_URL:-https://opensearch:9200} # seconds before a store request gives up JAVV_REQUEST_TIMEOUT: ${JAVV_REQUEST_TIMEOUT:-30.0} # check the store and create or update the indices before serving JAVV_BOOTSTRAP_ON_STARTUP: ${JAVV_BOOTSTRAP_ON_STARTUP:-true} # the user JAVV signs in to OpenSearch as: javv, which holds only the javv role below JAVV_OPENSEARCH_USERNAME: ${JAVV_OPENSEARCH_USERNAME:-javv} # --- secret (required, from .env): javv's password, which the OpenSearch service sets --- JAVV_OPENSEARCH_PASSWORD: ${JAVV_OPENSEARCH_PASSWORD:?set JAVV_OPENSEARCH_PASSWORD in .env} # a PEM file of CA certificates for a private CA. When empty, the backend uses the system CAs JAVV_OPENSEARCH_CA_BUNDLE: ${JAVV_OPENSEARCH_CA_BUNDLE:-} # false here: the demo certificate names node-0.example.com and localhost, not opensearch. # Its CA key is also public. The backend logs one warning at start for it. JAVV_OPENSEARCH_VERIFY_CERTS: ${JAVV_OPENSEARCH_VERIFY_CERTS:-false} # --- secrets (required, from .env) --- # the backend's secret key: hashes ingest tokens and session ids, signs download links. # A long random string. Do not change it: a new key makes every scanner token and session # invalid. JAVV_SECRET_KEY: ${JAVV_SECRET_KEY:?set JAVV_SECRET_KEY in .env} # the first admin's password, used once when the admin does not exist yet JAVV_BOOTSTRAP_ADMIN_PASSWORD: ${JAVV_BOOTSTRAP_ADMIN_PASSWORD:?set JAVV_BOOTSTRAP_ADMIN_PASSWORD in .env} # the first admin's user name JAVV_BOOTSTRAP_ADMIN_USERNAME: ${JAVV_BOOTSTRAP_ADMIN_USERNAME:-admin} # --- sign-in --- # the session cookie's Secure flag: keep true when browsers use https:// (through a proxy). # when people open JAVV over plain http, set false in .env, or sign-in fails JAVV_SESSION_COOKIE_SECURE: ${JAVV_SESSION_COOKIE_SECURE:-true} # hours a sign-in lasts JAVV_SESSION_TTL_HOURS: ${JAVV_SESSION_TTL_HOURS:-24.0} # hours an expired session row is kept before the session sweep deletes it JAVV_SESSION_SWEEP_GRACE_HOURS: ${JAVV_SESSION_SWEEP_GRACE_HOURS:-24.0} # failed logins per user name within the window before it is locked JAVV_LOGIN_MAX_ATTEMPTS: ${JAVV_LOGIN_MAX_ATTEMPTS:-5} # the lockout window, in minutes JAVV_LOGIN_LOCKOUT_MINUTES: ${JAVV_LOGIN_LOCKOUT_MINUTES:-15.0} # --- scanner pushes --- # largest push as it arrives (still compressed), in bytes (10 MiB) JAVV_INGEST_MAX_COMPRESSED_BYTES: ${JAVV_INGEST_MAX_COMPRESSED_BYTES:-10485760} # largest push once decompressed, in bytes (60 MiB) JAVV_INGEST_MAX_BODY_BYTES: ${JAVV_INGEST_MAX_BODY_BYTES:-62914560} # pushes per token per minute JAVV_INGEST_RATE_LIMIT_PER_MINUTE: ${JAVV_INGEST_RATE_LIMIT_PER_MINUTE:-120} # minutes a scan time may be ahead of the backend's clock JAVV_INGEST_FUTURE_TOLERANCE_MINUTES: ${JAVV_INGEST_FUTURE_TOLERANCE_MINUTES:-15} # --- triage, search and exports --- # a bulk action on at most this many findings applies at once; more is refused JAVV_BULK_INLINE_LIMIT: ${JAVV_BULK_INLINE_LIMIT:-5000} # the most findings that one bulk selection can hold. The backend refuses a larger one JAVV_BULK_MAX_TARGETS: ${JAVV_BULK_MAX_TARGETS:-10000} # how long a page of a search cursor stays open between pages JAVV_SEARCH_PIT_KEEP_ALIVE: ${JAVV_SEARCH_PIT_KEEP_ALIVE:-2m} # most rows an immediate export may hold; larger ones use a scheduled export JAVV_EXPORT_MAX_ROWS: ${JAVV_EXPORT_MAX_ROWS:-50000} # open search cursors and exports per user at one time JAVV_MAX_CONCURRENT_PITS_PER_PRINCIPAL: ${JAVV_MAX_CONCURRENT_PITS_PER_PRINCIPAL:-10} # batches of browser warnings and errors that one user can send in one minute # (a batch holds 20 at most) JAVV_CLIENT_EVENTS_RATE_LIMIT_PER_MINUTE: ${JAVV_CLIENT_EVENTS_RATE_LIMIT_PER_MINUTE:-60} # --- scheduled exports --- # hours a finished export is kept (Settings › Data & OpenSearch can change it) JAVV_EXPORT_TTL_HOURS: ${JAVV_EXPORT_TTL_HOURS:-24} # largest single export, in bytes (500 MiB); a larger one fails JAVV_EXPORT_MAX_BYTES: ${JAVV_EXPORT_MAX_BYTES:-524288000} # pause between export pages, in milliseconds, so exports leave room for pushes JAVV_REPORT_DRAIN_SLEEP_MS: ${JAVV_REPORT_DRAIN_SLEEP_MS:-200} # seconds without a heartbeat before a running export or job counts as abandoned JAVV_REPORT_LEASE_TTL_SECONDS: ${JAVV_REPORT_LEASE_TTL_SECONDS:-300} # --- Data inspector --- # most hits one inspector search may return JAVV_INSPECT_MAX_HITS: ${JAVV_INSPECT_MAX_HITS:-500} # largest inspector answer, in bytes (2 MiB) JAVV_INSPECT_MAX_RESPONSE_BYTES: ${JAVV_INSPECT_MAX_RESPONSE_BYTES:-2097152} # seconds before an inspector query gives up JAVV_INSPECT_TIMEOUT_SECONDS: ${JAVV_INSPECT_TIMEOUT_SECONDS:-10.0} # largest dashboard file that an import reads, in bytes (4 MiB) JAVV_DASHBOARD_IMPORT_MAX_BYTES: ${JAVV_DASHBOARD_IMPORT_MAX_BYTES:-4194304} # a Riskiest images panel with filters ranks its 100 images in one pass of the store, # so no image with the most findings is left out (false: faster, can leave one out) JAVV_EXACT_RANKING: ${JAVV_EXACT_RANKING:-true} # the MCP endpoint for AI agents at /api/v1/mcp: off until you turn it on JAVV_MCP_ENABLED: ${JAVV_MCP_ENABLED:-false} # requests that one MCP token can send in one minute JAVV_MCP_RATE_LIMIT_PER_MINUTE: ${JAVV_MCP_RATE_LIMIT_PER_MINUTE:-60} # longest tool answer, in characters: a longer one loses rows from its end JAVV_MCP_MAX_ANSWER_CHARS: ${JAVV_MCP_MAX_ANSWER_CHARS:-50000} # cluster retirement: the fleet defaults until a value is saved at runtime # days without an accepted scan before JAVV retires a cluster (0 means never) JAVV_CLUSTER_RETIRE_AFTER_DAYS: ${JAVV_CLUSTER_RETIRE_AFTER_DAYS:-45} # days before retirement that the warning banner and the bell start JAVV_CLUSTER_RETIREMENT_WARN_DAYS: ${JAVV_CLUSTER_RETIREMENT_WARN_DAYS:-7} # --- background jobs: cron expressions, read in TZ. An empty one never runs on a schedule --- # false stops every schedule; the jobs can still be run from the Data inspector JAVV_SCHEDULER_ENABLED: ${JAVV_SCHEDULER_ENABLED:-true} # builds queued exports and bulk actions JAVV_JOB_REPORT_DRAIN_CRON: ${JAVV_JOB_REPORT_DRAIN_CRON:-*/5 * * * *} # deletes expired exports and their leftovers JAVV_JOB_REPORT_SWEEP_CRON: ${JAVV_JOB_REPORT_SWEEP_CRON:-15 * * * *} # marks findings stale and ends expired risk acceptances JAVV_JOB_STALENESS_SWEEP_CRON: ${JAVV_JOB_STALENESS_SWEEP_CRON:-0 2 * * *} # starts new history indices, and deletes the ones older than the retention JAVV_JOB_LIFECYCLE_SWEEP_CRON: ${JAVV_JOB_LIFECYCLE_SWEEP_CRON:-0 3 * * *} # deletes the findings that no scan found for longer than the cleanup window JAVV_JOB_FINDINGS_CLEANUP_CRON: ${JAVV_JOB_FINDINGS_CLEANUP_CRON:-0 4 * * *} # retires the clusters that sent no scan for longer than their window, and brings back the # ones that scan again JAVV_JOB_CLUSTER_RETIREMENT_CRON: ${JAVV_JOB_CLUSTER_RETIREMENT_CRON:-15 4 * * *} # deletes expired sign-in sessions JAVV_JOB_SESSION_SWEEP_CRON: ${JAVV_JOB_SESSION_SWEEP_CRON:-30 4 * * *} # The backend is reached through the frontend's port. Publish it only for something that # must reach it directly (a Prometheus scrape of /metrics, which needs no sign-in). # ports: # - "8000:8000" frontend: image: ghcr.io/danube-labs/javv-frontend:0.9.6 # x-release-please-version restart: unless-stopped depends_on: backend: condition: service_healthy environment: # where /api, /auth and /readyz are forwarded: the backend service JAVV_BACKEND_URL: ${JAVV_BACKEND_URL:-http://backend:8000} # seconds a connection to the backend may take to open before the answer is a 502; a slow # answer (an export) is never cut JAVV_BACKEND_CONNECT_TIMEOUT: ${JAVV_BACKEND_CONNECT_TIMEOUT:-5} # the port the frontend listens on inside its container JAVV_FRONTEND_PORT: ${JAVV_FRONTEND_PORT:-8080} # debug | info | warning | error JAVV_LOG_LEVEL: ${JAVV_LOG_LEVEL:-info} # browsers and scanners both use this port: scanners push to /api/v1/ingest/scan through it. # To serve on another host port, change the left side ("80:8080"). ports: - "8080:8080" volumes: opensearch-data: # The security configuration of OpenSearch for JAVV. OpenSearch reads it on the first start of the # store with security on. The javv role allows only the calls that the backend makes. # docs/DEPLOYING.md gives the same role for a cluster that you run yourself. The mapping gives the # all_access role to admin, as the mapping file of the image does (this file replaces it), and the # javv role to javv. configs: opensearch-roles: content: | _meta: type: "roles" config_version: 2 javv: reserved: false description: "JAVV backend: its own indices, their snapshots, and cluster health" cluster_permissions: - "cluster:monitor/main" - "cluster:monitor/health" - "cluster:monitor/nodes/info" - "cluster:monitor/nodes/stats" - "cluster:monitor/state" - "cluster:monitor/shards" - "indices:admin/index_template/get" - "indices:admin/index_template/put" - "cluster:admin/snapshot/get" - "cluster:admin/snapshot/create" - "cluster:admin/snapshot/restore" - "indices:data/write/bulk" - "indices:data/read/mget" - "indices:data/read/scroll*" index_permissions: - index_patterns: ["findings", "javv-*", "system-*"] allowed_actions: - "indices:data/read/*" - "indices:data/write/*" - "indices:admin/create" - "indices:admin/get" - "indices:admin/mapping/put" - "indices:admin/mappings/get" - "indices:admin/aliases" - "indices:admin/aliases/get" - "indices:admin/refresh*" - "indices:monitor/*" - index_patterns: ["javv-*", "system-audit-log*"] allowed_actions: ["indices:admin/rollover"] - index_patterns: ["javv-*"] allowed_actions: ["indices:admin/delete"] # written by a restore and listed by the Data inspector, never read: a copy of # system-users holds password hashes - index_patterns: ["restored-*"] allowed_actions: ["indices:admin/create", "indices:data/write/*", "indices:monitor/*"] opensearch-roles-mapping: content: | _meta: type: "rolesmapping" config_version: 2 all_access: reserved: false backend_roles: ["admin"] description: "admin, the store's superuser" javv: reserved: false users: ["javv"] description: "the backend's user"