Desbloquear automaticamente o contêiner LUKS raiz após uma inicialização a quente

Por que as pessoas geralmente criptografam unidades de seus computadores pessoais e, às vezes, servidores? É claro que ninguém roubou fotos de seus gatos de estimação favoritos do disco! Isso é apenas azar: uma unidade criptografada exige que você insira uma frase-chave do teclado a cada inicialização e é longa e chata. Removê-lo para que, pelo menos às vezes, não seja necessário recrutá-lo. Sim, para que o significado da criptografia não seja perdido.


Gato pela atenção

Cat


Bem, removê-lo completamente não vai funcionar. Você pode criar um arquivo de chave em uma unidade flash USB, e ele também funcionará. E sem uma unidade flash (e sem um segundo computador na rede) é possível? Se você tiver sorte com o BIOS, pode quase! Abaixo do corte, haverá um guia sobre como configurar a criptografia de disco através do LUKS com as seguintes propriedades:


  1. A senha ou o arquivo de chave não é armazenado em nenhum lugar no formato aberto (ou no formato equivalente a aberto) quando o computador é desligado.
  2. Na primeira vez que você liga o computador, você deve inserir uma senha.
  3. Nas reinicializações subsequentes (antes de desligar), uma senha não é necessária.

As instruções foram testadas no CentOS 7.6, Ubuntu 19.04 e openSUSE Leap 15.1 em máquinas virtuais e em hardware real (desktop, laptop e dois servidores). Eles devem trabalhar em outras distribuições que tenham uma versão funcional do Dracut.


E sim, no bom sentido, isso deveria ter acabado no hub "administração anormal do sistema", mas não existe esse hub.


Sugiro usar um slot separado no contêiner LUKS e armazenar a chave para ele ... na RAM!


Que tipo de slot?

O contêiner LUKS implementa criptografia em vários níveis. Os dados úteis no disco são criptografados com uma cifra simétrica, geralmente aes-xts-plain64 . A chave para essa cifra simétrica (chave mestra) é gerada no estágio de criação do contêiner como uma sequência aleatória de bytes. A chave mestra é armazenada em forma criptografada, no caso geral - em várias cópias (slots). Por padrão, apenas um dos oito slots está ativo. Cada slot ativo possui uma frase-chave separada (ou um arquivo-chave separado), com o qual você pode descriptografar a chave mestra. Do ponto de vista do usuário, você pode desbloquear a unidade usando qualquer uma das várias frases-chave diferentes (ou arquivos-chave). No nosso caso, usando uma frase-chave (slot 0) ou um pedaço de memória usado como arquivo-chave (slot 6).


O BIOS na maioria das placas-mãe não limpa a memória durante a reinicialização ou você pode configurá-lo para não limpar (exceção conhecida: "Intel Corporation S1200SP / S1200SP, BIOS S1200SP.86B.03.01.0042.013020190050 em 30/01/2019"). Portanto, você pode armazenar a chave lá. Quando a energia é desligada, o conteúdo da própria RAM é apagado após um tempo, junto com uma cópia desprotegida da chave.


Então vamos lá.


Etapa 1: instalar o sistema em um disco criptografado usando LUKS


Nesse caso, a partição do disco (por exemplo, /dev/sda1 ) montada em /boot deve permanecer não criptografada e a outra partição na qual todo o resto (por exemplo, /dev/sda2 ) deve ser criptografado. O sistema de arquivos na partição criptografada pode ser qualquer um, você também pode usar o LVM para que o sistema de arquivos raiz, o volume para troca e tudo o mais, exceto /boot no mesmo contêiner. Isso corresponde ao particionamento de disco padrão no CentOS 7 e Debian ao escolher a opção de criptografia. O SUSE faz tudo de maneira diferente (criptografa /boot ) e, portanto, requer particionamento manual do disco.


