Reglas de tiempo en Iptables

Hay escenarios en donde se le solicita al sysadmin que aplique reglas de tiempo para el acceso a determinados equipos en la red local. Para poder ejecutar esta tarea, el sysadmin debe tener previamente establecido en su firewall (basado en reglas iptables), las diferentes ACLs con las direcciones MACs de los equipos de su red local, cada una con sus respectivos privilegios de acceso que les quiere otorgar.
Una vez definido esto, es muy sencillo establecer el tiempo de acceso. Según el Man de Iptables, la regla que se utiliza es time, la cual cuenta con varios parámetros:
--datestart YYYY[-MM[-DD[Thh[:mm[:ss]]]]]
--datestop YYYY[-MM[-DD[Thh[:mm[:ss]]]]]
Only match during the given time, which must be in ISO 8601 "T" notation. The possible time range is 1970-01-01T00:00:00 to 2038-01-19T04:17:07.
If --datestart or --datestop are not specified, it will default to 1970-01-01 and 2038-01-19, respectively.
--timestart hh:mm[:ss]
--timestop hh:mm[:ss]
Only match during the given daytime. The possible time range is 00:00:00 to 23:59:59. Leading zeroes are allowed (e.g. "06:03") and correctly interpreted as base-10.
[!] --monthdays day[,day...]
Only match on the given days of the month. Possible values are 1 to 31. Note that specifying 31 will of course not match on months which do not have a 31st day; the same goes for 28- or 29-day February.
[!] --weekdays day[,day...]
Only match on the given weekdays. Possible values are Mon, Tue, Wed, Thu, Fri, Sat, Sun, or values from 1 to 7, respectively. You may also use two-character variants (Mo, Tu, etc.).
--contiguous
When --timestop is smaller than --timestart value, match this as a single time period instead distinct intervals. See EXAMPLES.
--kerneltz
Use the kernel timezone instead of UTC to determine whether a packet meets the time regulations.
Para nuestro ejemplo de hoy utilizaremos --timestart y --timestopEl tiempo debe ser establecido en HH:MM (y también segundos).
iptables RULE -m time --timestart TIME --timestop TIME -j ACTION
En el siguiente ejemplo, aplicaremos la regla time de iptables a una ACL que contiene las direcciones MACs con su formato de 6 bloques de dos caracteres hexadecimales) la cual le restringiremos el acceso a los puertos 80,443 durante el día y los abriremos a partir de la 2 PM (14 H).
Adicionalmente la regla la restringiremos para el rango CIDR 192.168.0.0/24 y para dos interfaces de red (enp2s0 para la salida a internet y enp2s8 para la red local local):
# hora permitida de acceso a los puertos
allowtime=14:00
# hora de cierre de los puertos
droptime=23:59
# regla de tiempo
for macs in $(awk -F";" '{print $2}' /path_to_acl/macs.txt); do
  iptables -A INPUT -s 192.168.0.0/24 -i enp2s8 -m mac --mac-source $macs -p tcp -m multiport --dports 80,443 -m time --timestart $allowtime --timestop $droptime -j ACCEPT
  iptables -A FORWARD -s 192.168.0.0/24 -i enp2s8 -m mac --mac-source $macs -p tcp -m multiport --dports 80,443 -o enp2s0 -m time --timestart $allowtime --timestop $droptime -j ACCEPT
done
Adicionalmente podemos establecer los días de la semana de aplicación de la regla:
# hora permitida de acceso a los puertos
allowtime=14:00
# hora de cierre de los puertos
droptime=23:59
# regla de tiempo
for macs in $(awk -F";" '{print $2}' /path_to_acl/macs.txt); do
  iptables -A INPUT -s 192.168.0.0/24 -i enp2s8 -m mac --mac-source $macs -p tcp -m multiport --dports 80,443 -m time --timestart $allowtime --timestop $droptime --weekdays Mon,Tue,Wed,Thu,Fri -j ACCEPT
  iptables -A FORWARD -s 192.168.0.0/24 -i enp2s8 -m mac --mac-source $macs -p tcp -m multiport --dports 80,443 -o enp2s0 -m time --timestart $allowtime --timestop $droptime --weekdays Mon,Tue,Wed,Thu,Fri -j ACCEPT
