I treat secrets like dependency injection: You don’t make a thing that knows how to connect to the database / knows a secret / knows how to get a secret. Instead, you make a thing that takes an argument that is a database connection / secret. You bind late — at execution time. This keeps things very simple and needs no special frameworks / libraries / secrets-tools.
Concrete example:
In demo.nix:
{ pkgs ? import { }, }:
let
vmConfiguration = { lib, modulesPath, ... }: {
imports = [ (modulesPath + "/virtualisation/qemu-vm.nix") ];
config = {
networking.hostName = "demo";
system.stateVersion = lib.versions.majorMinor lib.version; # Ephemeral
boot.initrd.availableKernelModules = [ "iso9660" ];
fileSystems = lib.mkVMOverride {
"/suitcase" = {
device = "/dev/disk/by-label/suitcase";
fsType = "iso9660";
options = [ "ro" ];
};
};
systemd.services.demo-secret-access = {
description = "Demonstrate access to secret";
wants = [ "suitcase.mount" ];
after = [ "suitcase.mount" ];
wantedBy = [ "multi-user.target" ];
script = ''
echo "Demo: The secret is: $(cat /suitcase/secret)" >&2
'';
};
};
};
vmWrapper = { nixos, cdrtools, writeShellApplication, }:
writeShellApplication {
name = "demo";
runtimeInputs = [ cdrtools ];
text = ''
if (( $# < 1 ));then
echo usage: demo suitcase_path ... >&2
exit 1
fi
if [[ ! -d "$1" ]];then
echo Expected first argument to be a directory >&2
exit 1
fi
suitcase_contents=$(realpath "$1")
shift
d=
trap '[[ "$d" && -e "$d" ]] && rm -r "$d"' EXIT
d=$(mktemp -d)
cd "$d"
(umask 077; mkisofs -R -uid 0 -gid 0 -V suitcase -o suitcase.iso "$suitcase_contents")
${(nixos vmConfiguration).config.system.build.vm}/bin/run-demo-vm \
-drive file="$d/suitcase.iso",format=raw,id=suitcase,if=none,read-only=on,werror=report \
-device virtio-blk-pci,drive=suitcase \
"$@"
'';
};
in pkgs.callPackage vmWrapper { }
Use:
$ mkdir foo
$ (umask 077; echo hello > foo/secret)
$ $(nix-build demo.nix)/bin/demo foo
and the VM logs:
Nov 15 01:31:27 demo demo-secret-access-start[639]: Demo: The secret is: hello
Say more about why you don’t want to use encryption for this?
This is easily done with encryption: dd if=/dev/urnadom of=/dev/mraid0 oflag=sync bs=1M count=2
. It’s the bs=1M count=2
that makes it fast – you only need to clobber the first 2 megabytes of the disk, where the encryption keys are kept. (Yes, the keys are 2 entire megabytes! They’re stretched so that if the underlying hardware re-maps a sector to account for a physical media problem, only a small portion of the key is affected, rather than ‘the sector with the whole key’ being re-mapped. (Re-mapping sectors can leave mostly-but-not-entirely-inaccessible copies of old data laying around. We’d rather that happen to small chunks of a key rather than whole keys.))
How big is the drive? If you want 500 GB from a 10 TB drive, taking an image of the full drive would mean reading 20 times more data. Asking a failing drive to read a bunch of unallocated space that you don’t care about isn’t great.
But, sequential reading is very important to rotational hard drive performance.
So it mostly comes down to how sequential your 500 GB of important data is likely to be. If your 500 GB of important data is a hundred 5 GB files, they’re probably mostly stored sequentially on the disk and you would do better to attempt to recover them through the filesystem (mounted read-only). If your 500 GB of important data is in a hundred million 4k files, reading them out through the filesystem will likely be much slower and rougher on the drive than imaging the whole drive.