O resultado deve ser algo como o seguinte:


 $ lsblk NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT sda 8:0 0 10G 0 disk ├─sda1 8:1 0 1G 0 part /boot └─sda2 8:2 0 9G 0 part └─luks-d07a97d7-3258-408c-a17c-e2fb56701c69 253:0 0 9G 0 crypt ├─centos_centos--encrypt2-root 253:1 0 8G 0 lvm / └─centos_centos--encrypt2-swap 253:2 0 1G 0 lvm [SWAP] 

No caso de usar UEFI, também haverá uma partição de sistema EFI.


Para usuários Debian e Ubuntu: substitua o pacote initramfs-tools pelo dracut .
 # apt install --no-install-recommends dracut 


initramfs-tools implementa lógica incorreta no nosso caso, aplicada a seções criptografadas com um arquivo de chave. Essas seções são ignoradas completamente ou o conteúdo do arquivo-chave é copiado para o initramfs (ou seja, como resultado, para o disco), o que não é necessário.

Etapa 2: criar um arquivo de chave que será usado para desbloquear automaticamente a unidade após uma reinicialização a quente


128 bits aleatórios são suficientes para nós, ou seja, 16 bytes. O arquivo será armazenado em um disco criptografado; portanto, quem não conhece a chave de criptografia e não possui acesso root ao sistema carregado não a lê.


 # touch -m 0600 /root/key # head -c16 /dev/urandom > /root/key 

Existem bits verdadeiramente aleatórios suficientes no arquivo de chaves, para que o algoritmo PBKDF lento, que torna uma chave de criptografia difícil de escolher a partir de uma frase-chave potencialmente fraca, não seja realmente necessário. Portanto, ao adicionar uma chave, você pode reduzir o número de iterações:


 # cryptsetup luksAddKey --key-slot=6 --iter-time=1 /dev/sda2 /root/key Enter any existing passphrase: 

Como você pode ver, o arquivo de chave é armazenado em um disco criptografado e, portanto, não representa risco à segurança se o computador estiver desligado.


Etapa três: alocar espaço na memória física para armazenar a chave


O Linux possui pelo menos três drivers diferentes que permitem acessar a memória física em um endereço conhecido. Esse é o linux/drivers/char/mem.c , que também é responsável pelo dispositivo /dev/mem , além phram módulos phram (emula um chip MTD, fornece o dispositivo /dev/mtd0 ) e o nd_e820 (usado ao trabalhar com o NVDIMM, fornece /dev/pmem0 ). Todos eles têm características desagradáveis:


  • /dev/mem não /dev/mem ser gravado ao usar o Secure Boot se a distribuição usa o conjunto de patches LOCKDOWN de Matthew Garrett (e esse conjunto de patches é necessário se a distribuição oferecer suporte ao Secure Boot com um gerenciador de inicialização assinado pela Microsoft);
  • phram não phram disponível no CentOS e no Fedora - o mantenedor simplesmente não phram a opção correspondente ao criar o kernel;
  • nd_e820 precisa reservar pelo menos 128 megabytes de memória - é assim que o NVDIMM funciona. Mas esta é a única opção em execução no CentOS com inicialização segura.

Como não há opção ideal, todos os três são considerados abaixo.


Ao usar qualquer um dos métodos, é necessário extremo cuidado para não afetar acidentalmente dispositivos ou intervalos de memória diferentes do necessário. Isto é especialmente verdade para computadores que já possuem chips MTD ou módulos NVDIMM. Ou seja, /dev/mtd0 ou /dev/pmem0 pode não ser o dispositivo que corresponde à área de memória reservada para armazenar a chave. A numeração dos dispositivos existentes, nos quais os arquivos e scripts de configuração dependem, também pode ser confusa. Portanto, é recomendável desabilitar temporariamente todos os serviços que dependem de dispositivos existentes /dev/mtd* e /dev/pmem* .

É memmap memória física do Linux, passando a opção memmap para o memmap . Estamos interessados ​​em dois tipos dessa opção:


  • memmap=4K$0x10000000 reservas (ou seja, marcas reservadas para que o próprio kernel não use) 4 kilobytes de memória, começando com o endereço físico 0x10000000;
  • memmap=128M!0x10000000 marca 128 megabytes de memória física, começando com o endereço 0x10000000, como NVDIMM (obviamente falso, mas servirá para nós).

