1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.services.opendkim;
10
11 defaultSock = "local:/run/opendkim/opendkim.sock";
12
13 args =
14 [
15 "-f"
16 "-l"
17 "-p"
18 cfg.socket
19 "-d"
20 cfg.domains
21 "-k"
22 "${cfg.keyPath}/${cfg.selector}.private"
23 "-s"
24 cfg.selector
25 ]
26 ++ lib.optionals (cfg.configFile != null) [
27 "-x"
28 cfg.configFile
29 ];
30
31 configFile = pkgs.writeText "opendkim.conf" (
32 lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${value}") cfg.settings)
33 );
34in
35{
36 imports = [
37 (lib.mkRenamedOptionModule [ "services" "opendkim" "keyFile" ] [ "services" "opendkim" "keyPath" ])
38 ];
39
40 options = {
41 services.opendkim = {
42 enable = lib.mkEnableOption "OpenDKIM sender authentication system";
43
44 socket = lib.mkOption {
45 type = lib.types.str;
46 default = defaultSock;
47 description = "Socket which is used for communication with OpenDKIM.";
48 };
49
50 user = lib.mkOption {
51 type = lib.types.str;
52 default = "opendkim";
53 description = "User for the daemon.";
54 };
55
56 group = lib.mkOption {
57 type = lib.types.str;
58 default = "opendkim";
59 description = "Group for the daemon.";
60 };
61
62 domains = lib.mkOption {
63 type = lib.types.str;
64 default = "csl:${config.networking.hostName}";
65 defaultText = lib.literalExpression ''"csl:''${config.networking.hostName}"'';
66 example = "csl:example.com,mydomain.net";
67 description = ''
68 Local domains set (see {manpage}`opendkim(8)` for more information on datasets).
69 Messages from them are signed, not verified.
70 '';
71 };
72
73 keyPath = lib.mkOption {
74 type = lib.types.path;
75 description = ''
76 The path that opendkim should put its generated private keys into.
77 The DNS settings will be found in this directory with the name selector.txt.
78 '';
79 default = "/var/lib/opendkim/keys";
80 };
81
82 selector = lib.mkOption {
83 type = lib.types.str;
84 description = "Selector to use when signing.";
85 };
86
87 # TODO: deprecate this?
88 configFile = lib.mkOption {
89 type = lib.types.nullOr lib.types.path;
90 default = null;
91 description = "Additional opendkim configuration as a file.";
92 };
93
94 settings = lib.mkOption {
95 type =
96 with lib.types;
97 submodule {
98 freeformType = attrsOf str;
99 };
100 default = { };
101 description = "Additional opendkim configuration";
102 };
103 };
104 };
105
106 config = lib.mkIf cfg.enable {
107 users.users = lib.optionalAttrs (cfg.user == "opendkim") {
108 opendkim = {
109 group = cfg.group;
110 uid = config.ids.uids.opendkim;
111 };
112 };
113
114 users.groups = lib.optionalAttrs (cfg.group == "opendkim") {
115 opendkim.gid = config.ids.gids.opendkim;
116 };
117
118 environment = {
119 etc = lib.mkIf (cfg.settings != { }) {
120 "opendkim/opendkim.conf".source = configFile;
121 };
122 systemPackages = [ pkgs.opendkim ];
123 };
124
125 services.opendkim.configFile = lib.mkIf (cfg.settings != { }) configFile;
126
127 systemd.tmpfiles.rules = [
128 "d '${cfg.keyPath}' - ${cfg.user} ${cfg.group} - -"
129 ];
130
131 systemd.services.opendkim = {
132 description = "OpenDKIM signing and verification daemon";
133 after = [ "network.target" ];
134 wantedBy = [ "multi-user.target" ];
135
136 preStart = ''
137 cd "${cfg.keyPath}"
138 if ! test -f ${cfg.selector}.private; then
139 ${pkgs.opendkim}/bin/opendkim-genkey -s ${cfg.selector} -d all-domains-generic-key
140 echo "Generated OpenDKIM key! Please update your DNS settings:\n"
141 echo "-------------------------------------------------------------"
142 cat ${cfg.selector}.txt
143 echo "-------------------------------------------------------------"
144 fi
145 '';
146
147 serviceConfig = {
148 ExecStart = "${pkgs.opendkim}/bin/opendkim ${lib.escapeShellArgs args}";
149 User = cfg.user;
150 Group = cfg.group;
151 RuntimeDirectory = lib.optional (cfg.socket == defaultSock) "opendkim";
152 StateDirectory = "opendkim";
153 StateDirectoryMode = "0700";
154 ReadWritePaths = [ cfg.keyPath ];
155
156 AmbientCapabilities = [ ];
157 CapabilityBoundingSet = "";
158 DevicePolicy = "closed";
159 LockPersonality = true;
160 MemoryDenyWriteExecute = true;
161 NoNewPrivileges = true;
162 PrivateDevices = true;
163 PrivateMounts = true;
164 PrivateTmp = true;
165 PrivateUsers = true;
166 ProtectClock = true;
167 ProtectControlGroups = true;
168 ProtectHome = true;
169 ProtectHostname = true;
170 ProtectKernelLogs = true;
171 ProtectKernelModules = true;
172 ProtectKernelTunables = true;
173 ProtectSystem = "strict";
174 RemoveIPC = true;
175 RestrictAddressFamilies = [
176 "AF_INET"
177 "AF_INET6 AF_UNIX"
178 ];
179 RestrictNamespaces = true;
180 RestrictRealtime = true;
181 RestrictSUIDSGID = true;
182 SystemCallArchitectures = "native";
183 SystemCallFilter = [
184 "@system-service"
185 "~@privileged @resources"
186 ];
187 UMask = "0077";
188 };
189 };
190 };
191}