USB Key

:: security, networking, nixos,

Let’s talk about something unrelated to AI.

Here’s a fun little project I did a few years ago, before the AI age, that I’ve meant to write about but never did. Until now, while updating machines to Nixos version 26.05, when I used this project again!

I have a home file server, and I have a remote backup. But what do those have in common? They are connected to the internet and expose services like sshd. What if bad guys hack me? So I decided I ought to have something less exposed to remote attacks. I mean, I hope ssh is secure, and other services, but they are possibly less secure than a computer with no exposed services, or better yet, off. But a computer that is just off is not actively saving backups. I don’t need an active chore to remember to do backups1. So I decided a good balance would be a computer behind a firewall with no services exposed that only connects to a specific other machine occasionally to pull backups2.

Ok, so I’m going to have a computer that runs no services. But, do I want to go to the closet where it lives and plug in a monitor and keyboard every so often when I do operating system updates? No.

So I want to enable ssh connections when it is time to do updates, but otherwise keep them off. I thought about whether I could have some kind of switch, and thought about maybe a USB toggle switch I could get. But then I realized, I should just use the USB device connection as a switch.

And so: a more literal USB key3! I wrote some NixOS configuration, mostly Udev and Systemd configuration, to watch for the insertion of a USB device with a specific ID. When it is inserted, it runs the sshd service.

In case you thought it was funny and want to use it, here you go:

# ssh-dongle.nix
# Module for making openssh sshd start only when a particular USB device is inserted.
{config, lib, pkgs, ...}:
with lib;
{
  options = {
    services.ssh-dongle = {
      enable = mkOption {
        type = types.bool;
        default = false;
        internal = true;
        description = "Whether to require dongle to run ssh server.";
      };
      idVendor = mkOption {
        type = types.str;
        default = "aaaa";
        internal = true;
        description = "Vendor ID for USB device (the first four digits)";
      };
      idProduct = mkOption {
        type = types.str;
        default = "1111";
        internal = true;
        description = "Product ID for USB device (the last four digits)";
      };

    };
  };

  config = mkIf config.services.ssh-dongle.enable {
    # Disable sshd.service from auto-starting.
    # Note that it must still be enabled and configured separately from this module.
    systemd.services.sshd.wantedBy = lib.mkForce [ ];

    services.udev.extraRules =
    ''
    ACTION=="add", ATTRS{idVendor}=="${config.services.ssh-dongle.idVendor}", ATTRS{idProduct}=="${config.services.ssh-dongle.idProduct}", RUN+="${pkgs.systemd}/bin/systemctl start sshd.service"
    ACTION=="remove", ATTRS{idVendor}=="${config.services.ssh-dongle.idVendor}", ATTRS{idProduct}=="${config.services.ssh-dongle.idProduct}", RUN+="${pkgs.systemd}/bin/systemctl stop sshd.service"
    ''
    ;

  };
}

I think it doesn’t shut down the service correctly when removing the key, actually, but I always just reboot the machine after that anyway.

  1. Hence why I still have never gone as far as making EMP-resistant backups on bluray discs or something. And maybe EMP-resistant backups are a bit extreme? I mean, if an EMP device does go off near me, don’t I have bigger problems? This is also why I haven’t tried to figure out a realistic way for backups to survive thermonuclear war. At some point maybe my photos, records, source code, and other files just aren’t actually all that important. But in principle I would probably do bluray discs or magnetic tape or something if it weren’t such a hassle. 

  2. And copy messages about its status, so I can tell that backups are happening, see drive health warnings, etc. 

  3. Excessive? Probably. Fun? Yes!