#!/usr/bin/env bash # apply_chromium_extension_policy — Escribe de forma idempotente la política managed de Chromium # 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 keep_ids=() local -a block_ids=() # --- Parseo de argumentos --- while [[ $# -gt 0 ]]; do case "$1" in --keep) if [[ -z "${2:-}" ]]; then echo "apply_chromium_extension_policy: --keep requiere un ID de extensión" >&2 return 1 fi 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) if [[ -z "${2:-}" ]]; then echo "apply_chromium_extension_policy: --policy-path requiere un valor" >&2 return 1 fi policy_path="$2" shift 2 ;; --update-url) if [[ -z "${2:-}" ]]; then echo "apply_chromium_extension_policy: --update-url requiere un valor" >&2 return 1 fi update_url="$2" shift 2 ;; --dry-run) dry_run=1 shift ;; *) echo "apply_chromium_extension_policy: argumento desconocido: $1" >&2 echo "Uso: apply_chromium_extension_policy [--keep ]... [--block ]... [--policy-path ] [--update-url ] [--dry-run]" >&2 return 1 ;; esac done # --- 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 " >&2 return 1 fi # --- Construir el JSON --- # 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 kid in "${keep_ids[@]}"; do # Cada --keep puede ser "" (usa el update_url por defecto, Web Store) o # "=" para una extensión self-hosted (p.ej. file:// a un update.xml local). local id="${kid%%=*}" url="$update_url" [[ "$kid" == *=* ]] && url="${kid#*=}" [[ $first -eq 0 ]] && entries+=","$'\n' entries+=" \"${id};${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=$(cat </dev/null || true) if [[ "$current_content" == "$new_json" ]]; then echo "apply_chromium_extension_policy: política ya aplicada (sin cambios). Nada que hacer." return 0 fi 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 policy_dir backup_dir date_suffix=$(date +%Y%m%d) 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 sudo cp "$policy_path" "$backup_path" || { echo "apply_chromium_extension_policy: no se pudo crear el backup en ${backup_path}" >&2 return 1 } else cp "$policy_path" "$backup_path" || { echo "apply_chromium_extension_policy: no se pudo crear el backup en ${backup_path}" >&2 return 1 } fi else echo "apply_chromium_extension_policy: backup del día ya existe (${backup_path}), se omite." fi fi # --- Crear directorio padre si no existe --- local policy_dir policy_dir=$(dirname "$policy_path") if [[ ! -d "$policy_dir" ]]; then echo "apply_chromium_extension_policy: creando directorio ${policy_dir}" if [[ $EUID -ne 0 ]]; then sudo mkdir -p "$policy_dir" || { echo "apply_chromium_extension_policy: no se pudo crear el directorio ${policy_dir}" >&2 return 1 } else mkdir -p "$policy_dir" || { echo "apply_chromium_extension_policy: no se pudo crear el directorio ${policy_dir}" >&2 return 1 } fi fi # --- Escribir el JSON vía tmpfile + sudo cp --- local tmpfile tmpfile=$(mktemp /tmp/chromium_policy_XXXXXX.json) echo "$new_json" > "$tmpfile" if [[ $EUID -ne 0 ]]; then sudo cp "$tmpfile" "$policy_path" || { echo "apply_chromium_extension_policy: no se pudo escribir ${policy_path}" >&2 rm -f "$tmpfile" 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 fi return 1 } else cp "$tmpfile" "$policy_path" || { echo "apply_chromium_extension_policy: no se pudo escribir ${policy_path}" >&2 rm -f "$tmpfile" if [[ -n "$backup_path" && -f "$backup_path" ]]; then echo "apply_chromium_extension_policy: restaurando backup tras error..." cp "$backup_path" "$policy_path" 2>/dev/null || true fi return 1 } fi rm -f "$tmpfile" # --- Validar el JSON escrito --- local validation_ok=0 if command -v python3 &>/dev/null; then python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$policy_path" 2>/dev/null && validation_ok=1 elif command -v jq &>/dev/null; then jq . "$policy_path" &>/dev/null && validation_ok=1 else validation_ok=1 fi if [[ $validation_ok -eq 0 ]]; then echo "apply_chromium_extension_policy: el JSON escrito no es válido — restaurando backup" >&2 if [[ -n "$backup_path" && -f "$backup_path" ]]; then if [[ $EUID -ne 0 ]]; then sudo cp "$backup_path" "$policy_path" 2>/dev/null || true else cp "$backup_path" "$policy_path" 2>/dev/null || true fi fi return 1 fi # --- Resumen final --- echo "apply_chromium_extension_policy: política aplicada correctamente." echo " Ruta : ${policy_path}" 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 echo "" echo " AVISO: Chromium cachea la politica en memoria. Para que surta efecto:" echo " pkill -9 chromium (cierra TODOS los procesos)" echo " # Luego relanza Chromium. O desde un Chromium abierto:" echo " # chrome://policy → 'Reload policies'" } # Auto-ejecución al correr el archivo directo (bash file.sh / fn run). Si se hace `source`, # solo se define la función y no se ejecuta nada. if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then apply_chromium_extension_policy "$@" fi