Unverified Commit fb3723fe authored by Jean-François Roche's avatar Jean-François Roche Committed by GitHub
Browse files

nixos/tang: create module for tang server (#247037)

This commit adds a module for the tang server and the related nixos test.
parent 301949dc
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -1164,6 +1164,7 @@
  ./services/security/sshguard.nix
  ./services/security/sslmate-agent.nix
  ./services/security/step-ca.nix
  ./services/security/tang.nix
  ./services/security/tor.nix
  ./services/security/torify.nix
  ./services/security/torsocks.nix
+95 −0
Original line number Diff line number Diff line
{ config, lib, pkgs, ... }:
with lib;
let
  cfg = config.services.tang;
in
{
  options.services.tang = {
    enable = mkEnableOption "tang";

    package = mkOption {
      type = types.package;
      default = pkgs.tang;
      defaultText = literalExpression "pkgs.tang";
      description = mdDoc "The tang package to use.";
    };

    listenStream = mkOption {
      type = with types; listOf str;
      default = [ "7654" ];
      example = [ "198.168.100.1:7654" "[2001:db8::1]:7654" "7654" ];
      description = mdDoc ''
        Addresses and/or ports on which tang should listen.
        For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
      '';
    };

    ipAddressAllow = mkOption {
      example = [ "192.168.1.0/24" ];
      type = types.listOf types.str;
      description = ''
        Whitelist a list of address prefixes.
        Preferably, internal addresses should be used.
      '';
    };

  };
  config = mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ];

    systemd.services."tangd@" = {
      description = "Tang server";
      path = [ cfg.package ];
      serviceConfig = {
        StandardInput = "socket";
        StandardOutput = "socket";
        StandardError = "journal";
        DynamicUser = true;
        StateDirectory = "tang";
        RuntimeDirectory = "tang";
        StateDirectoryMode = "700";
        UMask = "0077";
        CapabilityBoundingSet = [ "" ];
        ExecStart = "${cfg.package}/libexec/tangd %S/tang";
        LockPersonality = true;
        MemoryDenyWriteExecute = true;
        NoNewPrivileges = true;
        DeviceAllow = [ "/dev/stdin" ];
        RestrictAddressFamilies = [ "AF_UNIX" ];
        DevicePolicy = "strict";
        PrivateDevices = true;
        PrivateTmp = true;
        PrivateUsers = true;
        ProcSubset = "pid";
        ProtectClock = true;
        ProtectControlGroups = true;
        ProtectHome = true;
        ProtectHostname = true;
        ProtectKernelLogs = true;
        ProtectKernelModules = true;
        ProtectKernelTunables = true;
        ProtectProc = "invisible";
        ProtectSystem = "strict";
        RestrictNamespaces = true;
        RestrictRealtime = true;
        RestrictSUIDSGID = true;
        SystemCallArchitectures = "native";
        SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ];
        IPAddressDeny = "any";
        IPAddressAllow = cfg.ipAddressAllow;
      };
    };

    systemd.sockets.tangd = {
      description = "Tang server";
      wantedBy = [ "sockets.target" ];
      socketConfig = {
        ListenStream = cfg.listenStream;
        Accept = "yes";
        IPAddressDeny = "any";
        IPAddressAllow = cfg.ipAddressAllow;
      };
    };
  };
  meta.maintainers = with lib.maintainers; [ jfroche julienmalka ];
}
+1 −0
Original line number Diff line number Diff line
@@ -807,6 +807,7 @@ in {
  systemd-userdbd = handleTest ./systemd-userdbd.nix {};
  systemd-homed = handleTest ./systemd-homed.nix {};
  tandoor-recipes = handleTest ./tandoor-recipes.nix {};
  tang = handleTest ./tang.nix {};
  taskserver = handleTest ./taskserver.nix {};
  tayga = handleTest ./tayga.nix {};
  teeworlds = handleTest ./teeworlds.nix {};

nixos/tests/tang.nix

0 → 100644
+81 −0
Original line number Diff line number Diff line
import ./make-test-python.nix ({ pkgs, ... }: {
  name = "tang";
  meta = with pkgs.lib.maintainers; {
    maintainers = [ jfroche ];
  };

  nodes.server =
    { config
    , pkgs
    , modulesPath
    , ...
    }: {
      imports = [
        "${modulesPath}/../tests/common/auto-format-root-device.nix"
      ];
      virtualisation = {
        emptyDiskImages = [ 512 ];
        useBootLoader = true;
        useEFIBoot = true;
        # This requires to have access
        # to a host Nix store as
        # the new root device is /dev/vdb
        # an empty 512MiB drive, containing no Nix store.
        mountHostNixStore = true;
      };

      boot.loader.systemd-boot.enable = true;

      networking.interfaces.eth1.ipv4.addresses = [
        { address = "192.168.0.1"; prefixLength = 24; }
      ];

      environment.systemPackages = with pkgs; [ clevis tang cryptsetup ];
      services.tang = {
        enable = true;
        ipAddressAllow = [ "127.0.0.1/32" ];
      };
    };
  testScript = ''
    start_all()
    machine.wait_for_unit("sockets.target")

    with subtest("Check keys are generated"):
      machine.wait_until_succeeds("curl -v http://127.0.0.1:7654/adv")
      key = machine.wait_until_succeeds("tang-show-keys 7654")

    with subtest("Check systemd access list"):
      machine.succeed("ping -c 3 192.168.0.1")
      machine.fail("curl -v --connect-timeout 3 http://192.168.0.1:7654/adv")

    with subtest("Check basic encrypt and decrypt message"):
      machine.wait_until_succeeds(f"""echo 'Hello World' | clevis encrypt tang '{{ "url": "http://127.0.0.1:7654", "thp":"{key}"}}' > /tmp/encrypted""")
      decrypted = machine.wait_until_succeeds("clevis decrypt < /tmp/encrypted")
      assert decrypted.strip() == "Hello World"
      machine.wait_until_succeeds("tang-show-keys 7654")

    with subtest("Check encrypt and decrypt disk"):
      machine.succeed("cryptsetup luksFormat --force-password --batch-mode /dev/vdb <<<'password'")
      machine.succeed(f"""clevis luks bind -s1 -y -f -d /dev/vdb tang '{{ "url": "http://127.0.0.1:7654", "thp":"{key}" }}' <<< 'password' """)
      clevis_luks = machine.succeed("clevis luks list -d /dev/vdb")
      assert clevis_luks.strip() == """1: tang '{"url":"http://127.0.0.1:7654"}'"""
      machine.succeed("clevis luks unlock -d /dev/vdb")
      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")
      machine.succeed("clevis luks unlock -d /dev/vdb")
      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")
      # without tang available, unlock should fail
      machine.succeed("systemctl stop tangd.socket")
      machine.fail("clevis luks unlock -d /dev/vdb")
      machine.succeed("systemctl start tangd.socket")

    with subtest("Rotate server keys"):
      machine.succeed("${pkgs.tang}/libexec/tangd-rotate-keys -d /var/lib/tang")
      machine.succeed("clevis luks unlock -d /dev/vdb")
      machine.succeed("find /dev/mapper -name 'luks*' -exec cryptsetup close {} +")

    with subtest("Test systemd service security"):
        output = machine.succeed("systemd-analyze security tangd@.service")
        machine.log(output)
        assert output[-9:-1] == "SAFE :-}"
  '';
})
+9 −4
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
, testers
, tang
, gitUpdater
, nixosTests
}:

stdenv.mkDerivation rec {
@@ -53,11 +54,14 @@ stdenv.mkDerivation rec {
  '';

  passthru = {
    tests.version = testers.testVersion {
    tests = {
      inherit (nixosTests) tang;
      version = testers.testVersion {
        package = tang;
        command = "${tang}/libexec/tangd --version";
        version = "tangd ${version}";
      };
    };
    updateScript = gitUpdater { };
  };

@@ -67,5 +71,6 @@ stdenv.mkDerivation rec {
    changelog = "https://github.com/latchset/tang/releases/tag/v${version}";
    maintainers = with lib.maintainers; [ fpletz ];
    license = lib.licenses.gpl3Plus;
    mainProgram = "tangd";
  };
}