OpenVPN com autenticação e autorização avançadas

O artigo discute a configuração do OpenVPN com recursos adicionais:

  • Certificados de token para autenticação primária (Rutoken como exemplo)
  • Back-end LDAP para autenticação secundária (usando o ActiveDirectory como exemplo)
  • filtrando recursos internos disponíveis para os usuários (via iptables)

Também descreve como configurar clientes para Linux, Windows e MacOS.

Configuração do servidor


Instale o OpenVPN


Pegue o script Nyr / openvpn-install , execute a partir do root.

git clone https://github.com/Nyr/openvpn-install.git cd openvpn-install 

O processo de inicialização fará algumas perguntas.

  • protocolo udp
  • porta 1194
  • Servidores DNS - Local
  • endereço de gateway IP externo na Internet através do qual o servidor VPN estará disponível

Há também uma versão com segurança aprimorada do script original - github.com/Angristan/OpenVPN-install . Possui mais configurações de criptografia com explicações sobre o porquê.

Gerenciamento de usuários


Adicionando
Se os tokens não forem usados, o usuário será adicionado através do mesmo script. O script basicamente gera uma configuração ovpn personalizada e insere um certificado assinado pelo certificado raiz lá.

Se tokens forem usados ​​(consulte a seção sobre tokens abaixo), o certificado será gravado manualmente com base na solicitação do certificado que é gerado no token. A configuração do usuário deve ser feita manualmente a partir do modelo existente (do mesmo a partir do qual o script de configuração é gerado). O modelo está aqui /etc/openvpn/client-common.txt . Ele não está incluído no pacote openvpn e é gerado pelo script durante o processo de configuração.

Excluir
A remoção de usuários é feita através do mesmo script de instalação. O certificado é adicionado à CRL , a nova CRL é enviada por push ao servidor vpn. O servidor considera todos os certificados que estão na CRL inválidos e se recusa a aceitar.

Como revogar um certificado manualmente:

 cd /etc/openvpn/easyrsa #   ./easyrsa revoke $CLIENT #   crl ./easyrsa gen-crl #   crl rm -rf /etc/openvpn/crl.pem #    cp /etc/openvpn/easy-rsa/pki/crl.pem /etc/openvpn/crl.pem # openvpn    crl,       nobody chown nobody:nobody /etc/openvpn/crl.pem 

Filtrando hosts disponíveis para clientes


Os clientes precisam ser limitados pelos hosts que eles podem acessar na rede quando se conectam ao openvpn.

Manualmente

A idéia é pegar pacotes mesmo na interface tun0 , na qual eles vêm de clientes e filtrá-los antes de entrar no NAT. Após o NAT, não haverá motivo para filtrá-los - todos terão um endereço IP do servidor openvpn na rede interna. Antes de entrar no NAT, os pacotes para cada usuário têm seu próprio endereço IP exclusivo (para a correspondência de endereços IP e usuários, consulte o arquivo /etc/openvpn/ipp.txt ).

Pacotes que passam pelo sistema (não vêm diretamente dele e não são recebidos, ou seja, são roteados pelo sistema) são processados ​​pela tabela FORWARD. As tabelas no iptables são processadas de cima para baixo, se nenhuma das regras da tabela levar a uma decisão sobre o destino do pacote, a regra padrão será acionada.

Preparando a tabela FORWARD:

 #   iptables -F FORWARD #     FORWARD -    iptables -P FORWARD DROP #     iptables -I FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT 

Regras de exemplo para um cliente específico. Como a regra padrão da tabela é DROP, resta apenas permitir os pares host + porta onde você puder. Permitir acesso à porta no host + executar ping no próprio host:

 iptables -I FORWARD -s 10.8.0.3 -i tun0 -d 10.0.2.3 -p tcp --dport 443 -j ACCEPT iptables -I FORWARD -s 10.8.0.3 -i tun0 -d 10.0.2.3 -p icmp --icmp-type echo-request -j ACCEPT 

No exemplo acima, o host 10.8.0.3 tem acesso permitido à porta 443 do host 10.0.2.3.

