Etiquetas

domingo, 3 de mayo de 2015

Unprivileged LXC en Debian Jessie

Linux Containers, o más conocidos como LXC son una joya relativamente reciente -hacen uso de los control groups o cgroups del kernel Linux- que permite correr un sistema operativo Linux dentro de los límites de un contenedor.
Correrlos como root suele ser directo, pero lo más recomendable es ejecutar todo el contenedor como un usuario sin privilegios. Dicha tarea no requiere demasiados esfuerzos en Ubuntu porque esa fue ajustada en detalle. En cambio, en Debian requiere bastante esfuerzo. Varios días, prueba y error compilando paquetes hasta poder conseguirlo ejecutar mis primeros contenedores sin privilegios en Debian. Estas son unas notas sobre el proceso, aún un work-in-progress.


Esta entrada no pretende ser un paso a paso sobre cómo crear contenedores unprivileged, sino más bien listar los prerequisitos necesarios para poder crearlos en Debian Jessie.
Uno de los mejores tutoriales es el de Stéphane Graber en este link: https://www.stgraber.org/2014/01/17/lxc-1-0-unprivileged-containers/
Lo que ocurre es que para poder ejecutar contenedores en Debian debemos realizar algunos ajustes previos:

1) Kernel con las features necesarias

- Un kernel custom con las features necesarios. En este caso compilé un kernel 3.16.6 basandome en la configuración usada por Ubuntu. El archivo config puede encontrarse aquí: config-3.16.0-23-generic

Aquellos que busquen una guía para el build puede encontrarse una aquí: http://www.equiscentrico.com.ar/2012/07/compilar-un-kernel-la-debian.html

También una versión para i386 de esa configuración puede descargarse de: https://www.dropbox.com/s/wazs1fhgru2hyly/linux-image-3.16.6-lxc-rtx_3.16.6-lxc-rtx-10.00.Custom_i386.deb?dl=0

Y los headers pueden encontrarse: https://www.dropbox.com/s/z5udb1stt3q9gor/linux-headers-3.16.6-lxc-rtx_3.16.6-lxc-rtx-10.00.Custom_i386.deb?dl=0

2) systemd parchado para lxc "unprivileged"

Para que el usuario tenga control sobre los cgroups del lxc se requiere una versión bien reciente de systemd, 217-2 en adelante. Yo usé la versión 219, tomandola de la rama experimental de Debian:

Agregar los repo experimantal a APT (/etc/apt/sources.list):
deb http://ftp.debian.org/debian experimental main

Y luego instalar el paquete:

# apt-get update && apt-get -t experimental install systemd

Otro paquete importante es libpam-systemd. Los paquetes instalados pueden verse:

$ dpkg -l | grep systemd
ii libpam-systemd:i386 219-8 i386 system and service manager - PAM module
ii libsystemd0:i386 219-8 i386 systemd utility library
ii systemd 219-8 i386 system and service manager
ii systemd-sysv 219-8 i386 system and service manager - SysV links

3) Versión apropiada de LXC

En este punto es importante hacer notar lo siguiente: debe usarse una versión de LXC de los repos de Debian y no una compilada por uno mismo. Porque se incluyen en la de Debian algunos parches para que LXC se lleve bien con systemd, como puede verse abajo:


dpkg-source: información: extrayendo lxc en lxc-1.0.7
dpkg-source: información: desempaquetando lxc_1.0.7.orig.tar.xz
dpkg-source: información: desempaquetando lxc_1.0.7-3.debian.tar.xz
dpkg-source: información: aplicando «0001-lxcinitdir.patch»
dpkg-source: información: aplicando «0002-sysvinit-directory.patch»
dpkg-source: información: aplicando «0003-sysvinit-lsb-headers.patch»
dpkg-source: información: aplicando «0004-sysvinit-lsb-functions.patch»
dpkg-source: información: aplicando «0005-sysvinit-lsb-lock.patch»
dpkg-source: información: aplicando «0006-lxc-attach-sigint.patch»
dpkg-source: información: aplicando «0007-lxc-patch-shebang.patch»
dpkg-source: información: aplicando «0008-lxc-debian-fuse.patch»
dpkg-source: información: aplicando «0009-lxc-debian-openssh-server.patch»
dpkg-source: información: aplicando «0010-lxc-debian-root-password.patch»
dpkg-source: información: aplicando «0011-lxc-debian-systemd.patch»
dpkg-source: información: aplicando «0012-lxc-debian-sysfs.patch»

