Files
dtic-DIIAA/adn/tools/cli/tailscale.rb
T

203 lines
7.6 KiB
Ruby

# frozen_string_literal: true
# adn/tools/cli/tailscale.rb — Subcomando para gestión de identidades Tailscale
# ==============================================================================
# Este subcomando permite alternar entre diferentes perfiles de Tailscale
# (ej. DASUTEN vs Personal) de forma segura utilizando candados.
#
# Usa `candados.rb sudo` como primitiva reutilizable para ejecutar
# comandos con privilegios (no implementa sudo propio).
#
# Perfiles:
# - dasuten: pcdasu0@frlr.utn.edu.ar (ecosistema DASUTEN)
# - personal: rmonla@gmail.com (otros ecosistemas/topologías)
require_relative '../core/colores'
module ADN
class SubcomandoTailscale
CANDADOS_PATH = File.expand_path('../seguridad/candados.rb', __dir__)
def initialize(args, logger)
@args = args
@logger = logger
end
def ejecutar
if @args.empty? || @args[0] == 'ayuda'
mostrar_ayuda
return
end
accion = @args.shift
case accion
when 'switch'
ejecutar_switch
when 'status', 'estado'
ejecutar_status
when 'list', 'listar'
ejecutar_list
when 'setup'
ejecutar_setup
else
puts "#{Color::RED}✗ Acción desconocida: '#{accion}'#{Color::RESET}"
mostrar_ayuda
exit 1
end
end
private
def mostrar_ayuda
puts <<~AYUDA
#{Color::CYAN}🌐 Gestión de Identidades Tailscale#{Color::RESET}
#{Color::YELLOW}Uso:#{Color::RESET} ./adn/tools/run tailscale <acción> [perfil]
#{Color::YELLOW}Acciones:#{Color::RESET}
switch <perfil> Alternar a un perfil (dasuten, personal)
status Ver estado actual de Tailscale y cuenta activa
list Listar perfiles disponibles en Tailscale
setup Configurar operador (una vez, evita pedir sudo)
#{Color::YELLOW}Perfiles:#{Color::RESET}
dasuten Ecosistema DASUTEN (pcdasu0@frlr.utn.edu.ar)
personal Otros ecosistemas (rmonla@gmail.com)
#{Color::YELLOW}Ejemplos:#{Color::RESET}
./adn/tools/run tailscale setup
./adn/tools/run tailscale switch dasuten
./adn/tools/run tailscale switch personal
./adn/tools/run tailscale status
AYUDA
end
# ─── Status ───────────────────────────────────────────────────────
def ejecutar_status
puts "#{Color::CYAN}🔍 Estado actual de Tailscale:#{Color::RESET}"
puts "#{Color::DIM}#{'─' * 60}#{Color::RESET}"
system('tailscale status')
puts ""
cuenta = `tailscale status --json 2>/dev/null | jq -r '.User[].LoginName' 2>/dev/null`.strip
if cuenta.empty? || cuenta == 'null'
puts "#{Color::YELLOW}⚠ No se pudo determinar la cuenta activa.#{Color::RESET}"
else
puts "#{Color::BOLD}Cuenta activa:#{Color::RESET} #{Color::GREEN}#{cuenta}#{Color::RESET}"
end
operador_ok = system('tailscale switch --list > /dev/null 2>&1')
if operador_ok
puts "#{Color::GREEN}✓ Operador configurado (switch sin sudo)#{Color::RESET}"
else
puts "#{Color::YELLOW}⚠ Operador NO configurado. Ejecutá: ./adn/tools/run tailscale setup#{Color::RESET}"
end
end
# ─── List ─────────────────────────────────────────────────────────
def ejecutar_list
puts "#{Color::CYAN}📋 Perfiles disponibles en Tailscale:#{Color::RESET}"
# Intentar sin sudo (operador configurado)
output = `tailscale switch --list 2>&1`
if $?.success?
puts output
else
# Fallback: usar candados sudo (reutilizable)
candados_sudo('tailscale', 'switch', '--list')
end
end
# ─── Setup ────────────────────────────────────────────────────────
def ejecutar_setup
@logger.info("Configurando operador Tailscale para #{ENV['USER']}...")
puts "#{Color::CYAN}⚙️ Configurando operador Tailscale...#{Color::RESET}"
puts "#{Color::DIM}Esto permite gestionar Tailscale sin sudo en el futuro.#{Color::RESET}"
success = candados_sudo('tailscale', 'set', "--operator=#{ENV['USER']}")
if success
puts "#{Color::GREEN}✅ Operador configurado exitosamente.#{Color::RESET}"
@logger.exito("Operador Tailscale configurado para #{ENV['USER']}")
else
puts "#{Color::RED}✗ No se pudo configurar el operador.#{Color::RESET}"
puts " Verificá la clave sudo en la bóveda: ruby candados.rb ayuda"
end
end
# ─── Switch ───────────────────────────────────────────────────────
def ejecutar_switch
perfil = @args.shift
if perfil.nil?
puts "#{Color::RED}✗ Falta nombre del perfil (dasuten|personal)#{Color::RESET}"
return
end
case perfil.downcase
when 'dasuten', 'd'
switch_to('dasuten')
when 'personal', 'p'
switch_to('personal')
else
puts "#{Color::YELLOW}⚠ Perfil no reconocido. Perfiles válidos: dasuten, personal#{Color::RESET}"
end
end
def switch_to(tipo)
@logger.info("Cambiando perfil Tailscale a: #{tipo}")
# 1. Obtener usuario de la bóveda
clave_user = tipo == 'dasuten' ? 'tailscale_dasuten_user' : 'tailscale_personal_user'
user = obtener_secreto(clave_user)
if user.nil? || user.empty?
@logger.error("No se encontró '#{clave_user}' en la bóveda.")
puts "#{Color::RED}✗ Credencial '#{clave_user}' no encontrada en candados.#{Color::RESET}"
return
end
puts "#{Color::CYAN}🔄 Cambiando a perfil: #{Color::BOLD}#{user}#{Color::RESET}..."
# 2. Intentar sin sudo (operador configurado)
if system("tailscale switch '#{user}' > /dev/null 2>&1")
exito_switch(tipo, user)
return
end
# 3. Fallback: usar candados sudo (primitiva reutilizable)
puts "#{Color::DIM}Usando candados sudo...#{Color::RESET}"
if candados_sudo('tailscale', 'switch', user)
exito_switch(tipo, user)
return
end
# 4. Perfil sin sesión → login manual
@logger.advertencia("El perfil #{tipo} no tiene sesión activa.")
puts "#{Color::YELLOW}⚠ El perfil '#{user}' no tiene sesión en este equipo.#{Color::RESET}"
puts " Necesitás iniciar sesión manualmente:"
puts " #{Color::BOLD}ruby candados.rb sudo tailscale login#{Color::RESET}"
end
def exito_switch(tipo, user)
puts "#{Color::GREEN}✅ Perfil Tailscale cambiado a '#{tipo}' (#{user}).#{Color::RESET}"
@logger.exito("Perfil Tailscale cambiado a #{tipo} (#{user})")
puts ""
ejecutar_status
end
# ─── Utilidades ───────────────────────────────────────────────────
# Obtiene un secreto de la bóveda (requiere sesión abierta)
def obtener_secreto(clave)
output = `ruby #{CANDADOS_PATH} get #{clave} 2>/dev/null`.strip
return nil if output.empty? || output.include?('Error') || output.include?('✗')
output
end
# Ejecuta un comando con sudo usando candados.rb sudo (primitiva reutilizable)
def candados_sudo(*cmd_parts)
system("ruby #{CANDADOS_PATH} sudo #{cmd_parts.join(' ')}")
end
end
end