obfuscation layer: shim v2 on :28443 (token-gated, de-Ollama'd paths, nginx fingerprint), tailnet-only dashboard :47077 (token gate), secrets staged
This commit is contained in:
26
app.py
26
app.py
@@ -357,5 +357,31 @@ def api_stop():
|
||||
cfg = load_config(); cfg["stopped"] = True; save_config(cfg)
|
||||
return jsonify(ok=True)
|
||||
|
||||
# --- obfuscated remote access: token-gated listener on the tailnet IP only ---
|
||||
TAILNET_IP = "100.120.108.13"
|
||||
REMOTE_PORT = 47077
|
||||
try:
|
||||
OPERATOR_TOKEN = open(os.path.join(APP_DIR, "operator_token.txt")).read().strip()
|
||||
except Exception:
|
||||
OPERATOR_TOKEN = ""
|
||||
|
||||
@app.before_request
|
||||
def _gate_remote():
|
||||
"""Requests arriving on the tailnet listener must carry the token; localhost is free."""
|
||||
if request.host.startswith(f"{TAILNET_IP}:{REMOTE_PORT}") and OPERATOR_TOKEN:
|
||||
auth = request.headers.get("X-Auth-Token", "")
|
||||
# also accept ?t= for browser links
|
||||
if not auth:
|
||||
auth = request.args.get("t", "")
|
||||
from hmac import compare_digest
|
||||
if not compare_digest(auth, OPERATOR_TOKEN):
|
||||
return ("not found", 404)
|
||||
return None
|
||||
|
||||
def _run_remote():
|
||||
app.run(host=TAILNET_IP, port=REMOTE_PORT, threaded=True, use_reloader=False)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import threading
|
||||
threading.Thread(target=_run_remote, daemon=True).start()
|
||||
app.run(host="127.0.0.1", port=8899, threaded=True)
|
||||
|
||||
19
engine.py
19
engine.py
@@ -18,7 +18,8 @@ log = logging.getLogger("mailer")
|
||||
# ------------------------------------------------------------------ config
|
||||
def load_config():
|
||||
defaults = {
|
||||
"ollama_url": "http://100.103.34.0:11440", # nightmare tailnet via non-chunked shim
|
||||
"ollama_url": "http://100.103.34.0:28443", # obfuscated shim (token-gated, nonstandard port)
|
||||
"ollama_token": "", # X-Auth-Token for the shim
|
||||
"ollama_model": "mgraffam/gemma4-heretic:12b",
|
||||
"ses_region": "us-east-1",
|
||||
"send_mode": "smtp", # smtp (works now) | ses (needs AWS keys)
|
||||
@@ -119,15 +120,22 @@ def init_db():
|
||||
|
||||
# ------------------------------------------------------------------ ollama
|
||||
class OllamaClient:
|
||||
def __init__(self, url, model, timeout=300):
|
||||
def __init__(self, url, model, timeout=300, token=""):
|
||||
self.url = url.rstrip("/")
|
||||
self.model = model
|
||||
self.timeout = timeout
|
||||
self.token = token
|
||||
self._fail_until = 0
|
||||
|
||||
def _headers(self):
|
||||
h = {}
|
||||
if self.token:
|
||||
h["X-Auth-Token"] = self.token
|
||||
return h
|
||||
|
||||
def healthy(self):
|
||||
try:
|
||||
r = requests.get(f"{self.url}/api/tags", timeout=5)
|
||||
r = requests.get(f"{self.url}/tags", headers=self._headers(), timeout=8)
|
||||
return r.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
@@ -136,7 +144,8 @@ class OllamaClient:
|
||||
if time.time() < self._fail_until:
|
||||
return None
|
||||
try:
|
||||
r = requests.post(f"{self.url}/api/generate",
|
||||
r = requests.post(f"{self.url}/gen",
|
||||
headers=self._headers(),
|
||||
json={"model": self.model, "prompt": prompt, "stream": False,
|
||||
"options": {"num_predict": 350, "temperature": 0.8}, "keep_alive": "15m"},
|
||||
timeout=self.timeout)
|
||||
@@ -326,7 +335,7 @@ class Engine:
|
||||
"total": len(rows), "started_at": datetime.now().isoformat(timespec='seconds')})
|
||||
log.info(f"campaign {campaign_id} start: {len(rows)} recipients")
|
||||
|
||||
ollama = OllamaClient(cfg["ollama_url"], cfg["ollama_model"]) if cfg.get("personalize") else None
|
||||
ollama = OllamaClient(cfg["ollama_url"], cfg["ollama_model"], token=cfg.get("ollama_token", "")) if cfg.get("personalize") else None
|
||||
if ollama:
|
||||
self.state["current"] = f"checking Ollama at {cfg['ollama_url']}..."
|
||||
if ollama.healthy():
|
||||
|
||||
73
ollama_shim_v2.py
Normal file
73
ollama_shim_v2.py
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Ollama shim v2 — obfuscated relay on nightmare (replaces v1, same systemd unit).
|
||||
- Binds tailnet-only 100.103.34.0:28443 (nonstandard port, nothing fingerprints as Ollama)
|
||||
- Bearer token required (X-Auth-Token); constant-time compare
|
||||
- Generic 404 + fake nginx server header on anything else (no Ollama fingerprint)
|
||||
- Proxies /gen -> localhost:11434/api/generate, returns ONE non-chunked body
|
||||
"""
|
||||
import json
|
||||
import hmac
|
||||
import http.server, socketserver, urllib.request
|
||||
|
||||
LOCAL = "http://127.0.0.1:11434"
|
||||
BIND = ("100.103.34.0", 28443)
|
||||
TOKEN = open("/opt/ollama-shim/shared_secret.txt").read().strip()
|
||||
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
protocol_version = "HTTP/1.0"
|
||||
server_version = "nginx"
|
||||
sys_version = ""
|
||||
|
||||
def _authed(self):
|
||||
got = self.headers.get("X-Auth-Token", "")
|
||||
return hmac.compare_digest(got, TOKEN)
|
||||
|
||||
def _deny(self):
|
||||
body = b'{"error":"not found"}'
|
||||
self.send_response(404)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def do_GET(self):
|
||||
if not self._authed():
|
||||
return self._deny()
|
||||
if self.path == "/tags":
|
||||
body = urllib.request.urlopen(LOCAL + "/api/tags", timeout=10).read()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
else:
|
||||
self._deny()
|
||||
|
||||
def do_POST(self):
|
||||
if not self._authed():
|
||||
return self._deny()
|
||||
if self.path != "/gen":
|
||||
return self._deny()
|
||||
n = int(self.headers.get("Content-Length", 0))
|
||||
payload = json.loads(self.rfile.read(n) or b"{}")
|
||||
payload["stream"] = False
|
||||
req = urllib.request.Request(LOCAL + "/api/generate",
|
||||
data=json.dumps(payload).encode(),
|
||||
headers={"Content-Type": "application/json"})
|
||||
with urllib.request.urlopen(req, timeout=600) as r:
|
||||
body = r.read()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
|
||||
def log_message(self, *a):
|
||||
pass
|
||||
|
||||
class TS(socketserver.ThreadingTCPServer):
|
||||
allow_reuse_address = True
|
||||
daemon_threads = True
|
||||
|
||||
if __name__ == "__main__":
|
||||
TS(BIND, H).serve_forever()
|
||||
1
operator_token.txt
Normal file
1
operator_token.txt
Normal file
@@ -0,0 +1 @@
|
||||
postal_3bf0d0793c45aeed3d2a46bc
|
||||
1
shared_secret.txt
Normal file
1
shared_secret.txt
Normal file
@@ -0,0 +1 @@
|
||||
nx_6eff2d6e040c4db43464d8979e320e45
|
||||
Reference in New Issue
Block a user