Como fechar o acesso:

 #         iptables -L FORWARD --line-numbers #     iptables -D FORWARD { } 

Então você precisa encontrar todas as regras para um cliente específico e excluí-las.

Durante a depuração, é conveniente verificar quais regras funcionam. Cada regra possui um contador de pacotes processados.

 #  ,      watch iptables -nvL FORWARD #     iptables -Z FORWARD 

Automaticamente

Um servidor openvpn tem a capacidade de executar scripts para determinadas ações. Em particular, ao conectar e desconectar clientes. Os scripts podem ser escritos em qualquer coisa, se forem executáveis. Dentro do script, as variáveis ​​de ambiente transmitem todos os tipos de parâmetros para a conexão atual. Estamos interessados ​​em variáveis:

  • common_name (nome do proprietário do certificado; o que leva ao campo de nome comum ao criar o certificado)
  • ifconfig_pool_remote_ip (endereço IP do cliente em tun0)
  • script_type (qual evento aconteceu - conecte ou desconecte).

Você precisa de privilégios de root para gerenciar o iptables. O Openvpn depois de conectar redefine os direitos a ninguém e executa scripts a partir dele. É ruim permitir que ninguém faça algo com o sudo e é melhor não usar um asterisco nas regras, mas de alguma forma você precisa permitir que o usuário controle as tabelas de ip.

 # /etc/sudoers.d/50_openvpn # #    nobody ALL = NOPASSWD: /sbin/iptables -A FORWARD* #     nobody ALL = NOPASSWD: /sbin/iptables -L FORWARD* #    nobody ALL = NOPASSWD: /sbin/iptables -D FORWARD* 

Na configuração do servidor, você precisa adicionar permissão para executar arquivos de terceiros e ativar dois ganchos responsáveis ​​por conectar e desconectar o usuário.

 script-security 2 client-connect /etc/openvpn/bin/hosts.rb client-disconnect /etc/openvpn/bin/hosts.rb 

O próprio script, que lê as configurações e aplica as regras para o iptables. O script funciona com os mesmos princípios descritos na seção anterior.

/openvpn/bin/hosts.rb
 #!/usr/bin/ruby # -*- coding: utf-8 -*- require 'pp' def log(string) puts 'hosts.rb: ' + string end def parse_config_file(name) config_path = "hosts/#{name}" unless File.exist?(config_path) puts "There is no specific configuration for #{name}." p name exit 0 end config_source = IO.read(config_path).split("\n") config = config_source.inject([]) do |result,line| ip, port, protocol = line.split(/\s+/) result << { ip: ip, port: port, protocol: protocol || 'tcp' } end end def get_config(name) user_config = parse_config_file(name) if user_config everybody_config = parse_config_file('everybody') end everybody_config + user_config end def apply_rule(rule) command = "sudo iptables #{rule}" log(command) system(command) end def remove_rule(number) command = "sudo iptables -D FORWARD #{number}" log(command) system(command) end def allow_target(source_ip, options) #         . apply_rule("-A FORWARD -s #{source_ip} -i tun0 -d #{options[:ip]} -p #{options[:protocol]} --dport #{options[:port]} -j ACCEPT") #       apply_rule("-A FORWARD -s #{source_ip} -i tun0 -d #{options[:ip]} -p icmp --icmp-type echo-request -j ACCEPT") end def clear_targets(source_ip) #      FORWARD,  source_ip. rules_exist = true while rules_exist table = `sudo iptables -L FORWARD --line-number`.split("\n") the_line = table.find do |line| fields = line.split(/\s+/) ip = fields[4] ip == source_ip end if the_line number = the_line.split(/\s+/)[0] remove_rule(number) else rules_exist = false end end end ################################################################################ script_type = ENV['script_type'] log(script_type) name = ENV['common_name'] source_ip = ENV['ifconfig_pool_remote_ip'] case script_type when 'client-connect' config = get_config(name) config.each{|target| allow_target(source_ip, target)} when 'client-disconnect' clear_targets(source_ip) else puts "Unknown script type #{script_type}." end 