Como se ve, esos parches son fundamentales. Pasé mucho tiempo rompiéndome la cabeza con unos deb compilados a partir del upstream de lxc. Esos parches están para eso. Entonces, con la versión que Jessie tiene en sus repos de LXC: 1.0.6 va bien. Lo ideal sería poder usar una un poco más nueva, como 1.0.7 (fuentes de sid), eso voy a intentar dentro de poco.

4) Instalar cgmanager y verificar que esté demonizado

$ dpkg -l cgmanager
ii cgmanager 0.36-1 all

Para cgmanager estoy usando la versión 0.36, que creo que hasta este momento es la más reciente. El DEB que preparé está aquí: https://www.dropbox.com/s/dvkg8sugcpqdc72/cgmanager_0.36-1_all.deb?dl=0

Luego de instalarlo, asegurarse de que quede activado en los scripts de inicio:

c$ systemctl status cgmanager
● cgmanager.service - Cgroup management daemon
Loaded: loaded (/usr/lib/systemd/system/cgmanager.service; enabled; vendor preset: enabled)
Active: active (running) since dom 2015-05-03 20:12:15 ART; 2h 22min ago
Main PID: 814 (cgmanager)
CGroup: /system.slice/cgmanager.service
‣ 814 /usr/sbin/cgmanager -m name=systemd
matias@matuxntbk:~/RamosLinux/lxc$ ps aux | grep cgmanager
root 814 0.0 0.2 2912 2072 ? Ss 20:12 0:00 /usr/sbin/cgmanager -m name=systemd

5) script de inicio y configuración de red lxc-net

También en el dropbox guardé una copia del script de configuración de red, lxc-net que es el encargado de iniciar dnsmasq con la configuración necesaria para utilizar una red tipo "nat", en la que cada contenedor "guest" tendrá una interfaz virtual con una red "interna" que hará nat a la dirección IP del host.

https://www.dropbox.com/s/s4yl5jn30w1apot/lxc-net.tar.gz?dl=0

Luego copiar los archivos, iniciar el script:

# systemctl start lxc-net

Y verificar con bridge utils que la interfaz tipo bridge se haya creado:

matias@matuxntbk:~/RamosLinux/lxc$ /sbin/brctl show
bridge name bridge id STP enabled interfaces
lxcbr0 8000.000000000000 no

6) Preparar el ambiente, siguiendo el tutorial de Stéphane (https://www.stgraber.org/2014/01/17/lxc-1-0-unprivileged-containers/)

6.1) Conviene agregar al usuario que usaremos para los containers al archivo sudoers para que pueda ejecutar cgm:

# /etc/sudoers o visudo
lxcuser ALL=NOPASSWD:/usr/bin/cgm

6.2) preparar los permisos:

$ sudo cgm create all foo
$ sudo cgm chown all foo $(id -u) $(id -g)
$ cgm modepid all foo $$

Aqui utilizamos cgm (cgroup manager) para crear el cgroup foo, y mover a él el pid del running shell. Eso permitirá que los procesos susecuentes se ejecuten bajo ese cgroup.

Notas finales: este es un borrador de un trabajo aún en progresos. Hay que señalar que las versiones que se necesitan por ejemplo de systemd son realmente bleeding-edge. Siendo systemd un proceso crítico (de él depende toda la estabilidad del sistema) conviene probar en profundidad esta configuración antes de aventurarse a hacer un deploy a producción.

Continuar »

martes, 17 de febrero de 2015

En los *nix, los pipelines son un caño

