Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docker-configurations/services/comfyui-qwen/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ COMFYUI_URL=http://localhost:8188
COMFYUI_USERNAME=admin
COMFYUI_PASSWORD=your_secure_password_here

# Token Bearer pour l'accès API (optionnel)
# Si non spécifié, un token sera généré automatiquement
COMFYUI_BEARER_TOKEN=your_bearer_token_here
# Credential ComfyUI-Login (meme valeur que .secrets/qwen-api-user.token,
# propage par scripts/secrets/render_envs.py depuis master.env #14382).
# Le compose NE l'interpole PAS (les '$' du bcrypt seraient tronques) :
# l'auth passe par le fichier bind-mounté, cette ligne sert au render.
COMFYUI_API_TOKEN=your_comfyui_api_token_here

# Configuration du mode invité (true/false)
# Si true, les utilisateurs non authentifiés peuvent accéder en mode invité
Expand Down
12 changes: 10 additions & 2 deletions docker-configurations/services/comfyui-qwen/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ services:
- COMFYUI_LOGIN_ENABLED=${COMFYUI_LOGIN_ENABLED:-true}
- COMFYUI_USERNAME=${COMFYUI_USERNAME}
- COMFYUI_PASSWORD=${COMFYUI_PASSWORD}
- COMFYUI_BEARER_TOKEN=${COMFYUI_BEARER_TOKEN}
- GUEST_MODE_ENABLED=${GUEST_MODE_ENABLED:-false}
- SECRET_KEY=${SECRET_KEY}
# Configuration comportement
Expand Down Expand Up @@ -120,12 +119,21 @@ services:
- COMFYUI_URL=http://comfyui-qwen:8188
- IDLE_TIMEOUT=${IDLE_TIMEOUT:-1200}
- CHECK_INTERVAL=${CHECK_INTERVAL:-60}
- COMFYUI_AUTH_TOKEN=${COMFYUI_RAW_TOKEN}
- COMFYUI_USERNAME=${COMFYUI_USERNAME:-admin}
- COMFYUI_PASSWORD=${COMFYUI_PASSWORD}
- LOG_LEVEL=${LOG_LEVEL:-INFO}
- TZ=${TZ:-Europe/Paris}

volumes:
# Credential ComfyUI-Login : par fichier, JAMAIS par env compose --
# l'interpolation compose mange les '$' des bcrypt ($2b$12$Iv... -> le
# conteneur recevrait un token tronque, #14382). Le monitor lit le
# fichier en fallback si COMFYUI_AUTH_TOKEN n'est pas defini.
- type: bind
source: ../../../.secrets/qwen-api-user.token
target: /secrets/qwen-api-user.token
read_only: true

working_dir: /app

command: ["python", "comfyui_idle_monitor.py"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,15 @@ fi
if [ "${COMFYUI_LOGIN_ENABLED:-false}" = "true" ]; then
echo "Configuration du token ComfyUI-Login..."
mkdir -p custom_nodes/ComfyUI-Login
echo "${COMFYUI_BEARER_TOKEN}" > custom_nodes/ComfyUI-Login/PASSWORD
# Credential depuis le fichier bind-mounté, pas depuis l'env compose :
# l'interpolation compose tronque les '$' du bcrypt ($2b$12$Iv...),
# le PASSWORD serait corrompu (#14382).
if [ -f /workspace/ComfyUI/.secrets/qwen-api-user.token ]; then
cp /workspace/ComfyUI/.secrets/qwen-api-user.token custom_nodes/ComfyUI-Login/PASSWORD
else
echo "ERREUR: /workspace/ComfyUI/.secrets/qwen-api-user.token absent -- impossible de configurer ComfyUI-Login" >&2
exit 1
fi
chmod 600 custom_nodes/ComfyUI-Login/PASSWORD
fi

Expand Down
22 changes: 20 additions & 2 deletions docker-configurations/services/shared/comfyui_idle_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,24 @@
import argparse
import threading
from datetime import datetime
from pathlib import Path
from typing import Optional
from functools import wraps

# Credential file bind-mounted read-only by the compose (#14382). The token
# never crosses compose env interpolation: the bcrypt form ($2b$12$Iv...)
# gets its '$' runs eaten there and the container would receive a truncated
# bearer (measured: 60c value -> 57c in-container).
_TOKEN_FILE = Path("/secrets/qwen-api-user.token")


def _read_token_file() -> Optional[str]:
try:
content = _TOKEN_FILE.read_text(encoding="utf-8").strip()
return content or None
except OSError:
return None

try:
import requests
except ImportError:
Expand Down Expand Up @@ -449,8 +464,11 @@ def run_standalone():
)
parser.add_argument(
"--token",
default=os.environ.get("COMFYUI_AUTH_TOKEN"),
help="Authentication token (optional, deprecated - use --username/--password)"
default=os.environ.get("COMFYUI_AUTH_TOKEN") or _read_token_file(),
help="Authentication token (optional, deprecated - use --username/--password). "
"Falls back to the bind-mounted /secrets/qwen-api-user.token (#14382) -- "
"the credential must not cross compose env interpolation, which "
"truncates the '$' runs of the bcrypt form."
)
parser.add_argument(
"--username",
Expand Down
21 changes: 19 additions & 2 deletions docs/genai/secrets-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,34 @@ python scripts/secrets/render_envs.py --check
| Hubs / git | `CIVITAI_TOKEN`, `GITHUB_TOKEN` = `GITHUB_ACCESS_TOKEN` | services + notebooks | Oui |
| Client API keys (server↔client) | `WHISPER_API_KEY`, `VLLM_API_KEY`, `TTS_API_KEY`, `QWEN_ASR_API_KEY`, `MUSICGEN_API_KEY`, `DEMUCS_API_KEY`, `FUNASR_API_KEY` | 1 service + notebooks | Moyen |
| Qdrant vector DB (client) | `QDRANT_API_KEY` | notebooks RAG / SemanticKernel / Argument_Analysis | Moyen (flip serveur = op inter-repo roo-extensions) |
| Tokens client ComfyUI | `COMFYUI_VIDEO_TOKEN`, `COMFYUI_API_TOKEN` | notebooks → services ComfyUI | Moyen |
| Tokens client ComfyUI | `COMFYUI_VIDEO_TOKEN`, `COMFYUI_API_TOKEN` = `COMFYUI_AUTH_TOKEN` | notebooks → services ComfyUI | Moyen |
| Session | `SECRET_KEY` | 1 service | Oui |

**Alias :** `HF_TOKEN`/`HUGGINGFACE_TOKEN` et `GITHUB_TOKEN`/`GITHUB_ACCESS_TOKEN` désignent le MÊME secret sous deux noms (services vs notebooks). Le bootstrap vérifie leur cohérence (abort si divergence).
**Alias :** `HF_TOKEN`/`HUGGINGFACE_TOKEN`, `GITHUB_TOKEN`/`GITHUB_ACCESS_TOKEN` et `COMFYUI_API_TOKEN`/`COMFYUI_AUTH_TOKEN` désignent le MÊME secret sous deux noms (services vs notebooks). Le bootstrap vérifie leur cohérence (abort si divergence).

**L'alias ne fabrique PAS le nom manquant — les deux doivent être écrits dans `master.env`.** `ALIASES` (render_envs.py) déclare la paire ; il ne dérive pas `HF_TOKEN` d'un `HUGGINGFACE_TOKEN` présent seul. Un `master.env` qui ne porte qu'un des deux noms laisse l'autre dans la liste `declared SECRET keys are absent from master.env (left untouched in services)` de `--check`, et **aucun consommateur ne le reçoit** — alors que le token existe et est valide. Mesuré le 2026-08-15 : `HUGGINGFACE_TOKEN` présent + `HF_TOKEN` absent, pour **655 sites de code lisant `HF_TOKEN`** contre 144 lisant `HUGGINGFACE_TOKEN`. Remède : écrire les deux lignes dans `master.env`, puis `render_envs.py`.

**`sync()` rafraîchit, il n'insère pas.** Une clé absente d'un `.env` cible n'y est pas ajoutée par le render : seules les clés **déjà déclarées** y sont réécrites depuis master (le gap-fill n'existe que pour un service *sans* `.env`, à partir des `${KEY}` de sa compose). Pour un `.env` côté notebooks (pas de compose), déclarer la ligne vide `CLE=` puis lancer le render.

**Un 403 sur un repo *gated* n'est pas un défaut de token.** Discriminer avant d'escalader — repo non-gated **avec** token → `206` (le token lit) ; repo gated **sans** token → `401` ; repo gated **avec** token → `403` = authentifié mais non autorisé, c'est-à-dire licence non acceptée par le compte porteur **ou** scope « read gated repos » absent du token fine-grained. Les deux se règlent sur huggingface.co, pas dans `master.env`.

### ComfyUI-Login (comfyui-qwen) — source unique du credential API (#14382)

Le middleware ComfyUI-Login valide le bearer **littéralement contre le fichier bind-mounté** `.secrets/qwen-api-user.token` (60 caractères, forme `$2b$` bcrypt — le hash *est* le token partagé). Avant #14382, le compose interpolait des noms que `render_envs.py` ne gérait pas (`COMFYUI_BEARER_TOKEN`, `COMFYUI_RAW_TOKEN`) : sur un `.env` rendu par le seul render, ils résolvent **vide** (mesure #14382) ; là où `auth_manager.py` les avait écrits, ce sont des copies non gérées qui dérivent à la prochaine rotation (pattern incident #6901). Le sidecar idle-monitor recevait en outre `COMFYUI_RAW_TOKEN` (le mot de passe formulaire, pas le bearer) → ses polls d'activité prenaient 401.

Le câblage depuis #14382 ne fait plus dériver — et ne passe **jamais** par l'interpolation d'env du compose : docker compose **interprète les `$` des valeurs** (le bcrypt `$2b$12$Iv…` devient un token tronqué 60c → 57c, mesuré en conteneur). Le credential passe par le **fichier** (les fichiers bind-mountés ne sont pas interpolés) :

