squid mudado — y la jaula arrancaba DEGRADADA cuando la arranca un init

El proxy de salida sirve desde la caja: `squid 7.7` en 2.29.29.217:1137 con la config, el `passwd`
de los tres usuarios y las dos ACL de gioser tal cual, dentro de una instancia qorpa y supervisado
por arje. El de gioser sigue vivo e intacto (7.6). Es un servicio con gente encima: el access.log
del origen mostraba CONNECT a api.anthropic.com y a claude.ai en el minuto anterior a mudarlo.

Control en los dos sentidos: desde una IP no autorizada, 407 igual que el original; desde localhost,
que su propia config permite, TCP_TUNNEL/200 a claude.ai y api.anthropic.com.

EL MURO QUE VALE, y es un bug de qorpa: **el rango de subuid se buscaba por `$USER`**. Una shell
interactiva lo trae; un init NO. Bajo arje la tarjeta arranca con PATH y HOME, el nombre salia
vacio, no habia rango, y la jaula caia al userns de UN SOLO ID — con lo que squid moria en
`setgid(15): (22) Invalid argument` y el supervisor lo reintentaba 80 veces. El aviso de qorpa
estaba impreso y era correcto («no hay rango para "" en /etc/subuid»), pero lo que se ve en el bucle
es el error de squid: **la degradacion silenciosa la paga el de mas abajo**. Y el MISMO comando a
mano funcionaba, porque la shell si pone `$USER`: el sintoma dependia de quien lo arrancaba.

⇒ `nombre_de_usuario()` deriva el nombre del UID REAL leyendo /etc/passwd y el entorno queda como
ultimo recurso, con la mitad pura (`nombre_en_passwd`) probada, control negativo incluido: un uid
que no esta NO inventa un nombre — devolver algo ahi haria buscar un rango ajeno.

Los otros tres muros quedan documentados en §6.27: provisionar ANTES de montar la config (pacman
aborta si el paquete no puede escribir sus defaults), `/dev/shm` que bwrap crea 0755 y squid no
puede usar al bajar a `proxy`, y el pidfile que en una jaula con `--unshare-pid` SIEMPRE parece
fresco porque cada corrida tiene su propio PID 2.

