Seguridad y accesos

Esta guia describe el bloque vigente de v3.0 para autenticacion, autorizacion y compuertas de acceso del sistema: login administrativo, roles, acceso por categoria o por lista blanca de correos, CAPTCHA, rate limits publicos, tokens y autenticacion de servicios REST. El contenido fue contrastado contra las pantallas Wicket, los EJB de login y validacion, el scheduler que limpia caches, y la capa JAX-RS que protege los WS.

Superficies cubiertas

  • Admin web: /login y todo /admin/*.
  • Publico/tokenizado: /token/*, formularios publicos y pantallas de acceso denegado.
  • REST: /elections-ws/*, incluyendo endpoints legacy, tablas y snapshots v2.
  • Monitoreo de IPs: /admin/ipaccess y tablas WS asociadas.

Autenticacion administrativa

  • La pantalla administrativa entra por /login y autentica segun WS_AUTH_METHOD de elections.properties, generado por Docker desde el .env.
  • Si WS_AUTH_METHOD=APP, la sesion usa autenticacion local contra UserAdmin. La UI calcula SHA-256 sobre la contrasena, lo representa en hexadecimal mayuscula y envia ese valor a userAdminLogin(). El metodo historico que hace el calculo aun se llama wantHashMd5(), pero su implementacion vigente ya no usa MD5.
  • Si WS_AUTH_METHOD toma otro valor, la sesion usa login() contra el framework externo centralizado mediante UtilsLogin.login().
  • PAI es el servicio historico de autenticacion centralizada del portal de LACNIC. En modo externo, el login no acepta cualquier rol retornado por PAI: primero filtra a los roles soportados por Elecciones y solo deja entrar si existe al menos uno entre elections-manager, elections-statutary-only o elections-non-statutary-only.
  • El rol elections-deleter por si solo no habilita login externo.
  • En modo local, si el login es exitoso la sesion agrega automaticamente elections-manager y elections-deleter; en la practica, un admin local autenticado entra con perfil administrativo pleno.
  • La UI web toma la IP cliente desde el primer valor de X-Forwarded-For, luego X-Real-IP y, si no hay proxy headers, remoteAddr.
  • La locale de sesion cae a espanol si no habia una locale valida previa.
  • Todo login exitoso o fallido deja actividad persistida con IP y usuario.

Inicio de una organizacion sin autenticacion PAI

  • Use WS_AUTH_METHOD=APP en el .env. Docker lo copia a elections.properties; no hace falta ni existe una variable FRESH_AUTH_METHOD.
  • docker-fresh.sh crea el primer administrador desde FRESH_ADMIN_USER, FRESH_ADMIN_PASSWORD y FRESH_ADMIN_EMAIL.
  • En una base PostgreSQL externa, los scripts de referencia crean esquema, parametros y templates, pero no crean administradores. Debe aprovisionarse la primera fila de useradmin siguiendo el procedimiento parametrizado de la guia de instanciacion.
  • El acceso local habitual es http://localhost:8098/elections/login; detras de un proxy es https://<servidor>/elections/login.
  • El campo TOTP se deja vacio con APP. Actualmente TOTP solo se envia al mecanismo externo.
  • Despues del primer ingreso, el menu Administradores permite crear cuentas, actualizar correos, cambiar contrasenas y eliminar cuentas.
  • Conserve al menos dos cuentas administrativas verificadas. No existe recuperacion autoservicio: si se pierde la unica cuenta, un operador con acceso a PostgreSQL debe restablecerla mediante el procedimiento de aprovisionamiento.

Autenticacion externa

  • El modo externo actual usa el cliente y contrato historico de PAI, la URL WS_LACNIC_AUTH_URL y los roles esperados por Elecciones.
  • No existe una integracion generica lista para configurar con OIDC, OAuth2, SAML o LDAP. Una organizacion que quiera usar Keycloak, Microsoft Entra ID, Okta u otro proveedor debe implementar un adaptador o exponer un servicio compatible con el contrato existente.
  • En modo externo, las cuentas y roles se administran en el proveedor; por eso el menu local Administradores no se muestra.

Autorizacion por roles y por eleccion

  • Las pantallas globales de administracion derivan de DashboardManagerBasePage y exigen elections-manager.
  • Las pantallas por eleccion derivan de DashboardElectionBasePage y admiten elections-manager, elections-statutary-only o elections-non-statutary-only.
  • Despues de la anotacion Wicket, el sistema vuelve a validar acceso con SecurityUtils.canAccessElection().
  • Un elections-manager entra a cualquier eleccion. Un usuario que acumule roles estatutario y no estatutario tambien queda habilitado para todas las categorias.
  • Un usuario statutary-only solo accede a elecciones STATUTORY; un usuario non-statutary-only solo accede a categorias no estatutarias.
  • Si el email del usuario autenticado aparece en Election.authorizedUserEmails, ese usuario puede acceder a esa eleccion aunque no coincida con la categoria habitual.
  • Los listados administrativos de elecciones aplican ese filtro: no muestran elecciones a las que la sesion no puede entrar.
  • Los accesos denegados por autorizacion Wicket derivan a /error401.
  • La gestion de administradores queda visible en la barra privada solo cuando la sesion esta en autenticacion local.

CAPTCHA de login

  • El CAPTCHA administrativo solo aparece si LOGIN_CAPTCHA_ENABLED vale true y existen las dos claves de Google: DataSiteKeyReCaptcha y SkGoogleApiReCaptcha.
  • Si LOGIN_CAPTCHA_ENABLED falta, viene vacio o contiene un valor invalido, el sistema usa true como valor seguro y compatible con instalaciones existentes.
  • La decision de mostrarlo en login es por IP, no por usuario: shouldShowLoginCaptcha(userName, ip) consulta la cache de intentos fallidos de esa IP.
  • El umbral sale de LOGIN_CAPTCHA_MAX_ATTEMPTS y por defecto es 5.
  • La UI muestra el CAPTCHA cuando la cantidad fallida ya es mayor o igual al umbral.
  • Los intentos fallidos incrementan la cache tanto en login local como en login externo.
  • Un login exitoso no limpia esa cache en el acto.
  • La limpieza la hace el scheduler horario usando LOGIN_CAPTCHA_RATE_LIMIT_CACHE_RESET_HOURS; si el parametro falta o es invalido, cae a 6 horas.

Rate limit de accesos publicos invalidos

  • Las paginas publicas y tokenizadas comparten un rate limit de accesos invalidos por IP.
  • El control vive en DashboardPublicBasePage y PublicTokenBasePage: si la IP ya quedo rate-limited, la respuesta corta en /error429.
  • Si el token o identificador no existe y la pagina devuelve Error404, el sistema registra el intento fallido en dos lugares: la tabla IpAccess y una cache en memoria.
  • El umbral sale de PUBLIC_FAILED_ACCESS_MAX_ATTEMPTS y por defecto es 50.
  • El bloqueo efectivo recien ocurre cuando el contador queda estrictamente mayor al umbral. Con el default, el 429 aparece desde el intento invalido numero 51.
  • La limpieza del bloqueo en memoria la hace el scheduler horario usando PUBLIC_FAILED_ACCESS_RATE_LIMIT_CACHE_RESET_HOURS; si falta o es invalido, cae a 1 hora.
  • La tabla IpAccess no se limpia con ese scheduler: conserva historial de primer intento, ultimo intento y acumulado.
  • Por eso /admin/ipaccess no es una blacklist activa ni una pantalla de desbloqueo: es un visor historico de intentos fallidos registrados.
  • La pagina /public-elections desactiva explicitamente este rate limit generico.

Tokens y compuertas publicas

  • Las paginas tokenizadas trabajan en dos capas.
  • Capa 1, identidad del token: cada pagina valida que el token exista y resuelva una entidad concreta. Ejemplos: UserVoter.voteToken, Election.resultToken, Auditor.resultToken, token de nominacion o token de apoyo.
  • Si esa validacion falla, la respuesta cae en Error404 y, salvo excepciones puntuales, cuenta para el rate limit publico.
  • Capa 2, ventana de acceso: una vez resuelto el token, la pagina aplica TokenAccessGate con switches manuales de la eleccion y ventanas de calendario publico.
  • Si el token existe pero la ventana aun no abrio, ya cerro o el switch manual esta apagado, no responde 404: deriva a PublicAccessDeniedPage con codigo funcional y countdown hacia la fecha relevante.
  • Esa segunda capa no se registra como acceso invalido: el contador de rate limit se incrementa por token/identificador inexistente, no por un acceso valido pero todavia cerrado.
  • Las compuertas publicas combinan flags como votingLinkAvailable, resultLinkAvailable, auditorLinkAvailable, doNominationLinkAvailable, nominationTasksLinkAvailable, nominationSupportLinkAvailable y publicElectionLinkAvailable con hitos N_1, N_2, N_12, N_16, N_17 y N_18 segun la ruta.
  • El resultado es deliberado: token invalido y acceso no habilitado son casos distintos en la implementacion y tambien en la experiencia de usuario.

CAPTCHA en formularios publicos

  • Cuando isShowCaptcha() devuelve verdadero y existe DataSiteKeyReCaptcha, el sistema activa CAPTCHA tambien en varios formularios publicos.
  • Quedo verificado en al menos estos flujos: recuperacion publica de links, preguntas de comunidad desde pagina publica, formulario publico moderno de preguntas y la herramienta publica de resize de foto.
  • La validacion usa el mismo backend isValidCaptchaResponse() que el login administrativo.

Autenticacion de servicios REST

  • La aplicacion JAX-RS registra dos recursos principales: ElectionsService y ElectionsTablesServices.
  • No hay endpoints REST anonimos en este bloque: el health check /hc, los endpoints legacy, los de tablas y tambien los snapshots /v2 pasan por autenticacion.
  • En modo APP, la regla es doble: header Authorization igual a WS_AUTH_TOKEN y IP cliente incluida en WS_AUTHORIZED_IPS; los tres valores se leen desde elections.properties.
  • En modo centralizado, el sistema reenvia el header Authorization al servicio remoto configurado en WS_LACNIC_AUTH_URL.
  • Para los WS internos generales exige rol remoto api-Elections.
  • Para los snapshots v2 exige rol remoto api-ElectionsPublicInformation.
  • En modo APP, la IP se valida contra WS_AUTHORIZED_IPS. En modo centralizado, se valida contra el set ipAllowed devuelto por el servicio remoto.
  • Si falla el token o la IP, responde 401. Si el metodo de autenticacion esta mal configurado o ocurre una excepcion, responde 500.
  • La capa REST toma la IP desde X-FORWARDED-FOR si existe; en caso contrario usa request.getRemoteAddr(). A diferencia de la UI web, no separa el primer hop ni consulta X-Real-IP.
  • La paginacion de tablas se recorta adicionalmente con WS_MAX_PAGE_SIZE.

Exposicion de parametros sensibles

  • La pantalla administrativa de parametros muestra valores en texto plano; no enmascara secretos.
  • Las tablas de monitoreo via WS si enmascaran algunas claves, pero solo estas: EMAIL_HOST, EMAIL_USER, EMAIL_PASSWORD, WS_AUTH_TOKEN y WS_AUTHORIZED_IPS.
  • Los reportes del monitor enmascaran las credenciales de correo, PAI, WS, Campus, MiLACNIC, OpenAI y reCAPTCHA; las claves públicas como DataSiteKeyReCaptcha no son secretos.

Que no existe hoy como control de acceso

  • Las contrasenas locales se almacenan como SHA-256 sin salt individual. Para un despliegue expuesto a Internet se recomienda planificar una migracion a Argon2id, bcrypt o PBKDF2.
  • No existe segundo factor para cuentas locales ni recuperacion autoservicio de contrasena.
  • No existe separacion de roles entre administradores locales: todos reciben elections-manager y elections-deleter.
  • No existe soporte generico configurable para OIDC, OAuth2, SAML o LDAP.
  • No existe desbloqueo manual desde la UI para limpiar el rate limit publico o el historial IpAccess.
  • No existe invalidacion inmediata del contador de CAPTCHA tras un login exitoso.
  • No existe autenticacion anonima para los snapshots REST v2, aunque su nombre refiera a informacion publica.
  • No existe en la pantalla de parametros una politica de ocultamiento por tipo de secreto.

Bloques relacionados

  • Parametros, templates e i18n: la configuracion base de claves y personalizacion quedo documentada en esta guia.
  • Votacion y links publicos: el detalle funcional de cada ruta tokenizada quedo documentado en esta guia.
  • Servicios REST: el catalogo de endpoints vigentes quedo documentado en esta guia.
  • Siguiente bloque recomendado: cerrar release notes v3.0 cuando termine el relevamiento integral.

Fuentes contrastadas

ElectionsManagerApp, ElectionsWebAdminSession, SecurityUtils, DashboardAdminBasePage, DashboardManagerBasePage, DashboardElectionBasePage, LoginDashboard, LoginPanel, AdminNavBarPanel, IpAccessDashboard, IpAccessListPanel, DashboardPublicBasePage, PublicTokenBasePage, PublicAccessDeniedPage, PublicElectionRecoverLinkPanel, PublicPhotoResizeDashboard, VotePublicPage, ResultPublicPage, AskCandidateQuestionPage, AuditPublicDashboardPage, ElectionsManagerEJBBean, ElectionsVoterEJBBean, ElectionsPreNominationEJBBean, ElectionsMonitorEJBBean, ElectionScheduler, ElectionsCaches, IpAccess, IpAccessDao, ElectionsRoles, WebServiceAuthentication, ElectionsService, ElectionsTablesServices, ElectionsServicesApplication y los tests WebServiceAuthenticationTest.