GMGall’s blog

Programação, Linux e o que mais der na telha

Arquivos da Categoria: Sysadmin

Criando suas próprias ações no fail2ban – parte 3

Criando suas próprias ações

Estrutura de uma ação

Cada ação é um arquivo no diretório action.d. Esses arquivos seguem a seguinte estrutura:

[Definition]

# Option:  actionstart
# Notes.:  comando executado ao iniciar o Fail2Ban.
# Values:  CMD
#
actionstart =


# Option:  actionstop
# Notes.:  comando executado ao encerrar o Fail2Ban
# Values:  CMD
#
actionstop =


# Option:  actioncheck
# Notes.:  comando executado antes de cada comando actionban
# Values:  CMD
#
actioncheck =


# Option:  actionban
# Notes.:  comando executado ao banir um IP. Observe que o comando
#          é executado com as permissões do usuário executando o Fail2Ban.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
actionban = ipfw add deny tcp from <ip> to <localhost> <port>


# Option:  actionunban
# Notes.:  comando executado ao "desbanir" um IP. Observe que o comando
#          é executado com as permissões do usuário executando o Fail2Ban.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
actionunban = ipfw delete `ipfw list | grep -i <ip> | awk '{print $1;}'`

[Init]

# Option:  port
# Notes.:  specifies port to monitor
# Values:  [ NUM | STRING ]
#
port = ssh

# Option:  localhost
# Notes.:  the local IP address of the network interface
# Values:  IP
#
localhost = 127.0.0.1

O arquivo que usei de exemplo acima já vem com o pacote e configura uma ação que usa o ipfw para bloquear os IPs. Traduzi os comentários da seção [Definition] para explicar o que cada entrada define.

A seção [Init] define “variáveis” que podem ser usadas ao longo do arquivo. Nesse exemplo, port e localhost, mas que variáveis definir é por conta do usuário. Lembrando que <ip> vira o IP/hostname casado no grupo <host> dos filtros.

Definindo uma ação

Existem ações predefinidas que bloqueiam por iptables, ipfw, shorewall, TCP wrappers, que avisam por e-mail a cada bloqueio… Mas não existe nenhuma que avise via Gtalk. Vamos criar uma ação que faz isso.

Usarei o programa sendxmpp para definir uma ação que enviará uma mensagem para mim no Gtalk a cada evento. Como configurar o sendxmpp para o Gtalk pode ser visto aqui. Conheci o sendxmpp num post do blog do João Eriberto Mota Filho (@eribertomota).

Vamos à listagem do arquivo action.d/gtalk.local:

[Definition]

actionstart = printf %%b "Hi,\n
              The jail <name> has been started successfully.\n
              Regards,\n
              Fail2Ban"|sendxmpp -t -u <from> -o gmail.com 

actionstop = printf %%b "Hi,\n
             The jail <name> has been stopped.\n
             Regards,\n
             Fail2Ban"|sendxmpp -t -u <from> -o gmail.com 

actioncheck =

actionban = printf %%b "Hi,\n
            The IP <ip> has just been banned by Fail2Ban after
            <failures> attempts against <name>.\n
            Regards,\n
            Fail2Ban"|sendxmpp -t -u <from> -o gmail.com <to>

actionunban =

[Init]

name = default

from =

to =

Essa ação enviará uma mensagem para o usuário definido em to tendo como remetente o usuário definido em from ao iniciar, parar e ao bloquear um IP.

Ativando sua ação

As ações são definidas por jail ou globalmente na seção [DEFAULT] de jail.local. Ações definidas nas jails tem prioridade sobre as definidas globalmente.

Observe o seguinte trecho de jail.local:

#
# ACTIONS
#

# Default banning action (e.g. iptables, iptables-new,
# iptables-multiport, shorewall, etc) It is used to define
# action_* variables. Can be overriden globally or per
# section within jail.local file
banaction = iptables-multiport

# email action. Since 0.8.1 upstream fail2ban uses sendmail
# MTA for the mailing. Change mta configuration parameter to mail
# if you want to revert to conventional 'mail'.
mta = sendmail

# Default protocol
protocol = tcp

#
# Action shortcuts. To be used to define action parameter

# The simplest action to take: ban only
action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s]

# ban & send an e-mail with whois report to the destemail.
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s]
%(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s]

# ban & send an e-mail with whois report and relevant log lines
# to the destemail.
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s]
%(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s]

