--- /dev/null +++ b/.envrc @@ -0,0 +1,5 @@ +if ! has nix_direnv_version || ! nix_direnv_version 1.4.0; then + source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/1.4.0/direnvrc" "sha256-4XfVDjv75eHMWN4G725VW7BoOV4Vl3vAabK4YXIfPyE=" +fi + +use flake --- a/flake.lock +++ b/flake.lock @@ -1,23 +1,57 @@ { "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1631561581, + "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "nixpkgs": { "locked": { - "lastModified": 1605344435, - "narHash": "sha256-Xx66M/eTwLc97sge6y210qMBZe2qwrpSqWagfEAOF0M=", + "lastModified": 1632011270, + "narHash": "sha256-UyEYSWTKB3boKu5JX/TrQtnAgaYvfSWT61VU8ZT1juk=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d67b00e8f0b378b1700e12f5b8e68c0706839c9a", + "rev": "7f59b4b5295b58659064a91d0bcc8e8a11d0b351", "type": "github" }, "original": { "owner": "nixos", + "ref": "staging-next", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "flake": false, + "locked": { + "lastModified": 1631758650, + "narHash": "sha256-7OAtO2V8omtPaoFBASTfPA5m8MzN5LX8agk0k5p8dH0=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "4e79ebf67452cca4ae938180728f9f513e828d5b", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" } } }, --- a/flake.nix +++ b/flake.nix @@ -2,9 +2,32 @@ description = "NixOS configuration management"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs"; + nixpkgs.url = "github:nixos/nixpkgs/staging-next"; + rust-overlay = { url = "github:oxalica/rust-overlay"; flake = false; }; + flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs }: with nixpkgs.lib; rec { + outputs = { self, rust-overlay, flake-utils, nixpkgs }: with nixpkgs.lib; rec { lib = import ./lib; - }; + } // flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs + { + inherit system; overlays = [ (import rust-overlay) ]; + }; + llvmPkgs = pkgs.buildPackages.llvmPackages_11; + rust = (pkgs.rustChannelOf { date = "2021-08-16"; channel = "nightly"; }).default.override { extensions = [ "rust-src" ]; }; + rustPlatform = pkgs.makeRustPlatform { cargo = rust; rustc = rust; }; + in + { + devShell = (pkgs.mkShell.override { stdenv = llvmPkgs.stdenv; }) { + nativeBuildInputs = with pkgs; [ + rust + cargo-edit + cargo-udeps + + pkgconfig + openssl + ]; + }; + }); } --- a/lib/default.nix +++ b/lib/default.nix @@ -1,29 +1,11 @@ { - fleetConfiguration = { nixpkgs, hosts, ... }@allConfig: + fleetConfiguration = { data, nixpkgs, hosts, ... }@allConfig: let - config = builtins.removeAttrs allConfig [ "nixpkgs" ]; + config = builtins.removeAttrs allConfig [ "nixpkgs" "data" ]; in rec { root = nixpkgs.lib.evalModules { - modules = - (import ../modules/modules.nix) ++ [ - config - ( - { ... }: { - options = { }; - config = { - # Secret data is available only via fleet build-systems - secrets = - if builtins?getEnv then - let - stringData = builtins.getEnv "SECRET_DATA"; - in - if stringData != "" then (builtins.fromJSON stringData) else { } - else { }; - }; - } - ) - ]; + modules = (import ../modules/modules.nix { inherit data; }) ++ [ config ]; specialArgs = { inherit nixpkgs; fleet = import ./fleetLib.nix { --- a/lib/fleetLib.nix +++ b/lib/fleetLib.nix @@ -1,34 +1,5 @@ # Shared functions for fleet configuration, available as `fleet` module argument { nixpkgs, hosts }: with nixpkgs.lib; rec { - mkSecret = - let - system = builtins.currentSystem; - pkgs = import nixpkgs { inherit system; }; - keys = builtins.getEnv "RAGE_KEYS"; - encryptCmd = "rage ${keys} -a"; - impuritySource = builtins.getEnv "IMPURITY_SOURCE"; - in - f: - let - data = f { inherit pkgs encryptCmd; }; - in - builtins.derivation { - inherit system; - name = "secret"; - - builder = "${pkgs.bash}/bin/bash"; - args = [ - ( - pkgs.writeTextFile { - name = "./build-${impuritySource}.sh"; - text = data.script; - executable = true; - } - ) - ]; - - PATH = "${pkgs.coreutils}/bin:${pkgs.rage}/bin${builtins.concatStringsSep "" (builtins.map (n: ":${n}/bin") data.utils)}"; - }; # Modules can't register hosts because of infinite recursion hostNames = attrNames hosts; hostsToAttrs = f: listToAttrs ( --- /dev/null +++ b/modules/hosts.nix @@ -0,0 +1,47 @@ +{ lib, fleet, ... }: with lib; +let + host = with types; { + options = { + modules = mkOption { + type = listOf anything; + description = "List of nixos modules"; + default = [ ]; + }; + network = mkOption { + type = submodule { + options = { + fleetIp = { + type = str; + description = "Ip which is available to all hosts in fleet"; + }; + }; + }; + description = "Network definition of host"; + }; + system = mkOption { + type = str; + description = "Type of system"; + }; + encryptionKey = mkOption { + type = str; + description = "Encryption key"; + }; + }; + }; +in +{ + options = with types; { + hosts = mkOption { + type = attrsOf (submodule host); + default = { }; + description = "Configurations of individual hosts"; + }; + }; + config.hosts = fleet.hostsToAttrs (host: { + modules = [ + ({ ... }: { + nixpkgs.overlays = [ (import ../pkgs) ]; + }) + ]; + }); +} --- a/modules/modules.nix +++ b/modules/modules.nix @@ -1,4 +1,5 @@ -[ - ./networking/wireguard - ./root.nix +{ data }: [ + ./hosts.nix + ./secrets + data ] --- a/modules/networking/wireguard/default.nix +++ /dev/null @@ -1,106 +0,0 @@ -{ config, lib, fleet, ... }: with lib; with fleet; let - cfg = config.networking.wireguard; - genWgKey = { owners }: { - inherit owners; - generator = mkSecret ( - { pkgs, encryptCmd }: { - utils = [ pkgs.wireguard-tools ]; - script = '' - key=$(wg genkey) - pub=$(echo $key | wg pubkey) - - mkdir -p $out - echo $key | ${encryptCmd} >$out/key - echo $pub >$out/pub_key - ''; - } - ); - }; - genWgPsk = { owners }: { - inherit owners; - generator = mkSecret ( - { pkgs, encryptCmd }: { - utils = [ pkgs.wireguard-tools ]; - script = '' - key=$(wg genpsk) - - mkdir -p $out - echo $key | ${encryptCmd} >$out/key - ''; - } - ); - }; - - hostKeys = listToAttrs ( - map - ( - hostName: { - name = "wg-key-${hostName}"; - value = genWgKey { - owners = [ hostName ]; - }; - } - ) - hostNames - ); - psks = listToAttrs ( - map - ( - { a, b }: { - name = "wg-psk-${a}-${b}"; - value = genWgPsk { - owners = [ a b ]; - }; - } - ) - hostsCartesian - ); -in -{ - options.networking.wireguard = with types; { - enable = mkEnableOption "wireguard"; - interface = mkOption { - type = str; - description = "Interface name for wireguard network"; - default = "fleet"; - }; - port = mkOption { - type = int; - description = "Port, on which wireguard interface should listen"; - default = 51871; - }; - allowedIPs = mkOption { - type = attrsOf (listOf str); - description = "Per host allowed ips"; - }; - }; - config = mkIf cfg.enable { - secrets = - (hostKeys // psks); - hosts = hostsToAttrs ( - hostName: { - modules = [ - { - networking.wireguard.enable = true; - networking.wireguard.interfaces.fleetwg = { - privateKeyFile = "/run/secrets/wg-key-${hostName}"; - peers = map - ( - peer: - let - pair = hostsPair hostName peer; - in - { - publicKey = config.secrets."wg-key-${peer}".data.key; - presharedKey = "/run/secrets/wg-psk-${pair.a}-${pair.b}"; - allowedIPs = cfg.allowedIPs.${peer}; - } - ) - hostNames; - }; - } - ]; - } - ); - }; -} --- a/modules/networking/wireguard/wgbuilder.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -key=$($WG genkey) -pub=$(echo $key | $WG pubkey) - -$COREUTILS/bin/mkdir -p $out -echo $key | $RAGE $recipients >$out/key -echo $pub >$out/pub_key --- a/modules/root.nix +++ /dev/null @@ -1,76 +0,0 @@ -{ lib, ... }: with lib; -let - secret = with types; { - options = { - owners = mkOption { - type = listOf str; - description = '' - List of hosts to encrypt secret for - - Secrets would be decrypted and stored to /run/secrets/$\{name} on owners - ''; - }; - generator = mkOption { - type = types.package; - description = "Derivation to execute for secret generation"; - }; - expireIn = mkOption { - type = nullOr int; - description = "Time in hours, in which this secret should be regenerated"; - default = null; - }; - data = mkOption { - type = attrsOf anything; - description = "Generated secret data, do not set it yourself"; - default = { }; - }; - }; - }; - host = with types; { - options = { - modules = mkOption { - type = listOf anything; - description = "List of nixos modules"; - default = [ ]; - }; - network = mkOption { - type = submodule { - options = { - fleetIp = { - type = str; - description = "Ip which is available to all hosts in fleet"; - }; - }; - }; - description = "Network definition of host"; - }; - system = mkOption { - type = str; - description = "Type of system"; - }; - }; - }; -in -{ - options = with types; { - hosts = mkOption { - type = attrsOf (submodule host); - default = { }; - description = "Configurations of individual hosts"; - }; - secrets = mkOption { - type = attrsOf (submodule secret); - default = { }; - description = "Secrets"; - }; - }; - config = { - secrets = - if builtins?getEnv then - let - stringData = builtins.getEnv "SECRET_DATA"; - in - if stringData != "" then (builtins.fromJSON stringData) else { } - else { }; - }; -} --- /dev/null +++ b/modules/secrets/default.nix @@ -0,0 +1,56 @@ +{ lib, fleet, config, ... }: with lib; +let + secret = with types; { + options = { + owners = mkOption { + type = listOf str; + description = '' + List of hosts to encrypt secret for + + Secrets would be decrypted and stored to /run/secrets/$\{name} on owners + ''; + }; + generator = mkOption { + type = package; + description = "Derivation to execute for secret generation"; + }; + expireIn = mkOption { + type = nullOr int; + description = "Time in hours, in which this secret should be regenerated"; + default = null; + }; + public = mkOption { + type = nullOr str; + description = "Secret public data"; + default = null; + }; + secret = mkOption { + type = str; + description = "Encrypted secret data"; + }; + }; + }; +in +{ + options = with types; { + secrets = mkOption { + type = attrsOf (submodule secret); + default = { }; + description = "Secrets"; + }; + }; + config = with fleet; { + hosts = hostsToAttrs (host: { + modules = [ + ./nixosModule.nix + { + secrets = mapAttrs + (secretName: v: { + inherit (v) public secret; + }) + (filterAttrs (_: v: builtins.elem host v.owners) config.secrets); + } + ]; + }); + }; +} --- /dev/null +++ b/modules/secrets/nixosModule.nix @@ -0,0 +1,60 @@ +{ lib, config, pkgs, ... }: with lib; +let + sysConfig = config; + secretType = types.submodule ({ config, ... }: { + config = { + path = mkOptionDefault "/run/secrets/${config._module.args.name}"; + }; + options = { + public = mkOption { + type = types.nullOr types.str; + description = "Secret public data"; + default = null; + }; + secret = mkOption { + type = types.str; + description = "Encrypted secret data"; + }; + mode = mkOption { + type = types.str; + description = "Secret mode"; + default = "0440"; + }; + owner = mkOption { + type = types.str; + description = "Owner of the secret"; + default = "root"; + }; + group = mkOption { + type = types.str; + description = "Group of the secret"; + default = sysConfig.users.users.${config.owner}.group; + }; + + path = mkOption { + type = types.str; + readOnly = true; + description = "Path to the decrypted secret"; + }; + }; + }); + secretsFile = pkgs.writeTextFile { + name = "secrets.json"; + text = builtins.toJSON config.secrets; + }; +in +{ + options = { + secrets = mkOption { + type = types.attrsOf secretType; + default = { }; + description = "Host-local secrets"; + }; + }; + config = { + system.activationScripts.decryptSecrets = '' + 1>&2 echo "setting up secrets" + ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets ${secretsFile} + ''; + }; +} --- /dev/null +++ b/pkgs/default.nix @@ -0,0 +1,5 @@ +pkgs: super: +with pkgs; +{ + fleet-install-secrets = callPackage ./fleet-install-secrets.nix { }; +} --- /dev/null +++ b/pkgs/fleet-install-secrets.nix @@ -0,0 +1,13 @@ +{ rustPlatform }: + +rustPlatform.buildRustPackage rec { + pname = "fleet-install-secrets"; + version = "0.0.1"; + name = "${pname}-${version}"; + + src = ../.; + cargoBuildFlags = "-p ${pname}"; + cargoLock = { + lockFile = ../Cargo.lock; + }; +}