Usar Linux Containers (LXC) en Debian – Parte 1

Como probablemente habrán leído en el post anterior, mis intentos por crear un contenedor LXC sin privilegios en Debian fracasaron miserablemente pero no todo está perdido, aún podemos intentar crear un contenedor con privilegios, es decir, un contenedor que es creado y ejecutado como root o sudo. Este tipo de contenedores son más peligrosos debido a que root dentro del contenedor es prácticamente root fuera del mismo. Esto no quiere decir que sea un riesgo de seguridad por sí mismo pero si de alguna forma un usuario como root dentro del contenedor consiguiera escapar del contenedor, ese usuario también sería root dentro del sistema anfitrión. No digo que escapar al contenedor sea fácil, o siquiera posible para un usuario promedio, pero al menos debemos conocer los riesgos.

Crear un contenedor con privilegios es, irónicamente, mucho más fácil (al menos en Debian) que crear uno sin privilegios. Basta con instalar el paquete lxc desde el gestor de paquetes; esto instalará también todos los demás paquetes requeridos por LXC. Una vez instalado, podemos ejecutar como sudo o root el comando lxc-create para crear el contenedor:

monstruosoft@PC:~$ sudo lxc-create -n privileged -t download
[sudo] password for monstruosoft:
Setting up the GPG keyring
Downloading the image index

---
DIST	RELEASE	ARCH	VARIANT	BUILD
---
alpine	3.1	amd64	default	20161024_17:50
alpine	3.1	armhf	default	20161024_17:50
alpine	3.1	i386	default	20161024_19:00
alpine	3.2	amd64	default	20161024_17:50
alpine	3.2	armhf	default	20161024_19:00
alpine	3.2	i386	default	20161024_17:50
alpine	3.3	amd64	default	20161024_17:50
alpine	3.3	armhf	default	20161024_17:50
alpine	3.3	i386	default	20161024_17:50
alpine	3.4	amd64	default	20161024_17:50
alpine	3.4	armhf	default	20161024_17:50
alpine	3.4	i386	default	20161024_17:50
alpine	edge	amd64	default	20161024_17:50
alpine	edge	armhf	default	20161024_17:50
alpine	edge	i386	default	20161024_17:50
archlinux	current	amd64	default	20161025_01:27
archlinux	current	i386	default	20161025_01:27
centos	6	amd64	default	20161025_02:16
centos	6	i386	default	20161025_02:16
centos	7	amd64	default	20161025_02:16
debian	jessie	amd64	default	20161024_22:42
debian	jessie	arm64	default	20161024_22:42
debian	jessie	armel	default	20161024_22:42
debian	jessie	armhf	default	20161024_22:42
debian	jessie	i386	default	20161024_22:42
debian	jessie	powerpc	default	20161024_22:42
debian	jessie	ppc64el	default	20161024_22:42
debian	jessie	s390x	default	20161024_22:42
debian	sid	amd64	default	20161024_22:42
debian	sid	arm64	default	20161024_22:42
debian	sid	armel	default	20161024_22:42
debian	sid	armhf	default	20161024_22:42
debian	sid	i386	default	20161024_22:42
debian	sid	powerpc	default	20161024_22:42
debian	sid	ppc64el	default	20161024_22:42
debian	sid	s390x	default	20161024_22:42
debian	stretch	amd64	default	20161024_22:42
debian	stretch	arm64	default	20161024_22:42
debian	stretch	armel	default	20161024_22:42
debian	stretch	armhf	default	20161024_22:42
debian	stretch	i386	default	20161024_22:42
debian	stretch	powerpc	default	20161024_22:42
debian	stretch	ppc64el	default	20161024_22:42
debian	stretch	s390x	default	20161024_22:42
debian	wheezy	amd64	default	20161024_22:42
debian	wheezy	armel	default	20161024_22:42
debian	wheezy	armhf	default	20161024_22:42
debian	wheezy	i386	default	20161024_22:42
debian	wheezy	powerpc	default	20161024_22:42
debian	wheezy	s390x	default	20161024_22:42
fedora	22	amd64	default	20161025_01:27
fedora	22	i386	default	20161025_01:27
fedora	23	amd64	default	20161025_02:03
fedora	23	i386	default	20161025_02:03
fedora	24	amd64	default	20161025_02:40
fedora	24	i386	default	20161025_02:40
gentoo	current	amd64	default	20161025_14:12
gentoo	current	i386	default	20161025_14:12
opensuse	13.2	amd64	default	20161025_00:53
oracle	6	amd64	default	20161025_11:40
oracle	6	i386	default	20161025_11:40
oracle	7	amd64	default	20161025_11:40
plamo	5.x	amd64	default	20161024_21:36
plamo	5.x	i386	default	20161024_21:36
plamo	6.x	amd64	default	20161024_21:36
plamo	6.x	i386	default	20161024_21:36
ubuntu	precise	amd64	default	20161025_03:49
ubuntu	precise	armel	default	20161025_03:49
ubuntu	precise	armhf	default	20161025_03:49
ubuntu	precise	i386	default	20161025_03:49
ubuntu	precise	powerpc	default	20161025_03:49
ubuntu	trusty	amd64	default	20161025_03:49
ubuntu	trusty	arm64	default	20161025_03:49
ubuntu	trusty	armhf	default	20161025_03:49
ubuntu	trusty	i386	default	20161025_03:49
ubuntu	trusty	powerpc	default	20161025_03:49
ubuntu	trusty	ppc64el	default	20161024_03:49
ubuntu	xenial	amd64	default	20161025_03:49
ubuntu	xenial	arm64	default	20161025_03:49
ubuntu	xenial	armhf	default	20161025_03:49
ubuntu	xenial	i386	default	20161025_03:49
ubuntu	xenial	powerpc	default	20161025_03:49
ubuntu	xenial	ppc64el	default	20161025_03:49
ubuntu	xenial	s390x	default	20161025_03:49
ubuntu	yakkety	amd64	default	20161025_03:49
ubuntu	yakkety	arm64	default	20161025_03:49
ubuntu	yakkety	armhf	default	20161025_03:49
ubuntu	yakkety	i386	default	20161025_03:49
ubuntu	yakkety	powerpc	default	20161025_03:49
ubuntu	yakkety	ppc64el	default	20161025_03:49
ubuntu	yakkety	s390x	default	20161025_03:49
ubuntu	zesty	amd64	default	20161025_03:49
ubuntu	zesty	arm64	default	20161025_03:49
ubuntu	zesty	armhf	default	20161025_03:49
ubuntu	zesty	i386	default	20161025_03:49
ubuntu	zesty	powerpc	default	20161025_03:49
ubuntu	zesty	ppc64el	default	20161025_03:49
ubuntu	zesty	s390x	default	20161025_03:49
---
Distribution: debian
Release: sid
Architecture: amd64

