Segundo factor de autenticación en sshd para Raspberry Pi (Arch Linux)

Hace tiempo ya vimos cómo configurar el servicio sshd de un BackTrack 5 para que utilice una clave TOTP (time-based one-time password) mediante el módulo PAM de Google Authenticator. Luego generábamos una clave secreta (en base 32) y la utilizábamos en la aplicación de nuestro teléfono móvil para tener siempre a mano el código que se genera secuencialmente cada 30 segundos.

Ahora implementaremos también ese segundo factor de autenticación en el servicio sshd de nuestro Arch Linux en la Raspberry Pi, pero esta vez sólo nos pedirá el código cuando la IP del cliente ssh no esté incluida en una lista blanca. El motivo está claro: es un auténtico coñazo tener que estar introduciendo el código cada vez que iniciamos una nueva sesión y es algo que quiero evitar al menos si estoy conectándome desde el equipo de casa.

Para ello y basándonos fundamentalmente en una entrada de moocode, forzaremos la ejecución de un script en Ruby después de validarnos con el primer método de autenticación (normalmente la típica contraseña). Este script primero comprobará si la IP del cliente ssh está en el fichero 'ips.txt' del usuario y, en el caso de no estarlo, no devolverá una shell hasta que el código TOTP sea correcto. Bastante sencillo como véreis a continuación.

Lo primero que vamos a hacer es instalar ruby (recordemos que partimos de un sistema bastante minimalista en su primera instalación):
[usuario1@pirobot1 ~]$ sudo pacman -S ruby
resolving dependencies...
looking for inter-conflicts...

Targets (2): libyaml-0.1.4-2  ruby-1.9.3_p385-1

Total Download Size:    2.89 MiB
Total Installed Size:   18.05 MiB