Y la pregunta que corresponde —¿no va en una receta?— con su respuesta: si, y sigue pendiente. La
jaula fue la via urgente. La diferencia con php-fpm importa: PHP no queremos que entre al corpus,
squid es C y su sitio natural es una receta como la de gitea.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sergio
2026-09-15 18:59:13 +00:00
co-authored by Claude Opus 5
parent e7369efe82
commit a1ccce2b65
2 changed files with 98 additions and 3 deletions
+47 -3
View File
@@ -1335,6 +1335,40 @@ fn leer_subid(fichero: &str, usuario: &str) -> Option<Rango> {
})
}
/// La mitad PURA de `nombre_de_usuario`: el primer nombre de `/etc/passwd` con ese uid.
fn nombre_en_passwd(passwd: &str, uid: u32) -> Option<String> {
passwd.lines().find_map(|linea| {
let mut c = linea.split(':');
let (nombre, _, u) = (c.next()?, c.next()?, c.next()?);
(u.parse::<u32>().ok()? == uid).then(|| nombre.to_string())
})
}
/// El nombre del usuario que corre esto, para buscar su rango en `/etc/subuid`.
///
/// ⚠ MEDIDO el 2026-09-15: esto leía `$USER`/`$LOGNAME` y nada más. Una shell interactiva los trae;
/// **un init NO**. Al poner `squid` bajo arje-zero, la tarjeta arrancó con `PATH`, `HOME` y poco
/// más, el nombre salió vacío, no hubo rango, y la jaula cayó al **userns de un solo id** — con lo
/// que squid murió en `setgid(15): (22) Invalid argument`. El aviso estaba y era correcto («no hay
/// rango para "" en /etc/subuid»), pero el servicio se caía en bucle y el error que se veía era el
/// de squid, no el nuestro: **la degradación silenciosa la paga el de más abajo**.
///
/// Ahora el nombre se deriva del **uid real** leyendo `/etc/passwd`, y el entorno es sólo un atajo.
/// El uid es el hecho; `$USER` es una etiqueta que cualquiera puede no poner (ver la nota de
/// `la-etiqueta-no-es-el-hecho`).
fn nombre_de_usuario(uid: u32) -> String {
if let Ok(passwd) = std::fs::read_to_string("/etc/passwd") {
if let Some(n) = nombre_en_passwd(&passwd, uid) {
return n;
}
}
// Último recurso: el entorno. Sirve cuando `/etc/passwd` no describe a este uid (un rootfs
// pelado, un uid prestado), y no estorba cuando sí.
std::env::var("USER")
.or_else(|_| std::env::var("LOGNAME"))
.unwrap_or_default()
}
/// El namespace vivo, con su tenedor. Al soltarlo, se mata al tenedor y el namespace desaparece.
struct UsernsMapeado {
tenedor: std::process::Child,
@@ -1365,9 +1399,7 @@ fn userns_con_rango(uid_dentro: u32) -> Result<UsernsMapeado> {
let gid = std::fs::metadata("/proc/self")
.map(|m| std::os::unix::fs::MetadataExt::gid(&m))
.unwrap_or(1000);
let usuario = std::env::var("USER")
.or_else(|_| std::env::var("LOGNAME"))
.unwrap_or_default();
let usuario = nombre_de_usuario(uid);
let ru = leer_subid("/etc/subuid", &usuario)
.with_context(|| format!("no hay rango para {usuario:?} en /etc/subuid"))?;
let rg = leer_subid("/etc/subgid", &usuario)
@@ -2790,6 +2822,18 @@ mod tests {
assert!(a.iter().any(|x| x == "/etc/resolv.conf"));
}
#[test]
fn el_usuario_sale_del_uid_y_no_del_entorno() {
let passwd = "root:x:0:0::/root:/bin/sh\nhttp:x:33:33::/srv/http:/usr/bin/nologin\n";
assert_eq!(nombre_en_passwd(passwd, 0).as_deref(), Some("root"));
assert_eq!(nombre_en_passwd(passwd, 33).as_deref(), Some("http"));
// El control que importa: un uid que no está NO inventa un nombre. Devolver algo aquí
// haría buscar un rango de subuid ajeno, que es peor que no encontrar ninguno.
assert_eq!(nombre_en_passwd(passwd, 1000), None);
// Una línea rota no puede tumbar el arranque de una jaula.
assert_eq!(nombre_en_passwd("basura\n\n:::\n", 0), None);
}
#[test]
fn los_padres_que_faltan_se_crean_y_los_de_la_imagen_no_se_tocan() {
// El control que importa es el NEGATIVO: si esto devolviera los directorios que la imagen
+51
View File
@@ -1593,6 +1593,57 @@ todavía no está**: el camino se abrió a mano en el `upper` de la instancia (`
`projects` — y ahí vive la memoria, **indexada POR RUTA del repo**. Mudarlo es una decisión sobre
qué se lleva, no una copia.
### 6.27 🧦 `squid` mudado — y la jaula que arrancaba DEGRADADA cuando la arranca un init *(2026-09-15)*
**El proxy de salida sirve desde la caja**: `squid 7.7` en `2.29.29.217:1137`, con la config, el
`passwd` de los tres usuarios y las dos ACL de gioser tal cual, dentro de la instancia `squid` y
supervisado por arje. **El de gioser sigue vivo e intacto** (7.6), así que los clientes que apuntan
a la IP vieja no se enteraron.
Es un servicio con gente encima: el `access.log` del origen mostraba `CONNECT api.anthropic.com` y
`CONNECT claude.ai` **en el minuto anterior a mudarlo**, desde IPs de Venezuela y con tres usuarios
autenticados. Por eso se mudó entero y sin reescribir nada.
Control en los dos sentidos: desde una IP no autorizada, **407 Proxy Authentication Required**
—igual que el original—; desde `localhost`, que su propia config permite, **TCP_TUNNEL/200 a
claude.ai y a api.anthropic.com**.
#### Los cuatro muros, y el cuarto es el que vale
1. **`provision` con la config ya montada aborta**: pacman dice «squid.conf.default exists in
filesystem» porque el paquete quiere escribir sus defaults donde está la concesión. ⇒ provisionar
**primero**, montar **después**. Queda escrito en el manifiesto.
2. **`/dev/shm` lo crea bwrap 0755 root**, y squid —que baja a `proxy`— muere con
`FATAL: shm_open(...): (13) Permission denied`. Un `chmod 1777` en el arranque.
3. **El fichero de PID sobrevive entre corridas y SIEMPRE parece fresco**: cada `run` tiene su propio
namespace de PIDs, así que el `/run/squid.pid` de la corrida anterior dice `PID 2`… y en la nueva
hay un PID 2 vivo. Squid concluye «Squid is already running» y se niega. ⇒ `rm -f` en el arranque.
Es una trampa general de cualquier servicio con pidfile dentro de una jaula con `--unshare-pid`.
4. 🧨 **El rango de subuid se buscaba por `$USER` — y un init no lo pone.** Bajo arje la tarjeta
arranca con `PATH` y `HOME` y poco más; el nombre salía **vacío**, no había rango, y la jaula caía
al **userns de un solo id**. Squid moría en `setgid(15): (22) Invalid argument` y el supervisor lo
reintentaba 80 veces. El aviso de qorpa era correcto y estaba impreso —«no hay rango para `""` en
/etc/subuid»— pero **lo que se ve en el bucle es el error de squid, no el nuestro: la degradación
silenciosa la paga el de más abajo**. Y el mismo comando a mano, desde una shell con `$USER`,
funcionaba perfecto: *el síntoma dependía de quién lo arrancaba*.
`nombre_de_usuario()` deriva el nombre del **uid real** leyendo `/etc/passwd`, y el entorno pasa
a ser el último recurso. El uid es el hecho; `$USER` es una etiqueta ([[la-etiqueta-no-es-el-hecho]]).
Las tarjetas llevan además `USER`/`LOGNAME` explícitos, que era el arreglo del día.
**`arjectl start` sobre un Ente ya vivo lo DUPLICA** (se vio con `gioser-php`: dos entes, uno
peleando por el mismo `upper`). Y matar el proceso de adentro **no libera la instancia**: el
`takana qorpa run` de afuera sigue vivo y el siguiente `run` choca. Para relanzar, `stop` y después
`start`, mirando `status` en el medio.
#### Y la pregunta que corresponde: ¿no va en una receta?
**Sí, y sigue pendiente.** El propio §6.2 pone a `squid` en la lista de recetas que esta mudanza
tiene que producir. Lo de hoy fue la vía URGENTE —el servicio estaba en uso y la jaula lo levanta en
minutos, sin compilar nada—, no la respuesta final. La diferencia con `php-fpm` importa:
**PHP no queremos que entre al corpus** (es un intérprete ajeno al proyecto, y la jaula es su lugar
definitivo), mientras que **`squid` es C, se compila con el lab y su sitio natural es una receta**
como la de `gitea`. Cuando esté, la instancia se tira: el manifiesto es descartable a propósito.
## 7. Reusar los scripts que ya existen, y no escribir de nuevo
Pedido explícito del usuario. El inventario de lo que ya hace el trabajo: