Por: @eduardoh Publicado em: 2022-07-18
OpenVPN MFA - OATH
Objetivo
Testar a funcionalidade de MFA para o serviço OpenVPN utilizando o OATH Toolkit como método de obtenção de Token.
Utilizar recursos gratuitos para habilitar essa funcionalidade ao produto.
O pacote responsável pelo formato MFA será o OATHtool, disponível na versão 2.6.2 e distribuído no repositório Epel. Acesse mais informações sobre o pacote aqui!
Informações
No ambiente de teste, foi utilizado um servidor sem o produto FWFLEX, na versão pura do AlmaLinux 8.6.
A versão do OpenVPN do produto FWFLEX é a 2.4.8-1, já na instalação pura do AlmaLinux foi utilizada a versão 2.4.12-1 disponível no Epel.
Os exemplos de arquivos de configurações e scripts seguindo a documentação, estão disponíveis aqui
Instalação
Como vamos utilizar uma máquina pura, vamos dividir o laboratório em duas partes: preparar o ambiente e configurar o serviço OpenVPN.
Preparando o ambiente
Instalando repositório Epel e aplicativos básicos:
yum install epel-release -y
yum update -y
yum install net-tools bind-utils vim curl git openvpn -y
Manipulando recursos
Como nosso ambiente é puro, precisamos ativar recursos de roteamento e também desativar alguns recursos de segurança.
Habilitando roteamento:
vim /etc/sysctl.conf
net.ipv4.ip_forward = 1
sysctl -p
Desabilitando SELinux:
vim /etc/selinux/config
SELINUX=permissive
setenforce 0
Desabilitando FirewallD:
systemctl stop firewalld
systemctl disable firewalld
Gerando certificados para o ambiente
Gerar certificados via OpenSSL criando a CA e mais dois certificados completos (Cert e Key) para o servidor VPN e para o cliente.
CN | Local | Senha |
---|---|---|
CA-RAIZ |
Servidor CA | 12345 |
vpn.mfa.itflex |
Servidor OpenVPN | — |
Colaborador |
Cliente OpenVPN | — |
Arquivos disponíveis aqui.
Configurando o OpenVPN sem MFA
O primeiro passo é estabelecer uma comunicação OpenVPN básica, apenas por certificado digital, onde o objetivo é garantir que o serviço básico de VPN está funcionando sem problemas.
Após possuir os certificados, iniciar o processo de configuração.
Configurando o OpenVPN Server
Editar o arquivo /etc/openvpn/server/server.conf
adicionando o seguinte conteúdo:
dev tun
proto udp4
port 1194
ca caCert.pem
cert serverCert.pem
key serverKey.pem
dh dh2048.pem
topology subnet
server 10.0.8.0 255.255.255.0
verb 4
keepalive 10 60
daemon
log /var/log/mfa_openvpn.log
Garantir que os certificados estejam com os mesmos nomes e na pasta /etc/openvpn/server/
.
Reiniciar e validar logs.
Configurando o OpenVPN Client
Para o cliente, iremos utilizar uma arquivo de configuração que já contém as chaves do mesmo:
remote vpn.mfa.itflex 1194
dev tun
proto udp4
client
verb 4
keepalive 10 60
persist-tun
<ca>
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
</ca>
<cert>
-----BEGIN CERTIFICATE-----
[...]
-----END CERTIFICATE-----
</cert>
<key>
-----BEGIN PRIVATE KEY-----
[...]
-----END PRIVATE KEY-----
</key>
Ativando recurso MFA no OpenVPN
Neste exemplo iremos utilizar apenas ferramentas gratuitas. Os próximos passos foram executados seguindo a documentação disponível no site oficial do OpenVPN.
Instalando novos recursos
Instalando o pacote OATHtool:
yum install oathtool -y
Atualizando arquivos de configuração
Atualize o arquivo de configuração do servidor OpenVPN adicionando o seguinte conteúdo ao final do mesmo:
vim /etc/openvpn/server/server.conf
[...]
script-security 2
auth-user-pass-verify ./oath.sh via-file
Conteúdo do script oath.sh:
#!/bin/sh
#
# Sample script to verify MFA using oath-tool
passfile=$1
# Get the user/pass from the tmp file
user=$(head -1 $passfile)
pass=$(tail -1 $passfile)
# Find the entry in our oath.secrets file, ignore case
secret=$(grep -i -m 1 "$user:" oath.secrets | cut -d: -f2)
# Calculate the code we should expect
code=$(oathtool --totp $secret)
if [ "$code" = "$pass" ];
then
exit 0
fi
# See if we have password and MFA, or just MFA
echo "$pass" | grep -q -i :
if [ $? -eq 0 ];
then
realpass=$(echo "$pass" | cut -d: -f1)
mfatoken=$(echo "$pass" | cut -d: -f2)
# put code here to verify $realpass, the code below the if validates $mfatoken or $pass if false
# exit 0 if the password is correct, the exit below will deny access otherwise
fi
# If we make it here, auth hasn't succeeded, don't grant access
exit 1
Atualize o arquivo de configuração do cliente OpenVPN adicionando o seguinte:
auth-user-pass
Lembrando que os arquivos e scripts estão disponíveis na sessão de informações.
Criando estrutura de arquivos para receber os tokens
No exemplo iremos utilizar um usuário simples chamado usuario@mfa.itflex
(não precisa ser igual ao CN do certificado). O formato do arquivo é simples, utilizando user:secret
touch /etc/openvpn/server/oath.secrets
Gerando uma nova chave para nosso usuário de exemplo:
head -10 /dev/urandom | sha256sum | cut -b 1-30
2d7c75094de3bb5ea34a12510faec9
O valor gerado com o comando acima, deverá ser adicionado após o usuário identificado no arquivo /etc/openvpn/server/oath.secrets
, contemplando o formato user:secret
.
O arquivo /etc/openvpn/server/oath.secrets
ficará da seguinte forma:
usuario@mfa.itflex:2d7c75094de3bb5ea34a12510faec9
Entendendo como funciona a geração token de acesso e integração com OpenVPN
Agora que temos as referências devidamente configurados, devemos então gerar o token de 6 dígitos que será utilizado para autenticar nosso usuário assim que ele iniciar o processo de conexão ao OpenVPN.
O software responsável pela geração deste token é o OATHtool, instalado anteriormente.
A geração do token acontece utilizando a secret
gerada anteriormente é utilizada como entrada no software OATHtool, assim ele gera um token aleatório de 6 dígitos para que o usuário possa utilizar o token ao invés da senha no momento da autenticação, esse token expira em 30 segundos, idêntico ao funcionamento de um sistema de token de acesso ao Gmail por exemplo.
Para o OpenVPN o funcionamento é simples, utiliza-se o funcionamento básico enviando dados de usuário e senha para um script externo (oath.sh), nesse script (disponível anteriormente) existe uma checagem rápida utilizando os dados passados e enviando a chave do usuário para o software OATHtool, se o código gerado com a secret do usuário for igual ao código que o script validou, o acesso a VPN é permitido, caso contrário ele barra o acesso, não sendo possível conectar com um token diferente do verdadeiro.
Abaixo um diagrama para exemplificar o funcionamento:
Utilizando a geração de token manualmente:
oathtool --totp -v [SECRET DO USUÁRIO]
cat oath.secrets
usuario@mfa.itflex:2d7c75094de3bb5ea34a12510faec9
oathtool --totp -v 2d7c75094de3bb5ea34a12510faec9
Hex secret: 2d7c75094de3bb5ea34a12510faec9
Base32 secret: FV6HKCKN4O5V5I2KCJIQ7LWJ
Digits: 6
Window size: 0
Step size (seconds): 30
Start time: 1970-01-01 00:00:00 UTC (0)
Current time: 2022-07-19 19:11:16 UTC (1658257876)
Counter: 0x34B6EFE (55275262)
015365
Neste momento já podemos conectar ao OpenVPN utilizando o usuário usuario@mfa.itflex
com o token 015365
gerado utilizando a chave do usuário.
Visualizando os logs no servidor:
Formas de autenticação
Autenticação por QRcode via Google Authenticator
Diante da facilidade em utilizar o Google Authenticator, no artigo usado como base para o desenvolvimento deste tutorial, temos a possibilidade de gerar um link que podemos inserir em um QRcode, podendo assim fazer a leitura do mesmo no aplicativo Google Authenticator e utilizar para gerar nosso token de autenticação de usuário de 6 dígitos.
Verificando o script
cat /etc/openvpn/server/oath-secret-gen.sh
#!/bin/sh
# works on FreeBSD, your mileage on Linux may vary
# Set your issuer string here, this isn't very intelligent and doesn't URL-encode the string
issuer='iTFLEX'
#userhash=$(head -10 /dev/urandom | sha256sum | cut -b 1-30)
userhash=412697e6a44563430fe17f910a26b4
base32=$(/usr/bin/oathtool --totp -s 90 -v "$userhash" | grep Base32 | awk '{print $3}')
echo "User String:"
echo "otpauth://totp/$issuer:$1?secret=$base32"
echo "oath.secrets entry:"
echo "$1:$userhash"
- O parâmetro
issuer
deverá ser o nome da organização. - O parâmetro
userhash
deverá ser a secret do usuário prestes a se conectar na VPN contida no arquivocat /etc/openvpn/server/oath.secrets
.
Foram usados valores fixos para facilitar nos testes em laboratório.
Executando o comando manualmente:
./oath-secret-gen.sh caca
User String:
otpauth://totp/iTFLEX:caca?secret=IETJPZVEIVRUGD7BP6IQUJVU
oath.secrets entry:
caca:412697e6a44563430fe17f910a26b4
Na saída do comando acima, temos os valores referentes ao link que devemos usar para gerar o QRcode: otpauth://totp/iTFLEX:caca?secret=IETJPZVEIVRUGD7BP6IQUJVU
Podemos usar então para enviar um e-mail ao usuário, por exemplo. Neste laboratório, utilizamos um simples script Python para capturar a informação do link, gerar um QRcode e enviar para o usuário.
Instalando o Python e suas dependências:
yum install python3 -y
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade Pillow
python3 -m pip install --upgrade qrcode
Script Python para geração do QRcode e envio de e-mail:
import json
import qrcode
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from subprocess import Popen, PIPE
from email.mime.base import MIMEBase
from email import encoders
import smtplib
# Gerando QRcode
cmd = "./oath-secret-gen.sh caca | head -2 | tail -1"
with Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE) as proc:
stdout, _ = proc.communicate()
token = stdout.decode()
qr = qrcode.make(token)
qr.save("/tmp/qrcode.jpg")
# Corpo da mensagem
msg = MIMEMultipart()
message = "O token é " + token
file = "/tmp/qrcode.jpg"
attachment = open(file, "rb")
part = MIMEBase("image", "jpeg")
part.set_payload((attachment).read())
encoders.encode_base64(part)
msg.attach(part)
# Credenciais e assunto
password = "SENHA-DO-EMAIL"
msg['From'] = "E-MAIL-DE-ORIGEM"
msg['To'] = "E-MAIL-DO-DESTINATÁRIO"
msg['Subject'] = "Aqui está o seu QRcode de acesso!"
msg
# Conexão e envio
msg.attach(MIMEText(message, 'plain'))
server = smtplib.SMTP('smtp.gmail.com', port=587)
server.starttls()
server.login(msg['From'], password)
server.sendmail(msg['From'], msg['To'], msg.as_string())
server.quit()
Fazendo teste de envio (lembrando que os valores são fixos, pode-se alterar de acordo com o necessário):
python3 send-email.py
Validando o recebimento do e-mail:
Agora, com o QRcode em mãos, podemos acessar o Google Authenticator em qualquer celular e escolher a opção de “ler QRcode”:
Será adicionada a nova “conta” referente ao usuário solicitado e então agora já podemos usar o Google Authenticator para gerar o token de acesso aos nossos usuários:
Links úteis
- Doc de referência - Carlão
- https://openvpn.net/blog/diy-mfa-setup-community-edition/
- http://www.nongnu.org/oath-toolkit/
Análise geral
A funcionalidade irá agregar muito valor ao produto. Sugiro bolarmos uma estragégia de negócio para essa funcionalidade, comparando recursos e a aderência dessa funcionalidade, pois irá gerar impacto para os usuários finais, precisamos saber do esforço em desenvolver e também do retorno dos usuários, pois o processo de autenticação, assim que ativarmos o MFA se torna mais demorado e burocrático, o que irá causar atrito entre usuários e administradores do FWFLEX. A critério de segurança, devemos mesclar mais de um fator além do MFA e certificado, uma sugestão seria obrigar o certificado a ter senha, sendo assim, o usuário informa a primeira vez e depois só gera o token a partir de um aplicativo como Google Authenticator.
Critérios levados em consideração:
-
Licenciamento: Além do recurso do OpenVPN que já temos conhecimento, com a integração do MFA utilizamos o software OATHtool, que está sob licença LGPLv2+ (libs) e GPLv3+ (tool).
-
Suporte: Até o mês de maio de 2021 a ferramenta era bastante atualizada segundo seu próprio fórum. O código da ferramenta está disponível no Gitlab.
-
Instalação: A instalação do software e integração ao OpenVPN é simples, pois existem scripts de apoio no artigo disponível que foi utilizado como base, uma das dificuldades foi o entendimento do fluxo, desde o login do usuário até a geração do token.
-
Configuração: Utiliza-se o recurso de
auth-user-pass-verify
seguido de script para a validação do token informado, isso acaba sendo um risco pensando em utilização a larga escala. -
Integração com o produto: A funcionalidade é aderente ao produto, um formato sugerido seria criar um novo modo de instância além do que já existe hoje, permitindo o administrador a criar um modelo de instância com suporte a MFA. Um ponto sobre os scripts, deveríamos melhorar/refatorar para a nossa realidade, levando em consideração mais aspectos ou detalhes, como no momento da conexão do usuário, identificar quem é e fazer o envio do QRcode, além de uma validação para saber se o usuário já inseriu a sua chave no seu aplicativo Google Authenticator, por exemplo. Outro ponto é possuir um padrão, além do token exigir que para instâncias MFA, o certificado digital do usuário tenha senha, quando o usuário conectar a primeira vez no OpenVPN a partir do seu dispositivo, ele vai inserir a senha do certificado e marcar para salvar, e em seguida o token, na próxima conexão somente o token vai ser exigido, pois a senha do certificado já vai estar salva no dispositivo. Assim aumentamos ainda mais a confiabilidade no processo de autenticação do usuário ao OpenVPN.
-
Performance: A utilização do parâmetro comentado anteriormente, o
auth-user-pass-verify
, sabemos que a larga escala acaba se mostrando lento e causando possíveis travamentos na instância. -
Limitações/impedimentos/riscos: Utilização do parâmetro
auth-user-pass-verify
causando lentidão e travamentos. Outra dúvida sobre ser um risco ou não, seria o custo x benefício de desenvolver essa integração para o produto, e o próprio processo do MFA causar atrito com o administrador do FW e seus usuários, pois o processo de MFA não é rápido, precisar seguir os passos, possuir o aplicativo, inserir o QRcode, etc, o que pode causar reclamações por parte do usuário e do administrador do FW para nós. O processo do MFA não tem como ser mudado, talvez podemos pensar em agilizar algo, mas precisamos antes saber da real aderência e se os usuários irão seguir o processo correto, ou vão reclamar da demora ou dificuldade (?) de conectar sem ao menos entender o que é o melhor para ele em questão de segurança. -
Referência de soluções alternativas: Conforme salientado pelo Carlão na sua doc, temos opções pagas (menos atraentes). Para maiores informações acessar a doc do Carlão.