Proceed with installation? [Y/n]
:: Retrieving packages from extra...
 ruby-1.9.3_p385-1-a...     2.8 MiB   730K/s 00:04 [######################] 100%
:: Retrieving packages from community...
 libyaml-0.1.4-2-armv6h    57.1 KiB  1071K/s 00:00 [######################] 100%
(2/2) checking package integrity                   [######################] 100%
(2/2) loading package files                        [######################] 100%
(2/2) checking for file conflicts                  [######################] 100%
(2/2) checking available disk space                [######################] 100%
(1/2) installing libyaml                           [######################] 100%
(2/2) installing ruby                              [######################] 100%
The default location of gem installs is $HOME/.gem/ruby
Add the following line to your PATH if you plan to install using gem
$(ruby -rubygems -e "puts Gem.user_dir")/bin
If you want to install to the system wide location, you must either:
edit /etc/gemrc or run gem with the --no-user-install flag.
Optional dependencies for ruby
    tk: for Ruby/TK
    ruby-docs: Ruby documentation
[usuario1@pirobot1 ~]$

Luego actualizamos todas las gemas del sistema. Para los no iniciados decir que las gemas de Ruby son paquetes de librerías para Ruby que se instalan en el sistema y quedan listas para ser usadas, con un simple require o con mecanismos que aporta el propio sistema.
[root@pirobot1 ~]# gem update --system
Updating rubygems-update

Después instalamos ROTP (The Ruby One Time Password Library) que además es compatible con la aplicación Google Authenticator:
[usuario1@pirobot1 rubygems]$ gem install rotp
Fetching: rotp-1.4.1.gem (100%)
Successfully installed rotp-1.4.1
1 gem installed
Installing ri documentation for rotp-1.4.1...
Installing RDoc documentation for rotp-1.4.1...

El siguiente paso es generar la clave secreta que compartirán la Raspberry Pi y el teléfono con la aplicación (Android o iPhone). Para ello utilizaremos el siguiente script:
#!/usr/bin/env ruby
require 'rubygems'
require 'rotp'

secret = ROTP::Base32.random_base32
data = "otpauth://totp/#{`hostname`.strip}?secret=#{secret}"
url = "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=#{data}"

puts "Your secret key is: #{secret}"
puts url
[usuario1@pirobot1 ~]$ sudo ruby generate_secret.rb
[sudo] password for usuario1:
Your secret key is: j2zv2e7diygll6gx
https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/pirobot1?secret=j2zv2e7diygll6gx

We can scan the QR code directly into Google Authenticator and then update our authorized_keys file as follows:

Como nos indica el mensaje anterior podemos escanear con el teléfono el código QR para que tengamos la secuencia para los TOTP preparada.


Ahora sólo nos queda preparar nuestro sencillo script, por ej. vi /usr/bin/2ndfactor.rb, y que me perdonen los eruditos por mis sucias manos de programador de Ruby:
#!/usr/bin/env ruby
require 'rubygems'
require 'rotp'

def ip2long(ip)
  long = 0
  ip.to_s.split(/\./).each_with_index do |b, i|
    long += b.to_i << ( 8*i )
  end
  long
end

# clave secreta OTP
secret = 'j2zv2e7diygll6gx'

# comprueba lista blanca de IPs
client_ip = ENV['SSH_CLIENT'].to_s.split(' ').first
client_ip = ip2long(client_ip)
ipLST='IP.txt'

File.readlines(ipLST).each do |line|
    white_client_ip = "#{line}"
    white_client_ip = ip2long(white_client_ip)
        if white_client_ip == client_ip
                STDOUT.write "IP en lista blanca. Bienvenid@\n"
                Kernel.exec ENV['SSH_ORIGINAL_COMMAND'] || ENV['SHELL']
        else
        end
end

# solicita al usuario el codigo de validacion
STDERR.write "Introduce el codigo de validacion: "
until validation_code = STDIN.gets.strip
  sleep 1
end

# comprueba si el codigo de validacion es correcto
abort "codigo NO valido" unless validation_code == ROTP::TOTP.new(secret).now.to_s

# el usuario se ha validado y obtendra el shell correspondiente
Kernel.exec ENV['SSH_ORIGINAL_COMMAND'] || ENV['SHELL']

Nota: Como véis en el script está la clave secreta para el TOTP, por lo que los permisos necesarios para su ejecución (755) pueden ser un riesgo en ciertos entornos en los que pudieramos necesitar enjaular el servicio o cifrar u ofuscar el fichero. Luego no digáis que no os he avisado ;)

Por último no olvidemos crear un fichero de texto con las IPs de nuestra lista blanca (con las que queremos que no se nos solicite código de validación):
[usuario1@pirobot1 ~]$ vi .ssh/ips.txt
192.168.23.23
192.168.23.2
192.168.1.35
~
~

Ya sólo queda añadir al final del fichero de configuración del demonio ssh (sshd_config) la siguiente línea para que ejecute el script:
[root@pirobot1 ~]# vi /etc/ssh/sshd_config
#       ForceCommand cvs server
ForceCommand /usr/bin/2ndfactor.rb

Y reiniciar el servicio para probar:
[root@pirobot1 ~]# systemctl restart sshd

Con una IP de nuestra lista blanca...
login as: usuario1
usuario1@192.168.1.36's password:
IP en lista blanca. Bienvenid@
[usuario1@pirobot1 ~]$

Sin una IP en la lista blanca e introduciendo los códigos correspondientes de validación...

[root@pirobot1 bin]# ssh usuario1@localhost
usuario1@localhost's password:
Introduce el codigo de validacion: 411528

¡Y ya lo tenemos! Ni que decir tiene que esto puede configurarse de manera similar en cualquier otra distribución Linux. 

En la próxima entrada relacionada veremos port knocking y es que, antes de publicar cualquier servicio de nuestra RPi en Internet, tendremos que estar seguros... 

"toc, toc, toc.."

Comentarios

  1. La semana pasada vi un post sobre como hacer lo mismo, pero empleando un módulo de pam.d
    Dejo aquí el enlace por si pudiera ser de interés: http://puppetlinux.blogspot.com.es/2013/02/ssh-2-pasos.html

    ResponderEliminar

Publicar un comentario