blob: 19f01b4ddc989512c8018c13540d0d1c427aeb0b [file] [log] [blame]
Skyler Grey82ea8052024-06-08 22:56:00 +00001# SPDX-FileCopyrightText: 2024 Clicks Codes
2#
3# SPDX-License-Identifier: GPL-3.0-only
4
5{
6 lib,
7 pkgs,
8 config,
9 ...
10}:
11let
12 cfg = config.clicks.security.sops;
13
14 guessFormat =
15 extension:
16 if extension == "json" then
17 "json"
18 else if extension == "yaml" || extension == "yml" then
19 "yaml"
20 else if extension == "env" then
21 "dotenv"
22 else if extension == "ini" then
23 "ini"
24 else
25 "binary";
26
27 getExtension =
28 filePath:
29 let
30 pathParts = builtins.split ''\.'' (builtins.toString filePath);
31 numPathParts = builtins.length pathParts;
32 in
33 builtins.elemAt pathParts (numPathParts - 1);
34in
35{
36 options.clicks.secrets =
37 let
38 generateNonBinarySopsPaths =
39 file: keys:
40 lib.lists.forEach keys (key: {
41 name = key;
42 value = config.sops.secrets."${lib.clicks.secrets.name file}:${key}".path;
43 });
44 in
45 lib.mkOption {
46 type = lib.types.attrsOf (
47 lib.types.submodule (
48 { ... }@submodule:
49 {
50 options = {
51 file = lib.mkOption {
52 type = lib.types.pathInStore;
53 description = "The store path to your secrets file";
54 };
55 group = lib.mkOption {
56 type = lib.types.str;
57 description = "The user the secret should be owned by.";
58 default = "root";
59 };
60 keys = lib.mkOption {
61 type = lib.types.nullOr (lib.types.listOf lib.types.str);
62 description = "List of keys to pull from the structured data.";
63 default = null;
64 };
65 neededForUsers = lib.mkEnableOption "This secret is needed for users";
66 paths = lib.mkOption {
67 type = lib.types.nullOr (lib.types.attrsOf lib.types.str);
68 description = "Automatically populated with the SOPS paths to your keys, null if you are using binary secrets";
69 default =
70 if guessFormat (getExtension submodule.config.file) != "binary" then
71 builtins.listToAttrs (generateNonBinarySopsPaths submodule.config.file submodule.config.keys)
72 else
73 null;
74 };
75 path = lib.mkOption {
76 type = lib.types.nullOr lib.types.str;
77 description = "Populated automatically with the SOPS path of the secret, null if you are using non binary secrets";
78 default =
79 if guessFormat (getExtension submodule.config.file) == "binary" then
80 config.sops.secrets.${lib.clicks.secrets.name submodule.config.file}.path
81 else
82 null;
83 };
84 };
85 }
86 )
87 );
88 description = "";
89 default = { };
90 };
91
92 config =
93 let
94 generateBinarySopsSecret = secret: {
95 name = lib.clicks.secrets.name secret.value.file;
96 value = {
97 mode = "0400";
98 owner = config.users.users.root.name;
99 group = config.users.groups.${secret.value.group}.name;
100 sopsFile = secret.value.file;
101 format = guessFormat (getExtension secret.value.file);
102 inherit (secret.value) neededForUsers;
103 };
104 };
105
106 generateNonBinarySopsSecrets =
107 secret:
108 lib.lists.forEach secret.value.keys (key: {
109 name = "${lib.clicks.secrets.name secret.value.file}:${key}";
110 value = {
111 mode = "0040";
112 owner = config.users.users.root.name;
113 group = config.users.groups.${secret.value.group}.name;
114 sopsFile = secret.value.file;
115 format = guessFormat (getExtension secret.value.file);
116 inherit (secret.value) neededForUsers;
117 inherit key;
118 };
119 });
120
121 secretsAsList = lib.attrsets.attrsToList config.clicks.secrets;
122
123 secretsAsSops = lib.pipe secretsAsList [
124 (map (
125 secret:
126 if guessFormat (getExtension secret.value.file) == "binary" then
127 generateBinarySopsSecret secret
128 else
129 generateNonBinarySopsSecrets secret
130 ))
131 lib.flatten
132 builtins.listToAttrs
133 ];
134 in
135 {
136 sops.secrets = secretsAsSops;
137 };
138}