Parte de la "filosofía" de Unix (o de la programación para sistemas *nix) puede resumirse según el principio "escribí programas que hagan una cosa, y que la hagan bien".
Los pipelines, "pipes" son el mecanismo que permite extender este principio combinando y comunicando distintos procesos, para que cada uno haga su tarea, de la mejor forma posible.
En esta entrada, la historia de la noche febril en que Ken Thompson escribió la llamada al sistema pipe() y un par de breves y mundanos ejemplos de uso de pipes en bash.


Una febril noche de 1973 y una orgía de one-liners

Dice Wikipedia ( Pipeline_(Unix) ) que el concepto de pipeline (el paso de la salida de un programa a la entrada de otro) fue inventado por Douglas McIlroy, uno de los autores de los primeros shells. McIlroy notó que buena parte del tiempo estaban dirigiendo la salida de un programa a la entrada de otro.
Sus ideas las implementó Ken Thopson en una "noche febril" de 1973, cuando agregó la llamada al sistema pipe() y el mecanismo de "pipe" | a shell, junto con varias utilitarios en la versión 3 de Unix. Al día siguiente surgieron los "one-liners" (entradas de una líneas que encadenaban varios programas): dice McIlroy "vi una orgía inolvidable de one-liners en la medida que cada uno se sumaba a la diversión de meter todo en un caño".

Comunicar procesos en Bash

Los "pipes" o "pipelines" son una de las formas más simples y básicas de comunicar procesos (IPC: "Inter Process Communication"). Una de las grandes ventajas de los shells *nix es la posibilidad de crear one-liners que son combinaciones de programas, que juntos hacen una tarea en la que cada uno contribuye.
Solo un ejemplo, tomado de www.bashoneliners.com:

$ /usr/bin/printf 'GET / \n' | nc yahoo.com.ar 80

En este ejemplo se puede grabar el html de yahoo.com.ar usando netcat como cliente. Esa era la orgía a la que McIlroy se refería.

Existen pipelines con nombre y pipelines sin nombre, los primeros son básicamente un tipo de archivo especial en el File System al cual cierto/s proceso/s pueden escribir y otros leer. Los pipeline sin nombre son los que más usamos en la cli, cuando se usa para dirigir la salida de un comando a la entrada de otro con el caracter '|'.
Un pipe funciona, oh sorpresa, como un archivo y puede leerse de la misma forma.
Sigue abajo un ejemplo con el que me divertí un rato y con la esperanza que le pueda servir al sr. @esturniolo:

#!/usr/bin/env bash
#### Script for testing porpouses. Showing unnamed pipes in bash
#### imagemagick must be installed, so: # aptitude install imagemagick.


function printUsage {
    echo >&2 'Error: no argument supplied.'
    echo >&2 "Usage $0 "
    echo >&2 "$0 will start looking for image files starting at the initial directory you supply"
exit 1

}

function checkDep {
    type $1 > /dev/null 2>&1
    if [ "$?" -ne 0 ]
    then
     echo >&2 "Dependency $1 not met. Install it!"
    exit 1
    fi
}
function main {
    echo "inicial dir=" $1
    COMMAND="find $1 -iregex .*\.\(jpg\|gif\|png\|jpeg\)$ -type f"
    $COMMAND | while read i
    do
    # Here you do whatever you want with each lined passed throgh the pipe.
    # That's the unix magic.
      echo $i
      # in this test we grab some data from image file properties.
      identify -format "%wx%h %[EXIF:DateTime] " $i
    done
    echo
}

# Test user input: parameter $1, which must be the initial dir.
if [ "$#" -eq 0 ]; then
    printUsage
fi
if [ ! -d $1 ]; then
    echo >&2 "$1 is not a valid directory, so it's none of my bussiness fellow."
    exit 1
fi

# For checking dependency pass bin/script to be checked as checkDep parameter:
checkDep identify
main $1

A este script se le pasa como único parámetro un directorio inicial para que busque recursivamente en él archivos de imágenes:

find $1 -iregex .*\.\(jpg\|gif\|png\|jpeg\)$ -type f