A opção c $ é adequada para uso com /dev/mem e phram , a opção c ! - para nd_e820 . Ao usar $ endereço inicial da memória reservada deve ser um múltiplo de 0x1000 (ou seja, 4 kilobytes), ao usar ! - um múltiplo de 0x8000000 (ou seja, 128 megabytes).


Importante: o cifrão ( $ ) nos arquivos de configuração do GRUB é um caractere especial e deve ser escapado. E duas vezes: uma vez - ao gerar o grub.cfg partir de /etc/default/grub , uma segunda vez - ao interpretar o arquivo de configuração resultante no estágio de inicialização. I.e. em /etc/default/grub , a seguinte linha deve aparecer eventualmente:


 GRUB_CMDLINE_LINUX="memmap=4K\\\$0x10000000 ...  ..." 

Sem escapar duas vezes do sinal $ , o sistema simplesmente não inicializa, pois pensa que possui apenas 4 kilobytes de memória. Não há tais dificuldades com um ponto de exclamação:


 GRUB_CMDLINE_LINUX="memmap=128M!0x10000000 ...  ..." 

O cartão de memória físico (e é necessário descobrir quais endereços reservar) é acessível ao root no pseudo- /proc/iomem :


 # cat /proc/iomem ... 000f0000-000fffff : reserved 000f0000-000fffff : System ROM 00100000-7ffddfff : System RAM 2b000000-350fffff : Crash kernel 73a00000-7417c25e : Kernel code 7417c25f-747661ff : Kernel data 74945000-74c50fff : Kernel bss 7ffde000-7fffffff : reserved 80000000-febfffff : PCI Bus 0000:00 fd000000-fdffffff : 0000:00:02.0 ... 

A RAM está marcada como "RAM do sistema", basta reservar uma de suas páginas para armazenar a chave. Adivinhar qual parte da memória do BIOS não toca na reinicialização não funcionará com confiabilidade antecipadamente. A menos que haja outro computador com exatamente a mesma versão do BIOS e a mesma configuração de memória no qual este manual já foi concluído. Portanto, no caso geral, você terá que agir por tentativa e erro. Como regra, quando o BIOS é reinicializado, ele altera os dados somente no início e no final de cada intervalo de memória. Geralmente, é suficiente retirar 128 megabytes ( 0x8000000 ) das bordas. Para máquinas virtuais KVM com 1 GB de memória ou mais, as opções propostas ( memmap=4K$0x10000000 e memmap=128M!0x10000000 ) funcionam.


Ao usar o módulo phram , precisamos de outro parâmetro de linha de comando do kernel, que, de fato, diz ao módulo qual parte da memória física usar - a nossa, reservada. O parâmetro é chamado phram.phram e contém três partes: o nome (arbitrário até 63 caracteres, será visível em sysfs ), o endereço inicial e o comprimento. O endereço inicial e o comprimento devem ser os mesmos do memmap , mas os sufixos K e M não M suportados.


 GRUB_CMDLINE_LINUX="memmap=4K\\\$0x10000000 phram.phram=savedkey,0x10000000,4096 ..." 

Após editar o arquivo /etc/default/grub você precisará gerar novamente o arquivo de configuração real que o GRUB lê na inicialização. O comando correto para isso depende da distribuição.


 # grub2-mkconfig -o /boot/grub2/grub.cfg # CentOS (Legacy BIOS) # grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg # CentOS (UEFI) # update-grub # Debian, Ubuntu # update-bootloader --reinit # SUSE 

Após atualizar a configuração do GRUB, o computador deve ser reiniciado, mas faremos isso mais tarde quando atualizarmos o initramfs.


Quarto passo: configure o LUKS para ler a chave da memória


/etc/crypttab configurações de criptografia da /etc/crypttab são armazenadas no /etc/crypttab . Cada linha consiste em quatro campos:


  • o dispositivo que deve ser obtido ao desbloquear,
  • dispositivo criptografado
  • onde obter o arquivo de teclas ( none significa digitar uma frase-chave no teclado),
  • campo opcional para opções.

