—–BEGIN PRIVATE KEY—–
4
—–END PRIVATE KEY—–
Keycloak é uma solução de IAM – Identity and Access Management – de código aberto desenvolvida pela Red Hat, que a utilizou como base para seu produto Red Hat Single Sign-On. Ele atua como um provedor de identidade e permite centralizar a autenticação e autorização para aplicativos e sistemas em ambientes corporativos.
A centralização da autenticação e da identidade de acesso traz vantagens no aspecto de segurança. Autenticação em múltiplos fatores e requisitos de senha podem ser impostos e, para a suspensão de um acesso, basta fazê-lo em um só lugar, sendo mais rápido e sem a chance de um outro acesso ser esquecido e mantido ativo como pode acontecer quando a identidade é distribuída em diferentes sistemas.
Keycloak é distribuído de algumas formas, entre elas os famosos pacotes de arquivo ZIP e TAR, bem como contêiner Docker, o que facilita a implantação e manutenção em ambientes de produção. Mas como esse projeto teve o objetivo de entender como funciona a aplicação, o arquivo foi escolhido.
Para a execução do Keycloak, será criado um usuário de serviço dedicado.
# useradd -m -d /opt/keycloak -s /usr/sbin/nologin keycloak
Keycloak é uma aplicação em Java e atualmente a documentação recomenda o OpenJDK 25. Além disso, ele utiliza o PostgreSQL. As configurações suportadas podem ser verificadas no link:
Considerando um sistema RHEL 10.2:
# dnf install java-25-openjdk postgresql-server
Configure o PostgreSQL substituindo o tipo de autenticação ident em conexões do tipo host por scram-sha-256, que utiliza senha. O ident utiliza o usuário no sistema operacional para autenticar no servidor de banco de dados, de forma similar ao peer mas para conexões TCP em vez de socket unix. Para isso, seria necessário utilizar um serviço de ident.
# postgresql-setup --initdb
# sed -i 's/ident/scram-sha-256/g' /var/lib/pgsql/data/pg_hba.conf
# systemctl enable --now postgresql.service
Criando o banco de dados no PostgreSQL:
# sudo -u postgres psql
postgres=# CREATE ROLE keycloak_op WITH LOGIN PASSWORD 'key&peele';
postgres=# CREATE DATABASE keycloak_db OWNER keycloak_op TEMPLATE template0 ENCODING 'UTF8';
Então, podemos executar o shell como o usuário de serviço criado para baixar e configurar o Keycloak:
# sudo -u keycloak bash
$ wget -P /tmp https://github.com/keycloak/keycloak/releases/download/26.7.4/keycloak-26.7.4.tar.gz
$ tar -xzvf /tmp/keycloak-26.7.4.tar.gz -C /opt/keycloak --strip-components=1
$ chmod +x /opt/keycloak/bin/*
O Keycloak tem um arquivo de configuração em /opt/keycloak/conf/keycloak.conf, onde é necessário determinar os seguintes parâmetros:
db=postgres
db-username=keycloak_op
db-password=key&peele
db-url=jdbc:postgresql://localhost/keycloak_db
hostname=https://keycloak.exemplo.com
# Configuração para uso com o proxy reverso HTTP, desabilitando SSL
http-enabled=true
proxy-headers=xforwarded
proxy-trusted-addresses=192.168.1.10
O template da configuração também oferece os parâmetros de certificado e chave SSL. Por padrão, o Keycloak escuta nas portas 8080 para HTTP e 8443 para HTTPS. No entanto, com o https:// especificado no parametro hostname ele já entende que no proxy reverso o acesso será feito pela porta padrão 443.
O Keycloak é compilado utilizando um framework chamado Quarkus, que faz uma espécie de intermediação entre o Java e o Keycloak. Quando uma aplicação inicia, o Quarkus tem uma sequência de etapas de descoberta dos componentes da aplicação. Uma recomendação é otimizar esse processo do Quarkus com uma espécie de “snapshot” que ele faz dos componentes da aplicação a serem iniciados, o que permite o Keycloak iniciar mais rápido.
$ /opt/keycloak/bin/kc.sh build
Então a aplicação pode ser testada com essa otimização sendo executada como:
$ /opt/keycloak/bin/kc.sh start --optimized
O comando de otimização deve ser executado novamente após atualizações do Keycloak. Falta agora criar o administrador inicial da aplicação no banco de dados.
$ /opt/keycloak/bin/kc.sh bootstrap-admin user
De volta ao shell como administrador ou root, é necessário agora liberar a porta para HTTP no firewall.
# firewall-cmd --permanent --add-port=8080/tcp
# firewall-cmd --reload
Criando um arquivo para a unidade de serviço do systemd em /etc/systemd/system/keycloak.service:
[Unit]
Description=Keycloak IAM
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=simple
User=keycloak
Group=keycloak
WorkingDirectory=/opt/keycloak
ExecStart=/opt/keycloak/bin/kc.sh start --optimized
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Então basta habilitar e iniciar o serviço com:
# systemctl enable --now keycloak.service
Após a instalação e a configuração do proxy reverso, o Keycloak pode ser acessado através da página web. O administrador criado através do comando de bootstrap é temporário, devendo se criar outro usuário administrador e excluir o usuário temporário. Para criar outro usuário administrador, deve-se estar no realm master (único criado por padrão na instalação).

Após criado, vá na aba de credenciais e adicione uma senha.

Então vá na aba “Role mapping” e adicione o papel de admin.

Após logar no novo usuário, exclua o usuário temporário.
Agora sobre a organização interna do Keycloak. Ele é dividido em realms, que são como domínios isolados. Cada realm tem seus usuários, grupos e configurações. O master, único realm existente por padrão após a instalação, é para gerenciamento do próprio Keycloak e não deve ser utilizado para criar usuários de aplicações. Em vez disso, outro realm deve ser criado.

Dentro desse novo realm pode-se criar um cliente, que é uma aplicação que irá utilizar o Keycloak como provedor de identidade. Vou utilizar o Nextcloud como exemplo por ser uma aplicação que tem suporte oficial a esse tipo de configuração. Podemos configurar o tipo de autenticação como OpenID Connect ou SAML. Seguindo com o exemplo utilizando OIDC:


A chave de autorização pode ser deixada desabilitada, porque a autorização é definida pelo servidor Nextcloud: ele determina qual usuário é administrador, qual usuário tem acesso a quais compartilhamentos de arquivos, etc.
A caixa “Standard Flow” determina o fluxo de autenticação padrão para navegadores web. Para fazer o login, o usuário é redirecionado para fazer login no Keycloak.
Alternativamente, existe a opção “Direct Access Grants”, em que o login é feito na página do Nextcloud, que envia as credenciais para o Keycloak.
“Standard Flow” é recomendado porque assim a senha do usuário fica restrita ao Keycloak, enquanto em “Direct Access Grants” a senha passa pela aplicação, o que em muitos casos é indesejado – como por exemplo quando o provedor do serviço e o cliente são organizações diferentes. Além disso, a ideia é sempre minimizar a exposição da senha. Assim, caso a aplicação seja comprometida não significa que a identidade do usuário também vai ser.
No Standard Flow, o fluxo de autenticação acontece da seguinte forma:
Os passos 5 e 6 podem parecer redundantes após o 3 e 4 mas não são. Essa etapa a mais existe porque o código de autorização passa através de um redirect do Keycloak para o Nextcloud, passando pelo navegador web do usuário. O Nextcloud então no passo 5 faz uma chamada diretamente para o Keycloak, que valida o código de autorização e retorna ao Nextcloud um bearer token que o Nextcloud utiliza como credencial do usuário para estabelecer a sessão.
O “Implicit Flow” corta essa etapa e devolve ao navegador o token de acesso diretamente, o que agiliza o login mas agora o navegador possui o token.
As URLs de redirect são geradas pelo plugin de OIDC do Nextcloud após o registro do provedor de identidade, podem ser substituídas depois caso ainda não as tenha.

Após a criação do cliente, crie o segredo para autenticação da aplicação no Keycloak.

Agora, deve ser configurado OpenID Connect no Nextcloud. A começar pela instalação do plugin:

Nas configurações de administração, basta registrar um novo provedor:

O endpoint de descoberta é /realms/<nome do seu dominio>/.well–known/openid-configuration.
Após essa configuração, o Nextcloud exibe a opção de login com o provedor configurado.

Ao clicar nesse botão, o usuário é levado à página de login do Keycloak.

Ao fazer o login, o Nextcloud cria o usuário e inicia a sessão.

Uma funcionalidade interessante do Keycloak é que ele pode também passar à aplicação os grupos aos quais o usuário pertence e, caso a aplicação tenha suporte, ela pode automaticamente provisionar os grupos e atribuir o usuário a eles.
Isso pode ser vantajoso quando recursos ou permissões na aplicação são concedidos a grupos em vez de contas individuais, de forma que o usuário pode ser criado já com os acessos desejados. No Nextcloud, por exemplo, um compartilhamento de arquivos pode ser feito para um grupo.
Para configurar isso no Keycloak, deve-se criar um mapeamento de grupos. Configurando apenas para um cliente específico, isso é feito nos detalhes do cliente, na aba “Client scopes”, no escopo dedicado do cliente, nextcloud-oidc-dedicated.

Basta adicionar um Mapper do tipo “Group Membership” com o nome que a aplicação espera no token. No caso do Nextcloud, o mapeamento foi configurado para groups. Desative a opção “Full group path” para evitar que o nome do grupo seja passado à aplicação como /nome-do-grupo.

No Nextcloud, o provedor de identidade precisa ter o provisionamento de grupos habilitado e o regex de whitelist especificado para os grupos desejados. Nesse caso, foi configurado para aceitar todos os grupos.

Feita essa configuração, podemos atribuir um grupo ao usuário existente no Keycloak, e verificar que, após novo login, o grupo também aparece automaticamente no Nextcloud.

Por padrão, as máquinas virtuais do Proxmox se conectam a uma bridge ligada à interface física do servidor. Dessa forma, todas as máquinas virtuais aparecem como máquinas reais na rede física onde o servidor está conectado.
Idealmente, as máquinas virtuais com portas expostas para a internet estariam isoladas da rede principal, mas como o roteador em uso é um roteador de uso doméstico, sem a opção de criação de VLAN ou atribuição de redes diferentes a interfaces diferentes, uma alternativa se apresentou viável: instalar um firewall como máquina virtual no Proxmox e fazer com que as máquinas virtuais se conectem a ele em vez de diretamente na rede física.
Para o firewall, temos algumas opções de sistema operacional, as mais notáveis de código livre sendo OPNsense e pfSense. O OPNsense é um fork do pfSense, que é um fork do m0n0wall. Eles são baseados no FreeBSD. OPNsense surgiu como um fork do pfSense em grande parte por uma questão de licenciamento, sendo distribuído sob a licença simplificada BSD, e é o firewall utilizado aqui.
Antes de criar a máquina virtual, precisamos criar pelo menos uma bridge no servidor Proxmox para a LAN do OPNsense, a qual ficarão conectadas as máquinas virtuais. A bridge vmbr0 existente no Proxmox vai ser utilizada para a WAN, conectando o OPNsense à LAN física. Para isso, no Proxmox, selecione seu servidor > System > Network e crie a bridge, dando apenas um nome.

Após criada, aplique as configurações.
Alternativamente, pelo terminal do Proxmox, adicione a bridge ao arquivo /etc/network/interfaces:
auto vmbr1
iface vmbr1 inet manual
bridge-ports none
bridge-stp off
bridge-fd 0
# OPNsense LAN
auto vmbr2
iface vmbr2 inet manual
bridge-ports none
bridge-stp off
bridge-fd 0
# OPNsense LAN2
Vale reparar que outras bridges podem ser criadas e atribuídas a uma interface de rede na máquina virtual. Eu decidi criar duas.
Após editado o arquivo, use o seguinte comando para aplicar a configuração:
# ifreload -a
Finalizada a criação das bridges, crie a máquina virtual, adicione as interfaces de rede que desejar, inicie e instale o OPNsense. Ao finalizar, reinicie e atribua às interfaces o uso como WAN ou LAN – e opcionalmente OPT1, OPT2, etc.

Feito isso, atribua um endereço IP à elas conforme necessário. Nesse caso, a WAN permaneceu em DHCP e a LAN e OPT1 foram configuradas com IP estático – e ativado o servidor DHCP para a rede.
Então, você já pode alterar a bridge a qual estão conectadas as interfaces de rede das máquinas virtuais, e o OPNsense já deve estar acessível via web através da LAN virtual. No entanto, ele ainda não está acessível através da WAN (a LAN física). Para isso, são necessárias três configurações:

Agora, você deve alterar no Proxmox a configuração da interface de rede das máquinas virtuais que desejar para que elas utilizem a bridge correspondente à interface LAN do OPNsense.
Você vai ver que, ao pingar um IP da sua LAN física a partir de uma máquina virtual conectada ao OPNsense, a máquina virtual vai receber a resposta. Agora, basta criar a regra que vai isolar a rede LAN do OPNsense da sua LAN física.
No OPNsense, crie uma regra para a LAN, bloqueando todo tráfego de entrada na interface LAN com destino a “WAN net” e coloque essa regra ao topo das regras padrão de permissão de entrada de tráfego na interface LAN.

Pelo fato de OPNsense ser um firewall do tipo stateful, para garantir que nenhuma conexão já estabelecida se mantenha, vá em Firewall > Diagnostics > States e, na aba Actions, resete a tabela de estado.
Agora, toda máquina virtual que for configurada no Proxmox e precise ficar isolada da rede LAN física, deve ser conectada à bridge correspondente à LAN do OPNsense – nesse caso, vmbr1.
Recomendação de segurança: no OPNsense, crie um usuário, inclua ao grupo admin e, após logar nele, desabilite o usuário root.
Posteriormente, eu vi que existem algumas opções de serviço DHCP. O padrão configurado na linha de comando é o “Dnsmasq DNS & DHCP” e é o mais leve mas parecia não ter a opção de reservas DHCP, então configurei o Kea DHCP, uma implementação mais recente desenvolvida pela Internet Systems Consortium (ISC), para ser usado com as interfaces LAN e OPT1.
A interface OPT1 foi configurada de forma parecida à LAN mas um pouco mais restrita, com acesso bloqueado tanto à “WAN net” quanto à “LAN net”. A ideia sendo que a LAN vai abrigar os servidores em uso, já relativamente bem configurados e mantidos, enquanto a rede na interface OPT1 vai abrigar máquinas virtuais de teste.

Outra configuração feita posteriormente: foi criada uma regra no firewall para permitir o acesso da rede WAN às redes LAN e OPT1.

Adicionalmente, no roteador da rede física, foi configurada uma rota para as redes LAN e OPT1 através do IP do firewall na WAN.
Dessa forma, conseguimos acessar da rede física as máquinas virtuais nas redes LAN e OPT1 através do IP individual delas, sem necessidade de configuração de encaminhamento de portas para cada uma, enquanto mantendo bloqueado o acesso das redes LAN e OPT1 à WAN.
Para a interface LAN, foi restringido o acesso à internet, criando grupos de IPs com alias e liberando o acesso a endereços essenciais, como repositórios de pacotes do Ubuntu e CentOS Stream para manter os sistemas atualizados, servidor de VPN para manter o acesso aos serviços web pelo proxy reverso, e acesso à Cloudflare para DNS sobre TLS, de modo a melhorar a segurança sem quebrar nenhum acesso previamente utilizado.

Web Application Firewall é um tipo de aplicação que inspeciona as requisições HTTP em busca de ameaças comuns, como injeções de SQL, cross-site scripting (XSS), tentativas de inclusão de arquivos remotos e injeção de comandos, antes de encaminhá-las ao servidor web.
São tipos de ameaças que costumam ser tratadas no back-end dos sites, mas nem sempre acontece. Eventualmente desenvolvedores esquecem de sanitizar um input em alguma parte do código e vulnerabilidades surgem. Um exemplo disso é a vulnerabilidade por injeção de SQL no Zabbix que foi encontrada no final de 2024.
Tendo isso em vista, é interessante ter uma camada extra de segurança que verifique cada requisição HTTP. É aí que entra o Web Application Firewall (WAF): ele atua como um proxy reverso + firewall. Recebe a requisição HTTP, inspeciona e, caso ela infrinja alguma regra, impede a conexão. Caso contrário, encaminha a requisição para o servidor web.
Existem diferentes alternativas de WAF, incluindo serviços em nuvem como o Azure Web Application Firewall. Mas minha infraestrutura já utilizava o nginx como proxy reverso para os meus servidores web, então busquei uma alternativa que fosse gratuita e que usasse poucos recursos de hardware, de forma que coubesse no meu VPS de 1 vCPU e 512 MB de RAM.
A que melhor se encaixou foi o ModSecurity, um módulo de código aberto originalmente escrito para trabalhar com o Apache, mas que hoje também funciona com o nginx. Para instalar no meu servidor, que roda Debian 12:
# apt install libnginx-mod-http-modsecurity modsecurity-crs
O pacote modsecurity-crs é o Core Rule Set da OWASP, um conjunto de regras base utilizado pelo ModSecurity para identificar e bloquear potenciais ameaças.
Verifique que o módulo está habilitado. Na pasta /etc/nginx/modules-enabled deve haver um softlink como
50-mod-http-modsecurity.conf -> /usr/share/nginx/modules-available/mod-http-modsecurity.conf
Em seguida, edite o arquivo de configuração /etc/nginx/modsecurity.conf e configure o seguinte parâmetro:
SecRuleEngine On
Edite também o arquivo /etc/nginx/modsecurity_includes.conf, copiando para ele as linhas em /usr/share/modsecurity-crs/owasp-crs.load que começam com “Include”. Essas são o conjunto base de regras da OWASP.
Seu arquivo deve ficar algo como:
include modsecurity.conf
#include /usr/share/modsecurity-crs/owasp-crs.load
Include /etc/modsecurity/crs/crs-setup.conf
Include /usr/share/modsecurity-crs/rules/*.conf
Feito isso, você pode editar os arquivos de configuração dos sites existente para adicionar no topo no bloco server as seguintes linhas:
modsecurity on;
modsecurity_rules_file /etc/nginx/modsecurity_includes.conf;
Teste as configurações e, se tudo estiver ok, reinicie o servidor web:
# nginx -t
# systemctl restart nginx
É isso, o WAF está funcionando.
Você pode testá-lo fazendo requisições http que ele deve bloquear e verificando tanto o resultado (ele rejeita com resposta 403) quanto os logs:
# tail -f /var/log/nginx/modsec_audit.log
Exemplos de requisições para fazer:
$ curl "https://pid1.com.br/?id=1+UNION+SELECT+1,2,3"
$ curl "https://pid1.com.br/?q=<script>alert('xss')</script>"
$ curl "https://pid1.com.br/?page=http://evil.com/shell.txt"
$ curl "https://pid1.com.br/?cmd=cat+/etc/passwd"
Você também pode colocar um input como “UNION SELECT” em algum campo do seu site e verificar que ele responde com 403.
Agora, problemas como falsos positivos podem acontecer. Eu fiquei impossibilitado de publicar ou salvar publicações como rascunho aqui no WordPress, por exemplo, recebendo o erro “Falha ao atualizar. A resposta não é um JSON válido”.
Para resolver isso, foi necessário coletar as informações do log para criar uma regra que permita esse caso e ignore a regra que causa o bloqueio.
O log foi o seguinte:
ModSecurity: Warning. Matched "Operator Pm' with parameter document.cookie document.write .parentnode .innerhtml window.location -moz-binding <!-- --> <![cdata[' against variable ARGS:json.content' (Value: <!-- wp:paragraph -->\x0a<p>teste4</p>\x0a<!-- /wp:paragraph -->\x0a\x0a<!-- wp:paragraph -->\x0a<p> (30 characters omitted)' ) [file "/usr/share/modsecurity-crs/rules/REQUEST-941-APPLICATION-ATTACK-XSS.conf"] [line "232"] [id "941180"] [rev ""] [msg "Node-Validator Blacklist Keywords"] [data "Matched Data: <!-- found within ARGS:json.content: <!-- wp:paragraph -->\x0a<p>teste4</p>\x0a<!-- /wp:paragraph -->\x0a\x0a<!-- wp:paragraph -->\x0a<p></p>\x0a<!-- /wp:paragraph -->"] [severity "2"] [ver "OWASP_CRS/3.3.4"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-xss"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/152/242"] [hostname "191.252.110.50"] [uri "/wp-json/wp/v2/posts/385"] [unique_id "176202912980.702618"] [ref "o0,4v13,112t:utf8toUnicode,t:urlDecodeUni,t:htmlEntityDecode,t:jsDecode,t:cssDecode,t:lowercase,t:removeNulls"] ModSecurity: Access denied with code 403 (phase 2). Matched "Operator Ge' with parameter 5' against variable TX:ANOMALY_SCORE' (Value: 5' ) [file "/usr/share/modsecurity-crs/rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "81"] [id "949110"] [rev ""] [msg "Inbound Anomaly Score Exceeded (Total Score: 5)"] [data ""] [severity "2"] [ver "OWASP_CRS/3.3.4"] [maturity "0"] [accuracy "0"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "191.252.110.50"] [uri "/wp-json/wp/v2/posts/385"] [unique_id "176202912980.702618"] [ref ""]
Nele, pode-se identificar:
A partir disso, pode-se criar um arquivo de configuração /etc/nginx/modsecurity_custom_exceptions.conf, com a seguinte regra:
SecRule REQUEST_URI "@beginsWith /wp-json/wp/v2/posts"\
"id:10001,phase:1,nolog,pass,ctl:ruleRemoveById=941180"
Esse arquivo deve ser incluído no /etc/nginx/modsecurity_includes.conf – adicione a linha:
Include modsecurity_custom_exceptions.conf
A regra é composta da seguinte forma:
SecRule VARIÁVEIS OPERADOR [AÇÕES]
Nesse caso,
Entre as ações:
Há também no log uma regra de ID 949110, mas ao ver a mensagem associada é possível verificar que ela não é a regra que identifica a ameaça em potencial, apenas a regra que determina um limite de ameaças detectadas. Não desabilite essa regra, isso abriria espaço para outras requisições HTTP que não devem ser recebidas pelo servidor. É preciso analisar o log com atenção e verificar a que mensagens cada regra está associada.
Esse tipo de ajuste também pode ser feito para outras regras em outras URIs de outros sites para os quais o nginx atua como proxy reverso, permitindo o uso do WAF para diferentes aplicações e sistemas.
Uma observação importante: a ordem em que as configurações são incluídas no arquivo /etc/nginx/modsecurity_includes.conf é importante, já que as regras são processadas de forma sequencial. Garanta que as exceções são incluídas para serem processadas antes das regras da OWASP.
Include modsecurity.conf
Include /etc/modsecurity/crs/crs-setup.conf
Include modsecurity_custom_exceptions.conf
Include /usr/share/modsecurity-crs/rules/*.conf
Para casos em que uploads de arquivos são feitos, como no Nextcloud, considere também ajustar no arquivo /etc/nginx/modsecurity.conf o parâmetro de tamanho máximo do corpo das requisições HTTP, especificado em bytes.
#SecRequestBodyLimit 13107200
SecRequestBodyLimit 31457280
O limite padrão é 12.5 MB, tendo sido ajustado para 30 MB. Idealmente ajustaria para um valor mais alto mas o consumo de memória pelo nginx ultrapassa o limite tolerado pelo systemd-oomd no meu servidor com 512 MB de RAM, levando o systemd-oomd a matar o processo.
Em casos de múltiplos sites, as configurações podem ser separadas da seguinte forma:
Em cada arquivo, se inclui as configurações, as exclusões e as regras. Dessa forma cada site tem suas regras, evitando que todas as exclusões sejam feitas pra todos os sites.
#Configuração base
Include ../modsecurity.conf
Include /etc/modsecurity/crs/crs-setup.conf
# Exclusões
SecRule REQUEST_URI "@beginsWith /wp-json/wp/v2/posts"\
"id:10001,phase:1,nolog,pass,ctl:ruleRemoveById=941180"
# Aplicação das regras
Include /usr/share/modsecurity-crs/rules/*.conf
Sendo uma alternativa ao ambiente Microsoft, o Google Workspace também conta com um serviço de diretório. O LDAP seguro pode ser usado para autorização e autenticação de usuários. O FreeRADIUS foi usado para implementar autenticação WPA2-Enterprise na rede Wi-Fi, com um detalhe: a autenticação precisava funcionar para 2 domínios de e-mail diferentes, de dois ambientes Google Workspace independentes.
Enquanto a autenticação com AD no FreeRADIUS é feita através do método de desafio e resposta, MSCHAPv2, o LDAP seguro do Google não implementa esse protocolo e precisa que a senha do usuário seja enviada em texto pleno dentro de um túnel SSL. Isso pode ser feito com a combinação de protocolos EAP-TTLS/PAP. Diferente do EAP-TLS, que exige o par de certificado/chave tanto do cliente quanto do servidor, o EAP-TTLS exige o par apenas do servidor.
O servidor utilizado foi o Ubuntu 24.04 mas o processo serve também para outras versões. Primeiro passo é criar o cliente LDAP no Google Workspace. No console de administrador, vá em Apps > LDAP > Adicionar Cliente
Atribua as permissões necessárias para o cliente:

Em seguida, baixe os certificados e ative o serviço.

Agora, preparando o servidor FreeRADIUS:
# apt install freeradius freeradius-ldap
Diferente do CentOS, o Ubuntu já gera os certificados pro TLS automaticamente no processo de instalação.
Diferentes clientes podem ser adicionados no arquivo /etc/freeradius/3.0/clients.conf em blocos do tipo:
client roteador0 {
ipaddr = 56.125.2.213
secret = senhaNoPost-it
require_message_authenticator = yes
shortname = rt0
}
Transfira o par de certificado/chave baixado do Google Workspace para o servidor, armazenando-os em /etc/freeradius/3.0/certs/google/, onde já é esperado pelo FreeRADIUS nas configurações a se seguir.
Partindo para os arquivos de configuração, a começar pelo LDAP, fazendo uma cópia do arquivo para cada ambiente Workspace:
# cp -a /etc/freeradius/3.0/mods-available/{ldap_google,ldap_workspace0}
# cp -a /etc/freeradius/3.0/mods-available/{ldap_google,ldap_workspace1}
Edite o arquivo, adicionando ou alterando as linhas:
ldap ldap_workspace0 {
identity = 'nomeDeClienteObtidoNoGoogleWorkspace'
password = 'senhaDeClienteObtidaNoGoogleWorkspace'
base_dn = 'dc=dominio0,dc=com'
user {
filter = "(mail=%{%{Stripped-User-Name}:-%{User-Name}})"
}
certificate_file = /caminho/do/cert/cliente/ldap
private_key_file = /caminho/da/chave/cliente/ldap
}
Faça o mesmo para ldap_workspace1. Habilite os módulos com:
# ln -s /etc/freeradius/3.0/mods-available/ldap_workspace0 /etc/freeradius/3.0/mods-enabled/
No arquivo /etc/freeradius/3.0/mods-enabled/eap, adicione ou altere as seguintes configurações:
eap {
default_eap_type = ttls
ttls {
default_eap_type = pap
}
}
Caso tenha alterado os certificados a serem usados, você também vai precisar editar as configurações referentes a isso no bloco de configurações comuns de TLS.
Você pode comentar outros módulos não utilizados, como TLS, PEAP e MSCHAPv2, deixando apenas as configurações comuns de TLS e o bloco do TTLS.
O arquivo /etc/freeradius/3.0/sites-enabled/default tem diversas opções, muitas podem (e devem) ser desabilitadas. Edite os blocos authorize e authenticate para que fiquem como abaixo:
server default {
authorize {
filter_username
preprocess
eap
expiration
logintime
}
authenticate {
eap
}
}
No arquivo /etc/freeradius/3.0/sites-enabled/inner-tunnel, edite os mesmos blocos, adicionando a condicional para os dois domínios:
server inner-tunnel {
authorize {
filter_username
if ("%{User-Name}" =~ /@dominio0\.com$/) {
ldap_workspace0
update control {
Auth-Type := ldap_workspace0
}
} elsif ("%{User-Name}" =~ /@dominio1\.com$/) {
ldap_workspace1
update control {
Auth-Type := ldap_workspace1
}
} else {
reject
}
expiration
logintime
}
authenticate {
Auth-Type ldap_workspace0 {
ldap_workspace0
}
Auth-Type ldap_workspace1 {
ldap_workspace1
}
Auth-Type PAP {
pap
}
}
}
Terminando a edição dos arquivos, você deve reiniciar o serviço para que as configurações tenham efeito, garantindo antes que todos os arquivos pertencem ao usuário e grupo freerad.
# chown -hR freerad:freerad /etc/freeradius
# systemctl restart freeradius.service
Caso necessário, você pode testar o FreeRADIUS em modo debug parando o serviço e executando-o manualmente da seguinte forma:
# freeradius -X
Uma funcionalidade interessante é a de atribuir uma VLAN específica dependendo da unidade organizacional do usuário no Google Workspace.
Para isso, basta utilizar blocos desse tipo dentro do bloco de post-auth:
if (&Client-Shortname == "rt0" && &User-Name =~ /@dominio0\.com$/i) {
if (&control:LDAP-UserDN =~ /ou=RECURSOS HUMANOS/i) {
update outer.session-state {
Tunnel-Type := VLAN
Tunnel-Medium-Type := IEEE-802
Tunnel-Private-Group-Id := "2"
}
}
}
Nesse caso, a melhor abordagem foi usar a condicional considerando o cliente do RADIUS e o ambiente Workspace porque os dois ambientes podem ter UOs diferentes e VLANs com IDs diferentes.
Como se pode ver pelo sinal =~ a checagem da UO não é exata e o mesmo nome poderia ser encontrado em outra UO, o que pode fazer com que a VLAN errada seja atribuída a um usuário.
Uma forma mais adequada de atribuir uma VLAN seria, ao invés de usar as unidades organizacionais do Workspace para determinar a VLAN diretamente, usar as unidades organizacionais para atribuir usuários a grupos dinâmicos como rh-wifi@dominio0.com que então vão ser verificados pelo FreeRADIUS da seguinte forma:
if (LDAP-Group == "cn=rh-wifi,ou=Groups,dc=dominio0,dc=com") {
update outer.session-state {
Tunnel-Type := VLAN
Tunnel-Medium-Type := IEEE-802
Tunnel-Private-Group-Id := "2"
}
}
RADIUS é um protocolo de rede que oferece gerenciamento centralizado de autorização e autenticação para acesso de usuários a serviços de rede. Muito usado, por exemplo, como uma alternativa mais segura de acesso a uma rede Wi-Fi, onde cada usuário usa suas credenciais para se conectar, em vez de todos compartilharem uma única senha.
FreeRADIUS é um sistema livre e aberto que implementa esse protocolo e pode ser configurado pra usar diferentes back-ends para autorização e autenticação, um deles é o Active Directory.
Pra configurar esse sistema, garanta antes que o servidor RADIUS possa encontrar e ingressar no domínio – aqui tratado como exemplo.com:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.0.10 radius-server.exemplo.com radius-server
Após isso, instale e configure o Samba, que vai ser usado para ingressar o computador no domínio. A partir daqui, as instruções são válidas para sistemas da família Red Hat, tendo sido usado o CentOS Stream 9.
# dnf install samba
Adapte a configuração abaixo para o seu domínio, alterando ou adicionando os seguintes parâmetros no arquivo /etc/samba/smb.conf :
[global]
workgroup = EXEMPLO
security = ads
winbind use default domain = yes
realm = EXEMPLO.COM
idmap config * : backend = tdb
idmap config * : range = 10000-19999
idmap config EXEMPLO : backend = rid
idmap config EXEMPLO : range = 20000-99999
Remova também o parâmetro a seguir para evitar o risco de conflito na autenticação:
[global]
passdb backend = tdbsam
Verifique se há algum erro na sua configuração com
# testparm
Agora, para ingressar o servidor no domínio:
# net ads join -U Administrator
Instale o pacote samba-winbind-clients, habilite o serviço winbind e teste a autenticação com ntlm_auth:
# dnf install samba-winbind-clients
# systemctl enable --now winbind.service
# ntlm_auth --request-nt-key --username=daniel --password='senhaqueanoteinoblocodenotas'
Um output como
: (0x0)
indica sucesso.
Agora que o servidor já faz parte do domínio e conseguimos autenticar usuários com ntlm_auth, podemos instalar os pacotes do FreeRADIUS para então configurá-lo
# dnf install freeradius freeradius-utils
Pra autenticação no AD, vamos usar a combinação de protocolos PEAP/MSCHAPv2.
Edite a o módulo mschap localizado em /etc/raddb/mods-enabled/mschap para alterar as seguintes configurações
mschap {
use_mppe = yes
require_encryption = yes
require_strong = yes
ntlm_auth = "/usr/bin/ntlm_auth --request-nt-key --username=%{%{Stripped-User-Name}:-%{%{User-Name}:-None}} --challenge=%{%{mschap:Challenge}:-00} --nt-response=%{%{mschap:NT-Response}:-00}"
}
Edite o módulo eap localizado em /etc/raddb/mods-enabled/eap para alterar a seguinte configuração:
eap {
default_eap_type = peap
}
Dica: os arquivos de configuração do FreeRADIUS são extensivamente comentados. Isso é ótimo porque ajuda a entender a configuração já na hora. Mas se quiser ver só a configuração, sem os comentários e linhas em branco:
$ grep -vE '^\s*#|^\s*$' /caminho/para/arquivo.txt
Na pasta /etc/raddb/certs, execute o arquivo bootstrap para gerar os certificados necessários para o TLS usado no PEAP.
Adicione o usuário radiusd ao grupo wbpriv para que o FreeRADIUS tenha acesso para ler a resposta do winbind à solicitação de autenticação, através do socket em /var/lib/samba/winbindd_privileged/pipe
# usermod -aG wbpriv radiusd
Abra as portas necessárias no firewall:
# firewall-cmd --permanent --add-port=1812/udp
# firewall-cmd --permanent --add-port=1813/udp
# firewall-cmd --reload
E agora basta iniciar o serviço radiusd:
# systemctl enable --now radiusd.service
Você pode testar a autenticação pelo FreeRADIUS com
radtest -t mschap daniel senhaqueanoteinoblocodenotas localhost 0 testing123
e configurar outros clientes do FreeRADIUS em /etc/raddb/clients.conf
Alguns clientes podem não aceitar o certificado gerado com as configurações padrão do FreeRADIUS, então é recomendado ajustar as configurações pra que o certificado seja emitido corretamente para o FQDN do servidor. O Easy RSA também pode ajudar a gerar e manter os certificados.