feat(browser): clean_chrome_profile_extensions + fix policy backup en managed/

Rediseño de apply_chromium_extension_policy y nueva función de purga in-place,
tras resolver por qué las extensiones bloqueadas reaparecían en Chromium 148.

- apply_chromium_extension_policy: añade --block (ExtensionInstallBlocklist).
  Reemplaza el modo ExtensionSettings "*": blocked (que rompía las extensiones
  unpacked vía --load-extension, p.ej. la de captura de web_proxy con el error
  'Loading of unpacked extensions is disabled by the administrator') por una
  blocklist específica. FIX RAÍZ: los backups se guardan fuera de policies/managed/
  (en policy-backups/), porque Chromium lee TODOS los archivos del directorio
  managed/ sin filtrar extensión de nombre — un extensions.json.bak ahí se mergea
  con la policy y reinyecta las extensiones del backup (location=7).
- clean_chrome_profile_extensions (nueva): purga in-place de un perfil existente
  (borra carpetas de Extensions/ + refs en Preferences/Secure Preferences) dejando
  solo la whitelist. Complementa la policy: la policy evita reinstalación, esta
  desinstala lo ya presente. Requiere chromium cerrado.

Ambas: dominio browser, grupo navegator, guard de auto-ejecución, dry-run.
This commit is contained in:
Egutierrez
2026-06-05 17:13:49 +02:00
parent 830f2d34de
commit e0fad0e82f
4 changed files with 450 additions and 50 deletions
@@ -1,12 +1,14 @@
#!/usr/bin/env bash
# apply_chromium_extension_policy — Escribe de forma idempotente la política managed de Chromium
# que fuerza la instalación de un conjunto de extensiones en CUALQUIER perfil del navegador.
# que fuerza la instalación de una whitelist de extensiones y bloquea (desinstala) una blocklist
# concreta, sin tocar el resto. Usa ExtensionInstallForcelist + ExtensionInstallBlocklist.
apply_chromium_extension_policy() {
local policy_path="/etc/chromium/policies/managed/extensions.json"
local update_url="https://clients2.google.com/service/update2/crx"
local dry_run=0
local -a ext_ids=()
local -a keep_ids=()
local -a block_ids=()
# --- Parseo de argumentos ---
while [[ $# -gt 0 ]]; do
@@ -16,7 +18,15 @@ apply_chromium_extension_policy() {
echo "apply_chromium_extension_policy: --keep requiere un ID de extensión" >&2
return 1
fi
ext_ids+=("$2")
keep_ids+=("$2")
shift 2
;;
--block)
if [[ -z "${2:-}" ]]; then
echo "apply_chromium_extension_policy: --block requiere un ID de extensión" >&2
return 1
fi
block_ids+=("$2")
shift 2
;;
--policy-path)
@@ -41,31 +51,53 @@ apply_chromium_extension_policy() {
;;
*)
echo "apply_chromium_extension_policy: argumento desconocido: $1" >&2
echo "Uso: apply_chromium_extension_policy --keep <ext_id> [--keep <ext_id>]... [--policy-path <path>] [--update-url <url>] [--dry-run]" >&2
echo "Uso: apply_chromium_extension_policy [--keep <ext_id>]... [--block <ext_id>]... [--policy-path <path>] [--update-url <url>] [--dry-run]" >&2
return 1
;;
esac
done
# --- Validar que hay al menos una extensión ---
if [[ ${#ext_ids[@]} -eq 0 ]]; then
echo "apply_chromium_extension_policy: se requiere al menos un --keep <ext_id>" >&2
# --- Validar que hay al menos una extensión a forzar o bloquear ---
if [[ ${#keep_ids[@]} -eq 0 && ${#block_ids[@]} -eq 0 ]]; then
echo "apply_chromium_extension_policy: se requiere al menos un --keep o un --block <ext_id>" >&2
return 1
fi
# --- Construir el JSON ---
local forcelist_entries=""
local first=1
for id in "${ext_ids[@]}"; do
if [[ $first -eq 0 ]]; then
forcelist_entries+=","$'\n'
fi
forcelist_entries+=" \"${id};${update_url}\""
first=0
done
# Dos claves complementarias, ninguna bloquea las extensiones unpacked (--load-extension),
# de modo que extensiones locales como la de captura de web_proxy siguen cargando:
# 1. ExtensionInstallForcelist: fuerza la instalación de la whitelist (--keep), que además
# no se puede desinstalar desde la UI.
# 2. ExtensionInstallBlocklist: bloquea Y desinstala las extensiones de la blocklist
# (--block) en cualquier perfil. Solo afecta a los IDs listados; el resto no se toca.
local forcelist_json="[]" blocklist_json="[]"
if [[ ${#keep_ids[@]} -gt 0 ]]; then
local entries="" first=1
for id in "${keep_ids[@]}"; do
[[ $first -eq 0 ]] && entries+=","$'\n'
entries+=" \"${id};${update_url}\""
first=0
done
forcelist_json=$(printf '[\n%s\n ]' "$entries")
fi
if [[ ${#block_ids[@]} -gt 0 ]]; then
local entries="" first=1
for id in "${block_ids[@]}"; do
[[ $first -eq 0 ]] && entries+=","$'\n'
entries+=" \"${id}\""
first=0
done
blocklist_json=$(printf '[\n%s\n ]' "$entries")
fi
local new_json
new_json=$(printf '{\n "ExtensionInstallForcelist": [\n%s\n ]\n}\n' "$forcelist_entries")
new_json=$(cat <<JSONEOF
{
"ExtensionInstallForcelist": ${forcelist_json},
"ExtensionInstallBlocklist": ${blocklist_json}
}
JSONEOF
)
# --- Modo dry-run ---
if [[ $dry_run -eq 1 ]]; then
@@ -88,11 +120,24 @@ apply_chromium_extension_policy() {
fi
# --- Backup del archivo existente ---
# CRÍTICO: el backup NUNCA puede vivir dentro del directorio de la policy. Chromium lee TODOS
# los archivos del directorio managed/ (sin filtrar por extensión de nombre), así que un
# "extensions.json.bak.YYYYMMDD" dentro de managed/ se mergea con la policy efectiva y reinyecta
# las extensiones del backup. Por eso el backup se guarda en un directorio hermano (policy-backups)
# que chromium no lee.
local backup_path=""
if [[ -f "$policy_path" ]]; then
local date_suffix
local date_suffix policy_dir backup_dir
date_suffix=$(date +%Y%m%d)
backup_path="${policy_path}.bak.${date_suffix}"
policy_dir="$(dirname "$policy_path")"
case "$(basename "$policy_dir")" in
managed|recommended) backup_dir="$(dirname "$policy_dir")/policy-backups" ;;
*) backup_dir="$policy_dir" ;;
esac
backup_path="${backup_dir}/$(basename "$policy_path").bak.${date_suffix}"
if [[ ! -d "$backup_dir" ]]; then
if [[ $EUID -ne 0 ]]; then sudo mkdir -p "$backup_dir" 2>/dev/null; else mkdir -p "$backup_dir"; fi
fi
if [[ ! -f "$backup_path" ]]; then
echo "apply_chromium_extension_policy: creando backup → ${backup_path}"
if [[ $EUID -ne 0 ]]; then
@@ -129,7 +174,7 @@ apply_chromium_extension_policy() {
fi
fi
# --- Escribir el JSON vía tmpfile + sudo cp / tee ---
# --- Escribir el JSON vía tmpfile + sudo cp ---
local tmpfile
tmpfile=$(mktemp /tmp/chromium_policy_XXXXXX.json)
echo "$new_json" > "$tmpfile"
@@ -138,7 +183,6 @@ apply_chromium_extension_policy() {
sudo cp "$tmpfile" "$policy_path" || {
echo "apply_chromium_extension_policy: no se pudo escribir ${policy_path}" >&2
rm -f "$tmpfile"
# Restaurar backup si hubo
if [[ -n "$backup_path" && -f "$backup_path" ]]; then
echo "apply_chromium_extension_policy: restaurando backup tras error..."
sudo cp "$backup_path" "$policy_path" 2>/dev/null || true
@@ -165,7 +209,6 @@ apply_chromium_extension_policy() {
elif command -v jq &>/dev/null; then
jq . "$policy_path" &>/dev/null && validation_ok=1
else
# Sin parser disponible, asumir OK (el JSON lo generamos nosotros)
validation_ok=1
fi
@@ -184,10 +227,15 @@ apply_chromium_extension_policy() {
# --- Resumen final ---
echo "apply_chromium_extension_policy: política aplicada correctamente."
echo " Ruta : ${policy_path}"
echo " Extensiones forzadas (${#ext_ids[@]}):"
for id in "${ext_ids[@]}"; do
echo " - ${id}"
done
if [[ ${#keep_ids[@]} -gt 0 ]]; then
echo " Forzadas (${#keep_ids[@]}):"
for id in "${keep_ids[@]}"; do echo " - ${id}"; done
fi
if [[ ${#block_ids[@]} -gt 0 ]]; then
echo " Bloqueadas/desinstaladas (${#block_ids[@]}):"
for id in "${block_ids[@]}"; do echo " - ${id}"; done
fi
echo " Extensiones unpacked (--load-extension, p.ej. web_proxy): NO afectadas."
if [[ -n "$backup_path" && -f "$backup_path" ]]; then
echo " Backup : ${backup_path}"
fi