Se o arquivo-chave existe, mas não se encaixa, o Dracut solicita a frase-chave. O que, de fato, será necessário na primeira inicialização.


Um exemplo do /etc/crypttab de uma distribuição recém-instalada:


 # cat /etc/crypttab #   luks-d07....69 UUID=d07....69 none 

O arquivo-chave no nosso caso será um pedaço de memória física. I.e. /dev/mem , /dev/mtd0 ou /dev/pmem0 , dependendo da tecnologia de acesso à memória selecionada. São necessárias opções para indicar qual parte do arquivo é a chave.


 # cat /etc/crypttab #   #   /dev/mem: luks-d07....69 UUID=d07....69 /dev/mem keyfile-offset=0x10000000,keyfile-size=16 #   phram: luks-d07....69 UUID=d07....69 /dev/mtd0 keyfile-size=16 #   nd_e820: luks-d07....69 UUID=d07....69 /dev/pmem0 keyfile-size=16 

Só que não vai funcionar assim.


O ponto é como o systemd determina quando um dispositivo pode ser desbloqueado. Ou seja, ele pega o dispositivo da terceira coluna e aguarda a unidade do dispositivo correspondente se tornar ativa. Parece lógico: não faz sentido tentar desbloquear o contêiner LUKS até que um dispositivo com um arquivo de chave seja exibido. Mas a unidade do dispositivo não é a mesma que o próprio dispositivo. O Systemd, por padrão, cria unidades de dispositivo apenas para dispositivos do kernel relacionados a subsistemas de dispositivos de bloco e interfaces de rede. Os dispositivos /dev/mem e /dev/mtd0 são caracter por caracter, portanto, não são monitorados por padrão e nunca serão reconhecidos como prontos.


Você precisará informar ao systemd que ele deve rastreá-las criando regras do /etc/udev/rules.d/99-mem.rules arquivo /etc/udev/rules.d/99-mem.rules :


 # /dev/mem KERNEL=="mem", TAG+="systemd" # /dev/mtd* KERNEL=="mtd*", TAG+="systemd" #  /dev/pmem*       

Etapa 5: regenerar o initramfs


Lembro a você: este artigo discute apenas distribuições usando o Dracut. Incluindo aqueles onde não é usado por padrão, mas é acessível e eficiente.

Você precisa regenerar o initramfs para atualizar o arquivo /etc/crypttab lá. E também - para incluir módulos adicionais do kernel e regras do udev lá. Caso contrário, o dispositivo /dev/mtd0 ou /dev/pmem0 não será criado. O parâmetro de configuração do Dracut force_drivers é responsável por ativar e carregar módulos adicionais do kernel, e o install_items é responsável por arquivos adicionais. Criamos o arquivo /etc/dracut.conf.d/mem.conf com o seguinte conteúdo (é necessário um espaço após as aspas de abertura, este é um separador):


 #   /dev/mem: install_items+=" /etc/udev/rules.d/99-mem.rules" #   phram: install_items+=" /etc/udev/rules.d/99-mem.rules" force_drivers+=" phram" #   nd_e820: force_drivers+=" nd_e820 nd_pmem" 

Na verdade, initramfs de regeneração:


 # dracut -f 

Para usuários do Debian e Ubuntu, o mantenedor colocou uma rake: o arquivo resultante é chamado incorretamente. Você precisa renomeá-lo para que ele seja nomeado da mesma maneira que prescrita na configuração do GRUB:
 # mv /boot/initramfs-5.0.0-19-generic.img /boot/initrd.img-5.0.0-19-generic 


Ao instalar novos kernels, a criação automática de initramfs via Dracut é realizada corretamente, o bug afeta apenas o lançamento manual do dracut -f .

Etapa 6: Reinicie seu computador


É necessária uma reinicialização para que as alterações nas configurações do GRUB e Dracut entrem em vigor.


 # reboot 

Nesse estágio, não há chave na memória, portanto, você precisará digitar uma senha.


