Дистрибутив NixOS

Thank you for reading this post, don't forget to subscribe!

NixOS — дис­три­бу­тив Linux, создан­ный поверх мене­дже­ра паке­тов Nix. Он исполь­зу­ет декла­ра­тив­ную кон­фи­гу­ра­цию и поз­во­ля­ет надеж­но обнов­лять систе­му. Пред­ла­га­ют­ся два основ­ных направ­ле­ния: теку­щий ста­биль­ный выпуск и Unstable после послед­ней разработки. 

Любая ОС Linux после уста­нов­ки тре­бу­ет базо­вой настрой­ки — уста­нов­ки необ­хо­ди­мых при­ло­же­ний, настрой­ки базо­вых служб и без­опас­но­сти. Обыч­но для таких целей я создаю тек­сто­вый файл и в него копи­рую базо­вые коман­ды по настрой­ке ОС и кон­фи­гу­ра­ци­он­ные фай­лы. У каж­дой вер­сии дис­три­бу­ти­ва Linux базо­вая настрой­ка систе­мы может отличаться.

Несколь­ко лет назад я наткнул­ся на дис­три­бу­тив для Linux  под назва­ни­ем NixOS — с декла­ра­тив­ным опи­са­ни­ем кон­фи­гу­ра­ции систе­мы и функ­ци­о­наль­ным мене­дже­ром паке­тов nix. Вся кон­фи­гу­ра­ция NixOS опи­сы­ва­ет­ся в кон­фи­гу­ра­ци­он­ном фай­ле /etc/nixos/configuration.nix, в кото­ром ука­зы­ва­ют­ся настрой­ки систе­мы, уста­нав­ли­ва­е­мые паке­ты и сер­ви­сы. На осно­ве это­го фай­ла коман­дой nixos-rebuild созда­ет­ся кон­фи­гу­ра­ция системы.

Будем ста­вить NixOS на вир­ту­аль­ную маши­ну. Так как  я исполь­зую неста­биль­ную вет­ку, то ее и будем ставить.

Установка NixOS

Ска­чи­ва­ем загру­зоч­ный ISO образ nixos-minimal с этой стра­ни­цы https://nixos.org/channels/nixos-unstable и запус­ка­ем его.

Допол­ни­тель­ную инфор­ма­цию по уста­нов­ке мож­но взять отсю­да https://nixos.org/nixos/manual/index.html#sec-installation.

Для удоб­ства рабо­ты запу­стим openssh сервис:

sudo systemctl start sshd

И зада­дим пароль для поль­зо­ва­те­ля nixos:

passwd nixos

Смот­рим теку­щий IP-адрес вир­ту­аль­ной маши­ны и под­клю­ча­ем­ся по это­му адре­су по ssh под поль­зо­ва­те­лем nixos.

ip addr show

После под­клю­че­ния к систе­ме про­ве­ря­ем, какие дис­ки у нас подключены:

Созда­ем раз­де­лы на дис­ке vda (в зави­си­мо­сти от типа вир­ту­аль­ной маши­ны дис­ки могут отли­чать­ся) и мон­ти­ру­ем их:

Гене­ри­ру­ем пер­во­на­чаль­ную кон­фи­гу­ра­цию системы:

sudo nixos-generate-config --root /mnt

После выпол­не­ния коман­ды созда­ют­ся 2 файла:

  • /mnt/etc/nixos/configuration.nix
  • /mnt/etc/nixos/hardware-configuration.nix

При­ве­дем файл кон­фи­гу­ра­ции /mnt/etc/nixos/configuration.nix к тако­му виду:


И запу­стим уста­нов­ку системы:
sudo nixos-install --no-root-passwd --root /mnt

Вни­ма­ние: ука­зы­вая пара­метр —no-root-passwd, мы запре­ща­ем вход в систе­му поль­зо­ва­те­лю root.

После уста­нов­ки систе­мы уста­но­вим пароль для наше­го поль­зо­ва­те­ля rebrain, что­бы после пере­за­груз­ки систе­мы мы смог­ли под­клю­чить­ся уда­лен­но по ssh:

Пер­во­на­чаль­ная уста­нов­ка завер­ше­на, пере­хо­дим к базо­вой настройке.

Настройка NixOS