done
Problemas conocidos
Esta regla ha venido presentando problemas con algunas distribuciones, específicamente hemos detectado que con Ubuntu 18.04.x LTS x64, y adicionalmente tiene algunas limitaciones:
1. No detecta los minutos (14.31) ni mucho menos segundos
2. No permite que el horario se extienda más allá de las 24 H: Este problema (descrito en el portal unix.stackexchange.com) consiste en que si queremos establecer un horario más allá de la hora 24 (por ejemplo: desde 2 PM hasta las 8 AM del siguiente día), habría que crear un segundo horario, justo cuando termina el primero, o sea, desde 00:00 hasta 08:00 y repetir cada regla de iptables para el nuevo horario. Una posible solución es invertirlo, o sea, poner el horario en que NO queremos que acceda esta ACL (ejemplo: 08:00 14:00) y cambiamos las reglas de iptables de ACCEPT a DROP. De esta manera el "resto" del tiempo nuestra lista tendría acceso. Pero también tiene sus desventajas, ya que es muy probable que tenga que agregar reglas iptables adicionales para autorizar estos puertos en el horario deseado.
3. Problemas con la hora: Es común que los sistemas no se sincronicen con la exactitud que quisiéramos ni todos usan la misma hora. Por ejemplo, tenemos el "Hardware Time" (controlado por la BIOS y se ajusta según la hora local y sus datos se almacenan en una batería), el "System Time" (ajustado manualmente por el usuario y actualizado por los servidores NTP), que trabajan con el Universal Time Coordinated (UTC). También esta la variante Greenwich Mean Time (GMT), que tiene la misma hora de UTC pero expresada de diferente manera (GMT es una zona horaria y UTC es un estándar de tiempo), por ejemplo 1 p.m. GMT = 13 horas UTC. También está el horario de verano o invierno, la hora Zulu... En fin, este es un tema que por su extensión no lo abordaremos en esta entrada, sin embargo lo relevante para nuestro caso es que esta situación afecta las aplicaciones que corren bajo Linux, y en especial las que corren a nivel de kernel, como iptables, por tanto también afecta las reglas basadas en tiempo.
Local Time vs UTC vs RTC
Entonces, para que funcionen esta regla de tiempo de iptables de manera correcta existen dos alternativas:
a. Cambiar todo el sistema a UTC-0 (explicado AQUI), incluyendo la BIOS
b. Agregar el parámetro --kerneltz a las reglas:
# hora permitida de acceso a los puertos
allowtime=14:00
# hora de cierre de los puertos
droptime=23:59
for macs in $(awk -F";" '{print $2}' /path_to_acl/macs.txt); do
  iptables -A INPUT -s 192.168.0.0/24 -i enp2s8 -m mac --mac-source $macs -p tcp -m multiport --dports 80,443 -m time --timestart $allowtime --timestop $droptime --kerneltz -j ACCEPT
  iptables -A FORWARD -s 192.168.0.0/24 -i enp2s8 -m mac --mac-source $macs -p tcp -m multiport --dports 80,443 -o enp2s0 -m time --timestart $allowtime --timestop $droptime --kerneltz -j ACCEPT
done
Como lo menciona el Man de iptables, lo que hace el parámetro --kerneltz es usar la zona horaria kernel, en lugar de UTC, para determinar si un paquete cumple con las regulaciones de tiempo, pero esto puede no cumplirse en todos los escenarios.
Hay una solución, no recomendada, y es hacer un forzado para que el sistema sincronice y lea RTC:
timedatectl list-timezones # set your zone
timedatectl set-timezone "America/Bogota" # example timezone
apt install ntp # to sync
ntpq -p  # to sync
crontab -e
# add line
@reboot root /usr/bin/ntpd -n  # to sync
service cron restart
hwclock --localtime --systohc  # to set RTC
timedatectl status # to check
hwclock -r
Y el resultado:
Nosotros recomendamos algo mucho más sencillo con hwclock. Tenga en cuenta los parámetros:
 -s, --hctosys        set the system time from the RTC
 -w, --systohc        set the RTC from the system time
