1{ config, options, pkgs, lib, ... }:
2with lib;
3let
4 cfg = config.services.aesmd;
5 opt = options.services.aesmd;
6
7 sgx-psw = pkgs.sgx-psw.override { inherit (cfg) debug; };
8
9 configFile = with cfg.settings; pkgs.writeText "aesmd.conf" (
10 concatStringsSep "\n" (
11 optional (whitelistUrl != null) "whitelist url = ${whitelistUrl}" ++
12 optional (proxy != null) "aesm proxy = ${proxy}" ++
13 optional (proxyType != null) "proxy type = ${proxyType}" ++
14 optional (defaultQuotingType != null) "default quoting type = ${defaultQuotingType}" ++
15 # Newline at end of file
16 [ "" ]
17 )
18 );
19in
20{
21 options.services.aesmd = {
22 enable = mkEnableOption (lib.mdDoc "Intel's Architectural Enclave Service Manager (AESM) for Intel SGX");
23 debug = mkOption {
24 type = types.bool;
25 default = false;
26 description = lib.mdDoc "Whether to build the PSW package in debug mode.";
27 };
28 environment = mkOption {
29 type = with types; attrsOf str;
30 default = { };
31 description = mdDoc "Additional environment variables to pass to the AESM service.";
32 # Example environment variable for `sgx-azure-dcap-client` provider library
33 example = {
34 AZDCAP_COLLATERAL_VERSION = "v2";
35 AZDCAP_DEBUG_LOG_LEVEL = "INFO";
36 };
37 };
38 quoteProviderLibrary = mkOption {
39 type = with types; nullOr path;
40 default = null;
41 example = literalExpression "pkgs.sgx-azure-dcap-client";
42 description = lib.mdDoc "Custom quote provider library to use.";
43 };
44 settings = mkOption {
45 description = lib.mdDoc "AESM configuration";
46 default = { };
47 type = types.submodule {
48 options.whitelistUrl = mkOption {
49 type = with types; nullOr str;
50 default = null;
51 example = "http://whitelist.trustedservices.intel.com/SGX/LCWL/Linux/sgx_white_list_cert.bin";
52 description = lib.mdDoc "URL to retrieve authorized Intel SGX enclave signers.";
53 };
54 options.proxy = mkOption {
55 type = with types; nullOr str;
56 default = null;
57 example = "http://proxy_url:1234";
58 description = lib.mdDoc "HTTP network proxy.";
59 };
60 options.proxyType = mkOption {
61 type = with types; nullOr (enum [ "default" "direct" "manual" ]);
62 default = if (cfg.settings.proxy != null) then "manual" else null;
63 defaultText = literalExpression ''
64 if (config.${opt.settings}.proxy != null) then "manual" else null
65 '';
66 example = "default";
67 description = lib.mdDoc ''
68 Type of proxy to use. The `default` uses the system's default proxy.
69 If `direct` is given, uses no proxy.
70 A value of `manual` uses the proxy from
71 {option}`services.aesmd.settings.proxy`.
72 '';
73 };
74 options.defaultQuotingType = mkOption {
75 type = with types; nullOr (enum [ "ecdsa_256" "epid_linkable" "epid_unlinkable" ]);
76 default = null;
77 example = "ecdsa_256";
78 description = lib.mdDoc "Attestation quote type.";
79 };
80 };
81 };
82 };
83
84 config = mkIf cfg.enable {
85 assertions = [{
86 assertion = !(config.boot.specialFileSystems."/dev".options ? "noexec");
87 message = "SGX requires exec permission for /dev";
88 }];
89
90 hardware.cpu.intel.sgx.provision.enable = true;
91
92 # Make sure the AESM service can find the SGX devices until
93 # https://github.com/intel/linux-sgx/issues/772 is resolved
94 # and updated in nixpkgs.
95 hardware.cpu.intel.sgx.enableDcapCompat = mkForce true;
96
97 systemd.services.aesmd =
98 let
99 storeAesmFolder = "${sgx-psw}/aesm";
100 # Hardcoded path AESM_DATA_FOLDER in psw/ae/aesm_service/source/oal/linux/aesm_util.cpp
101 aesmDataFolder = "/var/opt/aesmd/data";
102 in
103 {
104 description = "Intel Architectural Enclave Service Manager";
105 wantedBy = [ "multi-user.target" ];
106
107 after = [
108 "auditd.service"
109 "network.target"
110 "syslog.target"
111 ];
112
113 environment = {
114 NAME = "aesm_service";
115 AESM_PATH = storeAesmFolder;
116 LD_LIBRARY_PATH = makeLibraryPath [ cfg.quoteProviderLibrary ];
117 } // cfg.environment;
118
119 # Make sure any of the SGX application enclave devices is available
120 unitConfig.AssertPathExists = [
121 # legacy out-of-tree driver
122 "|/dev/isgx"
123 # DCAP driver
124 "|/dev/sgx/enclave"
125 # in-tree driver
126 "|/dev/sgx_enclave"
127 ];
128
129 serviceConfig = rec {
130 ExecStartPre = pkgs.writeShellScript "copy-aesmd-data-files.sh" ''
131 set -euo pipefail
132 whiteListFile="${aesmDataFolder}/white_list_cert_to_be_verify.bin"
133 if [[ ! -f "$whiteListFile" ]]; then
134 ${pkgs.coreutils}/bin/install -m 644 -D \
135 "${storeAesmFolder}/data/white_list_cert_to_be_verify.bin" \
136 "$whiteListFile"
137 fi
138 '';
139 ExecStart = "${sgx-psw}/bin/aesm_service --no-daemon";
140 ExecReload = ''${pkgs.coreutils}/bin/kill -SIGHUP "$MAINPID"'';
141
142 Restart = "on-failure";
143 RestartSec = "15s";
144
145 DynamicUser = true;
146 Group = "sgx";
147 SupplementaryGroups = [
148 config.hardware.cpu.intel.sgx.provision.group
149 ];
150
151 Type = "simple";
152
153 WorkingDirectory = storeAesmFolder;
154 StateDirectory = "aesmd";
155 StateDirectoryMode = "0700";
156 RuntimeDirectory = "aesmd";
157 RuntimeDirectoryMode = "0750";
158
159 # Hardening
160
161 # chroot into the runtime directory
162 RootDirectory = "%t/aesmd";
163 BindReadOnlyPaths = [
164 builtins.storeDir
165 # Hardcoded path AESM_CONFIG_FILE in psw/ae/aesm_service/source/utils/aesm_config.cpp
166 "${configFile}:/etc/aesmd.conf"
167 ];
168 BindPaths = [
169 # Hardcoded path CONFIG_SOCKET_PATH in psw/ae/aesm_service/source/core/ipc/SocketConfig.h
170 "%t/aesmd:/var/run/aesmd"
171 "%S/aesmd:/var/opt/aesmd"
172 ];
173
174 # PrivateDevices=true will mount /dev noexec which breaks AESM
175 PrivateDevices = false;
176 DevicePolicy = "closed";
177 DeviceAllow = [
178 # legacy out-of-tree driver
179 "/dev/isgx rw"
180 # DCAP driver
181 "/dev/sgx rw"
182 # in-tree driver
183 "/dev/sgx_enclave rw"
184 "/dev/sgx_provision rw"
185 ];
186
187 # Requires Internet access for attestation
188 PrivateNetwork = false;
189
190 RestrictAddressFamilies = [
191 # Allocates the socket /var/run/aesmd/aesm.socket
192 "AF_UNIX"
193 # Uses the HTTP protocol to initialize some services
194 "AF_INET"
195 "AF_INET6"
196 ];
197
198 # True breaks stuff
199 MemoryDenyWriteExecute = false;
200
201 # needs the ipc syscall in order to run
202 SystemCallFilter = [
203 "@system-service"
204 "~@aio"
205 "~@chown"
206 "~@clock"
207 "~@cpu-emulation"
208 "~@debug"
209 "~@keyring"
210 "~@memlock"
211 "~@module"
212 "~@mount"
213 "~@privileged"
214 "~@raw-io"
215 "~@reboot"
216 "~@resources"
217 "~@setuid"
218 "~@swap"
219 "~@sync"
220 "~@timer"
221 ];
222 SystemCallArchitectures = "native";
223 SystemCallErrorNumber = "EPERM";
224
225 CapabilityBoundingSet = "";
226 KeyringMode = "private";
227 LockPersonality = true;
228 NoNewPrivileges = true;
229 NotifyAccess = "none";
230 PrivateMounts = true;
231 PrivateTmp = true;
232 PrivateUsers = true;
233 ProcSubset = "pid";
234 ProtectClock = true;
235 ProtectControlGroups = true;
236 ProtectHome = true;
237 ProtectHostname = true;
238 ProtectKernelLogs = true;
239 ProtectKernelModules = true;
240 ProtectKernelTunables = true;
241 ProtectProc = "invisible";
242 ProtectSystem = "strict";
243 RemoveIPC = true;
244 RestrictNamespaces = true;
245 RestrictRealtime = true;
246 RestrictSUIDSGID = true;
247 UMask = "0066";
248 };
249 };
250 };
251}