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