As regras são armazenadas em arquivos correspondentes ao nome comum dos certificados na pasta /etc/openvpn/hosts . Eles especificam exatamente quais endereços IP estão disponíveis para um cliente específico. Separador - um número arbitrário de espaços. O endereço IP, a porta e o protocolo (tcp ou udp) são gravados através do separador.

 10.0.0.24 53 udp 10.0.0.25 53 udp 10.0.2.3 443 tcp 

Como resultado, a seguinte estrutura deve /etc/openvpn na pasta /etc/openvpn

├── bin
Hosts └── hosts.rb
Hosts── anfitriões
│ ├── user1
2 ├── user2
Everybody └── todo mundo
Server── server.conf
└── ...

User1 e user2 são arquivos no formato acima. Eles descrevem a quais hosts o usuário com o nome comum correspondente tem acesso.

Há outro arquivo everybody adicional, ele contém regras que se aplicam a todos os clientes, desde que exista um arquivo de configuração separado para esses clientes. Ou seja, se o usuário tiver uma lista de hosts para onde ele pode ir, essa lista e os hosts listados em everybody aplicados. Caso contrário, everybody não everybody aplicável. Por exemplo, é conveniente colocar um servidor DNS nesse arquivo.

Registo

O script de instalação inclui apenas o registro de conexões atuais (parâmetro status) . Para que um log comum apareça, você precisa adicionar uma linha à configuração do servidor ( /etc/openvpn/server.conf ):
 log-append /var/log/openvpn.log 


LDAP

Existe um plugin openvpn-auth-ldap que permite autenticar um usuário novamente via LDAP.

Entrega do pacote:

 sudo yum install openvpn-auth-ldap 

Adicione ao server.conf:

 plugin /usr/lib64/openvpn/plugin/lib/openvpn-auth-ldap.so "/etc/openvpn/ldap.conf" 

Crie uma configuração para o ldap no /etc/openvpn/ldap.conf :
 <LDAP> URL ldaps://{LDAP_DOMAIN_HERE} Timeout 15 TLSEnable no FollowReferrals yes BindDN "BIND_DN_HERE" Password "BIND_PASSWORD_HERE" </LDAP> <Authorization> BaseDN "{BASE_DN_HERE}" SearchFilter "(&(sAMAccountName=%u)(objectClass=organizationalPerson)(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" RequireGroup false </Authorization> 

Adicione a linha à configuração ovpn personalizada:

 auth-user-pass 

Assim, primeiro o usuário será solicitado a fornecer o nome de usuário e a senha do domínio, depois o PIN do token. Se uma dessas etapas falhar, a conexão não será estabelecida.

A descrição das opções para o ldap.conf está no repositório do plug-in . Ele suporta autenticação por associação ao grupo, mas eu não o testei.

Velocidade


O maior aumento na velocidade fornece a inclusão do modo udp. Isso é recomendado em todos os manuais. O ponto é que não faz sentido iniciar uma conexão de cliente tcp em um canal tcp. Um tcp no cliente é suficiente para fazer a entrega correta dos pacotes. Se os pacotes forem perdidos no canal udp, a conexão tcp do cliente controlará o ajuste de entrega.

A velocidade aumentará pelo menos porque não há necessidade de aguardar a confirmação da entrega de cada pacote no canal. Há um segundo problema com o tcp - um pacote tcp do cliente provavelmente não se encaixa em um pacote do canal vpn. O MTU é o mesmo, mas os cabeçalhos precisam ser adicionados ao pacote do cliente. Como resultado, você deve enviar dois pacotes dentro do canal vpn por pacote de usuário.

O TCP faz sentido usar quando é impossível de outra maneira. Por exemplo, quando o vpn funciona através do canal ssh.

Exemplo de uma configuração completa do servidor


