Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,16 @@
</section>
<section xml:id="sec-release-22.05-new-services">
<title>New Services</title>
<para>
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
<link xlink:href="https://github.com/intel/linux-sgx#install-the-intelr-sgx-psw">aesmd</link>,
the Intel SGX Architectural Enclave Service Manager. Available
as
<link linkend="opt-services.aesmd.enable">services.aesmd</link>.
</para>
</listitem>
</itemizedlist>
</section>
<section xml:id="sec-release-22.05-incompatibilities">
<title>Backward Incompatibilities</title>
Expand Down
2 changes: 2 additions & 0 deletions nixos/doc/manual/release-notes/rl-2205.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
47 changes: 47 additions & 0 deletions nixos/modules/hardware/cpu/intel-sgx.nix
Original file line number Diff line number Diff line change
@@ -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}"
'';
};
}
2 changes: 2 additions & 0 deletions nixos/modules/module-list.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -927,6 +928,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
Expand Down
227 changes: 227 additions & 0 deletions nixos/modules/services/security/aesmd.nix
Original file line number Diff line number Diff line change
@@ -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 <literal>default</literal> uses the system's default proxy.
If <literal>direct</literal> is given, uses no proxy.
A value of <literal>manual</literal> uses the proxy from
<option>services.aesmd.settings.proxy</option>.
'';
};
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";
};
};
};
}
62 changes: 62 additions & 0 deletions nixos/tests/aesmd.nix
Original file line number Diff line number Diff line change
@@ -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"
'';
})
1 change: 1 addition & 0 deletions nixos/tests/all-tests.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};
Expand Down
Loading