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:
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user