| Consommateur | Voie |
|---|---|
| middleware ComfyUI-Login (`entrypoint.sh`) | fichier bind-mounté `.secrets/qwen-api-user.token` (déjà le chemin privilégié) |
| `workspace/install_comfyui.sh` → `ComfyUI-Login/PASSWORD` | `cp` du fichier monté (l'`echo` de l'env compose écrivait un token tronqué) |
| sidecar `idle-monitor` (poll `/system_stats`) | fichier bind-mounté `/secrets/qwen-api-user.token`, lu en fallback par `comfyui_idle_monitor.py` |
| notebooks GenAI (`os.getenv("COMFYUI_AUTH_TOKEN") or os.getenv("COMFYUI_API_TOKEN")`, flip #16) | `GenAI/.env`, les deux noms gérés master (alias `COMFYUI_AUTH_TOKEN` = `COMFYUI_API_TOKEN`) |

**Rotation du credential comfyui-qwen** : éditer `COMFYUI_API_TOKEN` **et** `COMFYUI_AUTH_TOKEN` dans `master.env` (même valeur), régénérer `.secrets/qwen-api-user.token` avec cette valeur, `render_envs.py`, puis **recréer** le container (`docker compose up -d` — un `restart` ne relit ni les montages ni l'env d'interpolation ; cf règle du restart ci-dessous pour `COMFYUI_PASSWORD`).

`COMFYUI_RAW_TOKEN` (le mot de passe en clair avant hash, utile seulement au login formulaire `COMFYUI_USERNAME`/`COMFYUI_PASSWORD`) **ne transite plus par aucun `.env` géré** — la forme brute ne doit pas vivre dans les `.env` (seule la forme hashée y circule). `auth_manager.py` n'écrit plus `COMFYUI_BEARER_TOKEN`/`COMFYUI_RAW_TOKEN` dans les `.env`.

### Qdrant — convention client vs serveur (cross-repo)

Qdrant expose la **même** clé API sous **deux noms** selon le côté :
Expand Down
6 changes: 4 additions & 2 deletions scripts/genai-stack/core/auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,13 @@ def _update_env_file(self, env_path: Path, raw_token: str, bcrypt_hash: str) ->
updated_keys = set()

# Mapping des clés à mettre à jour
# NB #14382 : COMFYUI_BEARER_TOKEN / COMFYUI_RAW_TOKEN ne sont plus
# ecrits -- le compose lit ${COMFYUI_API_TOKEN} (cle geree par
# master.env via render_envs.py) et la forme brute ne doit pas vivre
# dans le .env de service.
updates = {
'COMFYUI_API_TOKEN': bcrypt_hash,
'COMFYUI_BEARER_TOKEN': bcrypt_hash, # Alias souvent utilisé
'QWEN_API_USER_TOKEN': bcrypt_hash, # Legacy
'COMFYUI_RAW_TOKEN': raw_token,
'COMFYUI_PASSWORD': raw_token # Pour login simple
}

Expand Down
13 changes: 11 additions & 2 deletions scripts/secrets/render_envs.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,12 @@
"QDRANT_API_KEY",
# OWUI native API (NB-20, #417) + TTS multi-voice gateway (#16, po-2023)
"OWUI_API_KEY", "TTS_GATEWAY_API_KEY",
# ComfyUI client tokens (notebook client <-> service must agree)
"COMFYUI_VIDEO_TOKEN", "COMFYUI_API_TOKEN",
# ComfyUI client tokens (notebook client <-> service must agree).
# COMFYUI_AUTH_TOKEN is the canonical notebook-client name (#16 flip:
# notebooks read ``os.getenv("COMFYUI_AUTH_TOKEN") or os.getenv("COMFYUI_API_TOKEN")``).
# Aliased to COMFYUI_API_TOKEN -- both names carry the credential the
# ComfyUI-Login middleware validates (bind-mounted token file, #14382).
"COMFYUI_VIDEO_TOKEN", "COMFYUI_API_TOKEN", "COMFYUI_AUTH_TOKEN",
# ComfyUI-Video web login password (user decision #10985, 2026-08-20):
# centralized in master.env under an INSTANCE-SCOPED name -- plain
# COMFYUI_PASSWORD must NOT enter SECRET_KEYS because comfyui-qwen carries
Expand Down Expand Up @@ -203,6 +207,11 @@
# ``QWEN_API_USER_TOKEN`` in legacy code paths). Both names MUST
# carry the same value; bootstrap enforces this on first sync. #10265.
"QWEN_API_USER_TOKEN": "QWEN_API_TOKEN",
# ComfyUI-Login bearer, notebook-client canonical name (#14382): notebooks
# read COMFYUI_AUTH_TOKEN first; must equal COMFYUI_API_TOKEN (the
# credential the middleware validates). A stale non-empty AUTH_TOKEN
# shadows the correct API_TOKEN in the ``or`` fallback chain -> 401.
"COMFYUI_AUTH_TOKEN": "COMFYUI_API_TOKEN",
}


Expand Down
Loading