# Choose default action. To change, just override value of 'action' with the
# interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local
# globally (section [DEFAULT]) or per specific section
action = %(action_)s

A variável action define a ação globalmente. As outras variáveis definidas antes (action_mvl, action_mw e action_) são atalhos úteis. Leia os comentários com atenção para entender como essas variáveis interagem.

Repare que mais de uma ação pode ser setada por linha e que cada ação pode receber parâmetros entre colchetes. Esses parâmetros definem os valores das variáveis declaradas na seção [Init]. Os atalhos action_mvl, action_mw e action_ são úteis por já ativarem ações e passarem parâmetros funcionais para tarefas rotineiras como banir e enviar um e-mail com informações úteis.

Para definir nossa ação gtalk globalmente, basta fazer

action = %(action_)s
        gtalk[name=%(__name__)s, from=gmgall, to=gmgall]

e recarregar as configurações do serviço:

# /etc/init.d/fail2ban reload

Funciona!

Screenshot do pidgin mostrando a mensagem enviada pela ação que acabamos de criar

Criando seus próprios filtros no fail2ban – parte 2

Criando seus próprios filtros

Se não existe um filtro pronto para o log que você deseja monitorar em filter.d, será necessário criar seu próprio filtro. Mostrarei como fazer isso através do exemplo que descrevo abaixo:

Cenário do exemplo

Mantenho um wiki moinmoin e desejo bloquear o acesso à ele pelos hosts que tentarem login por mais de 3 vezes sem sucesso. Vamos fazer um filtro para fazer esse bloqueio. O log do wiki é escrito em /var/log/moinmoin.log. Segue trecho desse log:

2011-07-12 15:45:40,447 MoinMoin.Page WARNING The page "MissingPage" could not be found. Check your underlay directory setting.
2011-07-12 15:45:44,002 MoinMoin.auth WARNING moin: performing login action | request from 192.168.0.10
2011-07-12 15:45:44,003 MoinMoin.auth WARNING moin: could not authenticate user u'GuilhermeGall' (not valid) | request from 192.168.0.10
2011-07-12 15:45:44,030 MoinMoin.Page WARNING The page "MissingPage" could not be found. Check your underlay directory setting.
2011-07-12 15:45:47,705 MoinMoin.auth WARNING moin: performing login action | request from 192.168.0.10
2011-07-12 15:45:47,706 MoinMoin.auth WARNING moin: could not authenticate user u'GuilhermeGall' (not valid) | request from 192.168.0.10
2011-07-12 15:45:47,732 MoinMoin.Page WARNING The page "MissingPage" could not be found. Check your underlay directory setting.
2011-07-12 15:55:59,473 MoinMoin.Page WARNING The page "MissingPage" could not be found. Check your underlay directory setting.
2011-07-12 16:08:51,543 MoinMoin.Page WARNING The page "MissingPage" could not be found. Check your underlay directory setting.
2011-07-13 09:09:01,908 MoinMoin.auth WARNING moin: performing login action | request from 192.168.0.7

Não é difícil perceber que as linhas com could not authenticate user u'GuilhermeGall' (not valid) representam as tentativas de login malsucedidas. Se desejamos bloquear os hosts de origem dessas tentativas temos que fazer a regex do filtro casar essas linhas e usar o IP que aparece nelas para executar nossa ação (por default, bloquear via iptables).

Antes de criar nosso filtro, vamos entender a estrutura de um filtro e como desenvolver nossas próprias regexes.

Estrutura de um filtro

Um filtro é simplesmente um arquivo com uma entrada failregex, que define as regexes que casam as linhas que representam as tentativas de login malsucedidas, e uma entrada ignoreregex, que define regexes que casam com linhas que devem ser ignoradas. Outras entradas podem existir, como before que faz um “import” de outro arquivo, mas failregex e ignoreregex são as essenciais e usadas na maioria dos casos.

Se for definir mais de uma regex para failregex ou ignoreregex, coloque uma por linha. Exemplo do arquivo filter.d/apache-auth.conf que já vem no pacote fail2ban:

[Definition]

# Option:  failregex
# Notes.:  regex to match the password failure messages in the logfile. The
#          host must be matched by a group named "host". The tag "<HOST>" can
#          be used for standard IP/hostname matching and is only an alias for
#          (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values:  TEXT
#
failregex = [[]client <HOST>[]] user .* authentication failure
            [[]client <HOST>[]] user .* not found
            [[]client <HOST>[]] user .* password mismatch

