1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.chrony;
7 chronyPkg = cfg.package;
8
9 stateDir = cfg.directory;
10 driftFile = "${stateDir}/chrony.drift";
11 keyFile = "${stateDir}/chrony.keys";
12
13 configFile = pkgs.writeText "chrony.conf" ''
14 ${concatMapStringsSep "\n" (server: "server " + server + " " + cfg.serverOption + optionalString (cfg.enableNTS) " nts") cfg.servers}
15
16 ${optionalString
17 (cfg.initstepslew.enabled && (cfg.servers != []))
18 "initstepslew ${toString cfg.initstepslew.threshold} ${concatStringsSep " " cfg.servers}"
19 }
20
21 driftfile ${driftFile}
22 keyfile ${keyFile}
23 ${optionalString (cfg.enableNTS) "ntsdumpdir ${stateDir}"}
24
25 ${optionalString (!config.time.hardwareClockInLocalTime) "rtconutc"}
26
27 ${cfg.extraConfig}
28 '';
29
30 chronyFlags = [ "-n" "-m" "-u" "chrony" "-f" "${configFile}" ] ++ cfg.extraFlags;
31in
32{
33 options = {
34 services.chrony = {
35 enable = mkOption {
36 type = types.bool;
37 default = false;
38 description = lib.mdDoc ''
39 Whether to synchronise your machine's time using chrony.
40 Make sure you disable NTP if you enable this service.
41 '';
42 };
43
44 package = mkOption {
45 type = types.package;
46 default = pkgs.chrony;
47 defaultText = literalExpression "pkgs.chrony";
48 description = lib.mdDoc ''
49 Which chrony package to use.
50 '';
51 };
52
53 servers = mkOption {
54 default = config.networking.timeServers;
55 defaultText = literalExpression "config.networking.timeServers";
56 type = types.listOf types.str;
57 description = lib.mdDoc ''
58 The set of NTP servers from which to synchronise.
59 '';
60 };
61
62 serverOption = mkOption {
63 default = "iburst";
64 type = types.enum [ "iburst" "offline" ];
65 description = lib.mdDoc ''
66 Set option for server directives.
67
68 Use "iburst" to rapidly poll on startup. Recommended if your machine
69 is consistently online.
70
71 Use "offline" to prevent polling on startup. Recommended if your
72 machine boots offline or is otherwise frequently offline.
73 '';
74 };
75
76 enableNTS = mkOption {
77 type = types.bool;
78 default = false;
79 description = lib.mdDoc ''
80 Whether to enable Network Time Security authentication.
81 Make sure it is supported by your selected NTP server(s).
82 '';
83 };
84
85 initstepslew = {
86 enabled = mkOption {
87 type = types.bool;
88 default = true;
89 description = lib.mdDoc ''
90 Allow chronyd to make a rapid measurement of the system clock error
91 at boot time, and to correct the system clock by stepping before
92 normal operation begins.
93 '';
94 };
95
96 threshold = mkOption {
97 type = types.either types.float types.int;
98 default = 1000; # by default, same threshold as 'ntpd -g' (1000s)
99 description = lib.mdDoc ''
100 The threshold of system clock error (in seconds) above which the
101 clock will be stepped. If the correction required is less than the
102 threshold, a slew is used instead.
103 '';
104 };
105 };
106
107 directory = mkOption {
108 type = types.str;
109 default = "/var/lib/chrony";
110 description = lib.mdDoc "Directory where chrony state is stored.";
111 };
112
113 extraConfig = mkOption {
114 type = types.lines;
115 default = "";
116 description = lib.mdDoc ''
117 Extra configuration directives that should be added to
118 `chrony.conf`
119 '';
120 };
121
122 extraFlags = mkOption {
123 default = [];
124 example = [ "-s" ];
125 type = types.listOf types.str;
126 description = lib.mdDoc "Extra flags passed to the chronyd command.";
127 };
128 };
129 };
130
131 config = mkIf cfg.enable {
132 meta.maintainers = with lib.maintainers; [ thoughtpolice ];
133
134 environment.systemPackages = [ chronyPkg ];
135
136 users.groups.chrony.gid = config.ids.gids.chrony;
137
138 users.users.chrony =
139 { uid = config.ids.uids.chrony;
140 group = "chrony";
141 description = "chrony daemon user";
142 home = stateDir;
143 };
144
145 services.timesyncd.enable = mkForce false;
146
147 systemd.services.systemd-timedated.environment = { SYSTEMD_TIMEDATED_NTP_SERVICES = "chronyd.service"; };
148
149 systemd.tmpfiles.rules = [
150 "d ${stateDir} 0750 chrony chrony - -"
151 "f ${driftFile} 0640 chrony chrony - -"
152 "f ${keyFile} 0640 chrony chrony - -"
153 ];
154
155 systemd.services.chronyd =
156 { description = "chrony NTP daemon";
157
158 wantedBy = [ "multi-user.target" ];
159 wants = [ "time-sync.target" ];
160 before = [ "time-sync.target" ];
161 after = [ "network.target" "nss-lookup.target" ];
162 conflicts = [ "ntpd.service" "systemd-timesyncd.service" ];
163
164 path = [ chronyPkg ];
165
166 unitConfig.ConditionCapability = "CAP_SYS_TIME";
167 serviceConfig = {
168 Type = "simple";
169 ExecStart = "${chronyPkg}/bin/chronyd ${builtins.toString chronyFlags}";
170
171 # Proc filesystem
172 ProcSubset = "pid";
173 ProtectProc = "invisible";
174 # Access write directories
175 ReadWritePaths = [ "${stateDir}" ];
176 UMask = "0027";
177 # Capabilities
178 CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_DAC_OVERRIDE" "CAP_NET_BIND_SERVICE" "CAP_SETGID" "CAP_SETUID" "CAP_SYS_RESOURCE" "CAP_SYS_TIME" ];
179 # Device Access
180 DeviceAllow = [ "char-pps rw" "char-ptp rw" "char-rtc rw" ];
181 DevicePolicy = "closed";
182 # Security
183 NoNewPrivileges = true;
184 # Sandboxing
185 ProtectSystem = "full";
186 ProtectHome = true;
187 PrivateTmp = true;
188 PrivateDevices = false;
189 PrivateUsers = false;
190 ProtectHostname = true;
191 ProtectClock = false;
192 ProtectKernelTunables = true;
193 ProtectKernelModules = true;
194 ProtectKernelLogs = true;
195 ProtectControlGroups = true;
196 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
197 RestrictNamespaces = true;
198 LockPersonality = true;
199 MemoryDenyWriteExecute = true;
200 RestrictRealtime = true;
201 RestrictSUIDSGID = true;
202 RemoveIPC = true;
203 PrivateMounts = true;
204 # System Call Filtering
205 SystemCallArchitectures = "native";
206 SystemCallFilter = [ "~@cpu-emulation @debug @keyring @mount @obsolete @privileged @resources" "@clock" "@setuid" "capset" "@chown" ];
207 };
208 };
209 };
210}