@@ -125,17 +125,80 @@ let
125125 '' ;
126126 } ;
127127
128+ keys = mkOption {
129+ type = types . attrsOf ( types . submodule {
130+ options = keyOptions ;
131+ } ) ;
132+ default = { } ;
133+ description = ''
134+ One or more configurations for signing keys.
135+ '' ;
136+ } ;
137+
128138 tokens = mkOption {
129139 type = types . listOf ( types . submodule {
130140 options = tokenOptions ;
131141 } ) ;
142+ default = [ ] ;
132143 description = ''
133144 One or more tokens issued by this provider.
134145 '' ;
135146 } ;
136147
137148 } ;
138149
150+ keyOptions = {
151+
152+ alg = mkOption {
153+ type = types . str ;
154+ description = ''
155+ Signing algorithm for this key.
156+ '' ;
157+ } ;
158+
159+ crv = mkOption {
160+ type = types . nullOr types . str ;
161+ default = null ;
162+ description = ''
163+ For EdDSA, the curve to use.
164+ '' ;
165+ } ;
166+
167+ keySize = mkOption {
168+ type = types . nullOr types . ints . positive ;
169+ default = null ;
170+ description = ''
171+ For RSA, the size of the generated keys.
172+ '' ;
173+ } ;
174+
175+ dir = mkOption {
176+ type = types . nullOr types . path ;
177+ default = null ;
178+ description = ''
179+ Optional custom path to store keys.
180+ '' ;
181+ } ;
182+
183+ lifespan = mkOption {
184+ type = types . nullOr types . ints . positive ;
185+ default = null ;
186+ description = ''
187+ Duration in seconds a key is used, before being rotated.
188+ '' ;
189+ } ;
190+
191+ publishMargin = mkOption {
192+ type = types . nullOr types . ints . positive ;
193+ default = null ;
194+ description = ''
195+ Duration in seconds before and after key lifespan during which the key
196+ is still announced.
197+ '' ;
198+ } ;
199+
200+ } ;
201+
139202 tokenOptions = {
140203
141204 path = mkOption {
154217 example = "/run/diridp/my-application/token" ;
155218 } ;
156219
220+ keyName = mkOption {
221+ type = types . nullOr types . str ;
222+ default = null ;
223+ description = ''
224+ If multiple keys are configured, which one to use for this token.
225+ '' ;
226+ } ;
227+
157228 claims = mkOption {
158229 type = types . attrs ;
159230 default = { } ;
@@ -210,12 +281,56 @@ let
210281
211282 cfg = config . services . diridp ;
212283
284+ supportedAlgorithms = [
285+ "EdDSA"
286+ "ES256" "ES384"
287+ "PS256" "PS384" "PS512"
288+ "RS256" "RS384" "RS512"
289+ ] ;
290+
291+ # Convert a string to snake case.
292+ # This only covers some of the known inputs in this module.
293+ toSnakeCase = input : pipe input [
294+ ( builtins . split "([[:upper:]]+)" )
295+ ( concatMapStrings ( s : if isList s then "_${ toLower ( head s ) } " else s ) )
296+ ] ;
297+
298+ # Common transformations on an attribute set for our YAML config output.
299+ prepareConfigSection = attrs : pipe attrs [
300+ ( filterAttrs ( name : value : value != null ) )
301+ ( mapAttrs' ( name : nameValuePair ( toSnakeCase name ) ) )
302+ ] ;
303+
304+ # Prepare the configuration file and perform checks.
213305 configFile = ( pkgs . formats . yaml { } ) . generate "diridp.yaml" {
214- providers = mapAttrs ( name : provider : {
215- inherit ( provider ) issuer claims tokens ;
216- } ) cfg . providers ;
306+ providers = mapAttrs ( name : provider :
307+ let
308+ numKeys = length ( attrNames provider . keys ) ;
309+ in prepareConfigSection ( {
310+ inherit ( provider ) issuer claims ;
311+ keys = mapAttrs ( name : key :
312+ # 'alg' must be one of the supported algorithms.
313+ assert elem key . alg supportedAlgorithms ;
314+ # 'crv' is required for EdDSA.
315+ assert key . alg == "EdDSA" -> key . crv != null ;
316+ # 'crv' can only be used with EdDSA.
317+ assert key . crv != null -> key . alg == "EdDSA" ;
318+ # 'keySize' can only be used with the RSA family.
319+ assert key . keySize != null -> ( hasPrefix "RS" key . alg ) || ( hasPrefix "PS" key . alg ) ;
320+ prepareConfigSection key
321+ ) provider . keys ;
322+ tokens = map ( token :
323+ # 'keyName' is required if there are multiple keys.
324+ assert token . keyName == null -> numKeys == 1 ;
325+ # 'keyName' must match a configured key.
326+ assert token . keyName != null -> hasAttr token . keyName provider . keys ;
327+ prepareConfigSection token
328+ ) provider . tokens ;
329+ } )
330+ ) cfg . providers ;
217331 } ;
218332
333+ # Prepare the startup script that creates directories.
219334 preStartScript = pkgs . writeShellScript "diridp-dirs" ( concatStringsSep "\n " ( map ( dir : ''
220335 install -d \
221336 -o ${ escapeShellArg dir . owner } \
0 commit comments