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