Downloading the image index
Downloading the rootfs
Downloading the metadata
The image cache is now ready
Unpacking the rootfs

---
You just created a Debian container (release=sid, arch=amd64, variant=default)

To enable sshd, run: apt-get install openssh-server

For security reason, container images ship without user accounts
and without a root password.

Use lxc-attach or chroot directly into the rootfs to set a root password
or create user accounts.

La anterior es la lista de contenedores que se pueden crear como sudo o root. En este caso he creado un contenedor de Debian Sid.

Una vez que hemos creado el contenedor, podemos iniciarlo ejecutando como sudo o root el comando lxc-start. A diferencia de los intentos por iniciar un contenedor sin privilegios, este debe ejecutarse sin problemas:

monstruosoft@PC:~$ sudo lxc-ls -f
NAME       STATE   AUTOSTART GROUPS IPV4 IPV6 
privileged STOPPED 0         -      -    -    
monstruosoft@PC:~$ sudo lxc-start -n privileged
monstruosoft@PC:~$ sudo lxc-ls -f
NAME       STATE   AUTOSTART GROUPS IPV4 IPV6 
privileged RUNNING 0         -      -    -

Como puedes ver, el comando lxc-ls ahora reporta que el contenedor se ha iniciado exitosamente y está ejecutándose. ¿Qué? ¿En dónde está ejecutándose? No ha aparecido ninguna ventana. A diferencia de otras formas de virtualización, como VirtualBox, LXC no muestra en una ventana el escritorio o pantalla de inicio del contenedor en ejecución. En LXC, cada contenedor en ejecución es tratado como si fuera una PC diferente; normalmente no puedes ver otras PCs pero puedes interactuar con ellas a través de tu red local, es por eso que el comando lxc-ls nos muestra la dirección IP del contenedor, aunque en este caso no tiene asignada ninguna.

LXC contiene una utilidad que ofrece otra forma de interactuar con el contenedor, el comando lxc-attach, que automáticamente nos ofrecerá una sesión de consola dentro del contenedor. Podemos probarlo ahora para ver que, en efecto, el contenedor está activo y en ejecución (el texto en rojo corresponde a la consola del contenedor):