example-server.conf
 port 1194 proto tcp dev tun sndbuf 0 rcvbuf 0 ca ca.crt cert server.crt key server.key dh dh.pem tls-auth ta.key 0 topology subnet server 10.8.0.0 255.255.255.0 ifconfig-pool-persist ipp.txt push "redirect-gateway def1 bypass-dhcp" push "dhcp-option DNS 10.0.0.25" push "dhcp-option DNS 10.0.0.24" keepalive 10 120 cipher AES-256-CBC comp-lzo user nobody group nobody persist-key persist-tun status openvpn-status.log verb 3 crl-verify crl.pem log-append /var/log/openvpn.log script-security 2 client-connect /etc/openvpn/bin/hosts.rb client-disconnect /etc/openvpn/bin/hosts.rb 


Configuração de token


Biblioteca PKCS # 11


Para trabalhar com tokens, você precisa de uma biblioteca especial. A biblioteca é necessária para criar pares de chaves e para a conexão real. Baixe para todas as plataformas pelo link .

Onde quer que o librtpkcs11ecp.so seja encontrado posteriormente - esta é a própria biblioteca que precisa ser baixada e colocada em algum lugar em um local conveniente.

Criando um certificado em um token


Gere um par de chaves no token. O parâmetro id aqui é o número de série do slot no token onde o par de chaves se encaixa.

 pkcs11-tool --module /usr/lib64/librtpkcs11ecp.so --keypairgen --key-type rsa:2048 -l --id 01 

Faça uma solicitação de certificado para a chave pública. No processo de criação de uma solicitação de certificado, a vida útil do certificado e o nome comum são definidos, usados ​​para filtrar os endereços IP disponíveis na rede. O nome comum deve corresponder ao logon no ActiveDirectory para que não haja confusão.

 openssl openssl> engine -t dynamic -pre SO_PATH:/usr/lib64/openssl/engines/pkcs11.so -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:/usr/lib64/librtpkcs11ecp.so openssl> req -engine pkcs11 -new -key slot_0-id_01 -keyform engine -out /home/john/good.req 

A solicitação recebida deve ser movida para a /etc/openvpn/easy-rsa/pki/reqs/ . A extensão do arquivo deve ser necessária.
Convertendo uma solicitação em um certificado:

 cd /etc/openvpn/easy-rsa/ ./easyrsa sign-req client good 

Depois disso, um certificado com o mesmo nome, mas com a extensão crt , aparecerá na pasta /etc/openvpn/easy-rsa/pki/issued/ .

Antes da gravação, o certificado deve ser convertido em DER:

 openssl x509 -in /home/user/user-cert.pem -out /home/user/user-cert.crt -outform DER 

Escrevendo um certificado para um token:

 pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -l -y cert -w /home/user/user-cert.crt --id 45 --label TEST 

Está escrito com base no artigo “Usando o Rutoken EDS com OpenSSL (RSA)” .

Usando token para autenticação


Localize o ID do certificado a ser apresentado ao servidor:

 $ openvpn --show-pkcs11-ids /usr/lib64/librtpkcs11ecp.so The following objects are available for use. Each object shown below may be used as parameter to --pkcs11-id option please remember to use single quote mark. Certificate DN: /CN=User1 Serial: 490B82C4000000000075 Serialized id: aaaa/bbb/41545F5349474E415455524581D2A1A1B23C4AA4CB17FAF7A4600 

Estamos interessados ​​no ID serializado aqui.

Opções que devem ser inseridas na configuração ovpn para que os tokens sejam selecionados:

 pkcs11-providers /usr/lib64/librtpkcs11ecp.so pkcs11-id 'aaaa/bbb/41545F5349474E415455524581D2A1A1B23C4AA4CB17FAF7A4600' 

A opção pkcs11-id deve estar entre aspas simples.

Este manual faz sentido em todas as plataformas. Você precisa especificar o caminho para a biblioteca e o ID do certificado no token. A biblioteca pode ser chamada de maneira um pouco diferente, seja .dll , não .so , mas o significado é o mesmo.

Nesse caso, você precisa remover as seções cert e key do arquivo ovpn, porque o certificado e a chave privada serão retirados do token.

A configuração completa do cliente (para Windows) fica assim:

