1{ pkgs, lib, ... }:
2
3# Based on
4# - https://web.mit.edu/kerberos/krb5-1.12/doc/admin/conf_files/krb5_conf.html
5# - https://manpages.debian.org/unstable/heimdal-docs/krb5.conf.5heimdal.en.html
6
7let
8 inherit (lib)
9 boolToString
10 concatMapStringsSep
11 concatStringsSep
12 filter
13 isAttrs
14 isBool
15 isList
16 mapAttrsToList
17 mkOption
18 singleton
19 splitString
20 ;
21 inherit (lib.types)
22 attrsOf
23 bool
24 coercedTo
25 either
26 enum
27 int
28 listOf
29 oneOf
30 path
31 str
32 submodule
33 ;
34in
35{
36 enableKdcACLEntries ? false,
37}:
38rec {
39 sectionType =
40 let
41 relation = oneOf [
42 (listOf (attrsOf value))
43 (attrsOf value)
44 value
45 ];
46 value = either (listOf atom) atom;
47 atom = oneOf [
48 int
49 str
50 bool
51 ];
52 in
53 attrsOf relation;
54
55 type =
56 let
57 aclEntry = submodule {
58 options = {
59 principal = mkOption {
60 type = str;
61 description = "Which principal the rule applies to";
62 };
63 access = mkOption {
64 type = coercedTo str singleton (
65 listOf (enum [
66 "all"
67 "add"
68 "cpw"
69 "delete"
70 "get-keys"
71 "get"
72 "list"
73 "modify"
74 ])
75 );
76 default = "all";
77 description = ''
78 The changes the principal is allowed to make.
79
80 :::{.important}
81 The "all" permission does not imply the "get-keys" permission. This
82 is consistent with the behavior of both MIT Kerberos and Heimdal.
83 :::
84
85 :::{.warning}
86 Value "all" is allowed as a list member only if it appears alone
87 or accompanied by "get-keys". Any other combination involving
88 "all" will raise an exception.
89 :::
90 '';
91 };
92 target = mkOption {
93 type = str;
94 default = "*";
95 description = "The principals that 'access' applies to.";
96 };
97 };
98 };
99
100 realm = submodule (
101 { name, ... }:
102 {
103 freeformType = sectionType;
104 options = {
105 acl = mkOption {
106 type = listOf aclEntry;
107 default = [
108 {
109 principal = "*/admin";
110 access = "all";
111 }
112 {
113 principal = "admin";
114 access = "all";
115 }
116 ];
117 description = ''
118 The privileges granted to a user.
119 '';
120 };
121 };
122 }
123 );
124 in
125 submodule {
126 freeformType = attrsOf sectionType;
127 options = {
128 include = mkOption {
129 default = [ ];
130 description = ''
131 Files to include in the Kerberos configuration.
132 '';
133 type = coercedTo path singleton (listOf path);
134 };
135 includedir = mkOption {
136 default = [ ];
137 description = ''
138 Directories containing files to include in the Kerberos configuration.
139 '';
140 type = coercedTo path singleton (listOf path);
141 };
142 module = mkOption {
143 default = [ ];
144 description = ''
145 Modules to obtain Kerberos configuration from.
146 '';
147 type = coercedTo path singleton (listOf path);
148 };
149
150 }
151 // (lib.optionalAttrs enableKdcACLEntries {
152 realms = mkOption {
153 type = attrsOf realm;
154 description = ''
155 The realm(s) to serve keys for.
156 '';
157 };
158 });
159 };
160
161 generate =
162 let
163 indent = str: concatMapStringsSep "\n" (line: " " + line) (splitString "\n" str);
164
165 formatToplevel =
166 args@{
167 include ? [ ],
168 includedir ? [ ],
169 module ? [ ],
170 ...
171 }:
172 let
173 sections = removeAttrs args [
174 "include"
175 "includedir"
176 "module"
177 ];
178 in
179 concatStringsSep "\n" (
180 filter (x: x != "") [
181 (concatStringsSep "\n" (mapAttrsToList formatSection sections))
182 (concatMapStringsSep "\n" (m: "module ${m}") module)
183 (concatMapStringsSep "\n" (i: "include ${i}") include)
184 (concatMapStringsSep "\n" (i: "includedir ${i}") includedir)
185 ]
186 );
187
188 formatSection = name: section: ''
189 [${name}]
190 ${indent (concatStringsSep "\n" (mapAttrsToList formatRelation section))}
191 '';
192
193 formatRelation =
194 name: relation:
195 if isAttrs relation then
196 ''
197 ${name} = {
198 ${indent (concatStringsSep "\n" (mapAttrsToList formatValue relation))}
199 }''
200 else if isList relation then
201 concatMapStringsSep "\n" (formatRelation name) relation
202 else
203 formatValue name relation;
204
205 formatValue =
206 name: value:
207 if isList value then concatMapStringsSep "\n" (formatAtom name) value else formatAtom name value;
208
209 formatAtom =
210 name: atom:
211 let
212 v = if isBool atom then boolToString atom else toString atom;
213 in
214 "${name} = ${v}";
215 in
216 name: value:
217 pkgs.writeText name ''
218 ${formatToplevel value}
219 '';
220}