Aquí lo mejor es copiar la hora de sistema (system time) a la hora del hardware (hardware time) y programarlo en crontab para que se ejecute en cada reinicio y así no afectar la configuración por defecto de Linux (en este caso de Ubuntu):
timedatectl set-ntp true
hwclock -w
Sin embargo, a pesar de lo anterior es altamente probable que esta regla de tiempo no funcione.
Reglas de Tiempo Alternativas
Una propuesta interesante es la descrita en el post IPTables Auto-Expiring Rules on Linux, con el comando "at". En el siguiente ejemplo baneamos una IP por una (1) hora:
iptables -I INPUT -s 10.0.0.10 -j DROP && \
{ echo "iptables -D INPUT -s 10.0.0.10 -j DROP" | at now + 1 hour; }
La lista de colas AT puede visualizarse emitiendo el comando 'atq' y los comandos en una entrada de cola particular, usando 'at -c #queue_id'.
Otra alternativa es un truco (cortesía de novatoz) que utiliza una comparativa con la hora actual y aplica la regla en base a esta.  No solo funciona bien en 18.04 sino resuelve el problema de la limitante del horario y cumple exactamente con nuestro objetivo. Veamos el siguiente ejemplo:
# IPTABLES TIME RULE
lan=enp2s8 # local interface
# format 24h
current_time=$(date +%H) 
allowed_time=17
denied_time=8

if [ $current_time -ge $allowed_time -a $current_time -lt $denied_time ]; then
    for macs in $(awk -F";" '{print $2}' /path_to_acl/macs.txt); do
     iptables -t nat -A PREROUTING -s 192.168.0.0/24 -i $lan -m mac --mac-source $macs -p tcp --dport 80 -j REDIRECT --to-port 8080
     iptables -A INPUT -s 192.168.0.0/24 -i $lan -m mac --mac-source $macs -p tcp -m multiport --dports 8080,443 -j ACCEPT
     iptables -A FORWARD -s 192.168.0.0/24 -i $lan -m mac --mac-source $macs -p tcp -m multiport --dports 8080,443 -o $internet -j ACCEPT
    done
fi
En la regla anterior  establecemos que la hora permitida (donde entrarán los equipos a la red local) es a partir de la 1 PM (14) y se bloquean a las 8 AM (8) del día siguiente. Entonces la regla ejecuta el comando date +%H para establecer la hora del servidor y (con if/for/fi) establece que si la hora actual es mayor a la hora permitida, los equipos pasan, de lo contrario se bloquean.
También podemos usar esta regla para excluir restricciones los fines de semana. Por ejemplo:
# IPTABLES TIME RULE
lan=enp2s8 # local interface
# format 24h
current_time=$(date +%H) 
allowed_time=17
denied_time=8
sat=6
sun=0
if [ \( $current_time -ge $allowed_time -a $current_time -lt $denied_time \) -o \( $current_day -eq $sat -o $current_day -eq $sun \) ]; then
    for macs in $(awk -F";" '{print $2}' /path_to_acl/macs.txt); do
     iptables -t nat -A PREROUTING -s 192.168.0.0/24 -i $lan -m mac --mac-source $macs -p tcp --dport 80 -j REDIRECT --to-port 8080
     iptables -A INPUT -s 192.168.0.0/24 -i $lan -m mac --mac-source $macs -p tcp -m multiport --dports 8080,443 -j ACCEPT
     iptables -A FORWARD -s 192.168.0.0/24 -i $lan -m mac --mac-source $macs -p tcp -m multiport --dports 8080,443 -o $internet -j ACCEPT
    done
fi
Donde si los días de la semana se representan del 0 al 6 (0 es domingo) y la regla en este caso solo aplica de lunes a viernes.
PD: No olvide cerrar los puertos declarados en estas reglas al final de las mismas.
iptables -t mangle -A PREROUTING -i $lan -m mac --mac-source $macs -j DROP
iptables -A INPUT -i $lan -m mac --mac-source $macs -j DROP
iptables -A FORWARD -i $lan -m mac --mac-source $macs -j DROP
Esta regla también puede establecer minutos y segundos, pero puede complicarse y tal vez sería para una próxima entrega.

Con la tecnología de Blogger.