monstruosoft@PC:~$ sudo lxc-ls -f
NAME       STATE   AUTOSTART GROUPS IPV4 IPV6 
privileged RUNNING 0         -      -    -    
monstruosoft@PC:~$ sudo lxc-attach -n privileged
root@privileged:/# ls
bin   dev  home  lib64	mnt  proc  run   selinux  sys  usr
boot  etc  lib   media	opt  root  sbin  srv      tmp  var
root@privileged:~# cat /etc/*-release | grep PRETTY
PRETTY_NAME="Debian GNU/Linux stretch/sid"
root@privileged:~# exit
exit
monstruosoft@PC:~$ cat /etc/*-release | grep PRETTY
PRETTY_NAME="Debian GNU/Linux 8 (jessie)"

Podemos ver que, dentro del contenedor, la versión de Linux reportada es Debian Stretch/Sid a diferencia de la versión de Debian Jessie reportada fuera del contenedor. Así podemos comprobar que el contenedor está activo y en ejecución pero en este punto se trata de una versión mínima de la distro que elegimos instalar y muchos comandos y programas no están disponibles. Esto tiene sentido ya que no tiene caso tener una instalación completa en un contenedor, en su lugar es deseable instalar sólo lo que sea necesario para el mismo. Aunque en teoría es posible instalar paquetes manualmente en el contenedor, la opción más obvia es configurar la red de manera que el contenedor pueda conectarse a internet. La configuración de la red se hace exactamente como se haría para otra PC en tu red local pero vayamos paso por paso.

Primero, detengamos el contenedor usando el comando lxc-stop:

monstruosoft@PC:~$ sudo lxc-ls -f
NAME       STATE   AUTOSTART GROUPS IPV4 IPV6 
privileged RUNNING 0         -      -    -    
monstruosoft@PC:~$ sudo lxc-stop -n privileged
monstruosoft@PC:~$ sudo lxc-ls -f
NAME       STATE   AUTOSTART GROUPS IPV4 IPV6 
privileged STOPPED 0         -      -    -

Ahora podemos editar el archivo de configuración del contenedor, ubicado en /var/lib/lxc/prvileged/config, para cambiar la configuración de red. LXC soporta varios tipos de configuración de red para los contenedores. En este caso usaremos la configuración veth para reemplazar la configuración predeterminada en Debian que es empty (que significa que de forma predeterminada el contenedor no tendrá ningún adaptador de red). Para activar la configuración de red veth en el contenedor, debemos editar la sección de Network configuration en el archivo de configuración del contenedor para que luzca de la siguiente manera:

monstruosoft@PC:~$ sudo nano /var/lib/lxc/privileged/config 
# Network configuration
lxc.network.type = veth
lxc.network.hwaddr = 00:FF:AA:xx:xx:xx
lxc.network.flags = up
lxc.network.ipv4 = 10.0.0.69

Al guardar estos cambios e iniciar nuevamente el contenedor podemos ver que esta vez LXC ha asignado automáticamente la IP definida al contenedor:

monstruosoft@PC:~$ sudo lxc-ls -f
NAME       STATE   AUTOSTART GROUPS IPV4 IPV6 
privileged STOPPED 0         -      -    -    
monstruosoft@PC:~$ sudo lxc-start -n privileged
monstruosoft@PC:~$ sudo lxc-ls -f
NAME       STATE   AUTOSTART GROUPS IPV4      IPV6 
privileged RUNNING 0         -      10.0.0.69 -

De la misma forma, podemos ver que LXC ha creado automáticamente un nuevo adaptador de red virtual en nuestro sistema:

monstruosoft@PC:~$ sudo ifconfig 
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:151 errors:0 dropped:0 overruns:0 frame:0
          TX packets:151 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:30770 (30.0 KiB)  TX bytes:30770 (30.0 KiB)

vethAC9K7O Link encap:Ethernet  HWaddr fe:b8:0e:56:8f:90  
          inet6 addr: fe80::fcb8:eff:fe56:8f90/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:14 errors:0 dropped:0 overruns:0 frame:0
          TX packets:27 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:2700 (2.6 KiB)  TX bytes:4233 (4.1 KiB)

Este adaptador de red virtual es el que usaremos para comunicarnos por red con el contenedor. Podemos hacer una sencilla prueba asignando una IP al adaptador de red virtual y haciendo un ping a la IP del contenedor:

monstruosoft@PC:~$ sudo ifconfig vethAC9K7O 10.0.0.96
monstruosoft@PC:~$ ping 10.0.0.69
PING 10.0.0.69 (10.0.0.69) 56(84) bytes of data.
64 bytes from 10.0.0.69: icmp_seq=1 ttl=64 time=0.067 ms
64 bytes from 10.0.0.69: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 10.0.0.69: icmp_seq=3 ttl=64 time=0.033 ms
64 bytes from 10.0.0.69: icmp_seq=4 ttl=64 time=0.040 ms
64 bytes from 10.0.0.69: icmp_seq=5 ttl=64 time=0.036 ms
^C
--- 10.0.0.69 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 3997ms
rtt min/avg/max/mdev = 0.033/0.042/0.067/0.013 ms

Como puedes ver, hemos establecido una conexión de red entre nuestra PC y el contenedor a través del adapatador de red virtual. En este punto podemos tener comunicación por red con el contenedor como lo haríamos con una PC real en nuestra red local. Tener el contenedor como si fuera parte de nuestra red local está bien, nos permitiría hacer algunas cosas que podríamos hacer con otra PC en la red local pero no podemos olvidar que el contenedor tiene una versión mínima de Debian y por lo tanto puede ser que no cuente aún con todas las herramientas y utilidades que deseamos. Para agregar paquetes al contenedor y configurarlo de acuerdo a nuestras necesidades, lo mejor sería que el contenedor pudiera acceder a los repositorios de paquetes en línea, sin embargo aún no cuenta con acceso a internet. Lo ideal para conectar el contenedor a internet es usar el adaptador de red virtual veth que fue creado antes para compartir la conexión a internet de nuestro sistema con el contenedor pero para ello es necesario definir una conexión de puente… y aquí es donde las cosas se pueden poner feas :(.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s