Após a reinicialização, você precisa verificar se o backup da memória funcionou corretamente. No mínimo, no pseudo- /proc/iomem memória necessária deve ser marcada como "reservada" (ao usar /dev/mem ou phram ) ou como "Memória Persistente (legada)".


Mesmo ao usar phram ou nd_e820 você precisa garantir que o dispositivo /dev/mtd0 ou /dev/pmem0 realmente se refira à área de memória reservada anteriormente e não a outra coisa.


 # cat /sys/class/mtd/mtd0/name #  : "savedkey" # cat /sys/block/pmem0/device/resource #     

Se não for assim, você precisará descobrir qual dos dispositivos /dev/mtd* ou /dev/pmem* "nosso" e, em seguida, corrigir / etc / crypttab, gerar novamente o initramfs e verificar novamente o resultado após outra reinicialização.


Sétima etapa: configurar a cópia do arquivo-chave na memória


O arquivo de chave será copiado para a memória antes da reinicialização. Uma das maneiras de executar qualquer comando no estágio de desligamento do sistema é registrá-lo na diretiva ExecStop no serviço systemd. Para que o systemd entenda que isso não é um daemon e não jure pela falta da diretiva ExecStart , você precisa especificar o tipo de serviço como ExecStart - ExecStart e também sugerir que o serviço seja considerado em execução, mesmo se nenhum processo de trabalho estiver associado a ele. Então, aqui está o arquivo /etc/systemd/system/savekey.service . É necessário deixar apenas uma das variantes fornecidas da diretiva ExecStop .


 [Unit] Description=Saving LUKS key into RAM Documentation=https://habr.com/ru/post/457396/ [Service] Type=oneshot RemainAfterExit=true #   /dev/mem: ExecStop=/bin/sh -c 'dd if=/root/key of=/dev/mem bs=1 seek=$((0x10000000))' #   /dev/mtd0: ExecStop=/bin/dd if=/root/key of=/dev/mtd0 #   /dev/pmem0: ExecStop=/bin/dd if=/root/key of=/dev/pmem0 [Install] WantedBy=default.target 

A construção com /bin/sh necessária, pois o dd não entende a notação hexadecimal.


Ativamos o serviço, verifique:


 # systemctl enable savekey # systemctl start savekey # reboot 

Durante a reinicialização subsequente, você não precisa inserir a senha no disco. E, se necessário, isso geralmente significa que o endereço inicial da área de memória reservada foi selecionado incorretamente. Não há problema em corrigir e regenerar vários arquivos e reiniciar o computador duas vezes.


Ao usar phram ou nd_e820 somente a configuração do GRUB precisará ser editada. Ao usar /dev/mem endereço inicial também é mencionado em /etc/crypttab (portanto, o initramfs precisará ser regenerado) e no serviço systemd.


Mas isso não é tudo.


Questões de segurança


Qualquer discussão sobre problemas de segurança é baseada em um modelo de ameaça. I.e. sobre os objetivos e meios do atacante. Estou ciente de que alguns dos exemplos abaixo são absurdos.


As situações com acesso físico a um computador desligado não são diferentes daquelas sem um armazenamento de chaves configurado na memória. Existem os mesmos tipos de ataques destinados a obter frases-chave, incluindo Evil Maid , e os mesmos recursos de segurança . Não paramos com eles, pois não há nada de novo.


Situações mais interessantes são quando o computador está ligado.


Situação 1 . O invasor não tem acesso físico ao computador, não conhece a senha, mas possui acesso root via ssh. O objetivo é a chave para descriptografar o disco. Por exemplo, para acessar backups setor por setor antigos de uma imagem de disco da máquina virtual.


Na verdade, a chave no disco está no arquivo /root/key . A questão é como isso se relaciona com o que aconteceu antes da implementação desta instrução. Resposta: para luks1, a ameaça não é nova. Existe um dmsetup table --target crypt --showkeys que mostra a chave mestra, ou seja, também dados que permitem acesso a backups antigos. Para luks2, a redução de segurança nesse cenário realmente ocorre: as chaves dm-crypt são armazenadas no chaveiro no nível do kernel e é impossível visualizá-las no espaço do usuário.