# Option:  ignoreregex
# Notes.:  regex to ignore. If this regex matches, the line is ignored.
# Values:  TEXT
#
ignoreregex =

Conforme pode ser lido nos comentários do arquivo acima, a tag <HOST> deve aparecer dentro da regex na posição onde aparece o IP/hostname do host ofensor. Repare que mais de uma regex foi definida para failregex – uma em cada linha – e que ignoreregex pode ser vazio.

Desenvolvendo suas próprias regexes

Para escrever suas próprias regexes para o fail2ban é preciso ter em mente o seguinte:

  • Em toda linha de uma failregex, a parte que casa com o IP/hostname deve estar envolta pela estrutura (?P<host> ... ). Essa estrutura é uma extensão específica do Python que atribui o nome <host> ao que foi casado pelo grupo. A tag <host> é como você informa ao fail2ban qual host estava tentando logar.
  • Como conveniência, é possível usar <HOST> nas suas regexes, conforme citei no tópico anterior. <HOST> é um alias para (?:::f{4,6}:)?(?P<host>\S+) que casa um IP/hostname dentro de um grupo chamado <host>. Vide item anterior.
  • Nas ações, a tag <ip> será substituída pelo IP do host casado pela tag <host>, por isso sempre deve existir um grupo nomeado <host>.
  • Para que uma linha de um log case com sua failregex, ela deve casar em duas partes: o início da linha tem que casar com um padrão de timestamp e o restante da linha deve casar com a regex definida em failregex. Se sua failregex possui a âncora ^, então a âncora refere-se ao início do restante da linha, após o timestamp.
  • Por último, mas não menos importante, o comando fail2ban-regex permite testar suas regexes antes de criar o filtro. Na realidade, como escrever suas próprias regexes pode envolver alguma – muita! – tentativa e erro no começo, eu diria que esse é o item mais importante. 🙂 Ele pode ser usado de duas maneiras:
fail2ban-regex /path/para/arquivo.log '^regex a ser testada$'

ou

fail2ban-regex 'linha exemplo de log' '^regex a ser testada$'

Definindo o filtro

A regex que casa com as linhas que representam as tentativas de login malsucedidas é:

MoinMoin\.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL) moin: could not authenticate user .* \(not valid\) \| request from <HOST>$

Não é do escopo desse artigo ensinar expressões regulares. Tem muito material bom sobre isso por aí, mas resumindo a expressão acima:

  • MoinMoin\.auth casa MoinMoin.auth
  • (DEBUG|INFO|WARNING|ERROR|CRITICAL) casa DEBUG, INFO, WARNING, ERROR ou CRITICAL. Eu poderia ter casado apenas um dos níveis de severidade, mas ainda estou decidindo em qual nível reportarei as mensagens referentes à tentativas de login.
  • moin: could not authenticate user casa moin: could not authenticate user
  • .* casa qualquer caractere em qualquer quantidade
  • \(not valid\) \| request from casa (not valid) | request from
  • <HOST> casa o IP/hostname. É a tal tag indicativa de onde está o IP/hostname que é substituída por (?:::f{4,6}:)?(?P<host>\S+)
  • $ casa o fim da linha

Testando uma linha de exemplo do log com o fail2ban-regex:

$ fail2ban-regex '2011-07-18 14:24:42,687 MoinMoin.auth WARNING moin: could not authenticate user u'UserName' (not valid) | request from 192.168.0.27' 'MoinMoin\.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL) moin: could not authenticate user .* \(not valid\) \| request from <HOST>$'

Running tests
=============

Use regex line : MoinMoin\.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL)...
Use single line: 2011-07-18 14:24:42,687 MoinMoin.auth WARNING moin...

Results
=======

Failregex
|- Regular expressions:
|  [1] MoinMoin\.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL) moin: could not authenticate user .* \(not valid\) \| request from <HOST>$
|
`- Number of matches:
   [1] 1 match(es)

Ignoreregex
|- Regular expressions:
|
`- Number of matches:

Summary
=======

Addresses found:
[1]
    192.168.0.27 (Mon Jul 18 14:24:42 2011)

