Files
Arian van Putten e142ebef75 nixos/spire: make Workload API socket reachable by non-root workloads
The unit's UMask=0027 was masking spire-agent's own
os.MkdirAll("/run/spire/agent/public", 0755) down to mode 0750, so any
process not in the spire-agent group got EACCES on connect() — defeating
the point of workload attestation, which is supposed to identify
arbitrary callers regardless of their unix identity.

Pre-create the directory via RuntimeDirectory so systemd applies
RuntimeDirectoryMode (0755) independent of umask; spire-agent's MkdirAll
then becomes a no-op.

Also exercises the path in the NixOS test by fetching an SVID as a
normal user.
2026-05-04 14:50:21 +02:00

167 lines
6.1 KiB
Nix

{
lib,
pkgs,
config,
...
}:
let
format = pkgs.formats.hcl1 { };
cfg = config.services.spire.agent;
in
{
meta.maintainers = [ lib.maintainers.arianvp ];
options.services.spire.agent = {
enable = lib.mkEnableOption "SPIRE agent";
package = lib.mkPackageOption pkgs "spire" { };
settings = lib.mkOption {
description = ''
SPIRE Agent configuration file options. See [the documentation](https://spiffe.io/docs/latest/deploying/spire_agent/) for all available options.
'';
type = lib.types.submodule {
freeformType = format.type;
options = {
agent = {
trust_domain = lib.mkOption {
type = lib.types.str;
description = "The trust domain that this agent belongs to (should be no more than 255 characters)";
example = "example.com";
};
data_dir = lib.mkOption {
type = lib.types.str;
default = "$STATE_DIRECTORY";
description = "A directory the agent can use for its runtime data";
};
server_address = lib.mkOption {
type = lib.types.str;
description = "DNS name or IP address of the SPIRE server";
example = "server.example.com";
};
server_port = lib.mkOption {
type = lib.types.port;
default = 8081;
description = "Port number of the SPIRE server";
};
socket_path = lib.mkOption {
type = lib.types.path;
default = "/run/spire/agent/public/api.sock";
description = "Location to bind the SPIRE Agent API socket (Unix only)";
};
join_token = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "An optional token which has been generated by the SPIRE server";
};
join_token_file = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Path to a file containing an optional join token which has been generated by the SPIRE server";
};
};
plugins = lib.mkOption {
description = ''
Built-in plugin types can be found at [the plugin types documentation](https://spiffe.io/docs/latest/deploying/spire_agent/#plugin-types).
See [plugin configuration](https://spiffe.io/docs/latest/deploying/spire_agent/#plugin-configuration) for options and how to configure external plugins.
'';
type = lib.types.submodule {
freeformType = format.type;
options.NodeAttestor = lib.mkOption {
default = { };
description = ''
Gathers information used to attest the agent's identity to the server. Generally paired with a server plugin of the same type.
'';
type = lib.types.submodule {
freeformType = format.type;
options.join_token = lib.mkOption {
default = null;
description = ''
The `join_token` is responsible for attesting the agent's identity using a one-time-use pre-shared key.
Must be used in conjunction with the server-side `join_token` plugin.
'';
type = lib.types.nullOr (
lib.types.submodule {
freeformType = format.type;
options.plugin_data = lib.mkOption {
type = lib.types.submodule { };
default = { };
description = ''
As a special case for node attestors, the join token itself is configured by a CLI flag (`-joinToken`)
or by configuring `join_token` in the agent's main config body.
'';
};
}
);
};
};
};
};
example = {
KeyManager.memory.plugin_data = { };
NodeAttestor.join_token.plugin_data = { };
WorkloadAttestor.systemd.plugin_data = { };
WorkloadAttestor.unix.plugin_data = { };
};
};
};
};
};
configFile = lib.mkOption {
type = lib.types.path;
defaultText = "Config file generated from services.spire.agent.settings";
default = format.generate "agent.conf" (lib.filterAttrsRecursive (_: v: v != null) cfg.settings);
description = ''
Path to the SPIRE agent configuration file. See [the documentation](https://spiffe.io/docs/latest/deploying/spire_agent/) for more information.
'';
};
expandEnv = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Expand environment $VARIABLES in the config file";
};
};
imports = [ ./agent-tpm.nix ];
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
# TODO: Switch to DynamicUser once https://github.com/NixOS/nixpkgs/issues/299476 lands
users.users.spire-agent = {
isSystemUser = true;
group = "spire-agent";
};
users.groups.spire-agent = { };
systemd.services.spire-agent = {
wantedBy = [ "multi-user.target" ];
description = "SPIRE agent";
serviceConfig = {
ExecStart =
"${lib.getExe' cfg.package "spire-agent"} run "
+ lib.cli.toCommandLineShellGNU { } {
inherit (cfg) expandEnv;
config = cfg.configFile;
};
Restart = "on-failure";
StateDirectory = "spire/agent";
StateDirectoryMode = "0700";
RuntimeDirectory = "spire/agent/public";
# TODO: Switch to DynamicUser once https://github.com/NixOS/nixpkgs/issues/299476 lands
# Without it, the systemd plugin can not talk to dbus
# DynamicUser = true;
User = "spire-agent";
Group = "spire-agent";
UMask = "0027";
# TODO: Hardening
};
};
};
}