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.
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:
- 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.
- Na primeira vez que você liga o computador, você deve inserir uma senha.
- 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 , . , , .