Date template hits:
0 hit(s): MONTH Day Hour:Minute:Second
0 hit(s): WEEKDAY MONTH Day Hour:Minute:Second Year
0 hit(s): WEEKDAY MONTH Day Hour:Minute:Second
0 hit(s): Year/Month/Day Hour:Minute:Second
0 hit(s): Day/Month/Year Hour:Minute:Second
0 hit(s): Day/Month/Year Hour:Minute:Second
0 hit(s): Day/MONTH/Year:Hour:Minute:Second
0 hit(s): Month/Day/Year:Hour:Minute:Second
2 hit(s): Year-Month-Day Hour:Minute:Second
0 hit(s): Day-MONTH-Year Hour:Minute:Second[.Millisecond]
0 hit(s): Day-Month-Year Hour:Minute:Second
0 hit(s): TAI64N
0 hit(s): Epoch
0 hit(s): ISO 8601
0 hit(s): Hour:Minute:Second
0 hit(s): 

Success, the total number of match is 1

However, look at the above section 'Running tests' which could contain important
information.

Perceba que o comando mostrou a regex e o número de casamentos, além do IP encontrado abaixo de “Addresses found”, indicando que a regex está correta. O número de casamentos presumivelmente seria maior que 1, se o comando fosse executado contra um arquivo de log ao invés de apenas contra uma linha de exemplo.

Vamos supor você tenha cometido um erro, como por exemplo não especificar um grupo chamado host na sua regex:

$ fail2ban-regex '2011-07-18 14:24:42,687 MoinMoin.auth WARNING moin: could not authenticate user u'UserName' (not valid) | request from 192.168.0.27' 'MoinMoin\.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL) moin: could not authenticate user .* \(not valid\) \| request from$'

Running tests
=============

Use regex line : MoinMoin\.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL)...
Use single line: 2011-07-18 14:24:42,687 MoinMoin.auth WARNING moin...

No 'host' group in 'MoinMoin\.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL) moin: could not authenticate user .* \(not valid\) \| request from$'
Cannot remove regular expression. Index 0 is not valid

Results
=======

Failregex
|- Regular expressions:
|  [1] MoinMoin\.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL) moin: could not authenticate user .* \(not valid\) \| request from$
|
`- Number of matches:
   [1] 0 match(es)

Ignoreregex
|- Regular expressions:
|
`- Number of matches:

Summary
=======

Sorry, no match

Look at the above section 'Running tests' which could contain important
information.

Perceba que o fail2ban-regex informa o erro No 'host' group in...

Como já temos a regex funcional, crie um arquivo com o nome do filtro em filter.d:

# cd /etc/fail2ban/
# cat filter.d/moinmoin.conf
[Definition]

# Option:  failregex
# Notes.:  regex to match the password failures messages in the logfile. The
#          host must be matched by a group named "host". The tag "<HOST>" can
#          be used for standard IP/hostname matching and is only an alias for
#          (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values:  TEXT
#
failregex = MoinMoin.auth (DEBUG|INFO|WARNING|ERROR|CRITICAL) moin: could not authenticate user .* \(not valid\) \| request from <HOST>$

# Option:  ignoreregex
# Notes.:  regex to ignore. If this regex matches, the line is ignored.
# Values:  TEXT
#
ignoreregex =

Crie uma jail que usa o filtro recém-criado em jail.local:

[moinmoin]

enabled = true
port = 80
filter = moinmoin
logpath = /var/log/moinmoin.log
maxretry = 3

A variável filter define o nome do filtro, que é o nome do arquivo criado em filter.d sem a extensão .conf.

Reinicie o fail2ban para ativar a nova jail:

# /etc/init.d/fail2ban restart

É interessante monitorar o log do próprio fail2ban, que por padrão fica em /var/log/fail2ban.log, para verificar se suas alterações foram aplicadas com sucesso:

# tail -f /var/log/fail2ban.log
2011-08-01 09:52:04,994 fail2ban.filter : INFO   Set findtime = 600
2011-08-01 09:52:04,994 fail2ban.actions: INFO   Set banTime = 600
2011-08-01 09:52:04,999 fail2ban.jail   : INFO   Creating new jail 'ssh'
2011-08-01 09:52:04,999 fail2ban.jail   : INFO   Jail 'ssh' uses poller
2011-08-01 09:52:05,000 fail2ban.filter : INFO   Added logfile = /var/log/auth.log
2011-08-01 09:52:05,001 fail2ban.filter : INFO   Set maxRetry = 3
2011-08-01 09:52:05,002 fail2ban.filter : INFO   Set findtime = 600
2011-08-01 09:52:05,002 fail2ban.actions: INFO   Set banTime = 600
2011-08-01 09:52:05,035 fail2ban.jail   : INFO   Jail 'moinmoin' started
2011-08-01 09:52:05,039 fail2ban.jail   : INFO   Jail 'ssh' started