Donde $1 es el primer parámetro que se le pasa al script. Ejemplo:

$ ./test_stu.sh /home/retux/fotos

La magia de pipe:

$COMMAND | while read i
do
    # Here you do whatever you want with each lined passed throgh the pipe.
     # That's the unix magic.
     echo $i
     # in this test we grab some data from image file properties.
     identify -format "%wx%h %[EXIF:DateTime] " $i
done

Cada línea que find en este caso devuelva por su stdout es redirigida por un pipe y almacenada en la var $i. Luego podemos hacer o que querramos, en este caso se llama al programa identify para que imprima algunos atributos de cada imagen, cuando se le pasa su ubicación, contenida en $i.
Continuar »

lunes, 16 de febrero de 2015

Mantener ssh más seguro. Hoy: ssh-agent

Como sysadmins en ambientes corporativos o en nuestras redes hogareñas openssh es el protocolo capaz de encriptar absolutamente todos el tráfico y permitirnos la administración remota de hosts o vps de forma segura.

Usar ssh de manera predeterminada con autenticación por password puede ser peligroso. Por eso, auth asimétrica con keys es la solución.

El desconfiao' puede hacer la prueba. Colocar un host en internet que acepte autenticación por password. En algunas horas los logs evidenciarán que el host fue objeto a algún ataque de diccionario. Si además la config de sshd permite ogin del usuario root (como suele ser el default: PermitRootLogin yes) las consecuencias pueden ser fatales. Si la contraseña es débil y el ataque tiene éxito los miscreants se habrán hecho de acceso al host y con el usuario con mayores privilegios del sistema.
Estas líneas asustadoras sólo pretenden exponer la gravedad. Un protocolo sumamente seguro como ssh versión 2 puede ser vulnerado solo por una configuración "permisiva" del usuario.

En otra entrada de este blog me ocupé sobre cómo usar criptografía asimétrica (par de claves pública/privada) para autenticar ssh:

Aquella vez, para mantener las cosas simples, al momento de crear las claves con ssh-keygen no se establecía una passphrase para el acceso a la clave privada. Eso efectivamente hace las cosas sencillas si se va a usar scp u otro programa en scripts. Pero qué pasa si alguien lograra tener acceso a la clave privada: como no hay passphrase podría tener acceso a los hosts que tienen su clave pública en el archvio authorized_keys.

Para hacer mejor las cosas existe ssh-agent.

Como su manpage explica el objeto de ssh-agent es

ssh-agent es un programa que guarda las llaves privadas que se usan para autentificación por llave pública. En general ssh-agent se inicia como demonio al principio de una sesión X. Otros programas funcionarán como clientes de ssh-agent cuando requieran acceder a otros hosts vía ssh.
Las aplicaciones pueden localizar a ssh-agent a través de dos variables del entorno:

SSH_AGENT_PID

SSH_AUTH_SOCK

Por ejemplo, en mi Debian, el entorno de escritorio Enlightenment inicia el ssh-agent.
Para usarlo con autenticación ssh por clave pública el procedimiento es similar al descripto en pero en el momento de crear las llaves, se debe registrar una contraseña fuerte (passphrase) para el acceso a la llave privada.
Luego, con ssh-add agregaremos la llave privada a la gestión de ssh-agent:

$ ssh-add /home/retux/.ssh/id_rsa

Luego, podremos listar las llaves que ssh-agent gestiona. Obtendremos algo así:

$ ssh-add -l
2048 04:07:3f:a8:28:ca:e7:11:22:33:55:.... .ssh/id_rsa (RSA)

Así las cosas, ssh-agent nos pedirá la primera vez que intentemos acceder a ese host ingresar las passphrase, pero el programa se encargará de gestionarla, y en lo sucesivo, mientras dure la sesión no nos la pedirá nuevamente.
El resultado es similar a no registrar passphrase al crear las llaves con ssh-keygen, pero con el enorme agregado en lo que hace a la seguridad. Si un malhechor se hace de la llave privada deberá conocer la passphrase para tener acceso.
Continuar »

