diff --git a/krebs/3modules/default.nix b/krebs/3modules/default.nix
index c06f3754e..df1c7db63 100644
--- a/krebs/3modules/default.nix
+++ b/krebs/3modules/default.nix
@@ -28,6 +28,7 @@ let
       ./realwallpaper.nix
       ./retiolum-bootstrap.nix
       ./retiolum.nix
+      ./secret.nix
       ./setuid.nix
       ./tinc_graphs.nix
       ./urlwatch.nix
diff --git a/krebs/3modules/secret.nix b/krebs/3modules/secret.nix
new file mode 100644
index 000000000..46802a661
--- /dev/null
+++ b/krebs/3modules/secret.nix
@@ -0,0 +1,39 @@
+{ config, lib, pkgs, ... }@args: with config.krebs.lib; let
+  cfg = config.krebs.secret;
+in {
+  options.krebs.secret = {
+    files = mkOption {
+      type = with types; attrsOf secret-file;
+      default = {};
+    };
+  };
+  config = lib.mkIf (cfg.files != {}) {
+    systemd.services.secret = let
+      # TODO fail if two files have the same path but differ otherwise
+      files = unique (map (flip removeAttrs ["_module"])
+                          (attrValues cfg.files));
+    in {
+      serviceConfig = {
+        Type = "oneshot";
+        RemainAfterExit = "yes";
+        SyslogIdentifier = "secret";
+        ExecStart = pkgs.writeDash "install-secret-files" ''
+          exit_code=0
+          ${concatMapStringsSep "\n" (file: ''
+            ${pkgs.coreutils}/bin/install \
+                  -D \
+                  --compare \
+                  --verbose \
+                  --mode=${shell.escape file.mode} \
+                  --owner=${shell.escape file.owner-name} \
+                  --group=${shell.escape file.group-name} \
+                  ${shell.escape file.source-path} \
+                  ${shell.escape file.path} \
+                || exit_code=1
+          '') files}
+          exit $exit_code
+        '';
+      };
+    };
+  };
+}
diff --git a/krebs/4lib/types.nix b/krebs/4lib/types.nix
index 7fb206928..55301add5 100644
--- a/krebs/4lib/types.nix
+++ b/krebs/4lib/types.nix
@@ -143,6 +143,19 @@ types // rec {
     merge = mergeOneOption;
   };
 
+  secret-file = submodule ({ config, ... }: {
+    options = {
+      path = mkOption { type = str; };
+      mode = mkOption { type = str; default = "0400"; };
+      owner-name = mkOption { type = str; default = "root"; };
+      group-name = mkOption { type = str; default = "root"; };
+      source-path = mkOption {
+        type = str;
+        default = toString <secrets> + "/${config._module.args.name}";
+      };
+    };
+  });
+
   suffixed-str = suffs:
     mkOptionType {
       name = "string suffixed by ${concatStringsSep ", " suffs}";