В настрой­ках вир­ту­аль­ной маши­ны отклю­ча­ем ISO образ и загру­жа­ем в уста­нов­лен­ную систе­му. Захо­дим по SSH с аутен­ти­фи­ка­ци­ей по паро­лю, ска­чи­ва­ем и рас­па­ко­вы­ва­ем архив с кон­фи­гу­ра­ци­ей в дирек­то­рию /etc/nixos.

У нас полу­ча­ет­ся такая струк­ту­ра конфигурации:

  • основ­ные настрой­ки систе­мы хра­нят­ся в дирек­то­рии /etc/nixos/nix-config/generic;
  • инди­ви­ду­аль­ные настрой­ки теку­ще­го сер­ве­ра — в дирек­то­рии /etc/nixos/nix-config/servers/example.

При­во­дим кон­фи­гу­ра­цию /etc/nixos/configuration.nix к тако­му виду:

Гене­ри­ру­ем хеш паро­ля для поль­зо­ва­те­ля rebrain командой:

mkpasswd -m sha-512

И встав­ля­ем резуль­тат в /etc/nixos/nix-config/servers/example/users.nix в hashedPassword, где _generated_hash_pass_ заме­ня­ем на наш хеш. В ито­ге кон­фи­гу­ра­ция будет выгля­деть так:

Что­бы авто­ри­зо­вать­ся на сер­ве­ре по клю­чу, про­пи­сы­ва­ем в файл generic/security/ssh-keys.nix пуб­лич­ный ключ:

Ссыл­ка на ключ work.rebrain_example уже про­пи­са­на в фай­ле servers/example/users.nix:

Вни­ма­ние! Пара­метр users.root = { hashedPassword = null; }; запре­ща­ет вход в систе­му  поль­зо­ва­те­лю root.

Если необ­хо­ди­мо управ­лять теку­щей систе­мой c помо­щью ути­ли­ты nixops, добав­ля­ем ssh ключ root пользователю:

Если в систе­ме вме­сто vda дис­ка исполь­зу­ет­ся sda, то в фай­ле /etc/nixos/nix-config/servers/example/configuration.nix меня­ем /dev/vda на /dev/sda:

Обнов­ля­ем кон­фи­гу­ра­цию систе­мы и перезагружаемся:


При пер­вом вхо­де zsh пред­ла­га­ет скон­фи­гу­ри­ро­вать файл ~/.zshrc,так как у нас уже гото­ва для него кон­фи­гу­ра­ция (в фай­ле generic/shell.nix), то про­пус­ка­ем кон­фи­гу­ра­цию и созда­ем пустой файл — вво­дим 0.

Созда­дим для нашей кон­фи­гу­ра­ции git-репозиторий:

Все коман­ды с git выпол­ня­ем от име­ни root, так как пра­ва пап­ки /etc/nixos при­над­ле­жат root поль­зо­ва­те­лю. Теперь мы можем отсле­жи­вать свою конфигурацию.

Пер­во­на­чаль­ная настрой­ка систе­мы завер­ше­на. Прой­дем­ся по основ­ным настройкам:

