Re-enable nginx Bearer auth on ia.enso /ollama
**Motivations:** - Restore gate on /ollama/; document Cursor streamFromAgentBackend note. **Root causes:** - N/A. **Correctifs:** - location /ollama/ if map + clear Authorization upstream; deploy script emits Bearer + websocket maps with retry bearer_only. **Evolutions:** - README Cursor subsection on streamFromAgentBackend (observed behavior); feature/services/infrastructure aligned. **Pages affectées:** - deploy/nginx/sites/ia.enso.4nkweb.com.conf - deploy/nginx/deploy-ia-enso-to-proxy.sh - deploy/nginx/README-ia-enso.md - deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example - docs/features/ia-enso-nginx-proxy-ollama-anythingllm.md - docs/services.md - docs/infrastructure.md
This commit is contained in:
parent
c13ce79696
commit
c4215044f0
@ -10,11 +10,11 @@ Reverse TLS vers l’hôte LAN **`192.168.1.164`** (Ollama + AnythingLLM ; IP su
|
|||||||
| **Ollama** API native (ex. liste des modèles) | `https://ia.enso.4nkweb.com/ollama/api/tags` |
|
| **Ollama** API native (ex. liste des modèles) | `https://ia.enso.4nkweb.com/ollama/api/tags` |
|
||||||
| **Ollama** API compatible OpenAI (Cursor, etc.) | base URL `https://ia.enso.4nkweb.com/ollama/v1` — ex. `https://ia.enso.4nkweb.com/ollama/v1/models` |
|
| **Ollama** API compatible OpenAI (Cursor, etc.) | base URL `https://ia.enso.4nkweb.com/ollama/v1` — ex. `https://ia.enso.4nkweb.com/ollama/v1/models` |
|
||||||
|
|
||||||
**Sécurité :** `/ollama/` n’a **pas** de garde Bearer nginx par défaut : toute personne qui peut joindre l’URL peut utiliser l’API Ollama. Restreindre par **pare-feu** (IP du proxy uniquement vers `.164`) ou réintroduire un `map` Bearer (voir `http-maps/ia-enso-ollama-bearer.map.conf.example`). AnythingLLM sous `/anythingllm/` reste derrière son **login applicatif**.
|
**Bearer nginx :** tout `/ollama/` exige `Authorization: Bearer <secret>` (fichier `map` sur le proxy). La valeur n’est **pas** transmise à Ollama (`Authorization` effacé en amont). AnythingLLM sous `/anythingllm/` : auth **applicative** uniquement.
|
||||||
|
|
||||||
| Chemin (relatif) | Backend | Port LAN | Protection |
|
| Chemin (relatif) | Backend | Port LAN | Protection |
|
||||||
|------------------|---------|----------|------------|
|
|------------------|---------|----------|------------|
|
||||||
| `/ollama/` | Ollama | `11434` | Aucune auth nginx (Ollama sans clé par défaut) |
|
| `/ollama/` | Ollama | `11434` | **Bearer** nginx |
|
||||||
| `/anythingllm/` | AnythingLLM | `3001` | Login AnythingLLM |
|
| `/anythingllm/` | AnythingLLM | `3001` | Login AnythingLLM |
|
||||||
|
|
||||||
**Contexte Cursor :** une URL en IP privée (ex. `http://192.168.1.164:11434`) peut être refusée par Cursor (`ssrf_blocked`). Un **nom public** HTTPS vers le proxy évite ce blocage si le DNS résolu depuis Internet n’est pas une IP RFC1918.
|
**Contexte Cursor :** une URL en IP privée (ex. `http://192.168.1.164:11434`) peut être refusée par Cursor (`ssrf_blocked`). Un **nom public** HTTPS vers le proxy évite ce blocage si le DNS résolu depuis Internet n’est pas une IP RFC1918.
|
||||||
@ -28,11 +28,14 @@ Reverse TLS vers l’hôte LAN **`192.168.1.164`** (Ollama + AnythingLLM ; IP su
|
|||||||
Depuis la racine du dépôt **`smart_ide`**, sur une machine avec accès SSH au bastion puis au proxy :
|
Depuis la racine du dépôt **`smart_ide`**, sur une machine avec accès SSH au bastion puis au proxy :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# accès LAN direct au proxy (.100), sans bastion (variable vide = pas de ProxyJump) :
|
export IA_ENSO_OLLAMA_BEARER_TOKEN='secret-ascii-sans-guillemets-ni-backslash'
|
||||||
|
# accès LAN direct au proxy (.100), sans bastion :
|
||||||
# export DEPLOY_SSH_PROXY_HOST=
|
# export DEPLOY_SSH_PROXY_HOST=
|
||||||
./deploy/nginx/deploy-ia-enso-to-proxy.sh
|
./deploy/nginx/deploy-ia-enso-to-proxy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Si `IA_ENSO_OLLAMA_BEARER_TOKEN` est absent, le script génère un token hex (affichage unique) à conserver pour Cursor.
|
||||||
|
|
||||||
### Prérequis sur le proxy
|
### Prérequis sur le proxy
|
||||||
|
|
||||||
- `http { include /etc/nginx/conf.d/*.conf; ... }` dans `/etc/nginx/nginx.conf` (sinon le script échoue avec un message explicite).
|
- `http { include /etc/nginx/conf.d/*.conf; ... }` dans `/etc/nginx/nginx.conf` (sinon le script échoue avec un message explicite).
|
||||||
@ -58,16 +61,17 @@ sudo certbot certonly --webroot -w /var/www/certbot -d ia.enso.4nkweb.com --non-
|
|||||||
|
|
||||||
| Chemin sur le proxy | Rôle |
|
| Chemin sur le proxy | Rôle |
|
||||||
|---------------------|------|
|
|---------------------|------|
|
||||||
| `/etc/nginx/conf.d/ia-enso-http-maps.conf` | `map` WebSocket `$connection_upgrade` (ou fichier stub si doublon ailleurs) |
|
| `/etc/nginx/conf.d/ia-enso-http-maps.conf` | `map_hash_bucket_size`, `map` Bearer `$ia_enso_ollama_authorized`, et souvent `map` WebSocket |
|
||||||
| `/etc/nginx/sites-available/ia.enso.4nkweb.com.conf` | `server` HTTP→HTTPS + HTTPS |
|
| `/etc/nginx/sites-available/ia.enso.4nkweb.com.conf` | `server` HTTP→HTTPS + HTTPS |
|
||||||
| Lien `sites-enabled/ia.enso.4nkweb.com.conf` | Activation du vhost |
|
| Lien `sites-enabled/ia.enso.4nkweb.com.conf` | Activation du vhost |
|
||||||
|
|
||||||
Si `nginx -t` échoue à cause d’un **doublon** `map $http_upgrade $connection_upgrade`, le script retente avec un **stub** commenté à la place du `map`.
|
Si `nginx -t` échoue à cause d’un **doublon** `map $http_upgrade $connection_upgrade`, le script retente avec **Bearer seul** (sans dupliquer le `map` WebSocket).
|
||||||
|
|
||||||
### Variables d’environnement du script
|
### Variables d’environnement du script
|
||||||
|
|
||||||
| Variable | Défaut | Rôle |
|
| Variable | Défaut | Rôle |
|
||||||
|----------|--------|------|
|
|----------|--------|------|
|
||||||
|
| `IA_ENSO_OLLAMA_BEARER_TOKEN` | généré (`openssl rand -hex 32`) | Secret pour `Authorization: Bearer …` |
|
||||||
| `IA_ENSO_SSH_KEY` | `~/.ssh/id_ed25519` | Clé privée SSH |
|
| `IA_ENSO_SSH_KEY` | `~/.ssh/id_ed25519` | Clé privée SSH |
|
||||||
| `IA_ENSO_PROXY_USER` | `ncantu` | Utilisateur SSH sur le proxy |
|
| `IA_ENSO_PROXY_USER` | `ncantu` | Utilisateur SSH sur le proxy |
|
||||||
| `IA_ENSO_PROXY_HOST` | `192.168.1.100` | Cible SSH (IP ou hostname LAN) |
|
| `IA_ENSO_PROXY_HOST` | `192.168.1.100` | Cible SSH (IP ou hostname LAN) |
|
||||||
@ -91,11 +95,9 @@ sudo certbot certonly --webroot -w /var/www/certbot -d ia.enso.4nkweb.com
|
|||||||
|
|
||||||
Adapter dans `sites/ia.enso.4nkweb.com.conf` les directives `ssl_certificate` / `ssl_certificate_key` si le répertoire `live/` diffère.
|
Adapter dans `sites/ia.enso.4nkweb.com.conf` les directives `ssl_certificate` / `ssl_certificate_key` si le répertoire `live/` diffère.
|
||||||
|
|
||||||
### 2. Maps HTTP (WebSocket ; Bearer optionnel)
|
### 2. Maps HTTP (Bearer + WebSocket)
|
||||||
|
|
||||||
**WebSocket (AnythingLLM)** : si `$connection_upgrade` n’existe pas déjà dans l’instance, inclure `http-maps/websocket-connection.map.conf.example` dans `http { }` ou utiliser le fichier `ia-enso-http-maps.conf` déployé par le script.
|
Le script déploie `ia-enso-http-maps.conf` avec `map_hash_bucket_size 256`, le `map` Bearer et le `map` WebSocket (ou Bearer seul si doublon WebSocket ailleurs). Installation manuelle : combiner `http-maps/ia-enso-ollama-bearer.map.conf.example` et `websocket-connection.map.conf.example` dans `http { }` si besoin.
|
||||||
|
|
||||||
**Bearer sur `/ollama/` (optionnel)** : pour réactiver une garde nginx, ajouter le `map` de `http-maps/ia-enso-ollama-bearer.map.conf.example` et dans `location /ollama/` un `if ($ia_enso_ollama_authorized = 0) { return 401; }` (+ `map_hash_bucket_size 256` si secret long). Ne pas commiter le secret.
|
|
||||||
|
|
||||||
### 3. Fichier `server`
|
### 3. Fichier `server`
|
||||||
|
|
||||||
@ -119,11 +121,12 @@ sudo nginx -t && sudo systemctl reload nginx
|
|||||||
### API Ollama via le proxy
|
### API Ollama via le proxy
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sS -o /dev/null -w "%{http_code}\n" https://ia.enso.4nkweb.com/ollama/v1/models
|
curl -sS -o /dev/null -w "%{http_code}\n" \
|
||||||
curl -sS -o /dev/null -w "%{http_code}\n" https://ia.enso.4nkweb.com/ollama/api/tags
|
-H "Authorization: Bearer <secret>" \
|
||||||
|
https://ia.enso.4nkweb.com/ollama/v1/models
|
||||||
```
|
```
|
||||||
|
|
||||||
Attendu : **200** sans en-tête `Authorization` (config par défaut sans Bearer nginx).
|
Attendu : **200** avec le bon secret ; **401** sans `Authorization` ou secret incorrect.
|
||||||
|
|
||||||
### AnythingLLM
|
### AnythingLLM
|
||||||
|
|
||||||
@ -133,9 +136,9 @@ Si les assets statiques échouent, vérifier la doc upstream (sous-chemin, en-t
|
|||||||
### Cursor
|
### Cursor
|
||||||
|
|
||||||
- URL de base OpenAI : `https://ia.enso.4nkweb.com/ollama/v1`
|
- URL de base OpenAI : `https://ia.enso.4nkweb.com/ollama/v1`
|
||||||
- Clé API : laisser vide ou valeur factice si Cursor l’exige (plus de Bearer nginx sur `/ollama/` par défaut).
|
- Clé API : **identique** au secret du `map` nginx (sans préfixe `Bearer ` dans le champ).
|
||||||
|
|
||||||
Si **`curl`** vers `/ollama/v1/models` renvoie **200** mais Cursor affiche **`ERROR_BAD_USER_API_KEY`**, l’échec vient **du client Cursor** (validation / infra Cursor), pas du proxy : [forum](https://forum.cursor.com/t/unauthorized-user-api-key-with-custom-openai-api-key-url/132572).
|
**`streamFromAgentBackend` (comportement observé)** : dans l’application Cursor (bundle Electron), le flux **Agent / chat** appelle une couche interne qui ouvre un stream vers **les serveurs Cursor** (`getAgentStreamResponse`, etc.), pas un `fetch` direct depuis ton poste vers ton URL OpenAI override. Cursor peut donc valider une **« User API key »** ou des droits **avant** ou **en parallèle** de l’usage de l’override. Si **`curl`** avec le Bearer vers `/ollama/v1/models` renvoie **200** mais Cursor affiche **`ERROR_BAD_USER_API_KEY`**, l’échec reste **côté client / infra Cursor** : [forum](https://forum.cursor.com/t/unauthorized-user-api-key-with-custom-openai-api-key-url/132572). Le code minifié du produit n’est pas dans ce dépôt ; seuls les noms de fonctions dans la stack trace décrivent ce chemin d’exécution.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -145,13 +148,21 @@ Sur **`192.168.1.164`**, n’autoriser **11434** et **3001** TCP que depuis **19
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Rotation du secret Bearer
|
||||||
|
|
||||||
|
1. Mettre à jour `"Bearer …"` dans `/etc/nginx/conf.d/ia-enso-http-maps.conf` (ou redéployer avec `IA_ENSO_OLLAMA_BEARER_TOKEN`).
|
||||||
|
2. `sudo nginx -t && sudo systemctl reload nginx`.
|
||||||
|
3. Mettre à jour la clé API dans Cursor (et tout autre client).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Dépannage
|
## Dépannage
|
||||||
|
|
||||||
| Symptôme | Piste |
|
| Symptôme | Piste |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
| `nginx -t` erreur sur `connection_upgrade` | Doublon de `map $http_upgrade $connection_upgrade` : retirer l’un des blocs ou laisser le stub du script. |
|
| `nginx -t` erreur sur `connection_upgrade` | Doublon de `map $http_upgrade $connection_upgrade` : retirer l’un des blocs ou laisser le déploiement « Bearer seul » du script. |
|
||||||
| `could not build map_hash` / `map_hash_bucket_size` | Uniquement si tu réactives un `map` Bearer avec un secret très long ; ajouter `map_hash_bucket_size 256;` dans `http { }`. |
|
| `could not build map_hash` / `map_hash_bucket_size` | Secret Bearer long : le fichier généré par le script inclut `map_hash_bucket_size 256;`. |
|
||||||
| `401` sur `/ollama/` | Bearer nginx réactivé manuellement : aligner client et `map`, ou désactiver la garde. |
|
| `401` sur `/ollama/` | Secret différent entre client et `map` ; en-tête `Authorization` absent ou mal formé. |
|
||||||
| `502` / timeout | Ollama ou AnythingLLM arrêtés sur le backend ; pare-feu ; mauvaise IP dans `upstream` (vérifier `grep server /etc/nginx/sites-available/ia.enso.4nkweb.com.conf` sur le proxy ; redéployer avec `IA_ENSO_BACKEND_IP=192.168.1.164`). |
|
| `502` / timeout | Ollama ou AnythingLLM arrêtés sur le backend ; pare-feu ; mauvaise IP dans `upstream` (vérifier `grep server /etc/nginx/sites-available/ia.enso.4nkweb.com.conf` sur le proxy ; redéployer avec `IA_ENSO_BACKEND_IP=192.168.1.164`). |
|
||||||
| Erreur SSL / `cannot load certificate` | Certificat absent : exécuter certbot sur le proxy pour `ia.enso.4nkweb.com`, ou adapter les chemins `ssl_certificate` dans le fichier site. |
|
| Erreur SSL / `cannot load certificate` | Certificat absent : exécuter certbot sur le proxy pour `ia.enso.4nkweb.com`, ou adapter les chemins `ssl_certificate` dans le fichier site. |
|
||||||
| Cursor `ssrf_blocked` | L’hôte utilisé résout encore vers une IP privée côté infrastructure Cursor ; vérifier DNS public / NAT. |
|
| Cursor `ssrf_blocked` | L’hôte utilisé résout encore vers une IP privée côté infrastructure Cursor ; vérifier DNS public / NAT. |
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
# Requires passwordless sudo for nginx on the proxy host.
|
# Requires passwordless sudo for nginx on the proxy host.
|
||||||
#
|
#
|
||||||
# Environment:
|
# Environment:
|
||||||
|
# IA_ENSO_OLLAMA_BEARER_TOKEN Bearer secret for /ollama (if unset, openssl rand -hex 32).
|
||||||
# IA_ENSO_SSH_KEY SSH private key (default: ~/.ssh/id_ed25519).
|
# IA_ENSO_SSH_KEY SSH private key (default: ~/.ssh/id_ed25519).
|
||||||
# IA_ENSO_PROXY_USER SSH user on proxy (default: ncantu).
|
# IA_ENSO_PROXY_USER SSH user on proxy (default: ncantu).
|
||||||
# IA_ENSO_PROXY_HOST Proxy IP or hostname (default: 192.168.1.100).
|
# IA_ENSO_PROXY_HOST Proxy IP or hostname (default: 192.168.1.100).
|
||||||
@ -37,26 +38,47 @@ elif [[ -z "$DEPLOY_SSH_PROXY_HOST" ]]; then
|
|||||||
fi
|
fi
|
||||||
export DEPLOY_SSH_PROXY_USER
|
export DEPLOY_SSH_PROXY_USER
|
||||||
|
|
||||||
|
TOKEN="${IA_ENSO_OLLAMA_BEARER_TOKEN:-}"
|
||||||
|
if [[ -z "$TOKEN" ]]; then
|
||||||
|
TOKEN="$(openssl rand -hex 32)"
|
||||||
|
echo "IA_ENSO_OLLAMA_BEARER_TOKEN was unset; generated token (store for Cursor API key):"
|
||||||
|
echo "$TOKEN"
|
||||||
|
echo "---"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$TOKEN" == *'"'* ]] || [[ "$TOKEN" == *'\'* ]]; then
|
||||||
|
echo "Token must not contain double quotes or backslashes." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ ! "$IA_ENSO_BACKEND_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
if [[ ! "$IA_ENSO_BACKEND_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
echo "IA_ENSO_BACKEND_IP must be an IPv4 address (got: ${IA_ENSO_BACKEND_IP})" >&2
|
echo "IA_ENSO_BACKEND_IP must be an IPv4 address (got: ${IA_ENSO_BACKEND_IP})" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# mode: full = websocket + bearer; bearer_only = bearer + map_hash (duplicate websocket elsewhere)
|
||||||
write_maps_file() {
|
write_maps_file() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
local with_websocket="$2"
|
local mode="$2"
|
||||||
if [[ "$with_websocket" == "1" ]]; then
|
{
|
||||||
cat <<'MAPEOF' >"$path"
|
cat <<'HASHOF'
|
||||||
|
map_hash_bucket_size 256;
|
||||||
|
HASHOF
|
||||||
|
if [[ "$mode" == "full" ]]; then
|
||||||
|
cat <<'MAPEOF'
|
||||||
map $http_upgrade $connection_upgrade {
|
map $http_upgrade $connection_upgrade {
|
||||||
default upgrade;
|
default upgrade;
|
||||||
'' close;
|
'' close;
|
||||||
}
|
}
|
||||||
MAPEOF
|
MAPEOF
|
||||||
else
|
fi
|
||||||
cat <<'STUB' >"$path"
|
cat <<MAPEOF
|
||||||
# ia-enso: $connection_upgrade is defined in another conf.d file; no duplicate map here.
|
map \$http_authorization \$ia_enso_ollama_authorized {
|
||||||
STUB
|
default 0;
|
||||||
fi
|
"Bearer ${TOKEN}" 1;
|
||||||
|
}
|
||||||
|
MAPEOF
|
||||||
|
} >"$path"
|
||||||
}
|
}
|
||||||
|
|
||||||
TMP_DIR="$(mktemp -d)"
|
TMP_DIR="$(mktemp -d)"
|
||||||
@ -66,8 +88,8 @@ cleanup() {
|
|||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
try_install() {
|
try_install() {
|
||||||
local with_ws="$1"
|
local mode="$1"
|
||||||
write_maps_file "${TMP_DIR}/ia-enso-http-maps.conf" "$with_ws"
|
write_maps_file "${TMP_DIR}/ia-enso-http-maps.conf" "$mode"
|
||||||
sed "s/__IA_ENSO_BACKEND_IP__/${IA_ENSO_BACKEND_IP}/g" "${SCRIPT_DIR}/sites/ia.enso.4nkweb.com.conf" >"${TMP_DIR}/ia.enso.4nkweb.com.conf"
|
sed "s/__IA_ENSO_BACKEND_IP__/${IA_ENSO_BACKEND_IP}/g" "${SCRIPT_DIR}/sites/ia.enso.4nkweb.com.conf" >"${TMP_DIR}/ia.enso.4nkweb.com.conf"
|
||||||
scp_copy "$IA_ENSO_SSH_KEY" "${TMP_DIR}/ia-enso-http-maps.conf" "$IA_ENSO_PROXY_USER" "$IA_ENSO_PROXY_HOST" "/tmp/ia-enso-http-maps.conf"
|
scp_copy "$IA_ENSO_SSH_KEY" "${TMP_DIR}/ia-enso-http-maps.conf" "$IA_ENSO_PROXY_USER" "$IA_ENSO_PROXY_HOST" "/tmp/ia-enso-http-maps.conf"
|
||||||
scp_copy "$IA_ENSO_SSH_KEY" "${TMP_DIR}/ia.enso.4nkweb.com.conf" "$IA_ENSO_PROXY_USER" "$IA_ENSO_PROXY_HOST" "/tmp/ia.enso.4nkweb.com.conf"
|
scp_copy "$IA_ENSO_SSH_KEY" "${TMP_DIR}/ia.enso.4nkweb.com.conf" "$IA_ENSO_PROXY_USER" "$IA_ENSO_PROXY_HOST" "/tmp/ia.enso.4nkweb.com.conf"
|
||||||
@ -91,16 +113,13 @@ REMOTE
|
|||||||
|
|
||||||
echo "Deploying ia.enso upstreams to ${IA_ENSO_BACKEND_IP} (Ollama :11434, AnythingLLM :3001)."
|
echo "Deploying ia.enso upstreams to ${IA_ENSO_BACKEND_IP} (Ollama :11434, AnythingLLM :3001)."
|
||||||
|
|
||||||
if ! try_install 1; then
|
if ! try_install full; then
|
||||||
echo "Retrying with stub maps file (websocket map likely already defined on proxy)..."
|
echo "Retrying with Bearer map only (websocket map likely already defined on proxy)..."
|
||||||
if ! try_install 0; then
|
if ! try_install bearer_only; then
|
||||||
echo "Deploy failed (SSH, sudo, nginx -t, or missing include /etc/nginx/conf.d/*.conf)." >&2
|
echo "Deploy failed (SSH, sudo, nginx -t, or missing include /etc/nginx/conf.d/*.conf)." >&2
|
||||||
echo "Re-run from a host with SSH access to the proxy (LAN direct: DEPLOY_SSH_PROXY_HOST=)." >&2
|
echo "Re-run from a host with SSH access (LAN: DEPLOY_SSH_PROXY_HOST=); set IA_ENSO_OLLAMA_BEARER_TOKEN to reuse secret." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Done. Public URLs (no nginx Bearer on /ollama/):"
|
echo "Done. Bearer required on /ollama/. Cursor base: https://ia.enso.4nkweb.com/ollama/v1 — API key = token above (if generated) or IA_ENSO_OLLAMA_BEARER_TOKEN."
|
||||||
echo " AnythingLLM: https://ia.enso.4nkweb.com/anythingllm/"
|
|
||||||
echo " Ollama native: https://ia.enso.4nkweb.com/ollama/api/tags"
|
|
||||||
echo " OpenAI-compat: https://ia.enso.4nkweb.com/ollama/v1"
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
# OPTIONAL: Bearer gate on /ollama/ (default repo site has no nginx auth on Ollama).
|
# Bearer gate for /ollama/ (matches default site: if ($ia_enso_ollama_authorized = 0) { return 401; }).
|
||||||
# Install inside `http { ... }` before server blocks that use $ia_enso_ollama_authorized, and add to
|
# Install inside `http { ... }` before server blocks that use $ia_enso_ollama_authorized:
|
||||||
# location /ollama/ { if ($ia_enso_ollama_authorized = 0) { return 401; } ... }
|
# include /etc/nginx/http-maps/ia-enso-ollama-bearer.map.conf;
|
||||||
#
|
#
|
||||||
# Copy without the .example suffix, set secret (ASCII, no double quotes in value).
|
# Copy without the .example suffix; set secret (ASCII, no double quotes in value).
|
||||||
|
# Cursor: OpenAI base .../ollama/v1 and API key = same secret (no "Bearer " in field).
|
||||||
|
|
||||||
map_hash_bucket_size 256;
|
map_hash_bucket_size 256;
|
||||||
|
|
||||||
|
|||||||
@ -4,12 +4,12 @@
|
|||||||
# AnythingLLM UI: https://ia.enso.4nkweb.com/anythingllm/
|
# AnythingLLM UI: https://ia.enso.4nkweb.com/anythingllm/
|
||||||
# Ollama OpenAI API: https://ia.enso.4nkweb.com/ollama/v1/ (e.g. .../v1/models, .../v1/chat/completions)
|
# Ollama OpenAI API: https://ia.enso.4nkweb.com/ollama/v1/ (e.g. .../v1/models, .../v1/chat/completions)
|
||||||
# Ollama native API: https://ia.enso.4nkweb.com/ollama/api/tags (and other /api/* paths)
|
# Ollama native API: https://ia.enso.4nkweb.com/ollama/api/tags (and other /api/* paths)
|
||||||
# /ollama/* has NO nginx Bearer gate (public inference if DNS is reachable); restrict at firewall or re-add map.
|
# /ollama/* requires Authorization: Bearer <secret> at nginx (map in conf.d); secret not forwarded to Ollama.
|
||||||
# Cursor base URL: https://ia.enso.4nkweb.com/ollama/v1
|
# Cursor base URL: https://ia.enso.4nkweb.com/ollama/v1 — API key field = same secret (no "Bearer " prefix).
|
||||||
#
|
#
|
||||||
# Prerequisites on the proxy host:
|
# Prerequisites on the proxy host:
|
||||||
# - TLS certificate for ia.enso.4nkweb.com (e.g. certbot).
|
# - TLS certificate for ia.enso.4nkweb.com (e.g. certbot).
|
||||||
# - Optional: include http-maps/websocket-connection.map.conf.example in http { } if not using deploy script maps file.
|
# - HTTP map $ia_enso_ollama_authorized (see deploy script / http-maps/ia-enso-ollama-bearer.map.conf.example).
|
||||||
#
|
#
|
||||||
# Upstream backend: replaced at deploy time (default 192.168.1.164). Manual install: replace __IA_ENSO_BACKEND_IP__.
|
# Upstream backend: replaced at deploy time (default 192.168.1.164). Manual install: replace __IA_ENSO_BACKEND_IP__.
|
||||||
|
|
||||||
@ -54,8 +54,12 @@ server {
|
|||||||
|
|
||||||
client_max_body_size 100M;
|
client_max_body_size 100M;
|
||||||
|
|
||||||
# Ollama: no nginx auth (Ollama itself does not enforce API keys by default).
|
# Ollama: nginx Bearer gate (map $ia_enso_ollama_authorized); Authorization cleared upstream.
|
||||||
location /ollama/ {
|
location /ollama/ {
|
||||||
|
if ($ia_enso_ollama_authorized = 0) {
|
||||||
|
return 401;
|
||||||
|
}
|
||||||
|
|
||||||
proxy_pass http://ia_enso_ollama/;
|
proxy_pass http://ia_enso_ollama/;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
@ -64,6 +68,8 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
|
|
||||||
|
proxy_set_header Authorization "";
|
||||||
|
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_read_timeout 3600s;
|
proxy_read_timeout 3600s;
|
||||||
proxy_send_timeout 3600s;
|
proxy_send_timeout 3600s;
|
||||||
|
|||||||
@ -4,46 +4,41 @@
|
|||||||
|
|
||||||
## Objective
|
## Objective
|
||||||
|
|
||||||
Expose Ollama and AnythingLLM on the public proxy hostname with HTTPS, path prefixes `/ollama` and `/anythingllm`. **Default:** no nginx Bearer on `/ollama/` (optional `map` in `http-maps/ia-enso-ollama-bearer.map.conf.example` to re-enable).
|
Expose Ollama and AnythingLLM on the public proxy hostname with HTTPS, path prefixes `/ollama` and `/anythingllm`, and **gate `/ollama/`** with a **Bearer token** at nginx (compatible with OpenAI clients that send `Authorization: Bearer <key>`). The secret is **not** forwarded to Ollama.
|
||||||
|
|
||||||
## Public URLs (HTTPS)
|
## Public URLs (HTTPS)
|
||||||
|
|
||||||
- AnythingLLM UI: `https://ia.enso.4nkweb.com/anythingllm/`
|
- AnythingLLM UI: `https://ia.enso.4nkweb.com/anythingllm/`
|
||||||
- Ollama native API (example): `https://ia.enso.4nkweb.com/ollama/api/tags`
|
- Ollama native API (example): `https://ia.enso.4nkweb.com/ollama/api/tags` — Bearer required at nginx
|
||||||
- OpenAI-compatible base (Cursor): `https://ia.enso.4nkweb.com/ollama/v1`
|
- OpenAI-compatible base (Cursor): `https://ia.enso.4nkweb.com/ollama/v1`
|
||||||
|
|
||||||
## Impacts
|
## Impacts
|
||||||
|
|
||||||
- **Proxy (nginx):** `server_name`, TLS, locations; `conf.d/ia-enso-http-maps.conf` holds WebSocket `map` when deployed by script (or stub if duplicate elsewhere).
|
- **Proxy (nginx):** `server_name`, TLS, locations; `conf.d/ia-enso-http-maps.conf` with `map_hash_bucket_size`, Bearer `map`, and WebSocket `map` (or Bearer-only if WebSocket map exists elsewhere).
|
||||||
- **Backend (192.168.1.164):** must accept connections from the proxy on `11434` and `3001`.
|
- **Backend (192.168.1.164):** must accept connections from the proxy on `11434` and `3001`.
|
||||||
- **Clients:** Cursor can use `https://ia.enso.4nkweb.com/ollama/v1` without a matching nginx secret if Bearer is disabled; hostname may avoid private-IP SSRF blocks when DNS resolves publicly.
|
- **Clients:** send `Authorization: Bearer <secret>` for `/ollama/*`; Cursor API key field = same secret as in the nginx `map`.
|
||||||
|
|
||||||
## Repository layout
|
## Repository layout
|
||||||
|
|
||||||
| Path | Purpose |
|
| Path | Purpose |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| `deploy/nginx/sites/ia.enso.4nkweb.com.conf` | `server` blocks ; upstreams use `__IA_ENSO_BACKEND_IP__` (default `192.168.1.164` substituted by `deploy-ia-enso-to-proxy.sh` or manual `sed`) |
|
| `deploy/nginx/sites/ia.enso.4nkweb.com.conf` | `server` blocks ; upstreams use `__IA_ENSO_BACKEND_IP__` |
|
||||||
| `deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example` | **Optional** Bearer `map` + `location /ollama/` `if` to re-enable auth |
|
| `deploy/nginx/http-maps/ia-enso-ollama-bearer.map.conf.example` | Bearer `map` reference for manual installs |
|
||||||
| `deploy/nginx/http-maps/websocket-connection.map.conf.example` | Example WebSocket `map` (manual install) |
|
| `deploy/nginx/http-maps/websocket-connection.map.conf.example` | WebSocket `map` reference |
|
||||||
| `deploy/nginx/deploy-ia-enso-to-proxy.sh` | SSH deploy: maps + site, `nginx -t`, reload; stub retry if websocket `map` already exists |
|
| `deploy/nginx/deploy-ia-enso-to-proxy.sh` | SSH deploy; retry Bearer-only if duplicate WebSocket `map` |
|
||||||
| `deploy/nginx/sites/ia.enso.4nkweb.com.http-only.conf` | Temporary HTTP-only vhost for first Let’s Encrypt `webroot` issuance when `live/ia.enso…` is missing |
|
| `deploy/nginx/sites/ia.enso.4nkweb.com.http-only.conf` | TLS bootstrap HTTP-only vhost |
|
||||||
| `deploy/nginx/README-ia-enso.md` | **Operator reference:** automated + manual steps, env vars, checks, troubleshooting, TLS bootstrap |
|
| `deploy/nginx/README-ia-enso.md` | Operator reference (includes note on Cursor `streamFromAgentBackend`) |
|
||||||
|
|
||||||
## Deployment modalities
|
## Deployment modalities
|
||||||
|
|
||||||
**Preferred:** run `./deploy/nginx/deploy-ia-enso-to-proxy.sh` from `smart_ide` on a host with SSH access (see `README-ia-enso.md`).
|
Run `./deploy/nginx/deploy-ia-enso-to-proxy.sh` with optional `IA_ENSO_OLLAMA_BEARER_TOKEN`. See `README-ia-enso.md`.
|
||||||
|
|
||||||
**Manual:** DNS → TLS (certbot) → WebSocket `map` if needed → install site → `nginx -t` → reload. Details: `deploy/nginx/README-ia-enso.md`.
|
|
||||||
|
|
||||||
Restrict backend ports on `192.168.1.164` to the proxy source where a host firewall is used.
|
|
||||||
|
|
||||||
## Analysis modalities
|
## Analysis modalities
|
||||||
|
|
||||||
- `curl` to `/ollama/v1/models` and `/ollama/api/tags` without `Authorization` (expect **200** when Bearer is off).
|
- `curl` to `/ollama/v1/models` with and without Bearer (200 / 401).
|
||||||
- Browser access to `/anythingllm/` and application login.
|
- Browser: `/anythingllm/`.
|
||||||
- Cursor connectivity; `ERROR_BAD_USER_API_KEY` may still be a Cursor client issue (see README forum link).
|
|
||||||
|
|
||||||
## Security notes
|
## Security notes
|
||||||
|
|
||||||
- **Default `/ollama/` is unauthenticated at nginx:** anyone who can reach the URL can call Ollama unless restricted by firewall or Ollama-level controls. Re-add Bearer using the example `map` if needed.
|
- Bearer secret is equivalent to an API key; rotate in `ia-enso-http-maps.conf` and client configs together.
|
||||||
- AnythingLLM remains protected by **its own** application authentication.
|
- AnythingLLM uses its own application login on `/anythingllm/`.
|
||||||
|
|||||||
@ -27,7 +27,7 @@ Internet access to backends uses **SSH ProxyJump** via `ncantu@4nk.myftp.biz` (s
|
|||||||
|
|
||||||
## Reverse proxy `ia.enso.4nkweb.com` (Ollama / AnythingLLM)
|
## Reverse proxy `ia.enso.4nkweb.com` (Ollama / AnythingLLM)
|
||||||
|
|
||||||
Hostname TLS sur le **proxy** `192.168.1.100` : préfixes `/ollama` et `/anythingllm` vers l’hôte LAN `192.168.1.164` (ports `11434` et `3001`, voir `deploy/nginx/sites/ia.enso.4nkweb.com.conf`). **`/ollama/`** sans garde Bearer nginx par défaut (option documentée dans `deploy/nginx/http-maps/`) ; AnythingLLM reste derrière son auth applicative.
|
Hostname TLS sur le **proxy** `192.168.1.100` : préfixes `/ollama` et `/anythingllm` vers l’hôte LAN `192.168.1.164` (ports `11434` et `3001`, voir `deploy/nginx/sites/ia.enso.4nkweb.com.conf`). **`/ollama/`** protégé par **Bearer** nginx (`map` dans `conf.d`) ; AnythingLLM reste derrière son auth applicative.
|
||||||
|
|
||||||
Documentation opérationnelle : [deploy/nginx/README-ia-enso.md](../deploy/nginx/README-ia-enso.md). Fiche évolution : [features/ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md).
|
Documentation opérationnelle : [deploy/nginx/README-ia-enso.md](../deploy/nginx/README-ia-enso.md). Fiche évolution : [features/ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md).
|
||||||
|
|
||||||
|
|||||||
@ -99,12 +99,13 @@ The last command must succeed after `OLLAMA_HOST=0.0.0.0:11434` and `host.docker
|
|||||||
|
|
||||||
## Public reverse proxy (ia.enso.4nkweb.com)
|
## Public reverse proxy (ia.enso.4nkweb.com)
|
||||||
|
|
||||||
When Ollama runs on a LAN host (e.g. `192.168.1.164` via `IA_ENSO_BACKEND_IP` / `deploy/nginx/sites/ia.enso.4nkweb.com.conf`) and must be reached via the **proxy** with HTTPS, use `deploy/nginx/` and **[deploy/nginx/README-ia-enso.md](../deploy/nginx/README-ia-enso.md)** (script `deploy-ia-enso-to-proxy.sh`, checks, troubleshooting). **Default:** no nginx Bearer on `/ollama/` (optional `http-maps/ia-enso-ollama-bearer.map.conf.example`).
|
When Ollama runs on a LAN host (e.g. `192.168.1.164` via `IA_ENSO_BACKEND_IP` / `deploy/nginx/sites/ia.enso.4nkweb.com.conf`) and must be reached via the **proxy** with HTTPS and a **Bearer** gate on `/ollama/`, use `deploy/nginx/` and **[deploy/nginx/README-ia-enso.md](../deploy/nginx/README-ia-enso.md)** (script `deploy-ia-enso-to-proxy.sh`, checks, troubleshooting).
|
||||||
|
|
||||||
**Full URLs**
|
**Full URLs**
|
||||||
|
|
||||||
- AnythingLLM UI: `https://ia.enso.4nkweb.com/anythingllm/`
|
- AnythingLLM UI: `https://ia.enso.4nkweb.com/anythingllm/`
|
||||||
- Ollama native API example: `https://ia.enso.4nkweb.com/ollama/api/tags`
|
- Ollama native API example: `https://ia.enso.4nkweb.com/ollama/api/tags` (header `Authorization: Bearer <secret>`)
|
||||||
- Cursor / OpenAI-compatible base URL: `https://ia.enso.4nkweb.com/ollama/v1`
|
- Cursor / OpenAI-compatible base URL: `https://ia.enso.4nkweb.com/ollama/v1`
|
||||||
|
- Cursor API key: same value as the Bearer secret in nginx `map`
|
||||||
|
|
||||||
Feature note: [ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md).
|
Feature note: [ia-enso-nginx-proxy-ollama-anythingllm.md](./features/ia-enso-nginx-proxy-ollama-anythingllm.md).
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user