client.ovpn
client
dev tun
proto tcp
sndbuf 0
rcvbuf 0
remote 78.47.37.247 22222
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
cipher AES-256-CBC
comp-lzo
setenv opt block-outside-dns
key-direction 1
verb 3

pkcs11-providers "c://Windows//System32//rtPKCS11ECP.dll"
pkcs11-id 'Aktiv\x20Co\x2E/Rutoken\x20ECP/342b871d/Rutoken/01'

-----BEGIN CERTIFICATE-----
{CERT_HERE}
-----END CERTIFICATE-----


<tls-auth>
#
# 2048 bit OpenVPN static key
#
-----BEGIN OpenVPN Static key V1-----
{KEY_HERE}
-----END OpenVPN Static key V1-----
</tls-auth>


Escrito com base em "Como adicionar autenticação de fator duplo a uma configuração OpenVPN usando cartões inteligentes do lado do cliente" .

Configuração do cliente


Linux


O Openvpn possui um bug que impede o usuário de inserir o código PIN a partir do token, se o pacote for construído com o suporte ao systemd. Como o systemd está em toda parte ultimamente, todos os pacotes que já estão disponíveis nos repositórios são compilados com seu suporte. Clientes no Linux precisam coletar o pacote por conta própria. Aqui está um exemplo de configuração que funcionou para mim no Arch Linux:

 ./configure \ --prefix=/usr \ --sbindir=/usr/bin \ --enable-iproute2 \ --enable-pkcs11 \ --enable-plugins \ --enable-x509-alt-username 

Você pode verificar se o openvpn foi criado com ou sem systemd usando o seguinte comando:

 openvpn --version | grep --color enable_systemd 

Mas os


No Mac OS, existe apenas um cliente gratuito - Tunnelblink .

Ele não sabe como inserir um código PIN a partir de um token da GUI. O erro é descrito, por exemplo, aqui - https://groups.google.com/forum/#!topic/tunnelblick-discuss/f_Rp_2nV-x8 Ignorado iniciando o openvpn a partir do console. Isso não é surpreendente, já que o cliente oficial do Windows também não sabe disso.

Também no Mac OS (ao contrário do Windows), são necessários scripts adicionais para configurar a rede. Se você simplesmente executar o openvpn a partir do console, o DNS não funcionará (talvez algo mais, apenas o DNS aparecerá).

O TunnelBlick possui esses scripts de configuração de rede, eles só precisam ser chamados ao estabelecer e desconectar a conexão. O que você precisa adicionar à configuração ovpn:

 script-security 2 up "/Applications/Tunnelblick.app/Contents/Resources/client.up.tunnelblick.sh -9 -d -f -m -w -ptADGNWradsgnw" down "/Applications/Tunnelblick.app/Contents/Resources/client.down.tunnelblick.sh -9 -d -f -m -w -ptADGNWradsgnw" 

Um script de exemplo para iniciar uma conexão openvpn, que pode ser colocada na área de trabalho e cutucada com o mouse:

 #!/bin/bash tunnelblick=/Applications/Tunnelblick.app/Contents/Resources/openvpn/openvpn-2.4.2-openssl-1.0.2k sudo $tunnelblick/openvpn --config $tunnelblick/user.ovpn 

Windows


Sob as janelas, tudo parece funcionar. O cliente oficial não sabe como inserir o código PIN a partir do token, ele consegue abrir o VPN manualmente à mão no console.

O mais importante é fazer tudo sob o administrador. Execute o instalador do cliente como administrador. Inicie um terminal no qual o openvpn também inicie com direitos de administrador, caso contrário não poderá controlar a interface de rede.

No Windows, o caminho da biblioteca para trabalhar com tokens deve ser registrado por meio de barras duplas. Isso se aplica às opções ovpn config e --show-pkcs11-ids na linha de comandos.

 pkcs11-providers "c://Windows//System32//rtPKCS11ECP.dll" pkcs11-id 'Aktiv\x20Co\x2E/Rutoken\x20ECP/342b871d/Rutoken/01' 

Source: https://habr.com/ru/post/pt433250/


All Articles