1. generic/nix-config/core.nix

  • imports = [ <nixpkgs/nixos/modules/profiles/hardened.nix> — исполь­зу­ем защи­щен­ный про­филь систе­мы. В неко­то­рых слу­ча­ях это может при­ве­сти к сни­же­нию производительности.
  • mkOverride 700 — зада­ет при­о­ри­тет настрой­кам, если он ука­зан. Напри­мер, если в кон­фи­гу­ра­ции servers/example/configuration.nix ука­зать boot.kernelPackages = pkgs.linuxPackages_5_4; то при­ме­нит­ся послед­ний вариант.
  • boot.kernelPackages = pkgs.linuxPackages_latest_hardened; — ука­зы­ва­ет при­ме­нять hardened ядро с пат­ча­ми https://github.com/anthraxx/linux-hardened.
  • hardware.ksm — акти­ви­ру­ем деду­пли­ка­цию памя­ти, в неко­то­рых слу­ча­ях поз­во­ля­ет сэко­но­мить ОЗУ.
  • environment.memoryAllocator.provider = mkOverride 700 «jemalloc»; — ука­зы­ва­ет исполь­зо­вать мене­джер памя­ти Jemalloc.
  • networking.enableIPv6.false; — отклю­ча­ем ipv6 про­то­кол, так как его не используем.
  • networking.dhcpcd.extraConfig = «\nnoipv6rs \nnoipv6»; — отклю­ча­ем ipv6 в служ­бе DHCPD.
  • networking.firewall.enable = false; — отклю­ча­ем фай­р­вол iptables, вме­сто него исполь­зу­ем nftables.
  • networking.useHostResolvConf = false; — исполь­зу­ет­ся в кон­тей­не­рах — исполь­зо­вать resolv.conf от хосто­вой маши­ны. Отключаем.
  • networking.usePredictableInterfaceNames = false; — воз­вра­ща­ем име­но­ва­ние сете­вых интер­фей­сов в ethX, так как у меня в вир­ту­аль­ных маши­нах исполь­зу­ют­ся иден­тич­ные интер­фей­сы и их коли­че­ство не пре­вы­ша­ет двух.
  • security.sudo.wheelNeedsPassword = false; — не запра­ши­вать каж­дый раз пароль перед запус­ком при­ло­же­ния от име­ни root. Так как мы исполь­зу­ем аутен­ти­фи­ка­цию по клю­чам, то нет смыс­ла каж­дый раз тре­бо­вать пароль.
  • services.journald — огра­ни­чи­ва­ем раз­мер жур­на­лов до 256 MiB.
  • services.qemuGuest — для вир­ту­аль­ных машин акти­ви­ру­ем QEMU Guest агент. В про­тив­ном слу­чае, в servers/custom_server/configuration.nix про­пи­сы­ва­ем — services.qemuGuest.enable = false;.
  • users.mutableUsers = false; — запре­тить сме­ну паро­ля. Пароль ука­зы­ва­ем в users.users.my-username.hashedPassword.

2. generic/nix-config/generic/overlays/default.nix

  • nur = import … — здесь добав­ля­ем поль­зо­ва­тель­ский репо­зи­то­рий с про­грам­ма­ми (пол­ный код в примере).

3. generic/programs/ssh.nix

  • startAgent — акти­ви­ру­ем ssh agent.
  • pubkeyAcceptedKeyTypes, hostKeyAlgorithms — исполь­зу­ем толь­ко ssh-ed25519 и rsa-sha2-512 ключи.
  • kexAlgorithms — исполь­зу­ем KEX алго­ритм curve25519-sha256@libssh.org.
  • ciphers — исполь­зу­ем шифр chacha20-poly1305@openssh.com.
  • macs — исполь­зу­ем hmac-sha2-512-etm@openssh.com алго­ритм аутен­ти­фи­ка­ции кода сообщения.
  • extraConfig — неболь­шие тви­ки ssh клиента.
  • generic/security/ssh-hosts.nix
  • programs.ssh.knownHosts — ука­зы­ва­ем при­ну­ди­тель­но пуб­лич­ные клю­чи извест­ных хостов. По IP мож­но так­же доба­вить дру­гие вир­ту­аль­ные маши­ны, куда тре­бу­ет­ся доступ по ssh. Если уда­лен­ный сер­вер каким-либо обра­зом под­ме­ни­ли, то ssh кли­ент выдаст ошибку.

4. generic/security/ssh-keys.nix — запи­сы­ва­ем пуб­лич­ные клю­чи для ssh аутентификации.
5. generic/services/fail2ban.nix — настра­и­ва­ем рабо­ту ути­ли­ты fail2ban c фай­р­во­лом nftables, добав­ля­ем, какие IP-адре­са игно­ри­ро­вать. Для сер­ви­са sshd зада­ем агрес­сив­ный метод сканирования.
6. generic/services/syslog-ng.nix — сохра­ня­ем логи ssh сер­ве­ра в отдель­ный файл.
7. generic/services/logrotate.nix — настра­и­ва­ем рота­цию ssh логов.
8. generic/services/sshd.nix

  • allowSFTP = true; — вклю­ча­ем под­держ­ку SFTP.
  • forwardX11 = mkDefault false; — запре­ща­ем исполь­зо­вать про­брос X11. Если тре­бу­ет­ся про­брос X11 при­ло­же­ний, добав­ля­ем в файл servers/example/configuration.nix стро­ку services.openssh.forwardX11 = true;.
  • startWhenNeeded = mkDefault true; — запус­кать SSH сер­вер толь­ко при акти­ва­ции запро­са на порт сер­ви­са (акти­ва­ция по systemd socket).
  • permitRootLogin = mkDefault «yes»; — раз­ре­шить поль­зо­ва­те­лю root захо­дить по SSH. Тре­бу­ет­ся для рабо­ты ути­ли­ты nixops. Мож­но отклю­чить, если не исполь­зу­е­те nixops — «no»;.
  • passwordAuthentication = false; challengeResponseAuthentication = false; — запре­ща­ем вход по паролю.
    useDns = false; — не исполь­зо­вать DNS сер­ве­ры при аутентификации.
  • macs — исполь­зу­ем hmac-sha2-512-etm@openssh.com алго­ритм аутен­ти­фи­ка­ции кода сообщения.
  • ciphers — исполь­зу­ем шифр chacha20-poly1305@openssh.com.
  • kexAlgorithms — исполь­зу­ем KEX алго­ритм curve25519-sha256@libssh.org. Боль­шин­ство ssh ска­не­ров будут отва­ли­вать­ся, так как не могут под­клю­чить­ся по ука­зан­ным алгоритмам.
  • authorizedKeysFiles — исполь­зо­вать откры­тые клю­чи авто­ри­за­ции из фай­лов, удо­вле­тво­ря­ю­щих шаб­ло­ну /etc/ssh/authorized_keys.d/%u, где %u — имя пользователя.
  • hostKeys — ука­зы­ва­ем исполь­зо­вать толь­ко rsa и ed25519 ключи.
  • extraConfig — неболь­шие тви­ки sshd сервера.

9. generic/services/unbound.nix — ста­вим локаль­ный ДНС сер­вер для кэши­ро­ва­ния запросов.
10. generic/ids.nix — зада­ем поль­зо­ва­те­лям фик­си­ро­ван­ный UID.
11. generic/locale.nix — настра­и­ва­ем шрифт, коди­ров­ку, лока­ли­за­цию, син­хро­ни­за­цию с сер­ве­ром вре­ме­ни и часо­вой пояс.
12. generic/pkgs.nix — уста­нав­ли­ва­ем часто исполь­зу­е­мые программы.
13. generic/shell.nix — настра­и­ва­ем shell, али­а­сы, про­грам­му nano и ZSH.
14. generic/tweaks.nix — неболь­шие тви­ки системы.
15. servers/example/configuration.nix — здесь сохра­ня­ем инди­ви­ду­аль­ные настрой­ки для теку­щей вир­ту­аль­ной маши­ны example.

    • boot.loader.grub.device = «/dev/vda»; — загру­жа­ем­ся с дис­ка /dev/vda.
    • boot.kernelParams = [ «lockdown=confidentiality» ]; — бло­ки­ру­ют­ся воз­мож­но­сти, поз­во­ля­ю­щие вно­сить изме­не­ния в рабо­та­ю­щее ядро из про­стран­ства поль­зо­ва­те­ля, и отклю­ча­ет­ся функ­ци­о­наль­ность, кото­рую мож­но исполь­зо­вать для извле­че­ния кон­фи­ден­ци­аль­ной инфор­ма­ции из ядра.
    • servers/example/users.nix — настра­и­ва­ем поль­зо­ва­те­ля rebrain.
    • hashedPassword — ука­зы­ва­ем фик­си­ро­ван­ный хеш паро­ля. Опция users.mutableUsers = false; поз­во­ля­ет запре­тить сме­ну паро­ля из консоли.
    • openssh.authorizedKeys.keys — про­пи­сы­ва­ем откры­тые ука­зан­ные в generic/security/ssh-keys.nix пуб­лич­ные ssh клю­чи, по кото­рым поль­зо­ва­тель может авто­ри­зо­вать­ся на сервере.
    • groups.ssh-users.members — про­пи­сы­ва­ем, каким поль­зо­ва­те­лям раз­ре­шен вход по ssh.

16. servers/example/services/fail2ban.nix

    • services.fail2ban.enable — акти­ви­ру­ем сер­вис fail2ban. По умол­ча­нию сер­вис отклю­чен в generic/services/fail2ban.nix.
    • services.fail2ban.jails.sshd — акти­ви­ру­ем jail sshd.
    • servers/example/services/firefall-nft.nix — акти­ви­ру­ем фай­р­вол nftables, откры­ва­ем 22 порт.

В ито­ге, мы закры­ли неко­то­рые вет­ки ата­ки и под­го­то­ви­ли шаб­лон, кото­рый мож­но исполь­зо­вать на дру­гих систе­мах на базе Linux NixOS.

Немного о Linux NixOS

Теперь подроб­нее о Linux NixOS. NixOS сохра­ня­ет все при­ло­же­ния и раз­лич­ные вер­сии кон­фи­гу­ра­ции систе­мы в соб­ствен­ных под­ка­та­ло­гах дирек­то­рии /nix/store. Напри­мер, при­ло­же­ние nano нахо­дит­ся в дирек­то­рии /nix/store/17xx786q2gdvjz01hx0j8rihmm5h2m88-nano-4.9.3 и на него созда­ет­ся сим­воль­ная ссылка:

Теку­щая кон­фи­гу­ра­ция систе­мы с уста­нов­лен­ны­ми при­ло­же­ни­я­ми рас­по­ло­же­на в /run/current-system, кото­рая ссы­ла­ет­ся на под­ка­та­лог в /nix/store

При обнов­ле­нии систе­мы созда­ет­ся под­ка­та­лог с новой кон­фи­гу­ра­ци­ей систе­мы в дирек­то­рии /nix/store и сим­воль­ная ссыл­ка /run/current-system пере­клю­ча­ет­ся на нее. В загруз­чик grub добав­ля­ет­ся новая запись с кон­фи­гу­ра­ци­ей систе­мы. Доба­вим про­грам­му mysql-client:

Сде­ла­ем ком­мит в нашем репозитории:

sudo git add nix-config/generic/pkgs.nix
sudo git commit -m "config: add mysql-client package"

Теперь запус­ка­ем обнов­ле­ние системы:

sudo nixos-rebuild switch

У нас уста­но­ви­лось новое при­ло­же­ние и сим­воль­ная ссыл­ка /run/current-system пере­клю­чи­лась на новую конфигурацию:

При­мер­но так выгля­дят несколь­ко кон­фи­гу­ра­ций систе­мы в /boot/grub/grub.cfg:


Пара­метр systemConfig ука­зы­ва­ет, какую вер­сию кон­фи­гу­ра­ции исполь­зо­вать для загруз­ки системы.

Про­смот­реть теку­щие кон­фи­гу­ра­ции систе­мы мож­но командой:

sudo nix-env --list-generations --profile /nix/var/nix/profiles/system

Коман­да выдаст при­мер­но такой результат:

1 2020-07-13 12:08:28
2 2020-07-13 12:27:27
3 2020-07-13 12:51:09
4 2020-07-13 13:05:37 (current)

Отка­тим­ся на преды­ду­щую конфигурацию:

Попро­бу­ем запу­стить mysql:

mysql --version
mysql: command not found

Кон­фи­гу­ра­ция систе­мы отка­ти­лась, и теперь у нас про­грам­ма mysql не установлена.

Вер­нем­ся к послед­ней конфигурации:

sudo nixos-rebuild switch

Так­же любую вер­сию кон­фи­гу­ра­ции систе­мы мож­но выбрать при ее запус­ке, в меню grub. Очи­стить ста­рые кон­фи­гу­ра­ции и осво­бо­дить место на дис­ке мож­но командой:

sudo nix-collect-garbage --delete-old

Давай­те доба­вим еще один сер­вис для мони­то­рин­га нашей вир­ту­аль­ной маши­ны. Созда­ем файл nix-config/servers/example/services/netdata.nix:

 

Добав­ля­ем его в nix-config/servers/example/configuration.nix:


 

И откры­ва­ем 19999 порт в фай­р­во­ле nftables для наше­го сервиса:


Обнов­ля­ем систему:
sudo nixos-rebuild switch

Сер­вис мони­то­рин­га активирован:


Откры­ва­ем в бра­у­зе­ре по 19999 пор­ту и про­ве­ря­ем работу.

Дела­ем ком­мит в нашем репозитории:

sudo git add -A
sudo git commit -m "config: add netdata service"

Про­ве­ря­ем изме­не­ния в коммите:

 

При­ме­ча­ние: Если при обнов­ле­нии систе­мы воз­ни­ка­ет ошибка:

updating GRUB 2 menu…
failed to create initrd secrets: No such file or directory
warning: error(s) occurred while switching to the new configuration

То перед обнов­ле­ни­ем выпол­ня­ем команду:

sudo rm /etc/ld-nix.so.preload

В послед­них изме­не­ни­ях была ошиб­ка, кото­рая при­во­ди­ла к некор­рект­ной сбор­ке систе­мы из-за исполь­зо­ва­ния про­вай­де­ра рас­пре­де­ле­ния памя­ти — environment.memoryAllocator.provider.