paranoid nixos post
Signed-off-by: Christine Dodrill <me@christine.website>
This commit is contained in:
parent
fbccd757d5
commit
d752cd91b1
|
@ -0,0 +1,683 @@
|
|||
---
|
||||
title: Paranoid NixOS Setup
|
||||
date: 2021-07-18
|
||||
author: ectamorphic
|
||||
series: nixos
|
||||
tags:
|
||||
- paranoid
|
||||
- noexec
|
||||
---
|
||||
|
||||
Most of the time you can get away with a fairly simple security posture on
|
||||
NixOS. Don't run services as root, separate each service into its own systemd
|
||||
units, don't run packages you don't trust the heritage of and most importantly
|
||||
don't give random people shell access with passwordless sudo.
|
||||
|
||||
Sometimes however, you have good reasons to want to lock everything down as much
|
||||
as humanly possible. This could happen when you want to create production
|
||||
servers for something security-critical such as a bastion host. In this post I'm
|
||||
going to show you a defense-in-depth model for making a NixOS server that is a
|
||||
bit more paranoid than usual, as well as explanations of all the moving parts.
|
||||
|
||||
## High-level Ideas
|
||||
|
||||
At a high-level I'm assuming the following things about this setup:
|
||||
|
||||
- It should be very difficult to get in as a passive attacker
|
||||
- But the defense doesn't stop at "just hope they don't get in"
|
||||
- It should be annoying for attackers to get a user-level shell
|
||||
- But ensure they'll be able to anyways if they're dedicated enough
|
||||
- It should be difficult for attackers to run their own code on the system
|
||||
- But ensure that it could happen and make evidence of that very loud
|
||||
- It should be aggrivating for attackers to access the package manager on the
|
||||
system
|
||||
- But ensure that they can't do anything very easily even if they can access the
|
||||
package manager itself
|
||||
|
||||
Some additional goals:
|
||||
|
||||
- Make the system only manageable by a central management system such as morph
|
||||
or nixops
|
||||
- Only make SSH visible over a VPN of some kind, such as
|
||||
[Tailscale](https://tailscale.com) or another WireGuard setup
|
||||
- Mount the root filesystem on a tmpfs
|
||||
- Have explicitly defined persistent folders
|
||||
- Mark everything as `noexec` except for the mount that `/nix/store` is on
|
||||
- Don't make the system too difficult to use in the process
|
||||
|
||||
[Disclaimer: I am a Tailscale employee. Tailscale did not review this
|
||||
post for accuracy or content, though this setup is based on conversations I've
|
||||
had with a coworker at Tailscale.](conversation://Cadey/enby)
|
||||
|
||||
Along the way we'll be making a system that I'm naming `meeka`. We'll put its
|
||||
configuration in a folder named `meeka`:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/configuation.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
networking.hostName = "meeka";
|
||||
services.openssh.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
## Low-hanging Fruit
|
||||
|
||||
There are some easy things we can get out of the way. One of the biggest ways
|
||||
that people get in is to make services visible to attack in the first place.
|
||||
|
||||
### The Firewall
|
||||
|
||||
Let's get one of the lowest-hanging fruits out of the way: the firewall. Most of
|
||||
the background radiation of the internet is in the form of automated probes to
|
||||
development ports and SSH traffic. NixOS actually includes a firewall by
|
||||
default! You can see more information on how to configure it
|
||||
[here](https://nixos.org/manual/nixos/stable/index.html#sec-firewall), but
|
||||
here's a good collection of values to use by default:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/firewall.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
networking.firewall.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
### VPN for Access
|
||||
|
||||
Generally, it's probably okay to use SSH over the unprotected internet for
|
||||
accessing your machines. However, this is all about maximum paranoia, so we're
|
||||
going to use a VPN to get into the machine. [Tailscale](https://tailscale.com/)
|
||||
is a fairly direct thing to set up in NixOS:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/tailscale.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
services.tailscale.enable = true;
|
||||
|
||||
# Tell the firewall to implicitly trust packets routed over Tailscale:
|
||||
networking.firewall.trustedInterfaces = [ "tailscale0" ];
|
||||
}
|
||||
```
|
||||
|
||||
When you boot into the server, you can log in like normal using the `tailscale
|
||||
up` command. You can probably isolate down the server using
|
||||
[ACLs](https://tailscale.com/kb/1018/acls/) if you want to make sure things are
|
||||
a bit more paranoid.
|
||||
|
||||
It may be good to set up a second way to get into the machine, just in case. I
|
||||
personally try to leave at least 3 ways into my servers, but the super paranoid
|
||||
production-facing servers should probably only be able to be connected to over a
|
||||
VPN of some kind.
|
||||
|
||||
If you want to see more about how to set up WireGuard on NixOS, see
|
||||
[here](https://christine.website/blog/my-wireguard-setup-2021-02-06) for more
|
||||
information.
|
||||
|
||||
## Locking Down the Hatches
|
||||
|
||||
Now that we're getting out of the easy stuff, let's go to the more defense in
|
||||
depth stuff. Here we're going to talk about separation of concerns and all those
|
||||
other fun things.
|
||||
|
||||
### Each Service Gets its own User Account
|
||||
|
||||
I am going to use the word "service" annoyingly vague here. In this world, a
|
||||
"service" is a human-oriented view of "computer does the thing I want it to do".
|
||||
This website you're reading this post on could be one service, and it should
|
||||
have a separate account from other services. See
|
||||
[here](https://christine.website/blog/nixops-services-2020-11-09) for more
|
||||
information on how to set this up.
|
||||
|
||||
### Lock Down Services Within Systemd
|
||||
|
||||
[systemd](https://www.freedesktop.org/wiki/Software/systemd/) is a suite of
|
||||
tools that NixOS uses to manage a huge chunk of the system. It is kinda
|
||||
complicated and very large in scope, however this also means that you get access
|
||||
to a lot of convenient security management features. One of them is the
|
||||
`Protect*` unit options in
|
||||
[`systemd.exec(5)`](https://man7.org/linux/man-pages/man5/systemd.exec.5.html),
|
||||
which can be used to lock down permissions to the resource and system call
|
||||
level. Let's cover some of my favorites that you can slipstream into services:
|
||||
|
||||
Also take a look at `systemd-analyze security yourservicename.service`, that
|
||||
will give you a lot more things to search through the systemd documentation for.
|
||||
|
||||
#### `ProtectHome`/`ProtectSystem`
|
||||
|
||||
These options allow you to change how systemd presents critical system files and
|
||||
`/home` to a given process. You can use this to remove the ability for a service
|
||||
to modify system files or peek into user's home directories, even as root. This
|
||||
allows you to put a lot more limits on a service's power.
|
||||
|
||||
#### `NoNewPrivileges`
|
||||
|
||||
If this is set, child processes of this service cannot gain more privileges
|
||||
period. Even if the child process is a suid binary.
|
||||
|
||||
[A suid binary is a binary that has the suid flag set. This makes the Linux
|
||||
kernel change the active user field of that binary to the owner of the binary
|
||||
when you run it. This is a huge part of how the magic behind sudo and ping
|
||||
works.](conversation://Mara/hacker)
|
||||
|
||||
#### `ProtectKernel{Logs,Modules,Tuneables}`
|
||||
|
||||
These ones are fairly simple so I'm gonna use some bullet trees for them:
|
||||
|
||||
- `ProtectKernelLogs`: If set to true, the service cannot access the kernel
|
||||
message buffer that you get by running `dmesg` or reading from `/proc/kmsg`.
|
||||
- `ProtectKernelModules`: If set to true, the service cannot load or unload
|
||||
kernel modules.
|
||||
- `ProtectKernelTunables`: If set to true, various twiddly bits in `/proc` and
|
||||
`/sys` that let you control tunable values in the kernel will be made
|
||||
read-only. Most of the time these values are set early in the system boot
|
||||
process and never twiddled with again, so it's reasonable to deny a service
|
||||
(and its child processes) access to these
|
||||
|
||||
[Why should I bother making all of these changes to my services though? Isn't it
|
||||
overkill to have a webapp running as a service user get denied access to even
|
||||
look at the kernel log?](conversation://Mara/hmm)
|
||||
|
||||
[To be honest, it can look like paranoid overkill, but this isn't just for the
|
||||
service itself. This is for defense in _depth_, which means that you want to
|
||||
make sure that things are reasonably secure even if an attacker manages to get
|
||||
code execution on one of your services. These settings prevent the service's
|
||||
view of the system from having too much detail, which can make the attacking
|
||||
process more annoying. Remember that the he goal here isn't to make the system
|
||||
attack-proof, nothing is. The goal is to annoy the attacker enough that they
|
||||
give up. This is not perfect and probably will fall apart <a
|
||||
href="https://www.usenix.org/system/files/1401_08-12_mickens.pdf">if your enemy
|
||||
is the Mossad</a>, but it's at least an attempt to lock things down just in case
|
||||
the attackers aren't sending their "A" game. You may also want to look into
|
||||
`InaccessiblePaths` to block away other folders that you deem "forbidden" as
|
||||
facts and circumstances demand.](conversation://Cadey/enby)
|
||||
|
||||
### Lock Down Nix Access
|
||||
|
||||
Nix is the package manager for NixOS. Nix can be invoked by users. Nix lets
|
||||
users access things like compilers and scripting languages. These can be used to
|
||||
run exploit tools. This can be understandably problematic from a security
|
||||
standpoint.
|
||||
|
||||
NixOS has an option called
|
||||
[`nix.allowedUsers`](https://nixos.org/manual/nixos/stable/options.html#opt-nix.allowedUsers)
|
||||
that lets you specify which users or groups are allowed to do anything with the
|
||||
Nix daemon, and by extension the Nix package manager. For a fairly standard
|
||||
setup, you can probably get away with the following which allows everyone that
|
||||
can `sudo` to access the Nix daemon:
|
||||
|
||||
```nix
|
||||
# configuration/meeka/nix.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
nix.allowedUsers = [ "@wheel" ];
|
||||
}
|
||||
```
|
||||
|
||||
However if you want to prevent everyone but root, you can use a configuration
|
||||
like this:
|
||||
|
||||
```nix
|
||||
# configuration/meeka/nix.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
nix.allowedUsers = [ "root" ];
|
||||
}
|
||||
```
|
||||
|
||||
You may also want to block access to the NixOS cache CDN with an external
|
||||
firewall rule if you really don't trust things. You can block it by blocking the
|
||||
fastly IP range `151.101.0.0/16`.
|
||||
|
||||
[I'd suggest doing this firewall change on the level above the NixOS machine
|
||||
itself, just in case the machine gets owned and then they ditch your firewall
|
||||
rules in an effort to aid in exfiltration.](conversation://Mara/hacker)
|
||||
|
||||
## Making the System Amnesiac
|
||||
|
||||
Most of these steps go way deep down the security rabbit hole. A lot of these
|
||||
are focused on limiting access to persistent storage so that persistence is
|
||||
opted into, not opted out of. These steps will essentially mount the root
|
||||
filesystem on a tmpfs that is cleared out on every reboot, with persistent data
|
||||
written to a subfolder in `/nix` that a symlink/bindmount farm is linked to.
|
||||
Most of these steps will require you to reprovision your NixOS machines and may
|
||||
require you to build your own custom images for cloud providers. Your experience
|
||||
and mileage may vary.
|
||||
|
||||
These steps will be based on the excellent work done in these posts/projects:
|
||||
|
||||
- [impermanence](https://github.com/nix-community/impermanence)
|
||||
- [NixOS ❄: tmpfs as root](https://elis.nu/blog/2020/05/nixos-tmpfs-as-root/)
|
||||
- [Erase your darlings](https://grahamc.com/blog/erase-your-darlings)
|
||||
|
||||
### Partitioning/Setup
|
||||
|
||||
Normally the NixOS partition setup looks a bit like this:
|
||||
|
||||
- `/boot` for either BIOS boot or EFI files
|
||||
- 2x ram for swap (swap is not a panacea, however it can sometimes give you
|
||||
valuable time to debug a problem)
|
||||
- `/` for everything else
|
||||
|
||||
[It's worth noting that technically NixOS works fine if you make only one big
|
||||
filesystem and put `/boot` on there directly, but this may only pan out for BIOS
|
||||
booting systems.](conversation://Mara/hacker)
|
||||
|
||||
Given that `/` is going to become an in-memory tmpfs, we can instead move the
|
||||
partitioning to look like this:
|
||||
|
||||
- `/boot` for either BIOS boot or EFI files
|
||||
- 2x ram for swap
|
||||
- `/nix` for everything else
|
||||
|
||||
Assuming you are [installing NixOS from scratch in a VM to test this part
|
||||
out](https://nixos.org/manual/nixos/stable/index.html#sec-installation), the
|
||||
partitioning setup commands could look something like this:
|
||||
|
||||
```sh
|
||||
dev=/dev/vda # replace me with the actual device
|
||||
parted ${dev} -- mklabel msdos
|
||||
parted ${dev} -- mkpart primary ext4 1M 512M
|
||||
parted ${dev} -- set 1 boot on
|
||||
parted ${dev} -- mkpart primary ext4 512MiB 100%
|
||||
mkfs.ext4 -L boot ${dev}1
|
||||
mkfs.ext4 -L nix ${dev}2
|
||||
```
|
||||
|
||||
[Wait, ext4? I thought you were a zfs stan?](conversation://Mara/hmm)
|
||||
|
||||
[I normally am, however in this case it's probably better to keep the scary
|
||||
production servers as boring and vanilla as possible, especially when doing a
|
||||
more weird setup like this](conversation://Cadey/enby)
|
||||
|
||||
The exact size of your /boot partition may vary based on facts and
|
||||
circumstances, however in practice I've found 512 MB to be a not-terrible
|
||||
default.
|
||||
|
||||
Make your "root mount" with a tmpfs:
|
||||
|
||||
```sh
|
||||
mount -t tmpfs none /mnt
|
||||
```
|
||||
|
||||
Then you need to create the persistent folders on `/nix/persist`. I've found
|
||||
these defaults to be not-horrible:
|
||||
|
||||
```sh
|
||||
mkdir -p /mnt/{boot,nix,etc/{nixos,ssh},var/{lib,log},srv}
|
||||
```
|
||||
|
||||
[We use `/srv` as the home for our services. Adjust this as your facts and
|
||||
circumstances demand.](conversation://Mara/hacker)
|
||||
|
||||
Then mount those two partitions to your tmpfs:
|
||||
|
||||
```sh
|
||||
mount ${dev}1 /mnt/boot
|
||||
mount ${dev}2 /mnt/nix
|
||||
```
|
||||
|
||||
And create matching folders in `/mnt/nix/persist`:
|
||||
|
||||
```sh
|
||||
mkdir -p /mnt/nix/persist/{etc/{nixos,ssh},var/{lib,log},srv}
|
||||
```
|
||||
|
||||
Then finally create some bind mounts to tie everything together for the
|
||||
meantime. These bindmounts will be handled by impermanence in the future,
|
||||
however for now the quick and dirty method will suffice:
|
||||
|
||||
```sh
|
||||
mount -o bind /mnt/nix/persist/etc/nixos /mnt/etc/nixos
|
||||
mount -o bind /mnt/nix/persist/var/log /mnt/var/log
|
||||
```
|
||||
|
||||
Then generate a base config with `nixos-generate-config`:
|
||||
|
||||
```sh
|
||||
nixos-generate-config --root /mnt
|
||||
```
|
||||
|
||||
And open `/etc/nixos/hardware-configuration.nix` to edit the settings for the
|
||||
tmpfs mount on `/`. At a high level you'll need to change this:
|
||||
|
||||
```nix
|
||||
fileSystems."/" = {
|
||||
device = "none";
|
||||
fsType = "tmpfs";
|
||||
options = [ "defaults" "mode=755" ];
|
||||
};
|
||||
```
|
||||
|
||||
to this:
|
||||
|
||||
```nix
|
||||
fileSystems."/" = {
|
||||
device = "none";
|
||||
fsType = "tmpfs";
|
||||
options = [ "defaults" "size=2G" "mode=755" ];
|
||||
};
|
||||
```
|
||||
|
||||
This will limit `/` to taking up 2 GB of storage at most. This will mostly
|
||||
contain temporary files and the like, but you should adjust this as makes sense
|
||||
given the amount of ram your systems have. I personally think that 512 MB could
|
||||
make sense depending on what you are doing.
|
||||
|
||||
### Using Impermanence
|
||||
|
||||
Now we get to add [impermanence](https://github.com/nix-community/impermanence)
|
||||
to the mix to handle making all of those pesky bind mounts for us on boot. One
|
||||
of the easiest ways you can add its module to the nix search path is to set the
|
||||
`NIX_PATH` environment variable like this:
|
||||
|
||||
```sh
|
||||
export NIX_PATH=nixpkgs=channel:nixos-21.05:impermanence=https://github.com/nix-community/impermanence/archive/refs/heads/master.tar.gz:nixos-config=/etc/nixos/configuration.nix
|
||||
```
|
||||
|
||||
This will set the import path `<impermanence>` to point to the git repository
|
||||
for impermanence. Depending on your security needs you may want to mirror the
|
||||
impermanence git repo, but keep in mind it needs to point to a tarball for Nix
|
||||
to understand what to do with it.
|
||||
|
||||
Once you have that added, you can add the impermanence configuration to your
|
||||
`/etc/nixos/configuration.nix`:
|
||||
|
||||
```nix
|
||||
environment.persistence."/nix/persist" = {
|
||||
directories = [
|
||||
"/etc/nixos" # nixos system config files, can be considered optional
|
||||
"/srv" # service data
|
||||
"/var/lib" # system service persistent data
|
||||
"/var/log" # the place that journald dumps it logs to
|
||||
];
|
||||
};
|
||||
```
|
||||
|
||||
Finally you'll want to set these configuration lines for files in `/etc/sshd`.
|
||||
I've tried doing it directly in `environment.persistence.<name>.directories`
|
||||
directly but it seems to make `sshd.service` unable to generate its host keys,
|
||||
which is slightly important for sshd to work at all. These lines will point the
|
||||
files to the right places:
|
||||
|
||||
```nix
|
||||
environment.etc."ssh/ssh_host_rsa_key".source
|
||||
= "/nix/persist/etc/ssh/ssh_host_rsa_key";
|
||||
environment.etc."ssh/ssh_host_rsa_key.pub".source
|
||||
= "/nix/persist/etc/ssh/ssh_host_rsa_key.pub";
|
||||
environment.etc."ssh/ssh_host_ed25519_key".source
|
||||
= "/nix/persist/etc/ssh/ssh_host_ed25519_key";
|
||||
environment.etc."ssh/ssh_host_ed25519_key.pub".source
|
||||
= "/nix/persist/etc/ssh/ssh_host_ed25519_key.pub";
|
||||
```
|
||||
|
||||
The machine ID may be important too if you want to read logs locally after you
|
||||
reboot, or if you have any services that expect the machine ID to not change.
|
||||
|
||||
```nix
|
||||
environment.etc."machine-id".source
|
||||
= "/nix/persist/etc/machine-id";
|
||||
```
|
||||
|
||||
From here you can continue with `nixos-install` like normal (though you may want
|
||||
to add `--no-root-passwd` if you added a default root password to your config
|
||||
for bootstrap reasons only). However if you want to be lazy you can read below
|
||||
where I show you how to automatically create an ISO that does all this for you.
|
||||
|
||||
### Repeatable Base Image with an ISO
|
||||
|
||||
Using the setup I mentioned [in a past
|
||||
post](https://christine.website/blog/my-homelab-2021-06-08), you can create an
|
||||
automatic install ISO that will take a blank disk to a state where you can SSH
|
||||
into it and configure it further using a tool like
|
||||
[morph](https://github.com/DBCDK/morph). Take a look at [this
|
||||
folder](https://github.com/Xe/nixos-configs/tree/master/media/autoinstall-paranoid)
|
||||
in my nixos-configs repo for more information. Most of the magic is done with
|
||||
the `build` script. It's basically the last few sections of this article turned
|
||||
into nix files. If you build it yourself you'll want to take care with the line
|
||||
that looks like this:
|
||||
|
||||
```nix
|
||||
users.users.root.initialPassword = "hunter2";
|
||||
users.users.root.openssh.authorizedKeys.keyFiles = [ (fetchKeys "Xe") ];
|
||||
```
|
||||
|
||||
This sets the root password to `hunter2` (a reasonably secure default for
|
||||
bootstrapping systems only, holy crap do not use this in production) so you can
|
||||
log in with the console and the list of SSH keys from
|
||||
[here](https://github.com/Xe.keys). Replace `Xe` with your GitHub username. This
|
||||
is not the most deterministic, but if GitHub is down you probably have bigger
|
||||
problems. It's also a decent crutch to help you bootstrap things. If this
|
||||
bothers you you can set authorized keys as normal:
|
||||
|
||||
```nix
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-yolo swag420blazeit"
|
||||
];
|
||||
```
|
||||
|
||||
You can turn this into an EC2 image with something like
|
||||
[packer](https://www.packer.io/).
|
||||
|
||||
## Audit Tracing
|
||||
|
||||
The Linux kernel has some fancy auditing powers that are criminally under-used.
|
||||
|
||||
[Isn't that because the audit subsystem has the ergonomics of driving a
|
||||
submarine down a road?](conversation://Mara/happy)
|
||||
|
||||
Well, yes but until I learn how to summon the right kinds of daemons, I can
|
||||
start with this audit rule to log every single time a program is attempted to be
|
||||
run:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/auditd.nix
|
||||
{ ... }:
|
||||
{
|
||||
security.auditd.enable = true;
|
||||
security.audit.enable = true;
|
||||
security.audit.rules = [
|
||||
"-a exit,always -F arch=b64 -S execve"
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
You can monitor these logs with `journalctl -f`. If you don't see any audit logs
|
||||
show up, ssh in from another window and run some commands like `ls`. You should
|
||||
see a flurry of them show up.
|
||||
|
||||
### Send All Logs Off-Machine
|
||||
|
||||
You should really treat all system-local logs as radioactive. They are
|
||||
liabilities and in some cases can present problematic situations when faced with
|
||||
questionable interpretations of things like the GDPR. Not to mention attackers
|
||||
will be tempted to wipe all record of their attacks from them. I don't really
|
||||
have a suggestion for the best practice here, but I'm sure that people smarter
|
||||
than me have come up with good suggestions in my place. Either way, get them off
|
||||
the system as fast as possible.
|
||||
|
||||
You should probably have some process scraping the audit logs to check for
|
||||
programs outside of `/nix/store` being executed. That can sometimes point to
|
||||
signs of a break-in.
|
||||
|
||||
[Quis custodiet ipsos custodes?](conversation://Numa/delet)
|
||||
|
||||
## Optional Steps
|
||||
|
||||
Normally a lot of these suggestions are aimed at not totally interfering with
|
||||
normal usability so that in case you need to debug things you can do so with
|
||||
surgical precision. However, depending on your level of paranoia you may want to
|
||||
go a step further and disable some things that most may consider to be a "core
|
||||
part of basic usability". Just be aware that these things may make debugging an
|
||||
errant system difficult.
|
||||
|
||||
### Rip Out `sudo`
|
||||
|
||||
[sudo](https://www.sudo.ws/) is a commonly used tool that allows users to assume
|
||||
superuser powers for a short amount of time. The things they do with `sudo` are
|
||||
logged to the system, but this project has been known to occasionally have
|
||||
security issues.
|
||||
|
||||
[Isn't that because it's written in C and C is inherently unsafe even though
|
||||
hordes of "experts" decry otherwise?](conversation://Mara/hmm)
|
||||
|
||||
[Don't say that, you'll incite the horde.](conversation://Cadey/facepalm)
|
||||
|
||||
NixOS lets us rip that out if we want to:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/sudo.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
security.sudo.enable = false;
|
||||
}
|
||||
```
|
||||
|
||||
If you want to keep it around but instead limit its use to users that are in the
|
||||
`wheel` group, you can instead opt for something like this:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/sudo.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
security.sudo.execWheelOnly = true;
|
||||
}
|
||||
```
|
||||
|
||||
### Rip Out Default Packages
|
||||
|
||||
By default NixOS comes with a few packages like nano, perl and rsync to help you
|
||||
get started using it. These are great and all, but can be slightly incredibly
|
||||
problematic from a security standpoint. Rip them out like this:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/no-defaults.nix
|
||||
{ lib, ... }:
|
||||
|
||||
{
|
||||
environment.defaultPackages = lib.mkForce [];
|
||||
}
|
||||
```
|
||||
|
||||
[The `lib.mkForce` function forcibly overrides the contents of that value to
|
||||
what you give as an argument. This is useful for saying "no, heck you, I want it
|
||||
to be set to this no matter what anyone else says". This can be a useful hammer
|
||||
when correcting the security model of NixOS services when you have a good reason
|
||||
to.](conversation://Mara/hacker)
|
||||
|
||||
### Disable sshd Features
|
||||
|
||||
sshd is great. You can use it to log into systems, proxy traffic and more. sshd
|
||||
is also horrible because you can proxy traffic and more, turning a machine into
|
||||
an unexpected jumpbox for attackers. This is not ideal for machines that you
|
||||
don't expect to be jumpboxes. Disable this feature and some more (such as X11
|
||||
forwarding, SSH agent forwarding and stream-local forwarding) like this:
|
||||
|
||||
```nix
|
||||
# configuration/meeka/sshd.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
services.openssh = {
|
||||
passwordAuthentication = false;
|
||||
allowSFTP = false; # Don't set this if you need sftp
|
||||
challengeResponseAuthentication = false;
|
||||
extraConfig = ''
|
||||
AllowTcpForwarding yes
|
||||
X11Forwarding no
|
||||
AllowAgentForwarding no
|
||||
AllowStreamLocalForwarding no
|
||||
AuthenticationMethods publickey
|
||||
'';
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Mark All Partitions but `/nix/store` as `noexec`
|
||||
|
||||
This is the most paranoid of the ideas in this post. The idea is that if you
|
||||
lock down the package manager so random services can't install software and you
|
||||
also make it impossible for them to write and run executable files outside of
|
||||
`/nix/store`, it becomes very difficult to exploit kernel bugs to get root. Add
|
||||
this with the other systemd isolation features that disable access to device
|
||||
nodes and twiddly system flags and you have a defense in depth setup that will
|
||||
make an attacker's life hard. They will have to get code execution in your
|
||||
services to do any damage.
|
||||
|
||||
Keep in mind that doing this will likely break the heck out of Nix when it needs
|
||||
to build things. In my testing it's been fine, however I am not an expert in
|
||||
these things. Something else to keep in mind is that you should configure your
|
||||
services to be denied access to `/nix/persist` and instead only allow them
|
||||
access to individual paths in the bind mounts on `/`, just in case they do that
|
||||
to try and sneak an executable through. This will not stop them from making a
|
||||
shell script and running it with `bash ./foo.sh`, but it will make it annoying
|
||||
to run things like C executables, which is much more important in this case.
|
||||
|
||||
For this you can set the following NixOS options:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/noexec.nix
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
fileSystems."/".options = [ "noexec" ];
|
||||
fileSystems."/etc/nixos".options = [ "noexec" ];
|
||||
fileSystems."/srv".options = [ "noexec" ];
|
||||
fileSystems."/var/log".options = [ "noexec" ];
|
||||
}
|
||||
```
|
||||
|
||||
This will make `/nix/store` (or symlinks to files in `/nix/store`) the only
|
||||
binaries that are allowed to be executed. This is a rather extreme step, but it
|
||||
should fairly sufficiently prevent any attacker from getting very far with
|
||||
exploits written in languages like C (which also means that it prevents bitcoin
|
||||
miner bots from running).
|
||||
|
||||
## PCI Compliance Tip
|
||||
|
||||
PCI Compliance requires you to have an antivirus program installed on every
|
||||
server. It doesn't say anything about the program _running_, but just it being
|
||||
installed is enough. Get one step closer to PCI compliance with this one neat
|
||||
trick:
|
||||
|
||||
```nix
|
||||
# hosts/meeka/pci-compliance-pass.nix
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
environment.systemPackages = with pkgs; [ clamav ];
|
||||
}
|
||||
```
|
||||
|
||||
[...doesn't that defeat the spirit of the thing?](conversation://Mara/hmm)
|
||||
|
||||
[To be honest, if you get to the end of those post and have an "all yes config"
|
||||
of this setup, installing an antivirus program to satisfy requirements that were
|
||||
primarily written for windows servers is probably one of the easiest steps you
|
||||
can take.](conversation://Cadey/coffee)
|
||||
|
||||
---
|
||||
|
||||
All in all, this entire setup will let you get a rather paranoid configuration
|
||||
that will reject everything outside of the golden path of what you told the
|
||||
machines to do. It will take some work to get to here (as well as being willing
|
||||
to experiment with a few virtual machines to test this process a few times
|
||||
before feeling safe enough to put this into production), but the end result
|
||||
should be a decently secure setup.
|
||||
|
||||
Obligatory warning: don't put this directly into production unless you know what
|
||||
you are doing, or at least can claim you know what you are doing with enough
|
||||
certainty to make servers difficult to debug. Have a way to "break the glass"
|
||||
and go back to a less noexec setup if you need to, it will save your ass.
|
||||
|
||||
[Oh, also be sure to import all of those random `.nix` files if you want to use
|
||||
it in one cohesive system config. That may be a slight bit entirely essential.
|
||||
^\_^](conversation://Mara/hacker)
|
Loading…
Reference in New Issue