As duas últimas linhas nos mostra as jails iniciadas. A ssh, que já vem configurada, e a moinmoin, que acabamos de configurar.

Introdução ao fail2ban – parte 1

Introdução

O fail2ban é um software que monitora os logs do sistema e em caso de X (sendo X configurável) tentativas de autenticação sem sucesso em diversos serviços toma uma atitude, que pode ser colocar o host ofensor em /etc/hosts.deny, “dropar” seus pacotes via iptables ou qualquer outra ação definida pelo usuário.

Instalação do fail2ban

Em máquinas Debian, a melhor maneira de instalar o fail2ban é via apt-get:

# apt-get update
# apt-get install fail2ban

As configurações default bloqueiam via iptables por 10 minutos os hosts que tentarem sem sucesso login via ssh 6 vezes. O fail2ban cria uma chain com nome no padrão fail2ban-serviço:

# iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
fail2ban-ssh tcp -- anywhere anywhere multiport dports ssh

Chain FORWARD (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain fail2ban-ssh (1 references)
target prot opt source destination
RETURN all -- anywhere anywhere

Lembrando que todo esse comportamento é configurável. O bloqueio pode ser feito via TCP-wrappers (/etc/hosts.{allow,deny}), e muitos outros serviços são suportados.

Entendendo os arquivos de configuração

Alguns termos usados pelo fail2ban:

  • filter: um filtro define uma regex que casa um padrão correspondente a uma tentativa de login mal sucedido nos arquivos de log;
  • action: uma ação define os comandos que são executados nos mais diversos eventos, como bloquear um host (ex: bloquear via TCP-wrappers ou iptables), iniciar o fail2ban (ex: criar as chains no firewall) e parar o fail2ban (ex: remover as chains criadas ao iniciar);
  •  jail: uma jail é uma combinação de um filtro com uma ou várias actions. O fail2ban pode lidar com diversas jails ao mesmo tempo.

Uma jail é como dar a seguinte ordem ao fail2ban: “bloqueie via iptables por 10 minutos os hosts que aparecerem 3 vezes em /var/log/auth.log com falha de autenticação”. Nesse exemplo, bloquear via iptables é uma ação e a regex que casa a falha de autenticação é o filtro.

Os arquivos de configuração ficam em /etc/fail2ban.

# cd /etc/fail2ban
# ls -l
total 17
drwxr-xr-x 2 root root 1024 Jun 28 14:30 action.d
-rw-r--r-- 1 root root 859 Feb 27 2008 fail2ban.conf
drwxr-xr-x 2 root root 1024 Jun 28 14:30 filter.d
-rw-r--r-- 1 root root 6683 Jun 28 2010 jail.conf

Os diretórios action.d e filter.d mantêm as configurações de ações e filtros, respectivamente. Os que vêm com o pacote já devem atender à maior parte das necessidades. O arquivo fail2ban.conf contém configurações gerais do daemon fail2ban-server, como path do arquivo de log do fail2ban, path do arquivo socket usado para o cliente de linha de comando se comunicar com o daemon etc.

O arquivo jail.conf é o mais importante, já que configura as jails. No tópico abaixo será explicado como modificar uma jail.

Mudando as configurações default

Na maior parte do tempo, os filtros e ações que vêm com o pacote atendem às necessidades, bastando usá-los nas suas jails. A única jail que vem ativada por padrão é a que bloqueia os hosts que tentarem logar mais de 6 vezes via SSH. Como exemplo, será mostrado como alterar o número de tentativas antes do bloqueio de 6 para 3.

Primeiramente crie uma cópia do arquivo jail.conf chamada jail.local e faça as suas modificações nesse arquivo:

# cp jail.conf jail.local
# vim jail.local

O trecho abaixo configura uma jail chamada ssh

[ssh]

enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 6

Mude para:

[ssh]

enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3

Faça o fail2ban reler os arquivos de configuração:

# /etc/init.d/fail2ban reload
Reloading authentication failure monitor: fail2ban.

Não faça as alterações diretamente em jail.conf. Apesar de funcionar, o arquivo pode ser sobrescrito por atualizações no pacote fail2ban. O fail2ban aplica as regras primeiro do jail.conf depois do jail.local.