| # SPDX-FileCopyrightText: 2024 Clicks Codes |
| # |
| # SPDX-License-Identifier: GPL-3.0-only |
| |
| { |
| lib, |
| pkgs, |
| config, |
| ... |
| }: |
| let |
| cfg = config.clicks.security.sops; |
| |
| guessFormat = |
| extension: |
| if extension == "json" then |
| "json" |
| else if extension == "yaml" || extension == "yml" then |
| "yaml" |
| else if extension == "env" then |
| "dotenv" |
| else if extension == "ini" then |
| "ini" |
| else |
| "binary"; |
| |
| getExtension = |
| filePath: |
| let |
| pathParts = builtins.split ''\.'' (builtins.toString filePath); |
| numPathParts = builtins.length pathParts; |
| in |
| builtins.elemAt pathParts (numPathParts - 1); |
| in |
| { |
| options.clicks.secrets = |
| let |
| generateNonBinarySopsPaths = |
| file: keys: |
| lib.lists.forEach keys (key: { |
| name = key; |
| value = config.sops.secrets."${lib.clicks.secrets.name file}:${key}".path; |
| }); |
| in |
| lib.mkOption { |
| type = lib.types.attrsOf ( |
| lib.types.submodule ( |
| { ... }@submodule: |
| { |
| options = { |
| file = lib.mkOption { |
| type = lib.types.pathInStore; |
| description = "The store path to your secrets file"; |
| }; |
| group = lib.mkOption { |
| type = lib.types.str; |
| description = "The user the secret should be owned by."; |
| default = "root"; |
| }; |
| keys = lib.mkOption { |
| type = lib.types.nullOr (lib.types.listOf lib.types.str); |
| description = "List of keys to pull from the structured data."; |
| default = null; |
| }; |
| neededForUsers = lib.mkEnableOption "This secret is needed for users"; |
| paths = lib.mkOption { |
| type = lib.types.nullOr (lib.types.attrsOf lib.types.str); |
| description = "Automatically populated with the SOPS paths to your keys, null if you are using binary secrets"; |
| default = |
| if guessFormat (getExtension submodule.config.file) != "binary" then |
| builtins.listToAttrs (generateNonBinarySopsPaths submodule.config.file submodule.config.keys) |
| else |
| null; |
| }; |
| path = lib.mkOption { |
| type = lib.types.nullOr lib.types.str; |
| description = "Populated automatically with the SOPS path of the secret, null if you are using non binary secrets"; |
| default = |
| if guessFormat (getExtension submodule.config.file) == "binary" then |
| config.sops.secrets.${lib.clicks.secrets.name submodule.config.file}.path |
| else |
| null; |
| }; |
| }; |
| } |
| ) |
| ); |
| description = ""; |
| default = { }; |
| }; |
| |
| config = |
| let |
| generateBinarySopsSecret = secret: { |
| name = lib.clicks.secrets.name secret.value.file; |
| value = { |
| mode = "0400"; |
| owner = config.users.users.root.name; |
| group = config.users.groups.${secret.value.group}.name; |
| sopsFile = secret.value.file; |
| format = guessFormat (getExtension secret.value.file); |
| inherit (secret.value) neededForUsers; |
| }; |
| }; |
| |
| generateNonBinarySopsSecrets = |
| secret: |
| lib.lists.forEach secret.value.keys (key: { |
| name = "${lib.clicks.secrets.name secret.value.file}:${key}"; |
| value = { |
| mode = "0040"; |
| owner = config.users.users.root.name; |
| group = config.users.groups.${secret.value.group}.name; |
| sopsFile = secret.value.file; |
| format = guessFormat (getExtension secret.value.file); |
| inherit (secret.value) neededForUsers; |
| inherit key; |
| }; |
| }); |
| |
| secretsAsList = lib.attrsets.attrsToList config.clicks.secrets; |
| |
| secretsAsSops = lib.pipe secretsAsList [ |
| (map ( |
| secret: |
| if guessFormat (getExtension secret.value.file) == "binary" then |
| generateBinarySopsSecret secret |
| else |
| generateNonBinarySopsSecrets secret |
| )) |
| lib.flatten |
| builtins.listToAttrs |
| ]; |
| in |
| { |
| sops.secrets = secretsAsSops; |
| }; |
| } |