viernes, 21 de marzo de 2014

PAM establecer horarios de uso del sistema, ejemplo para contron paternal

Un tip "quick and dirty" fácil rápido y directo para limitar los horarios en los que ciertos usuarios del sistema pueden loguearse.
En conjunto con otras utilidades sirve para configurar eso llamado "control paternal".


El Linux, como otras variantes de *nix trae implementado PAM (Pluggable Authentication Modules), que básicamente es una API para autenticación de usuarios, pero además como su nombre lo indica PAM incluye varios módulos que con sus "botones y perillas" (configurables en archivos de config, claro) que permiten por ejemplo limitar la asignación de recursos que el kernel provee a cada usuario (ej. RAM, tamaño de la pila, CPU, etc). En ese caso el archivo de config es /etc/pam.d/limits.conf (véase man limts.conf).
Pero el tip rápido y furioso de hoy se ocupa de otro módulo: pam_time.so que nos permite configurar los horarios en que ciertos usuarios podrán loguearse al sistema.

En una entrada previa en Equiscéntrico me ocupé sobre cómo configurar iptables para que algunos usuarios usaran selectivamente los servidores DNS de openDNS y de esa forma establecer un filtro básico de sites no convenientes para niños ver: (http://www.equiscentrico.com.ar/2010/09/filtrar-facilmente-contenido-web.html ).
En otras ocasiones queremos establecer horarios en que los pibes puedan usar las compus, para que no le resten demasiado tiempo a otras actividades. Ahí es donde el módulo pam_time.so viene a nuestra ayuda.
Rápidamente, para activarlo tenemos que incluir la siguiente línea en al archivo /etc/pam.d/login (esa es su ubicación en Debian, en otras distros o sabores *nix debe ser similar). Y también en /etc/pam.d/kdm o el gestor de logins que use nuestro ambiente de escritorio, en este caso es kde.

# BOF modulo para limites horarios
account requisite pam_time.so
# EOF limites horarios

Editando estos dos archivos se activará la verificación de horarios tanto al loguin GUI como a las consolas. Lo más probable es que nuestros hijos usan más el entorno gráfico pero si uno de ellos usara más las consolas, convendría dejarle la compu libre las 24 hs :)
Hablando en serio, si acaso también quisiéramos limitar el acesso por horarios vía ssh habrá que configurarlo en el archivo correspondiente: /etc/pam.d/sshd.
Ahora tendremos que configurar los usuarios a los que queremos aplicar las restricciones, eso se hace en /etc/security/time.conf

Ejemplo:
# usuario 1 (dinix)
*;*;dinix;!Wk1115-1910
*;*;dinix;Wd0000-2400
# Usuario 2 (ada)
*;*;ada;!Wk1115-1910
*;*;ada;Wd0000-2400

Aquí configuramos que de lu-vi tanto el usuario dinix como ada no puedan loguearse entre las 11:15 y las 19:10 !Wk1115-1910 pero que los fines de semana (Wd) puedan loguearse las 24 hs. Errores que cometamos durante la config se puede seguir en el syslog.

Más info se puede obterner con man time.conf·

Una cosita más. Si el usuario ya se encuntra logueado al momento en que comienza la franja horaria restringida no hay un mecanismo que lo desloguee automáticamente. Eso debe implementarse, por ejemplo desde un script disparado por crontab para complementar este módulo de PAM.
Este fue un tip "quick and dirty". Si hay errores, sugerencias, el feedback es siempre bienvenido.
Continuar »

martes, 25 de febrero de 2014

Proyecto de fin de semana: Leds de status para Raspberry Pi

Cuando la Raspberry Pi se usa como servidor, o simplemente headless (sin monitor) resulta útil tener algunos indicadores de su status de funcionamiento. Este proyectito tiene una parte de hard muy simple (un driver de corriente e inversores) y de soft, para colocar en 1 los pines adecuados del GPIO.
El que quiera trabajar con GPIO debe hacerlo con sumo cuidado, así que corre por su propia cuenta y riesgo.

