htpasswd: reescritura en sitio cuando /etc/nginx no es escribible (alta de usuarios y cambio de contraseña)

This commit is contained in:
Leopoldo Rodríguez 2026-10-13 20:41:24 -06:00
parent 62a31c05d7
commit 84a171ca73

View File

@ -495,18 +495,33 @@ def _leer_htpasswd():
def _escribir_htpasswd(lineas):
"""Reescribe el archivo de forma atómica, conservando permisos y dueño."""
"""Reescribe el archivo de usuarios.
Si la carpeta es escribible (p. ej. en pruebas) se hace de forma atómica (temporal + rename)
conservando permisos y dueño. En producción el archivo vive en /etc/nginx, que es de root y
NO es escribible por el servicio aunque el archivo sí lo sea (chown leo:www-data + 640):
entonces se reescribe EN SITIO (r+ / truncate), que solo requiere permiso sobre el archivo.
Las rutas que llaman a esta función toman antes el candado HTPASSWD_LOCK."""
contenido = "\n".join(l for l in lineas if l is not None) + "\n"
carpeta = os.path.dirname(HTPASSWD) or "."
st = os.stat(HTPASSWD)
tmp = os.path.join(carpeta, ".htpasswd.tmp-" + uuid.uuid4().hex[:8])
with open(tmp, "w", encoding="utf-8") as f:
f.write("\n".join(lineas) + "\n")
os.chmod(tmp, st.st_mode & 0o7777)
try:
os.chown(tmp, st.st_uid, st.st_gid)
except PermissionError:
pass
os.replace(tmp, HTPASSWD)
if os.access(carpeta, os.W_OK):
tmp = os.path.join(carpeta, ".htpasswd.tmp-" + uuid.uuid4().hex[:8])
with open(tmp, "w", encoding="utf-8") as f:
f.write(contenido)
os.chmod(tmp, st.st_mode & 0o7777)
try:
os.chown(tmp, st.st_uid, st.st_gid)
except PermissionError:
pass
os.replace(tmp, HTPASSWD)
return
with open(HTPASSWD, "r+", encoding="utf-8") as f: # PermissionError aquí = archivo no escribible
f.seek(0)
f.write(contenido)
f.truncate()
f.flush()
os.fsync(f.fileno())
@app.get("/api/usuarios")