diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index c84a3e3b0193..144b277438a5 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -19,8 +19,16 @@
New Services - - + + + + aesmd, + the Intel SGX Architectural Enclave Service Manager. Available + as + services.aesmd. + + +
Backward Incompatibilities diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index 45ed69cf1b03..4418c8142a14 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -8,6 +8,8 @@ In addition to numerous new and upgraded packages, this release has the followin ## New Services {#sec-release-22.05-new-services} +- [aesmd](https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw), the Intel SGX Architectural Enclave Service Manager. Available as [services.aesmd](#opt-services.aesmd.enable). + ## Backward Incompatibilities {#sec-release-22.05-incompatibilities} - `pkgs.ghc` now refers to `pkgs.targetPackages.haskellPackages.ghc`. diff --git a/nixos/modules/hardware/cpu/intel-sgx.nix b/nixos/modules/hardware/cpu/intel-sgx.nix new file mode 100644 index 000000000000..046479400587 --- /dev/null +++ b/nixos/modules/hardware/cpu/intel-sgx.nix @@ -0,0 +1,47 @@ +{ config, lib, ... }: +with lib; +let + cfg = config.hardware.cpu.intel.sgx.provision; + defaultGroup = "sgx_prv"; +in +{ + options.hardware.cpu.intel.sgx.provision = { + enable = mkEnableOption "access to the Intel SGX provisioning device"; + user = mkOption { + description = "Owner to assign to the SGX provisioning device."; + type = types.str; + default = "root"; + }; + group = mkOption { + description = "Group to assign to the SGX provisioning device."; + type = types.str; + default = defaultGroup; + }; + mode = mkOption { + description = "Mode to set for the SGX provisioning device."; + type = types.str; + default = "0660"; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = hasAttr cfg.user config.users.users; + message = "Given user does not exist"; + } + { + assertion = (cfg.group == defaultGroup) || (hasAttr cfg.group config.users.groups); + message = "Given group does not exist"; + } + ]; + + users.groups = optionalAttrs (cfg.group == defaultGroup) { + "${cfg.group}" = { }; + }; + + services.udev.extraRules = '' + SUBSYSTEM=="misc", KERNEL=="sgx_provision", OWNER="${cfg.user}", GROUP="${cfg.group}", MODE="${cfg.mode}" + ''; + }; +} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 3cc9ea88e17b..1f826220a0f3 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -45,6 +45,7 @@ ./hardware/ckb-next.nix ./hardware/cpu/amd-microcode.nix ./hardware/cpu/intel-microcode.nix + ./hardware/cpu/intel-sgx.nix ./hardware/corectrl.nix ./hardware/digitalbitbox.nix ./hardware/device-tree.nix @@ -928,6 +929,7 @@ ./services/search/kibana.nix ./services/search/meilisearch.nix ./services/search/solr.nix + ./services/security/aesmd.nix ./services/security/certmgr.nix ./services/security/cfssl.nix ./services/security/clamav.nix diff --git a/nixos/modules/services/security/aesmd.nix b/nixos/modules/services/security/aesmd.nix new file mode 100644 index 000000000000..bb53bc49e259 --- /dev/null +++ b/nixos/modules/services/security/aesmd.nix @@ -0,0 +1,227 @@ +{ config, pkgs, lib, ... }: +with lib; +let + cfg = config.services.aesmd; + + sgx-psw = pkgs.sgx-psw.override { inherit (cfg) debug; }; + + configFile = with cfg.settings; pkgs.writeText "aesmd.conf" ( + concatStringsSep "\n" ( + optional (whitelistUrl != null) "whitelist url = ${whitelistUrl}" ++ + optional (proxy != null) "aesm proxy = ${proxy}" ++ + optional (proxyType != null) "proxy type = ${proxyType}" ++ + optional (defaultQuotingType != null) "default quoting type = ${defaultQuotingType}" ++ + # Newline at end of file + [ "" ] + ) + ); +in +{ + options.services.aesmd = { + enable = mkEnableOption "Intel's Architectural Enclave Service Manager (AESM) for Intel SGX"; + debug = mkOption { + type = types.bool; + default = false; + description = "Whether to build the PSW package in debug mode."; + }; + settings = mkOption { + description = "AESM configuration"; + default = { }; + type = types.submodule { + options.whitelistUrl = mkOption { + type = with types; nullOr str; + default = null; + example = "http://whitelist.trustedservices.intel.com/SGX/LCWL/Linux/sgx_white_list_cert.bin"; + description = "URL to retrieve authorized Intel SGX enclave signers."; + }; + options.proxy = mkOption { + type = with types; nullOr str; + default = null; + example = "http://proxy_url:1234"; + description = "HTTP network proxy."; + }; + options.proxyType = mkOption { + type = with types; nullOr (enum [ "default" "direct" "manual" ]); + default = if (cfg.settings.proxy != null) then "manual" else null; + example = "default"; + description = '' + Type of proxy to use. The default uses the system's default proxy. + If direct is given, uses no proxy. + A value of manual uses the proxy from + . + ''; + }; + options.defaultQuotingType = mkOption { + type = with types; nullOr (enum [ "ecdsa_256" "epid_linkable" "epid_unlinkable" ]); + default = null; + example = "ecdsa_256"; + description = "Attestation quote type."; + }; + }; + }; + }; + + config = mkIf cfg.enable { + assertions = [{ + assertion = !(config.boot.specialFileSystems."/dev".options ? "noexec"); + message = "SGX requires exec permission for /dev"; + }]; + + hardware.cpu.intel.sgx.provision.enable = true; + + systemd.services.aesmd = + let + storeAesmFolder = "${sgx-psw}/aesm"; + # Hardcoded path AESM_DATA_FOLDER in psw/ae/aesm_service/source/oal/linux/aesm_util.cpp + aesmDataFolder = "/var/opt/aesmd/data"; + aesmStateDirSystemd = "%S/aesmd"; + in + { + description = "Intel Architectural Enclave Service Manager"; + wantedBy = [ "multi-user.target" ]; + + after = [ + "auditd.service" + "network.target" + "syslog.target" + ]; + + environment = { + NAME = "aesm_service"; + AESM_PATH = storeAesmFolder; + LD_LIBRARY_PATH = storeAesmFolder; + }; + + # Make sure any of the SGX application enclave devices is available + unitConfig.AssertPathExists = [ + # legacy out-of-tree driver + "|/dev/isgx" + # DCAP driver + "|/dev/sgx/enclave" + # in-tree driver + "|/dev/sgx_enclave" + ]; + + serviceConfig = rec { + ExecStartPre = pkgs.writeShellScript "copy-aesmd-data-files.sh" '' + set -euo pipefail + whiteListFile="${aesmDataFolder}/white_list_cert_to_be_verify.bin" + if [[ ! -f "$whiteListFile" ]]; then + ${pkgs.coreutils}/bin/install -m 644 -D \ + "${storeAesmFolder}/data/white_list_cert_to_be_verify.bin" \ + "$whiteListFile" + fi + ''; + ExecStart = "${sgx-psw}/bin/aesm_service --no-daemon"; + ExecReload = ''${pkgs.coreutils}/bin/kill -SIGHUP "$MAINPID"''; + + Restart = "on-failure"; + RestartSec = "15s"; + + DynamicUser = true; + Group = "sgx"; + SupplementaryGroups = [ + config.hardware.cpu.intel.sgx.provision.group + ]; + + Type = "simple"; + + WorkingDirectory = storeAesmFolder; + StateDirectory = "aesmd"; + StateDirectoryMode = "0700"; + RuntimeDirectory = "aesmd"; + RuntimeDirectoryMode = "0750"; + + # Hardening + + # chroot into the runtime directory + RootDirectory = "%t/aesmd"; + BindReadOnlyPaths = [ + builtins.storeDir + # Hardcoded path AESM_CONFIG_FILE in psw/ae/aesm_service/source/utils/aesm_config.cpp + "${configFile}:/etc/aesmd.conf" + ]; + BindPaths = [ + # Hardcoded path CONFIG_SOCKET_PATH in psw/ae/aesm_service/source/core/ipc/SocketConfig.h + "%t/aesmd:/var/run/aesmd" + "%S/aesmd:/var/opt/aesmd" + ]; + + # PrivateDevices=true will mount /dev noexec which breaks AESM + PrivateDevices = false; + DevicePolicy = "closed"; + DeviceAllow = [ + # legacy out-of-tree driver + "/dev/isgx rw" + # DCAP driver + "/dev/sgx rw" + # in-tree driver + "/dev/sgx_enclave rw" + "/dev/sgx_provision rw" + ]; + + # Requires Internet access for attestation + PrivateNetwork = false; + + RestrictAddressFamilies = [ + # Allocates the socket /var/run/aesmd/aesm.socket + "AF_UNIX" + # Uses the HTTP protocol to initialize some services + "AF_INET" + "AF_INET6" + ]; + + # True breaks stuff + MemoryDenyWriteExecute = false; + + # needs the ipc syscall in order to run + SystemCallFilter = [ + "@system-service" + "~@aio" + "~@chown" + "~@clock" + "~@cpu-emulation" + "~@debug" + "~@keyring" + "~@memlock" + "~@module" + "~@mount" + "~@privileged" + "~@raw-io" + "~@reboot" + "~@resources" + "~@setuid" + "~@swap" + "~@sync" + "~@timer" + ]; + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + + CapabilityBoundingSet = ""; + KeyringMode = "private"; + LockPersonality = true; + NoNewPrivileges = true; + NotifyAccess = "none"; + PrivateMounts = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + RemoveIPC = true; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + UMask = "0066"; + }; + }; + }; +} diff --git a/nixos/tests/aesmd.nix b/nixos/tests/aesmd.nix new file mode 100644 index 000000000000..59c04fe7e96a --- /dev/null +++ b/nixos/tests/aesmd.nix @@ -0,0 +1,62 @@ +import ./make-test-python.nix ({ pkgs, lib, ... }: { + name = "aesmd"; + meta = { + maintainers = with lib.maintainers; [ veehaitch ]; + }; + + machine = { lib, ... }: { + services.aesmd = { + enable = true; + settings = { + defaultQuotingType = "ecdsa_256"; + proxyType = "direct"; + whitelistUrl = "http://nixos.org"; + }; + }; + + # Should have access to the AESM socket + users.users."sgxtest" = { + isNormalUser = true; + extraGroups = [ "sgx" ]; + }; + + # Should NOT have access to the AESM socket + users.users."nosgxtest".isNormalUser = true; + + # We don't have a real SGX machine in NixOS tests + systemd.services.aesmd.unitConfig.AssertPathExists = lib.mkForce [ ]; + }; + + testScript = '' + with subtest("aesmd.service starts"): + machine.wait_for_unit("aesmd.service") + status, main_pid = machine.systemctl("show --property MainPID --value aesmd.service") + assert status == 0, "Could not get MainPID of aesmd.service" + main_pid = main_pid.strip() + + with subtest("aesmd.service runtime directory permissions"): + runtime_dir = "/run/aesmd"; + res = machine.succeed(f"stat -c '%a %U %G' {runtime_dir}").strip() + assert "750 aesmd sgx" == res, f"{runtime_dir} does not have the expected permissions: {res}" + + with subtest("aesm.socket available on host"): + socket_path = "/var/run/aesmd/aesm.socket" + machine.wait_until_succeeds(f"test -S {socket_path}") + machine.succeed(f"test 777 -eq $(stat -c '%a' {socket_path})") + for op in [ "-r", "-w", "-x" ]: + machine.succeed(f"sudo -u sgxtest test {op} {socket_path}") + machine.fail(f"sudo -u nosgxtest test {op} {socket_path}") + + with subtest("Copies white_list_cert_to_be_verify.bin"): + whitelist_path = "/var/opt/aesmd/data/white_list_cert_to_be_verify.bin" + whitelist_perms = machine.succeed( + f"nsenter -m -t {main_pid} ${pkgs.coreutils}/bin/stat -c '%a' {whitelist_path}" + ).strip() + assert "644" == whitelist_perms, f"white_list_cert_to_be_verify.bin has permissions {whitelist_perms}" + + with subtest("Writes and binds aesm.conf in service namespace"): + aesmd_config = machine.succeed(f"nsenter -m -t {main_pid} ${pkgs.coreutils}/bin/cat /etc/aesmd.conf") + + assert aesmd_config == "whitelist url = http://nixos.org\nproxy type = direct\ndefault quoting type = ecdsa_256\n", "aesmd.conf differs" + ''; +}) diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 06305460c6ac..f86cc2544dab 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -23,6 +23,7 @@ in { _3proxy = handleTest ./3proxy.nix {}; acme = handleTest ./acme.nix {}; + aesmd = handleTest ./aesmd.nix {}; agda = handleTest ./agda.nix {}; airsonic = handleTest ./airsonic.nix {}; amazon-init-shell = handleTest ./amazon-init-shell.nix {}; diff --git a/pkgs/os-specific/linux/sgx/psw/default.nix b/pkgs/os-specific/linux/sgx/psw/default.nix new file mode 100644 index 000000000000..e449c99b0776 --- /dev/null +++ b/pkgs/os-specific/linux/sgx/psw/default.nix @@ -0,0 +1,190 @@ +{ stdenv +, lib +, fetchurl +, cmake +, coreutils +, curl +, file +, glibc +, makeWrapper +, nixosTests +, protobuf +, python3 +, sgx-sdk +, shadow +, systemd +, util-linux +, which +, debug ? false +}: +stdenv.mkDerivation rec { + inherit (sgx-sdk) version versionTag src; + pname = "sgx-psw"; + + postUnpack = + let + ae.prebuilt = fetchurl { + url = "https://download.01.org/intel-sgx/sgx-linux/${versionTag}/prebuilt_ae_${versionTag}.tar.gz"; + hash = "sha256-nGKZEpT2Mx0DLgqjv9qbZqBt1pQaSHcnA0K6nHma3sk"; + }; + dcap = rec { + version = "1.11"; + filename = "prebuilt_dcap_${version}.tar.gz"; + prebuilt = fetchurl { + url = "https://download.01.org/intel-sgx/sgx-dcap/${version}/linux/${filename}"; + hash = "sha256-ShGScS4yNLki04RNPxxLvqzGmy4U1L0gVETvfAo8w9M="; + }; + }; + in + sgx-sdk.postUnpack + '' + # Make sure we use the correct version of prebuilt DCAP + grep -q 'ae_file_name=${dcap.filename}' "$src/external/dcap_source/QuoteGeneration/download_prebuilt.sh" \ + || (echo "Could not find expected prebuilt DCAP ${dcap.filename} in linux-sgx source" >&2 && exit 1) + + tar -zxf ${ae.prebuilt} -C $sourceRoot/ + tar -zxf ${dcap.prebuilt} -C $sourceRoot/external/dcap_source/QuoteGeneration/ + ''; + + nativeBuildInputs = [ + cmake + file + makeWrapper + python3 + sgx-sdk + which + ]; + + buildInputs = [ + curl + protobuf + ]; + + hardeningDisable = lib.optionals debug [ + "fortify" + ]; + + postPatch = '' + # https://github.com/intel/linux-sgx/pull/730 + substituteInPlace buildenv.mk --replace '/bin/cp' 'cp' + substituteInPlace psw/ae/aesm_service/source/CMakeLists.txt \ + --replace '/usr/bin/getconf' 'getconf' + + # https://github.com/intel/SGXDataCenterAttestationPrimitives/pull/205 + substituteInPlace ./external/dcap_source/QuoteGeneration/buildenv.mk \ + --replace '/bin/cp' 'cp' + substituteInPlace external/dcap_source/tools/SGXPlatformRegistration/Makefile \ + --replace '/bin/cp' 'cp' + substituteInPlace external/dcap_source/tools/SGXPlatformRegistration/buildenv.mk \ + --replace '/bin/cp' 'cp' + + patchShebangs \ + linux/installer/bin/build-installpkg.sh \ + linux/installer/common/psw/createTarball.sh \ + linux/installer/common/psw/install.sh + ''; + + dontUseCmakeConfigure = true; + + # Randomly fails if enabled + enableParallelBuilding = false; + + buildFlags = [ + "psw_install_pkg" + ] ++ lib.optionals debug [ + "DEBUG=1" + ]; + + installFlags = [ + "-C linux/installer/common/psw/output" + "DESTDIR=$(TMPDIR)/install" + ]; + + postInstall = '' + installDir=$TMPDIR/install + sgxPswDir=$installDir/opt/intel/sgxpsw + + mv $installDir/usr/lib64/ $out/lib/ + ln -sr $out/lib $out/lib64 + + # Install udev rules to lib/udev/rules.d + mv $sgxPswDir/udev/ $out/lib/ + + # Install example AESM config + mkdir $out/etc/ + mv $sgxPswDir/aesm/conf/aesmd.conf $out/etc/ + rmdir $sgxPswDir/aesm/conf/ + + # Delete init service + rm $sgxPswDir/aesm/aesmd.conf + + # Move systemd services + mkdir -p $out/lib/systemd/system/ + mv $sgxPswDir/aesm/aesmd.service $out/lib/systemd/system/ + mv $sgxPswDir/remount-dev-exec.service $out/lib/systemd/system/ + + # Move misc files + mkdir $out/share/ + mv $sgxPswDir/licenses $out/share/ + + # Remove unnecessary files + rm $sgxPswDir/{cleanup.sh,startup.sh} + rm -r $sgxPswDir/scripts + + mv $sgxPswDir/aesm/ $out/ + + mkdir $out/bin + makeWrapper $out/aesm/aesm_service $out/bin/aesm_service \ + --prefix LD_LIBRARY_PATH : ${lib.makeLibraryPath [ protobuf ]}:$out/aesm \ + --run "cd $out/aesm" + + # Make sure we didn't forget to handle any files + rmdir $sgxPswDir || (echo "Error: The directory $installDir still contains unhandled files: $(ls -A $installDir)" >&2 && exit 1) + ''; + + # Most—if not all—of those fixups are not relevant for NixOS as we have our own + # NixOS module which is based on those files without relying on them. Still, it + # is helpful to have properly patched versions for non-NixOS distributions. + postFixup = '' + header "Fixing aesmd.service" + substituteInPlace $out/lib/systemd/system/aesmd.service \ + --replace '@aesm_folder@' \ + "$out/aesm" \ + --replace 'Type=forking' \ + 'Type=simple' \ + --replace "ExecStart=$out/aesm/aesm_service" \ + "ExecStart=$out/bin/aesm_service --no-daemon"\ + --replace "/bin/mkdir" \ + "${coreutils}/bin/mkdir" \ + --replace "/bin/chown" \ + "${coreutils}/bin/chown" \ + --replace "/bin/chmod" \ + "${coreutils}/bin/chmod" \ + --replace "/bin/kill" \ + "${coreutils}/bin/kill" + + header "Fixing remount-dev-exec.service" + substituteInPlace $out/lib/systemd/system/remount-dev-exec.service \ + --replace '/bin/mount' \ + "${util-linux}/bin/mount" + + header "Fixing linksgx.sh" + # https://github.com/intel/linux-sgx/pull/736 + substituteInPlace $out/aesm/linksgx.sh \ + --replace '/usr/bin/getent' \ + '${glibc.bin}/bin/getent' \ + --replace '/usr/sbin/usermod' \ + '${shadow}/bin/usermod' + ''; + + passthru.tests = { + service = nixosTests.aesmd; + }; + + meta = with lib; { + description = "Intel SGX Architectural Enclave Service Manager"; + homepage = "https://github.com/intel/linux-sgx"; + maintainers = with maintainers; [ veehaitch citadelcore ]; + platforms = [ "x86_64-linux" ]; + license = with licenses; [ bsd3 ]; + }; +} diff --git a/pkgs/os-specific/linux/sgx-sdk/default.nix b/pkgs/os-specific/linux/sgx/sdk/default.nix similarity index 97% rename from pkgs/os-specific/linux/sgx-sdk/default.nix rename to pkgs/os-specific/linux/sgx/sdk/default.nix index cb9d140e4edd..e08511272af3 100644 --- a/pkgs/os-specific/linux/sgx-sdk/default.nix +++ b/pkgs/os-specific/linux/sgx/sdk/default.nix @@ -21,13 +21,13 @@ , validatePkgConfig , writeShellScript , writeText +, debug ? false }: -with lib; stdenv.mkDerivation rec { pname = "sgx-sdk"; version = "2.14.100.2"; - versionTag = concatStringsSep "." (take 2 (splitVersion version)); + versionTag = lib.concatStringsSep "." (lib.take 2 (lib.splitVersion version)); src = fetchFromGitHub { owner = "intel"; @@ -140,6 +140,8 @@ stdenv.mkDerivation rec { buildFlags = [ "sdk_install_pkg" + ] ++ lib.optionals debug [ + "DEBUG=1" ]; enableParallelBuilding = true; @@ -264,7 +266,7 @@ stdenv.mkDerivation rec { passthru.tests = callPackage ./samples.nix { }; - meta = { + meta = with lib; { description = "Intel SGX SDK for Linux built with IPP Crypto Library"; homepage = "https://github.com/intel/linux-sgx"; maintainers = with maintainers; [ sbellem arturcygan veehaitch ]; diff --git a/pkgs/os-specific/linux/sgx-sdk/ipp-crypto.nix b/pkgs/os-specific/linux/sgx/sdk/ipp-crypto.nix similarity index 100% rename from pkgs/os-specific/linux/sgx-sdk/ipp-crypto.nix rename to pkgs/os-specific/linux/sgx/sdk/ipp-crypto.nix diff --git a/pkgs/os-specific/linux/sgx-sdk/samples.nix b/pkgs/os-specific/linux/sgx/sdk/samples.nix similarity index 100% rename from pkgs/os-specific/linux/sgx-sdk/samples.nix rename to pkgs/os-specific/linux/sgx/sdk/samples.nix diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 4a1798545b7f..e23e45590f89 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -22737,7 +22737,9 @@ with pkgs; seturgent = callPackage ../os-specific/linux/seturgent { }; - sgx-sdk = callPackage ../os-specific/linux/sgx-sdk { }; + sgx-sdk = callPackage ../os-specific/linux/sgx/sdk { }; + + sgx-psw = callPackage ../os-specific/linux/sgx/psw { }; shadow = callPackage ../os-specific/linux/shadow { };