Esta necesidad surgió primero de un pedido de un amigo, que necesitaba un gabinete que alojara una Raspberry Pi y un HD de 2.5" que funcionarían 24/7 como servidor web, y que se encontraría en un IDC (Internet Data Center). Como los leds internos de la Raspberry suelen quedar ocultos en la mayoría de los gabinetes y este no era la excepción, quería agregar un par de leds que indicaran:

1) "Power On", la raspberry tiene conectada la alimentación, pero no concluyó el boot. (Led en Rojo)
2) "Ready", la raspberry concluyó satisfactoriamente el boot (led verde).

1 y 2) es un led bicolor de 5 mm.

3) "ethernet": un segundo led (ambar 5 mm) se pone en ON cuando la ethernet está activa, se apaga si no hay portadora.

Si alguna vez la Raspberry tuviera un problema el resposable de operaciones del IDC tendría un primer indicador a través de los leds. Se puede ver en la primera foto.

El circuito del driver de corriente e inversor sigue a continuación:


Se usó un CI 40106 que contiene 5 inversores lógicos, por la siguiente razón: como driver de corriente, es decir para no cargar directamente el GPIO de la raspberry. Es totalmente cierto que un par de leds podrían conectarse directamente, pero hay que tener en cuenta siempre que si conectamos otras "cosas" al GPIO la corriente total es la sumatoria de todas. Por eso, la utilización del driver ayudaría a la estabilidad general de la RasPi.

Tanto el código como los binarios empaquetados para instalar en la RasPi se pueden encontrar aquí:

https://github.com/retux/raspileds_status

$ git clone https://github.com/retux/raspileds_status --depth 0

Más info sobre interfaces de corriente para GPIO se puede ver aquí: http://elinux.org/RPi_GPIO_Interface_Circuits

RaspiLed-status requiere usar dos pines del GPIO configurados como salida. Como se podrá ver en las notas del código se eligieron los pines GPIO4 (para el status ethernet, led ambar) y el GPIO17 para status de la CPU (power=>rojo, ready=>verde).
El circuito funciona muy simple: cuando el GPIO17 está en cero el primer inversor tendrá a su salida un 1, encendiendo el led en rojo. Esa lógica se invierte cuando ponemos un 1 a la salida del GPIO17.
Dos scripts SysV se encargan de iniciar los dos programas al inicio (/etc/init.d/readyled y /etc/init.d/raspiledsts). si se quisiera cambiar el pin GPIO17 por otro es en readyled donde habrá que editarlo:

### BEGIN INIT INFO
# Provides: readyled
# Required-Start: $locale_fs $syslog
# Required-Stop: $locale_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Turn raspiled ready on/off at boot time
# Description: RaspiLed ready (GPIO pin 17) indicates system has finished boot process.
### END INIT INFO

#!/bin/sh

case "$1" in
start)
echo "Turning on ready LED "
/usr/bin/gpiopinctrl 17 1
;;
stop)
echo "Turning off ready LED"
/usr/bin/gpiopinctrl 17 0
;;
*)
echo "Usage: /etc/init.d/raspiledsts {start|stop}"
exit 1
;;
esac
exit 0

Una vez que el sistema concluya el boot se pondrá en verde el led de status, una vez que demos "shutdown" como el script pasará a stop, se pondrá rojo, indicando que queda con la tensión conectada, pero apagada.
El programa netifledwatch es el encargado de sensar el status de ethernet, es un demonio que a intervalos regulares (1/2 segundo) lee el archivo carrier del pseudo FS /sys. (ej. si la interfaz es eth0 y la portadora ethernet está activa aparecerá un 1 en el archivo, como se ve aquí:

$ cat /sys/class/net/eth0/carrier
1

Con esa lectura el programa lo único que hace es poner en 1 o 0 segun corresponda el GPIO4.

RaspiLeds-status en otro gabinete.

Matías Gutiérrez Reto (retux)
Continuar »