mirror of
https://github.com/n0stal6ic/vault-tools.git
synced 2026-07-15 18:00:04 +02:00
Add files via upload
This commit is contained in:
+172
@@ -0,0 +1,172 @@
|
||||
import sqlite3
|
||||
import threading
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
TOKEN = "changeme" # API Token
|
||||
DB_PATH = Path("vault.db") # SQLite DB
|
||||
HOST = "0.0.0.0" # Address
|
||||
PORT = 0000 # Port
|
||||
|
||||
_local = threading.local()
|
||||
|
||||
def get_conn() -> sqlite3.Connection:
|
||||
if not hasattr(_local, "conn"):
|
||||
_local.conn = sqlite3.connect(DB_PATH)
|
||||
_local.conn.row_factory = sqlite3.Row
|
||||
return _local.conn
|
||||
|
||||
def has_table(service: str) -> bool:
|
||||
cur = get_conn().execute(
|
||||
"SELECT count(name) FROM sqlite_master WHERE type='table' AND name=?", (service,)
|
||||
)
|
||||
return cur.fetchone()[0] == 1
|
||||
|
||||
def create_table(service: str) -> None:
|
||||
if has_table(service):
|
||||
return
|
||||
conn = get_conn()
|
||||
conn.execute(f"""
|
||||
CREATE TABLE IF NOT EXISTS `{service}` (
|
||||
id INTEGER NOT NULL UNIQUE,
|
||||
kid TEXT NOT NULL COLLATE NOCASE,
|
||||
key_ TEXT NOT NULL COLLATE NOCASE,
|
||||
PRIMARY KEY(id AUTOINCREMENT),
|
||||
UNIQUE(kid, key_)
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
def db_get_key(kid: str, service: str) -> Optional[str]:
|
||||
for svc in {service, service.lower(), service.upper()}:
|
||||
if not has_table(svc):
|
||||
continue
|
||||
row = get_conn().execute(
|
||||
f"SELECT key_ FROM `{svc}` WHERE kid=? AND key_!=?", (kid, "0" * 32)
|
||||
).fetchone()
|
||||
if row:
|
||||
return row[0]
|
||||
return None
|
||||
|
||||
def db_get_keys(service: str) -> list[tuple[str, str]]:
|
||||
if not has_table(service):
|
||||
return []
|
||||
rows = get_conn().execute(
|
||||
f"SELECT kid, key_ FROM `{service}` WHERE key_!=?", ("0" * 32,)
|
||||
).fetchall()
|
||||
return [(r[0], r[1]) for r in rows]
|
||||
|
||||
def db_insert_key(service: str, kid: str, key: str) -> bool:
|
||||
create_table(service)
|
||||
conn = get_conn()
|
||||
existing = conn.execute(
|
||||
f"SELECT id FROM `{service}` WHERE kid=? AND key_=?", (kid, key)
|
||||
).fetchone()
|
||||
if existing:
|
||||
return False
|
||||
conn.execute(f"INSERT INTO `{service}` (kid, key_) VALUES (?, ?)", (kid, key))
|
||||
conn.commit()
|
||||
return True
|
||||
|
||||
def db_get_services() -> list[str]:
|
||||
rows = get_conn().execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name != 'sqlite_sequence'"
|
||||
).fetchall()
|
||||
return [r[0] for r in rows]
|
||||
|
||||
def db_key_count(service: str) -> int:
|
||||
if not has_table(service):
|
||||
return 0
|
||||
row = get_conn().execute(f"SELECT count(*) FROM `{service}`").fetchone()
|
||||
return row[0]
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
print(f" Vault DB : {DB_PATH.resolve()}")
|
||||
print(f" Listening: http://{HOST}:{PORT}/vault/")
|
||||
print(f" Token : {TOKEN}")
|
||||
yield
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
def ok(data: Any) -> JSONResponse:
|
||||
return JSONResponse({"status_code": 200, "message": data})
|
||||
|
||||
def err(msg: str, code: int = 400) -> JSONResponse:
|
||||
return JSONResponse({"status_code": code, "error": msg}, status_code=code)
|
||||
|
||||
|
||||
@app.post("/vault/")
|
||||
async def vault(request: Request) -> JSONResponse:
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception:
|
||||
return err("Invalid JSON body")
|
||||
|
||||
# auth
|
||||
if body.get("token") != TOKEN:
|
||||
return err("Unauthorized", 401)
|
||||
|
||||
method = body.get("method", "")
|
||||
params = body.get("params", {})
|
||||
|
||||
if method == "GetKey":
|
||||
kid = params.get("kid", "").strip().lower()
|
||||
service = params.get("service", "").strip()
|
||||
if not kid:
|
||||
return JSONResponse({"status": "error", "message": "KID is required"}, status_code=400)
|
||||
if not service:
|
||||
return JSONResponse({"status": "error", "message": "service is required"}, status_code=400)
|
||||
|
||||
key = db_get_key(kid, service)
|
||||
if key:
|
||||
return ok({"keys": [f"{kid}:{key}"], "session_id": None})
|
||||
return ok({"keys": [], "session_id": None})
|
||||
|
||||
elif method == "GetKeys":
|
||||
service = params.get("service", "").strip()
|
||||
if not service:
|
||||
return JSONResponse({"status": "error", "message": "service is required"}, status_code=400)
|
||||
|
||||
pairs = db_get_keys(service)
|
||||
keys = [f"{kid}:{key}" for kid, key in pairs]
|
||||
return ok({"keys": keys, "count": len(keys), "session_id": None})
|
||||
|
||||
elif method == "InsertKey":
|
||||
kid = params.get("kid", "").strip().lower()
|
||||
key = params.get("key", "").strip().lower()
|
||||
service = params.get("service", "").strip()
|
||||
|
||||
if not kid or not key or not service:
|
||||
return JSONResponse({"status": "error", "message": "kid:key and service are required"}, status_code=400)
|
||||
if len(kid) != 32 or len(key) != 32:
|
||||
return JSONResponse({"status": "error", "message": "kid:key must be 32 hex characters"}, status_code=400)
|
||||
if key == "0" * 32:
|
||||
return JSONResponse({"status": "error", "message": "NULL keys are not allowed"}, status_code=400)
|
||||
|
||||
inserted = db_insert_key(service, kid, key)
|
||||
return ok({"inserted": inserted, "status": "inserted" if inserted else "cached", "session_id": None})
|
||||
|
||||
elif method == "GetServices":
|
||||
services = db_get_services()
|
||||
return ok({"services": services, "session_id": None})
|
||||
|
||||
elif method == "Stats":
|
||||
services = db_get_services()
|
||||
stats = {svc: db_key_count(svc) for svc in services}
|
||||
total = sum(stats.values())
|
||||
return ok({"services": stats, "total_keys": total, "session_id": None})
|
||||
|
||||
else:
|
||||
return JSONResponse(
|
||||
{"status_code": 400, "error": f"Unknown method: {method}", "type": "invalid_method"},
|
||||
status_code=400,
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host=HOST, port=PORT)
|
||||
Reference in New Issue
Block a user