Situação 2 . O invasor pode usar o teclado e olhar para a tela, mas não está pronto para abrir o gabinete. Por exemplo, usei a senha vazada da IPMI ou interceptei uma sessão noVNC na nuvem. Ele não conhece a frase-chave, também não conhece outras senhas. O objetivo é o acesso root.


Por favor: reinicie via Ctrl-Alt-Del , adicionando a opção do kernel init=/bin/sh via GRUB. A senha não era necessária, pois a chave foi lida com êxito na memória. Para se proteger disso, você teria que impedir que o GRUB carregasse o que não está no menu. Infelizmente, essa funcionalidade é implementada de maneira diferente em diferentes distribuições.


A partir da versão 7.2, o CentOS possui o grub2-setpassword , que realmente protege o GRUB com uma senha. Outras distribuições podem ter seus próprios utilitários para a mesma tarefa. Se não estiverem, você poderá editar diretamente os arquivos no diretório /etc/grub.d e gerar grub.cfg .


No arquivo /etc/grub.d/10_linux , altere a variável CLASS, adicione a opção --unrestricted ao final, se não estiver lá:


 CLASS="--class gnu-linux --class gnu --class os --unrestricted" 

No arquivo /etc/grub.d/40_custom adicione as linhas especificando o nome de usuário e a senha necessários para editar a linha de comando do kernel:


 set superusers="root" password_pbkdf2 root grub.pbkdf2....... #    grub2-mkpasswd-pbkdf2 

Ou, se essa funcionalidade precisar ser desativada, aqui está uma linha como esta:


 set superusers="" 

Situação 3 . O invasor tem acesso ao computador incluído, permitindo a inicialização a partir de mídia não confiável. Pode ser um acesso físico sem abrir o caso ou acessar via IPMI. O objetivo é o acesso root.


Ele pode carregar seu GRUB a partir de uma unidade flash USB ou CD-ROM e adicionar init=/bin/sh aos parâmetros do seu kernel, como no exemplo anterior. Portanto, a inicialização de qualquer mídia horrível deve ser proibida no BIOS. E também proteja as configurações do BIOS alteradas com uma senha.


Situação 4 . O invasor tem acesso físico ao computador incluído, incluindo a capacidade de abrir o caso. O objetivo é descobrir a chave ou obter acesso root.


Em geral, esta é uma situação perdedora de qualquer maneira. O ataque aos módulos de memória, resfriando-os ( ataque de inicialização a frio ) não foi cancelado. Além disso, teoricamente (não foi verificado), você pode tirar vantagem do fato de que os discos SATA modernos oferecem suporte à reconexão a quente. Quando o computador reiniciar, desconecte o disco, altere grub.cfg para init=/bin/sh , reconecte, permita que o sistema reinicie. Acontece (se bem entendi) o acesso root.


O mesmo pode ser feito por um funcionário inescrupuloso de uma hospedagem em nuvem, fazendo um instantâneo de uma máquina virtual com sua modificação subsequente.


Outros assuntos


Manter a chave na memória durante uma reinicialização é uma zombaria. Use após livre na sua forma mais pura. Uma solução mais limpa é usar o kexec e adicionar a chave aos initramfs gerados dinamicamente. Ele também protege contra a substituição de parâmetros do kernel . Sim, se o kexec estiver funcionando. Distribuições modernas tornaram a configuração do kexec muito complicada .


Nos datacenters, e mais ainda na nuvem, o poder nunca desaparece. Acontece que a frase-chave não é mais necessária? De fato, se você tem tanta certeza disso, pode excluí-lo. Ele se tornará um servidor em funcionamento, a chave do disco que ninguém conhece¹ e, portanto, não será divulgado, mas o sistema no qual pode ser atualizado usando meios regulares. — sudo poweroff .


¹ /root/key — , cron.


? IPMI, . IPMI Java. .


? SSH . ! . , sudo reboot , ?


, . SSH , . , , .

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


All Articles