Files
GTI_GERENCIADOR_FISCAL/deploy/hub/docker-compose.prod.yml
T
gtiandClaude Opus 5.5 64d3fc56e2 feat: SSO from GTI Gerenciador and the Gerenciador visual identity
Collaborators logged into GTI Gerenciador open GTI Fiscal without a
second password. The panel signs a 60 s JWT (Ed25519; the Hub holds only
the public key); the Hub checks signature, iss/aud/exp/nbf, maximum
lifetime, nonce against a one-time state cookie set when the flow starts
at GET /login (login-CSRF protection) and a single-use jti
(sso_used_assertions). Collaborators are provisioned in admin_users with
auth_source=GTI_GERENCIADOR and no password; a CHECK constraint makes a
passwordless local account or a federated account with a password
impossible (migration 0021). Sessions last FEDERATED_SESSION_SECONDS
(1 h) and are renewed silently through the panel.

The local admin stays as break-glass at /login/local, audited. Logout ends
only the Hub session. Every SSO success/rejection (stable reason code),
logout and local login is audited with the real client IP, never the
token. verify_password now fails closed on a missing hash instead of
raising.

Visual identity: gti-theme.css is a verbatim copy of the panel's
design system (tokens, sidebar, light/dark theme) plus its tema.js,
sidebar.js and logo; control-panel.css keeps only Hub components, all
colours from the panel tokens. Page structure and features unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 12:21:26 -03:00

79 lines
3.0 KiB
YAML

# GTI Fiscal Hub — production compose (RC1). Only two containers: PostgreSQL + Hub. No Kubernetes/Redis/RabbitMQ.
#
# cd deploy/hub && cp .env.example .env && (edit .env) && docker compose -f docker-compose.prod.yml --env-file .env up -d --build
#
# PostgreSQL is on an internal network only: it publishes NO port and is not reachable from the LAN/internet.
# The Hub binds to HUB_BIND (default 127.0.0.1): put a TLS reverse proxy (HTTPS) in front of it for the Agents.
# Migrations run automatically at Hub start (Dockerfile CMD: alembic upgrade head).
name: gti-fiscal-hub
x-logging: &logging
driver: json-file
options:
max-size: "10m"
max-file: "5"
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: gti_fiscal
POSTGRES_USER: gti
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required (URL-safe, e.g. openssl rand -hex 24)}
volumes:
- postgres_data:/var/lib/postgresql/data
- ${BACKUP_DIR:-./backups}:/backups
networks: [internal]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gti -d gti_fiscal"]
interval: 10s
timeout: 5s
retries: 12
logging: *logging
fiscal-hub:
build:
context: ../..
dockerfile: central/Dockerfile
image: gti-fiscal-hub:${GTI_FISCAL_VERSION:-1.0.0-rc1}
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
APP_ENV: production
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
LOG_LEVEL: ${LOG_LEVEL:-INFO}
BASE_URL: ${BASE_URL:?BASE_URL is required (public HTTPS URL of the Hub)}
AGENT_ONLINE_THRESHOLD_SECONDS: ${AGENT_ONLINE_THRESHOLD_SECONDS:-120}
# Reverse proxy on another host: uvicorn only honours X-Forwarded-For/-Proto
# from these addresses (real client IP in audit/session records, scheme=https).
FORWARDED_ALLOW_IPS: ${FORWARDED_ALLOW_IPS:-127.0.0.1}
# 1 = session/CSRF cookies are sent only over HTTPS. Set once TLS is in front.
WEB_COOKIE_SECURE: ${WEB_COOKIE_SECURE:-0}
# SSO from GTI Gerenciador (central/app/sso.py): the panel's Ed25519 PUBLIC key
# and its /sso/fiscal URL. Both empty = SSO off (local login only).
SSO_GERENCIADOR_CHAVE_PUBLICA: ${SSO_GERENCIADOR_CHAVE_PUBLICA:-}
SSO_START_URL: ${SSO_START_URL:-}
FEDERATED_SESSION_SECONDS: ${FEDERATED_SESSION_SECONDS:-3600}
# AGENT_DEV_TOKEN is intentionally NOT set: real Agents are registered with central/scripts/register_agent.py.
ports:
- "${HUB_BIND:-127.0.0.1}:${HUB_PORT:-8000}:8000"
networks: [internal, edge]
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 15s
timeout: 5s
retries: 10
start_period: 30s
logging: *logging
networks:
internal:
internal: true # no route to the outside: PostgreSQL is never exposed
edge: {}
volumes:
postgres_data: