uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
236
nixos/modules/services/security/aesmd.nix
Normal file
236
nixos/modules/services/security/aesmd.nix
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
{ config, options, pkgs, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.aesmd;
|
||||
opt = options.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;
|
||||
defaultText = literalExpression ''
|
||||
if (config.${opt.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;
|
||||
|
||||
# Make sure the AESM service can find the SGX devices until
|
||||
# https://github.com/intel/linux-sgx/issues/772 is resolved
|
||||
# and updated in nixpkgs.
|
||||
hardware.cpu.intel.sgx.enableDcapCompat = mkForce 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";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
201
nixos/modules/services/security/certmgr.nix
Normal file
201
nixos/modules/services/security/certmgr.nix
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.certmgr;
|
||||
|
||||
specs = mapAttrsToList (n: v: rec {
|
||||
name = n + ".json";
|
||||
path = if isAttrs v then pkgs.writeText name (builtins.toJSON v) else v;
|
||||
}) cfg.specs;
|
||||
|
||||
allSpecs = pkgs.linkFarm "certmgr.d" specs;
|
||||
|
||||
certmgrYaml = pkgs.writeText "certmgr.yaml" (builtins.toJSON {
|
||||
dir = allSpecs;
|
||||
default_remote = cfg.defaultRemote;
|
||||
svcmgr = cfg.svcManager;
|
||||
before = cfg.validMin;
|
||||
interval = cfg.renewInterval;
|
||||
inherit (cfg) metricsPort metricsAddress;
|
||||
});
|
||||
|
||||
specPaths = map dirOf (concatMap (spec:
|
||||
if isAttrs spec then
|
||||
collect isString (filterAttrsRecursive (n: v: isAttrs v || n == "path") spec)
|
||||
else
|
||||
[ spec ]
|
||||
) (attrValues cfg.specs));
|
||||
|
||||
preStart = ''
|
||||
${concatStringsSep " \\\n" (["mkdir -p"] ++ map escapeShellArg specPaths)}
|
||||
${cfg.package}/bin/certmgr -f ${certmgrYaml} check
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.services.certmgr = {
|
||||
enable = mkEnableOption "certmgr";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.certmgr;
|
||||
defaultText = literalExpression "pkgs.certmgr";
|
||||
description = "Which certmgr package to use in the service.";
|
||||
};
|
||||
|
||||
defaultRemote = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1:8888";
|
||||
description = "The default CA host:port to use.";
|
||||
};
|
||||
|
||||
validMin = mkOption {
|
||||
default = "72h";
|
||||
type = types.str;
|
||||
description = "The interval before a certificate expires to start attempting to renew it.";
|
||||
};
|
||||
|
||||
renewInterval = mkOption {
|
||||
default = "30m";
|
||||
type = types.str;
|
||||
description = "How often to check certificate expirations and how often to update the cert_next_expires metric.";
|
||||
};
|
||||
|
||||
metricsAddress = mkOption {
|
||||
default = "127.0.0.1";
|
||||
type = types.str;
|
||||
description = "The address for the Prometheus HTTP endpoint.";
|
||||
};
|
||||
|
||||
metricsPort = mkOption {
|
||||
default = 9488;
|
||||
type = types.ints.u16;
|
||||
description = "The port for the Prometheus HTTP endpoint.";
|
||||
};
|
||||
|
||||
specs = mkOption {
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
exampleCert =
|
||||
let
|
||||
domain = "example.com";
|
||||
secret = name: "/var/lib/secrets/''${name}.pem";
|
||||
in {
|
||||
service = "nginx";
|
||||
action = "reload";
|
||||
authority = {
|
||||
file.path = secret "ca";
|
||||
};
|
||||
certificate = {
|
||||
path = secret domain;
|
||||
};
|
||||
private_key = {
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0600";
|
||||
path = secret "''${domain}-key";
|
||||
};
|
||||
request = {
|
||||
CN = domain;
|
||||
hosts = [ "mail.''${domain}" "www.''${domain}" ];
|
||||
key = {
|
||||
algo = "rsa";
|
||||
size = 2048;
|
||||
};
|
||||
names = {
|
||||
O = "Example Organization";
|
||||
C = "USA";
|
||||
};
|
||||
};
|
||||
};
|
||||
otherCert = "/var/certmgr/specs/other-cert.json";
|
||||
}
|
||||
'';
|
||||
type = with types; attrsOf (either path (submodule {
|
||||
options = {
|
||||
service = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = "The service on which to perform <action> after fetching.";
|
||||
};
|
||||
|
||||
action = mkOption {
|
||||
type = addCheck str (x: cfg.svcManager == "command" || elem x ["restart" "reload" "nop"]);
|
||||
default = "nop";
|
||||
description = "The action to take after fetching.";
|
||||
};
|
||||
|
||||
# These ought all to be specified according to certmgr spec def.
|
||||
authority = mkOption {
|
||||
type = attrs;
|
||||
description = "certmgr spec authority object.";
|
||||
};
|
||||
|
||||
certificate = mkOption {
|
||||
type = nullOr attrs;
|
||||
description = "certmgr spec certificate object.";
|
||||
};
|
||||
|
||||
private_key = mkOption {
|
||||
type = nullOr attrs;
|
||||
description = "certmgr spec private_key object.";
|
||||
};
|
||||
|
||||
request = mkOption {
|
||||
type = nullOr attrs;
|
||||
description = "certmgr spec request object.";
|
||||
};
|
||||
};
|
||||
}));
|
||||
description = ''
|
||||
Certificate specs as described by:
|
||||
<link xlink:href="https://github.com/cloudflare/certmgr#certificate-specs" />
|
||||
These will be added to the Nix store, so they will be world readable.
|
||||
'';
|
||||
};
|
||||
|
||||
svcManager = mkOption {
|
||||
default = "systemd";
|
||||
type = types.enum [ "circus" "command" "dummy" "openrc" "systemd" "sysv" ];
|
||||
description = ''
|
||||
This specifies the service manager to use for restarting or reloading services.
|
||||
See: <link xlink:href="https://github.com/cloudflare/certmgr#certmgryaml" />.
|
||||
For how to use the "command" service manager in particular,
|
||||
see: <link xlink:href="https://github.com/cloudflare/certmgr#command-svcmgr-and-how-to-use-it" />.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.specs != {};
|
||||
message = "Certmgr specs cannot be empty.";
|
||||
}
|
||||
{
|
||||
assertion = !any (hasAttrByPath [ "authority" "auth_key" ]) (attrValues cfg.specs);
|
||||
message = ''
|
||||
Inline services.certmgr.specs are added to the Nix store rendering them world readable.
|
||||
Specify paths as specs, if you want to use include auth_key - or use the auth_key_file option."
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
systemd.services.certmgr = {
|
||||
description = "certmgr";
|
||||
path = mkIf (cfg.svcManager == "command") [ pkgs.bash ];
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
inherit preStart;
|
||||
|
||||
serviceConfig = {
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
ExecStart = "${cfg.package}/bin/certmgr -f ${certmgrYaml}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
222
nixos/modules/services/security/cfssl.nix
Normal file
222
nixos/modules/services/security/cfssl.nix
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
{ config, options, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.cfssl;
|
||||
in {
|
||||
options.services.cfssl = {
|
||||
enable = mkEnableOption "the CFSSL CA api-server";
|
||||
|
||||
dataDir = mkOption {
|
||||
default = "/var/lib/cfssl";
|
||||
type = types.path;
|
||||
description = ''
|
||||
The work directory for CFSSL.
|
||||
|
||||
<note><para>
|
||||
If left as the default value this directory will automatically be
|
||||
created before the CFSSL server starts, otherwise you are
|
||||
responsible for ensuring the directory exists with appropriate
|
||||
ownership and permissions.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
default = "127.0.0.1";
|
||||
type = types.str;
|
||||
description = "Address to bind.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
default = 8888;
|
||||
type = types.port;
|
||||
description = "Port to bind.";
|
||||
};
|
||||
|
||||
ca = mkOption {
|
||||
defaultText = literalExpression ''"''${cfg.dataDir}/ca.pem"'';
|
||||
type = types.str;
|
||||
description = "CA used to sign the new certificate -- accepts '[file:]fname' or 'env:varname'.";
|
||||
};
|
||||
|
||||
caKey = mkOption {
|
||||
defaultText = literalExpression ''"file:''${cfg.dataDir}/ca-key.pem"'';
|
||||
type = types.str;
|
||||
description = "CA private key -- accepts '[file:]fname' or 'env:varname'.";
|
||||
};
|
||||
|
||||
caBundle = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Path to root certificate store.";
|
||||
};
|
||||
|
||||
intBundle = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Path to intermediate certificate store.";
|
||||
};
|
||||
|
||||
intDir = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Intermediates directory.";
|
||||
};
|
||||
|
||||
metadata = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = ''
|
||||
Metadata file for root certificate presence.
|
||||
The content of the file is a json dictionary (k,v): each key k is
|
||||
a SHA-1 digest of a root certificate while value v is a list of key
|
||||
store filenames.
|
||||
'';
|
||||
};
|
||||
|
||||
remote = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = "Remote CFSSL server.";
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = "Path to configuration file. Do not put this in nix-store as it might contain secrets.";
|
||||
};
|
||||
|
||||
responder = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Certificate for OCSP responder.";
|
||||
};
|
||||
|
||||
responderKey = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = "Private key for OCSP responder certificate. Do not put this in nix-store.";
|
||||
};
|
||||
|
||||
tlsKey = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = "Other endpoint's CA private key. Do not put this in nix-store.";
|
||||
};
|
||||
|
||||
tlsCert = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Other endpoint's CA to set up TLS protocol.";
|
||||
};
|
||||
|
||||
mutualTlsCa = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Mutual TLS - require clients be signed by this CA.";
|
||||
};
|
||||
|
||||
mutualTlsCn = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.str;
|
||||
description = "Mutual TLS - regex for whitelist of allowed client CNs.";
|
||||
};
|
||||
|
||||
tlsRemoteCa = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "CAs to trust for remote TLS requests.";
|
||||
};
|
||||
|
||||
mutualTlsClientCert = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Mutual TLS - client certificate to call remote instance requiring client certs.";
|
||||
};
|
||||
|
||||
mutualTlsClientKey = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Mutual TLS - client key to call remote instance requiring client certs. Do not put this in nix-store.";
|
||||
};
|
||||
|
||||
dbConfig = mkOption {
|
||||
default = null;
|
||||
type = types.nullOr types.path;
|
||||
description = "Certificate db configuration file. Path must be writeable.";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
default = 1;
|
||||
type = types.enum [ 0 1 2 3 4 5 ];
|
||||
description = "Log level (0 = DEBUG, 5 = FATAL).";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
users.groups.cfssl = {
|
||||
gid = config.ids.gids.cfssl;
|
||||
};
|
||||
|
||||
users.users.cfssl = {
|
||||
description = "cfssl user";
|
||||
home = cfg.dataDir;
|
||||
group = "cfssl";
|
||||
uid = config.ids.uids.cfssl;
|
||||
};
|
||||
|
||||
systemd.services.cfssl = {
|
||||
description = "CFSSL CA API server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = lib.mkMerge [
|
||||
{
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
Restart = "always";
|
||||
User = "cfssl";
|
||||
Group = "cfssl";
|
||||
|
||||
ExecStart = with cfg; let
|
||||
opt = n: v: optionalString (v != null) ''-${n}="${v}"'';
|
||||
in
|
||||
lib.concatStringsSep " \\\n" [
|
||||
"${pkgs.cfssl}/bin/cfssl serve"
|
||||
(opt "address" address)
|
||||
(opt "port" (toString port))
|
||||
(opt "ca" ca)
|
||||
(opt "ca-key" caKey)
|
||||
(opt "ca-bundle" caBundle)
|
||||
(opt "int-bundle" intBundle)
|
||||
(opt "int-dir" intDir)
|
||||
(opt "metadata" metadata)
|
||||
(opt "remote" remote)
|
||||
(opt "config" configFile)
|
||||
(opt "responder" responder)
|
||||
(opt "responder-key" responderKey)
|
||||
(opt "tls-key" tlsKey)
|
||||
(opt "tls-cert" tlsCert)
|
||||
(opt "mutual-tls-ca" mutualTlsCa)
|
||||
(opt "mutual-tls-cn" mutualTlsCn)
|
||||
(opt "mutual-tls-client-key" mutualTlsClientKey)
|
||||
(opt "mutual-tls-client-cert" mutualTlsClientCert)
|
||||
(opt "tls-remote-ca" tlsRemoteCa)
|
||||
(opt "db-config" dbConfig)
|
||||
(opt "loglevel" (toString logLevel))
|
||||
];
|
||||
}
|
||||
(mkIf (cfg.dataDir == options.services.cfssl.dataDir.default) {
|
||||
StateDirectory = baseNameOf cfg.dataDir;
|
||||
StateDirectoryMode = 700;
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
services.cfssl = {
|
||||
ca = mkDefault "${cfg.dataDir}/ca.pem";
|
||||
caKey = mkDefault "${cfg.dataDir}/ca-key.pem";
|
||||
};
|
||||
};
|
||||
}
|
||||
151
nixos/modules/services/security/clamav.nix
Normal file
151
nixos/modules/services/security/clamav.nix
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
clamavUser = "clamav";
|
||||
stateDir = "/var/lib/clamav";
|
||||
runDir = "/run/clamav";
|
||||
clamavGroup = clamavUser;
|
||||
cfg = config.services.clamav;
|
||||
pkg = pkgs.clamav;
|
||||
|
||||
toKeyValue = generators.toKeyValue {
|
||||
mkKeyValue = generators.mkKeyValueDefault { } " ";
|
||||
listsAsDuplicateKeys = true;
|
||||
};
|
||||
|
||||
clamdConfigFile = pkgs.writeText "clamd.conf" (toKeyValue cfg.daemon.settings);
|
||||
freshclamConfigFile = pkgs.writeText "freshclam.conf" (toKeyValue cfg.updater.settings);
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "clamav" "updater" "config" ] "Use services.clamav.updater.settings instead.")
|
||||
(mkRemovedOptionModule [ "services" "clamav" "updater" "extraConfig" ] "Use services.clamav.updater.settings instead.")
|
||||
(mkRemovedOptionModule [ "services" "clamav" "daemon" "extraConfig" ] "Use services.clamav.daemon.settings instead.")
|
||||
];
|
||||
|
||||
options = {
|
||||
services.clamav = {
|
||||
daemon = {
|
||||
enable = mkEnableOption "ClamAV clamd daemon";
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
|
||||
default = { };
|
||||
description = ''
|
||||
ClamAV configuration. Refer to <link xlink:href="https://linux.die.net/man/5/clamd.conf"/>,
|
||||
for details on supported values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
updater = {
|
||||
enable = mkEnableOption "ClamAV freshclam updater";
|
||||
|
||||
frequency = mkOption {
|
||||
type = types.int;
|
||||
default = 12;
|
||||
description = ''
|
||||
Number of database checks per day.
|
||||
'';
|
||||
};
|
||||
|
||||
interval = mkOption {
|
||||
type = types.str;
|
||||
default = "hourly";
|
||||
description = ''
|
||||
How often freshclam is invoked. See systemd.time(7) for more
|
||||
information about the format.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
type = with types; attrsOf (oneOf [ bool int str (listOf str) ]);
|
||||
default = { };
|
||||
description = ''
|
||||
freshclam configuration. Refer to <link xlink:href="https://linux.die.net/man/5/freshclam.conf"/>,
|
||||
for details on supported values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf (cfg.updater.enable || cfg.daemon.enable) {
|
||||
environment.systemPackages = [ pkg ];
|
||||
|
||||
users.users.${clamavUser} = {
|
||||
uid = config.ids.uids.clamav;
|
||||
group = clamavGroup;
|
||||
description = "ClamAV daemon user";
|
||||
home = stateDir;
|
||||
};
|
||||
|
||||
users.groups.${clamavGroup} =
|
||||
{ gid = config.ids.gids.clamav; };
|
||||
|
||||
services.clamav.daemon.settings = {
|
||||
DatabaseDirectory = stateDir;
|
||||
LocalSocket = "${runDir}/clamd.ctl";
|
||||
PidFile = "${runDir}/clamd.pid";
|
||||
TemporaryDirectory = "/tmp";
|
||||
User = "clamav";
|
||||
Foreground = true;
|
||||
};
|
||||
|
||||
services.clamav.updater.settings = {
|
||||
DatabaseDirectory = stateDir;
|
||||
Foreground = true;
|
||||
Checks = cfg.updater.frequency;
|
||||
DatabaseMirror = [ "database.clamav.net" ];
|
||||
};
|
||||
|
||||
environment.etc."clamav/freshclam.conf".source = freshclamConfigFile;
|
||||
environment.etc."clamav/clamd.conf".source = clamdConfigFile;
|
||||
|
||||
systemd.services.clamav-daemon = mkIf cfg.daemon.enable {
|
||||
description = "ClamAV daemon (clamd)";
|
||||
after = optional cfg.updater.enable "clamav-freshclam.service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ clamdConfigFile ];
|
||||
|
||||
preStart = ''
|
||||
mkdir -m 0755 -p ${runDir}
|
||||
chown ${clamavUser}:${clamavGroup} ${runDir}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkg}/bin/clamd";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -USR2 $MAINPID";
|
||||
PrivateTmp = "yes";
|
||||
PrivateDevices = "yes";
|
||||
PrivateNetwork = "yes";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.clamav-freshclam = mkIf cfg.updater.enable {
|
||||
description = "Timer for ClamAV virus database updater (freshclam)";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnCalendar = cfg.updater.interval;
|
||||
Unit = "clamav-freshclam.service";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.clamav-freshclam = mkIf cfg.updater.enable {
|
||||
description = "ClamAV virus database updater (freshclam)";
|
||||
restartTriggers = [ freshclamConfigFile ];
|
||||
after = [ "network-online.target" ];
|
||||
preStart = ''
|
||||
mkdir -m 0755 -p ${stateDir}
|
||||
chown ${clamavUser}:${clamavGroup} ${stateDir}
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkg}/bin/freshclam";
|
||||
SuccessExitStatus = "1"; # if databases are up to date
|
||||
PrivateTmp = "yes";
|
||||
PrivateDevices = "yes";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
340
nixos/modules/services/security/fail2ban.nix
Normal file
340
nixos/modules/services/security/fail2ban.nix
Normal file
|
|
@ -0,0 +1,340 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.fail2ban;
|
||||
|
||||
fail2banConf = pkgs.writeText "fail2ban.local" cfg.daemonConfig;
|
||||
|
||||
jailConf = pkgs.writeText "jail.local" ''
|
||||
[INCLUDES]
|
||||
|
||||
before = paths-nixos.conf
|
||||
|
||||
${concatStringsSep "\n" (attrValues (flip mapAttrs cfg.jails (name: def:
|
||||
optionalString (def != "")
|
||||
''
|
||||
[${name}]
|
||||
${def}
|
||||
'')))}
|
||||
'';
|
||||
|
||||
pathsConf = pkgs.writeText "paths-nixos.conf" ''
|
||||
# NixOS
|
||||
|
||||
[INCLUDES]
|
||||
|
||||
before = paths-common.conf
|
||||
|
||||
after = paths-overrides.local
|
||||
|
||||
[DEFAULT]
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.fail2ban = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to enable the fail2ban service.
|
||||
|
||||
See the documentation of <option>services.fail2ban.jails</option>
|
||||
for what jails are enabled by default.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.fail2ban;
|
||||
defaultText = literalExpression "pkgs.fail2ban";
|
||||
type = types.package;
|
||||
example = literalExpression "pkgs.fail2ban_0_11";
|
||||
description = "The fail2ban package to use for running the fail2ban service.";
|
||||
};
|
||||
|
||||
packageFirewall = mkOption {
|
||||
default = pkgs.iptables;
|
||||
defaultText = literalExpression "pkgs.iptables";
|
||||
type = types.package;
|
||||
example = literalExpression "pkgs.nftables";
|
||||
description = "The firewall package used by fail2ban service.";
|
||||
};
|
||||
|
||||
extraPackages = mkOption {
|
||||
default = [];
|
||||
type = types.listOf types.package;
|
||||
example = lib.literalExpression "[ pkgs.ipset ]";
|
||||
description = ''
|
||||
Extra packages to be made available to the fail2ban service. The example contains
|
||||
the packages needed by the `iptables-ipset-proto6` action.
|
||||
'';
|
||||
};
|
||||
|
||||
maxretry = mkOption {
|
||||
default = 3;
|
||||
type = types.ints.unsigned;
|
||||
description = "Number of failures before a host gets banned.";
|
||||
};
|
||||
|
||||
banaction = mkOption {
|
||||
default = "iptables-multiport";
|
||||
type = types.str;
|
||||
example = "nftables-multiport";
|
||||
description = ''
|
||||
Default banning action (e.g. iptables, iptables-new, iptables-multiport,
|
||||
shorewall, etc) It is used to define action_* variables. Can be overridden
|
||||
globally or per section within jail.local file
|
||||
'';
|
||||
};
|
||||
|
||||
banaction-allports = mkOption {
|
||||
default = "iptables-allport";
|
||||
type = types.str;
|
||||
example = "nftables-allport";
|
||||
description = ''
|
||||
Default banning action (e.g. iptables, iptables-new, iptables-multiport,
|
||||
shorewall, etc) It is used to define action_* variables. Can be overridden
|
||||
globally or per section within jail.local file
|
||||
'';
|
||||
};
|
||||
|
||||
bantime-increment.enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Allows to use database for searching of previously banned ip's to increase
|
||||
a default ban time using special formula, default it is banTime * 1, 2, 4, 8, 16, 32...
|
||||
'';
|
||||
};
|
||||
|
||||
bantime-increment.rndtime = mkOption {
|
||||
default = "4m";
|
||||
type = types.str;
|
||||
example = "8m";
|
||||
description = ''
|
||||
"bantime-increment.rndtime" is the max number of seconds using for mixing with random time
|
||||
to prevent "clever" botnets calculate exact time IP can be unbanned again
|
||||
'';
|
||||
};
|
||||
|
||||
bantime-increment.maxtime = mkOption {
|
||||
default = "10h";
|
||||
type = types.str;
|
||||
example = "48h";
|
||||
description = ''
|
||||
"bantime-increment.maxtime" is the max number of seconds using the ban time can reach (don't grows further)
|
||||
'';
|
||||
};
|
||||
|
||||
bantime-increment.factor = mkOption {
|
||||
default = "1";
|
||||
type = types.str;
|
||||
example = "4";
|
||||
description = ''
|
||||
"bantime-increment.factor" is a coefficient to calculate exponent growing of the formula or common multiplier,
|
||||
default value of factor is 1 and with default value of formula, the ban time grows by 1, 2, 4, 8, 16 ...
|
||||
'';
|
||||
};
|
||||
|
||||
bantime-increment.formula = mkOption {
|
||||
default = "ban.Time * (1<<(ban.Count if ban.Count<20 else 20)) * banFactor";
|
||||
type = types.str;
|
||||
example = "ban.Time * math.exp(float(ban.Count+1)*banFactor)/math.exp(1*banFactor)";
|
||||
description = ''
|
||||
"bantime-increment.formula" used by default to calculate next value of ban time, default value bellow,
|
||||
the same ban time growing will be reached by multipliers 1, 2, 4, 8, 16, 32...
|
||||
'';
|
||||
};
|
||||
|
||||
bantime-increment.multipliers = mkOption {
|
||||
default = "1 2 4 8 16 32 64";
|
||||
type = types.str;
|
||||
example = "2 4 16 128";
|
||||
description = ''
|
||||
"bantime-increment.multipliers" used to calculate next value of ban time instead of formula, coresponding
|
||||
previously ban count and given "bantime.factor" (for multipliers default is 1);
|
||||
following example grows ban time by 1, 2, 4, 8, 16 ... and if last ban count greater as multipliers count,
|
||||
always used last multiplier (64 in example), for factor '1' and original ban time 600 - 10.6 hours
|
||||
'';
|
||||
};
|
||||
|
||||
bantime-increment.overalljails = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
example = true;
|
||||
description = ''
|
||||
"bantime-increment.overalljails" (if true) specifies the search of IP in the database will be executed
|
||||
cross over all jails, if false (dafault), only current jail of the ban IP will be searched
|
||||
'';
|
||||
};
|
||||
|
||||
ignoreIP = mkOption {
|
||||
default = [ ];
|
||||
type = types.listOf types.str;
|
||||
example = [ "192.168.0.0/16" "2001:DB8::42" ];
|
||||
description = ''
|
||||
"ignoreIP" can be a list of IP addresses, CIDR masks or DNS hosts. Fail2ban will not ban a host which
|
||||
matches an address in this list. Several addresses can be defined using space (and/or comma) separator.
|
||||
'';
|
||||
};
|
||||
|
||||
daemonConfig = mkOption {
|
||||
default = ''
|
||||
[Definition]
|
||||
logtarget = SYSLOG
|
||||
socket = /run/fail2ban/fail2ban.sock
|
||||
pidfile = /run/fail2ban/fail2ban.pid
|
||||
dbfile = /var/lib/fail2ban/fail2ban.sqlite3
|
||||
'';
|
||||
type = types.lines;
|
||||
description = ''
|
||||
The contents of Fail2ban's main configuration file. It's
|
||||
generally not necessary to change it.
|
||||
'';
|
||||
};
|
||||
|
||||
jails = mkOption {
|
||||
default = { };
|
||||
example = literalExpression ''
|
||||
{ apache-nohome-iptables = '''
|
||||
# Block an IP address if it accesses a non-existent
|
||||
# home directory more than 5 times in 10 minutes,
|
||||
# since that indicates that it's scanning.
|
||||
filter = apache-nohome
|
||||
action = iptables-multiport[name=HTTP, port="http,https"]
|
||||
logpath = /var/log/httpd/error_log*
|
||||
findtime = 600
|
||||
bantime = 600
|
||||
maxretry = 5
|
||||
''';
|
||||
}
|
||||
'';
|
||||
type = types.attrsOf types.lines;
|
||||
description = ''
|
||||
The configuration of each Fail2ban “jail”. A jail
|
||||
consists of an action (such as blocking a port using
|
||||
<command>iptables</command>) that is triggered when a
|
||||
filter applied to a log file triggers more than a certain
|
||||
number of times in a certain time period. Actions are
|
||||
defined in <filename>/etc/fail2ban/action.d</filename>,
|
||||
while filters are defined in
|
||||
<filename>/etc/fail2ban/filter.d</filename>.
|
||||
|
||||
NixOS comes with a default <literal>sshd</literal> jail;
|
||||
for it to work well,
|
||||
<option>services.openssh.logLevel</option> should be set to
|
||||
<literal>"VERBOSE"</literal> or higher so that fail2ban
|
||||
can observe failed login attempts.
|
||||
This module sets it to <literal>"VERBOSE"</literal> if
|
||||
not set otherwise, so enabling fail2ban can make SSH logs
|
||||
more verbose.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
warnings = mkIf (config.networking.firewall.enable == false && config.networking.nftables.enable == false) [
|
||||
"fail2ban can not be used without a firewall"
|
||||
];
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
environment.etc = {
|
||||
"fail2ban/fail2ban.local".source = fail2banConf;
|
||||
"fail2ban/jail.local".source = jailConf;
|
||||
"fail2ban/fail2ban.conf".source = "${cfg.package}/etc/fail2ban/fail2ban.conf";
|
||||
"fail2ban/jail.conf".source = "${cfg.package}/etc/fail2ban/jail.conf";
|
||||
"fail2ban/paths-common.conf".source = "${cfg.package}/etc/fail2ban/paths-common.conf";
|
||||
"fail2ban/paths-nixos.conf".source = pathsConf;
|
||||
"fail2ban/action.d".source = "${cfg.package}/etc/fail2ban/action.d/*.conf";
|
||||
"fail2ban/filter.d".source = "${cfg.package}/etc/fail2ban/filter.d/*.conf";
|
||||
};
|
||||
|
||||
systemd.services.fail2ban = {
|
||||
description = "Fail2ban Intrusion Prevention System";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
partOf = optional config.networking.firewall.enable "firewall.service";
|
||||
|
||||
restartTriggers = [ fail2banConf jailConf pathsConf ];
|
||||
|
||||
path = [ cfg.package cfg.packageFirewall pkgs.iproute2 ] ++ cfg.extraPackages;
|
||||
|
||||
unitConfig.Documentation = "man:fail2ban(1)";
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/fail2ban-server -xf start";
|
||||
ExecStop = "${cfg.package}/bin/fail2ban-server stop";
|
||||
ExecReload = "${cfg.package}/bin/fail2ban-server reload";
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
PIDFile = "/run/fail2ban/fail2ban.pid";
|
||||
# Capabilities
|
||||
CapabilityBoundingSet = [ "CAP_AUDIT_READ" "CAP_DAC_READ_SEARCH" "CAP_NET_ADMIN" "CAP_NET_RAW" ];
|
||||
# Security
|
||||
NoNewPrivileges = true;
|
||||
# Directory
|
||||
RuntimeDirectory = "fail2ban";
|
||||
RuntimeDirectoryMode = "0750";
|
||||
StateDirectory = "fail2ban";
|
||||
StateDirectoryMode = "0750";
|
||||
LogsDirectory = "fail2ban";
|
||||
LogsDirectoryMode = "0750";
|
||||
# Sandboxing
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
PrivateDevices = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectControlGroups = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Add some reasonable default jails. The special "DEFAULT" jail
|
||||
# sets default values for all other jails.
|
||||
services.fail2ban.jails.DEFAULT = ''
|
||||
${optionalString cfg.bantime-increment.enable ''
|
||||
# Bantime incremental
|
||||
bantime.increment = ${boolToString cfg.bantime-increment.enable}
|
||||
bantime.maxtime = ${cfg.bantime-increment.maxtime}
|
||||
bantime.factor = ${cfg.bantime-increment.factor}
|
||||
bantime.formula = ${cfg.bantime-increment.formula}
|
||||
bantime.multipliers = ${cfg.bantime-increment.multipliers}
|
||||
bantime.overalljails = ${boolToString cfg.bantime-increment.overalljails}
|
||||
''}
|
||||
# Miscellaneous options
|
||||
ignoreip = 127.0.0.1/8 ${optionalString config.networking.enableIPv6 "::1"} ${concatStringsSep " " cfg.ignoreIP}
|
||||
maxretry = ${toString cfg.maxretry}
|
||||
backend = systemd
|
||||
# Actions
|
||||
banaction = ${cfg.banaction}
|
||||
banaction_allports = ${cfg.banaction-allports}
|
||||
'';
|
||||
# Block SSH if there are too many failing connection attempts.
|
||||
# Benefits from verbose sshd logging to observe failed login attempts,
|
||||
# so we set that here unless the user overrode it.
|
||||
services.openssh.logLevel = lib.mkDefault "VERBOSE";
|
||||
services.fail2ban.jails.sshd = mkDefault ''
|
||||
enabled = true
|
||||
port = ${concatMapStringsSep "," (p: toString p) config.services.openssh.ports}
|
||||
'';
|
||||
};
|
||||
}
|
||||
64
nixos/modules/services/security/fprintd.nix
Normal file
64
nixos/modules/services/security/fprintd.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.fprintd;
|
||||
fprintdPkg = if cfg.tod.enable then pkgs.fprintd-tod else pkgs.fprintd;
|
||||
|
||||
in
|
||||
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.fprintd = {
|
||||
|
||||
enable = mkEnableOption "fprintd daemon and PAM module for fingerprint readers handling";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = fprintdPkg;
|
||||
defaultText = literalExpression "if config.services.fprintd.tod.enable then pkgs.fprintd-tod else pkgs.fprintd";
|
||||
description = ''
|
||||
fprintd package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
tod = {
|
||||
|
||||
enable = mkEnableOption "Touch OEM Drivers library support";
|
||||
|
||||
driver = mkOption {
|
||||
type = types.package;
|
||||
example = literalExpression "pkgs.libfprint-2-tod1-goodix";
|
||||
description = ''
|
||||
Touch OEM Drivers (TOD) package to use.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.dbus.packages = [ cfg.package ];
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
systemd.packages = [ cfg.package ];
|
||||
|
||||
systemd.services.fprintd.environment = mkIf cfg.tod.enable {
|
||||
FP_TOD_DRIVERS_DIR = "${cfg.tod.driver}${cfg.tod.driver.driverPath}";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
156
nixos/modules/services/security/haka.nix
Normal file
156
nixos/modules/services/security/haka.nix
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# This module defines global configuration for Haka.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.haka;
|
||||
|
||||
haka = cfg.package;
|
||||
|
||||
hakaConf = pkgs.writeText "haka.conf"
|
||||
''
|
||||
[general]
|
||||
configuration = ${if lib.strings.hasPrefix "/" cfg.configFile
|
||||
then "${cfg.configFile}"
|
||||
else "${haka}/share/haka/sample/${cfg.configFile}"}
|
||||
${optionalString (builtins.lessThan 0 cfg.threads) "thread = ${cfg.threads}"}
|
||||
|
||||
[packet]
|
||||
${optionalString cfg.pcap ''module = "packet/pcap"''}
|
||||
${optionalString cfg.nfqueue ''module = "packet/nqueue"''}
|
||||
${optionalString cfg.dump.enable ''dump = "yes"''}
|
||||
${optionalString cfg.dump.enable ''dump_input = "${cfg.dump.input}"''}
|
||||
${optionalString cfg.dump.enable ''dump_output = "${cfg.dump.output}"''}
|
||||
|
||||
interfaces = "${lib.strings.concatStringsSep "," cfg.interfaces}"
|
||||
|
||||
[log]
|
||||
# Select the log module
|
||||
module = "log/syslog"
|
||||
|
||||
# Set the default logging level
|
||||
#level = "info,packet=debug"
|
||||
|
||||
[alert]
|
||||
# Select the alert module
|
||||
module = "alert/syslog"
|
||||
|
||||
# Disable alert on standard output
|
||||
#alert_on_stdout = no
|
||||
|
||||
# alert/file module option
|
||||
#file = "/dev/null"
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.haka = {
|
||||
|
||||
enable = mkEnableOption "Haka";
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.haka;
|
||||
defaultText = literalExpression "pkgs.haka";
|
||||
type = types.package;
|
||||
description = "
|
||||
Which Haka derivation to use.
|
||||
";
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
default = "empty.lua";
|
||||
example = "/srv/haka/myfilter.lua";
|
||||
type = types.str;
|
||||
description = ''
|
||||
Specify which configuration file Haka uses.
|
||||
It can be absolute path or a path relative to the sample directory of
|
||||
the haka git repo.
|
||||
'';
|
||||
};
|
||||
|
||||
interfaces = mkOption {
|
||||
default = [ "eth0" ];
|
||||
example = [ "any" ];
|
||||
type = with types; listOf str;
|
||||
description = ''
|
||||
Specify which interface(s) Haka listens to.
|
||||
Use 'any' to listen to all interfaces.
|
||||
'';
|
||||
};
|
||||
|
||||
threads = mkOption {
|
||||
default = 0;
|
||||
example = 4;
|
||||
type = types.int;
|
||||
description = ''
|
||||
The number of threads that will be used.
|
||||
All system threads are used by default.
|
||||
'';
|
||||
};
|
||||
|
||||
pcap = mkOption {
|
||||
default = true;
|
||||
type = types.bool;
|
||||
description = "Whether to enable pcap";
|
||||
};
|
||||
|
||||
nfqueue = mkEnableOption "nfqueue";
|
||||
|
||||
dump.enable = mkEnableOption "dump";
|
||||
dump.input = mkOption {
|
||||
default = "/tmp/input.pcap";
|
||||
example = "/path/to/file.pcap";
|
||||
type = types.path;
|
||||
description = "Path to file where incoming packets are dumped";
|
||||
};
|
||||
|
||||
dump.output = mkOption {
|
||||
default = "/tmp/output.pcap";
|
||||
example = "/path/to/file.pcap";
|
||||
type = types.path;
|
||||
description = "Path to file where outgoing packets are dumped";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
assertions = [
|
||||
{ assertion = cfg.pcap != cfg.nfqueue;
|
||||
message = "either pcap or nfqueue can be enabled, not both.";
|
||||
}
|
||||
{ assertion = cfg.nfqueue -> !dump.enable;
|
||||
message = "dump can only be used with nfqueue.";
|
||||
}
|
||||
{ assertion = cfg.interfaces != [];
|
||||
message = "at least one interface must be specified.";
|
||||
}];
|
||||
|
||||
|
||||
environment.systemPackages = [ haka ];
|
||||
|
||||
systemd.services.haka = {
|
||||
description = "Haka";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${haka}/bin/haka -c ${hakaConf}";
|
||||
ExecStop = "${haka}/bin/hakactl stop";
|
||||
User = "root";
|
||||
Type = "forking";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
77
nixos/modules/services/security/haveged.nix
Normal file
77
nixos/modules/services/security/haveged.nix
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.haveged;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.haveged = {
|
||||
|
||||
enable = mkEnableOption ''
|
||||
haveged entropy daemon, which refills /dev/random when low.
|
||||
NOTE: does nothing on kernels newer than 5.6.
|
||||
'';
|
||||
# source for the note https://github.com/jirka-h/haveged/issues/57
|
||||
|
||||
refill_threshold = mkOption {
|
||||
type = types.int;
|
||||
default = 1024;
|
||||
description = ''
|
||||
The number of bits of available entropy beneath which
|
||||
haveged should refill the entropy pool.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
# https://github.com/jirka-h/haveged/blob/a4b69d65a8dfc5a9f52ff8505c7f58dcf8b9234f/contrib/Fedora/haveged.service
|
||||
systemd.services.haveged = {
|
||||
description = "Entropy Daemon based on the HAVEGE algorithm";
|
||||
unitConfig = {
|
||||
Documentation = "man:haveged(8)";
|
||||
DefaultDependencies = false;
|
||||
ConditionKernelVersion = "<5.6";
|
||||
};
|
||||
wantedBy = [ "sysinit.target" ];
|
||||
after = [ "systemd-tmpfiles-setup-dev.service" ];
|
||||
before = [ "sysinit.target" "shutdown.target" "systemd-journald.service" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.haveged}/bin/haveged -w ${toString cfg.refill_threshold} --Foreground -v 1";
|
||||
Restart = "always";
|
||||
SuccessExitStatus = "137 143";
|
||||
SecureBits = "noroot-locked";
|
||||
CapabilityBoundingSet = [ "CAP_SYS_ADMIN" "CAP_SYS_CHROOT" ];
|
||||
# We can *not* set PrivateTmp=true as it can cause an ordering cycle.
|
||||
PrivateTmp = false;
|
||||
PrivateDevices = true;
|
||||
ProtectSystem = "full";
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "newuname" "~@mount" ];
|
||||
SystemCallErrorNumber = "EPERM";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
106
nixos/modules/services/security/hockeypuck.nix
Normal file
106
nixos/modules/services/security/hockeypuck.nix
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.hockeypuck;
|
||||
settingsFormat = pkgs.formats.toml { };
|
||||
in {
|
||||
meta.maintainers = with lib.maintainers; [ etu ];
|
||||
|
||||
options.services.hockeypuck = {
|
||||
enable = lib.mkEnableOption "Hockeypuck OpenPGP Key Server";
|
||||
|
||||
port = lib.mkOption {
|
||||
default = 11371;
|
||||
type = lib.types.port;
|
||||
description = "HKP port to listen on.";
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = settingsFormat.type;
|
||||
default = { };
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
hockeypuck = {
|
||||
loglevel = "INFO";
|
||||
logfile = "/var/log/hockeypuck/hockeypuck.log";
|
||||
indexTemplate = "''${pkgs.hockeypuck-web}/share/templates/index.html.tmpl";
|
||||
vindexTemplate = "''${pkgs.hockeypuck-web}/share/templates/index.html.tmpl";
|
||||
statsTemplate = "''${pkgs.hockeypuck-web}/share/templates/stats.html.tmpl";
|
||||
webroot = "''${pkgs.hockeypuck-web}/share/webroot";
|
||||
|
||||
hkp.bind = ":''${toString cfg.port}";
|
||||
|
||||
openpgp.db = {
|
||||
driver = "postgres-jsonb";
|
||||
dsn = "database=hockeypuck host=/var/run/postgresql sslmode=disable";
|
||||
};
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Configuration file for hockeypuck, here you can override
|
||||
certain settings (<literal>loglevel</literal> and
|
||||
<literal>openpgp.db.dsn</literal>) by just setting those values.
|
||||
|
||||
For other settings you need to use lib.mkForce to override them.
|
||||
|
||||
This service doesn't provision or enable postgres on your
|
||||
system, it rather assumes that you enable postgres and create
|
||||
the database yourself.
|
||||
|
||||
Example:
|
||||
<literal>
|
||||
services.postgresql = {
|
||||
enable = true;
|
||||
ensureDatabases = [ "hockeypuck" ];
|
||||
ensureUsers = [{
|
||||
name = "hockeypuck";
|
||||
ensurePermissions."DATABASE hockeypuck" = "ALL PRIVILEGES";
|
||||
}];
|
||||
};
|
||||
</literal>
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.hockeypuck.settings.hockeypuck = {
|
||||
loglevel = lib.mkDefault "INFO";
|
||||
logfile = "/var/log/hockeypuck/hockeypuck.log";
|
||||
indexTemplate = "${pkgs.hockeypuck-web}/share/templates/index.html.tmpl";
|
||||
vindexTemplate = "${pkgs.hockeypuck-web}/share/templates/index.html.tmpl";
|
||||
statsTemplate = "${pkgs.hockeypuck-web}/share/templates/stats.html.tmpl";
|
||||
webroot = "${pkgs.hockeypuck-web}/share/webroot";
|
||||
|
||||
hkp.bind = ":${toString cfg.port}";
|
||||
|
||||
openpgp.db = {
|
||||
driver = "postgres-jsonb";
|
||||
dsn = lib.mkDefault "database=hockeypuck host=/var/run/postgresql sslmode=disable";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.hockeypuck = {
|
||||
isSystemUser = true;
|
||||
group = "hockeypuck";
|
||||
description = "Hockeypuck user";
|
||||
};
|
||||
users.groups.hockeypuck = {};
|
||||
|
||||
systemd.services.hockeypuck = {
|
||||
description = "Hockeypuck OpenPGP Key Server";
|
||||
after = [ "network.target" "postgresql.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
WorkingDirectory = "/var/lib/hockeypuck";
|
||||
User = "hockeypuck";
|
||||
ExecStart = "${pkgs.hockeypuck}/bin/hockeypuck -config ${settingsFormat.generate "config.toml" cfg.settings}";
|
||||
Restart = "always";
|
||||
RestartSec = "5s";
|
||||
LogsDirectory = "hockeypuck";
|
||||
LogsDirectoryMode = "0755";
|
||||
StateDirectory = "hockeypuck";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
58
nixos/modules/services/security/hologram-agent.nix
Normal file
58
nixos/modules/services/security/hologram-agent.nix
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{pkgs, config, lib, ...}:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.hologram-agent;
|
||||
|
||||
cfgFile = pkgs.writeText "hologram-agent.json" (builtins.toJSON {
|
||||
host = cfg.dialAddress;
|
||||
});
|
||||
in {
|
||||
options = {
|
||||
services.hologram-agent = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the Hologram agent for AWS instance credentials";
|
||||
};
|
||||
|
||||
dialAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost:3100";
|
||||
description = "Hologram server and port.";
|
||||
};
|
||||
|
||||
httpPort = mkOption {
|
||||
type = types.str;
|
||||
default = "80";
|
||||
description = "Port for metadata service to listen on.";
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
boot.kernelModules = [ "dummy" ];
|
||||
|
||||
networking.interfaces.dummy0.ipv4.addresses = [
|
||||
{ address = "169.254.169.254"; prefixLength = 32; }
|
||||
];
|
||||
|
||||
systemd.services.hologram-agent = {
|
||||
description = "Provide EC2 instance credentials to machines outside of EC2";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requires = [ "network-link-dummy0.service" "network-addresses-dummy0.service" ];
|
||||
preStart = ''
|
||||
/run/current-system/sw/bin/rm -fv /run/hologram.sock
|
||||
'';
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.hologram}/bin/hologram-agent -debug -conf ${cfgFile} -port ${cfg.httpPort}";
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ ];
|
||||
}
|
||||
130
nixos/modules/services/security/hologram-server.nix
Normal file
130
nixos/modules/services/security/hologram-server.nix
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
{pkgs, config, lib, ...}:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.hologram-server;
|
||||
|
||||
cfgFile = pkgs.writeText "hologram-server.json" (builtins.toJSON {
|
||||
ldap = {
|
||||
host = cfg.ldapHost;
|
||||
bind = {
|
||||
dn = cfg.ldapBindDN;
|
||||
password = cfg.ldapBindPassword;
|
||||
};
|
||||
insecureldap = cfg.ldapInsecure;
|
||||
userattr = cfg.ldapUserAttr;
|
||||
baseDN = cfg.ldapBaseDN;
|
||||
enableldapRoles = cfg.enableLdapRoles;
|
||||
roleAttr = cfg.roleAttr;
|
||||
groupClassAttr = cfg.groupClassAttr;
|
||||
};
|
||||
aws = {
|
||||
account = cfg.awsAccount;
|
||||
defaultrole = cfg.awsDefaultRole;
|
||||
};
|
||||
stats = cfg.statsAddress;
|
||||
listen = cfg.listenAddress;
|
||||
cachetimeout = cfg.cacheTimeoutSeconds;
|
||||
});
|
||||
in {
|
||||
options = {
|
||||
services.hologram-server = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the Hologram server for AWS instance credentials";
|
||||
};
|
||||
|
||||
listenAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0:3100";
|
||||
description = "Address and port to listen on";
|
||||
};
|
||||
|
||||
ldapHost = mkOption {
|
||||
type = types.str;
|
||||
description = "Address of the LDAP server to use";
|
||||
};
|
||||
|
||||
ldapInsecure = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to connect to LDAP over SSL or not";
|
||||
};
|
||||
|
||||
ldapUserAttr = mkOption {
|
||||
type = types.str;
|
||||
default = "cn";
|
||||
description = "The LDAP attribute for usernames";
|
||||
};
|
||||
|
||||
ldapBaseDN = mkOption {
|
||||
type = types.str;
|
||||
description = "The base DN for your Hologram users";
|
||||
};
|
||||
|
||||
ldapBindDN = mkOption {
|
||||
type = types.str;
|
||||
description = "DN of account to use to query the LDAP server";
|
||||
};
|
||||
|
||||
ldapBindPassword = mkOption {
|
||||
type = types.str;
|
||||
description = "Password of account to use to query the LDAP server";
|
||||
};
|
||||
|
||||
enableLdapRoles = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to assign user roles based on the user's LDAP group memberships";
|
||||
};
|
||||
|
||||
groupClassAttr = mkOption {
|
||||
type = types.str;
|
||||
default = "groupOfNames";
|
||||
description = "The objectclass attribute to search for groups when enableLdapRoles is true";
|
||||
};
|
||||
|
||||
roleAttr = mkOption {
|
||||
type = types.str;
|
||||
default = "businessCategory";
|
||||
description = "Which LDAP group attribute to search for authorized role ARNs";
|
||||
};
|
||||
|
||||
awsAccount = mkOption {
|
||||
type = types.str;
|
||||
description = "AWS account number";
|
||||
};
|
||||
|
||||
awsDefaultRole = mkOption {
|
||||
type = types.str;
|
||||
description = "AWS default role";
|
||||
};
|
||||
|
||||
statsAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = "Address of statsd server";
|
||||
};
|
||||
|
||||
cacheTimeoutSeconds = mkOption {
|
||||
type = types.int;
|
||||
default = 3600;
|
||||
description = "How often (in seconds) to refresh the LDAP cache";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.hologram-server = {
|
||||
description = "Provide EC2 instance credentials to machines outside of EC2";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.hologram}/bin/hologram-server --debug --conf ${cfgFile}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
60
nixos/modules/services/security/infnoise.nix
Normal file
60
nixos/modules/services/security/infnoise.nix
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.infnoise;
|
||||
in {
|
||||
options = {
|
||||
services.infnoise = {
|
||||
enable = mkEnableOption "the Infinite Noise TRNG driver";
|
||||
|
||||
fillDevRandom = mkOption {
|
||||
description = ''
|
||||
Whether to run the infnoise driver as a daemon to refill /dev/random.
|
||||
|
||||
If disabled, you can use the `infnoise` command-line tool to
|
||||
manually obtain randomness.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.infnoise ];
|
||||
|
||||
services.udev.extraRules = ''
|
||||
SUBSYSTEM=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", SYMLINK+="infnoise", TAG+="systemd", GROUP="dialout", MODE="0664", ENV{SYSTEMD_WANTS}="infnoise.service"
|
||||
'';
|
||||
|
||||
systemd.services.infnoise = mkIf cfg.fillDevRandom {
|
||||
description = "Infinite Noise TRNG driver";
|
||||
|
||||
bindsTo = [ "dev-infnoise.device" ];
|
||||
after = [ "dev-infnoise.device" ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.infnoise}/bin/infnoise --dev-random --debug";
|
||||
Restart = "always";
|
||||
User = "infnoise";
|
||||
DynamicUser = true;
|
||||
SupplementaryGroups = [ "dialout" ];
|
||||
DeviceAllow = [ "/dev/infnoise" ];
|
||||
DevicePolicy = "closed";
|
||||
PrivateNetwork = true;
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true; # only reads entropy pool size and watermark
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
345
nixos/modules/services/security/kanidm.nix
Normal file
345
nixos/modules/services/security/kanidm.nix
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
let
|
||||
cfg = config.services.kanidm;
|
||||
settingsFormat = pkgs.formats.toml { };
|
||||
# Remove null values, so we can document optional values that don't end up in the generated TOML file.
|
||||
filterConfig = lib.converge (lib.filterAttrsRecursive (_: v: v != null));
|
||||
serverConfigFile = settingsFormat.generate "server.toml" (filterConfig cfg.serverSettings);
|
||||
clientConfigFile = settingsFormat.generate "kanidm-config.toml" (filterConfig cfg.clientSettings);
|
||||
unixConfigFile = settingsFormat.generate "kanidm-unixd.toml" (filterConfig cfg.unixSettings);
|
||||
|
||||
defaultServiceConfig = {
|
||||
BindReadOnlyPaths = [
|
||||
"/nix/store"
|
||||
"-/etc/resolv.conf"
|
||||
"-/etc/nsswitch.conf"
|
||||
"-/etc/hosts"
|
||||
"-/etc/localtime"
|
||||
];
|
||||
CapabilityBoundingSet = "";
|
||||
# ProtectClock= adds DeviceAllow=char-rtc r
|
||||
DeviceAllow = "";
|
||||
# Implies ProtectSystem=strict, which re-mounts all paths
|
||||
# DynamicUser = true;
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateNetwork = true;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = true;
|
||||
ProcSubset = "pid";
|
||||
ProtectClock = true;
|
||||
ProtectHome = true;
|
||||
ProtectHostname = true;
|
||||
# Would re-mount paths ignored by temporary root
|
||||
#ProtectSystem = "strict";
|
||||
ProtectControlGroups = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectProc = "invisible";
|
||||
RestrictAddressFamilies = [ ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = [ "@system-service" "~@privileged @resources @setuid @keyring" ];
|
||||
# Does not work well with the temporary root
|
||||
#UMask = "0066";
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
options.services.kanidm = {
|
||||
enableClient = lib.mkEnableOption "the Kanidm client";
|
||||
enableServer = lib.mkEnableOption "the Kanidm server";
|
||||
enablePam = lib.mkEnableOption "the Kanidm PAM and NSS integration.";
|
||||
|
||||
serverSettings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options = {
|
||||
bindaddress = lib.mkOption {
|
||||
description = "Address/port combination the webserver binds to.";
|
||||
example = "[::1]:8443";
|
||||
type = lib.types.str;
|
||||
};
|
||||
# Should be optional but toml does not accept null
|
||||
ldapbindaddress = lib.mkOption {
|
||||
description = ''
|
||||
Address and port the LDAP server is bound to. Setting this to <literal>null</literal> disables the LDAP interface.
|
||||
'';
|
||||
example = "[::1]:636";
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
};
|
||||
origin = lib.mkOption {
|
||||
description = "The origin of your Kanidm instance. Must have https as protocol.";
|
||||
example = "https://idm.example.org";
|
||||
type = lib.types.strMatching "^https://.*";
|
||||
};
|
||||
domain = lib.mkOption {
|
||||
description = ''
|
||||
The <literal>domain</literal> that Kanidm manages. Must be below or equal to the domain
|
||||
specified in <literal>serverSettings.origin</literal>.
|
||||
This can be left at <literal>null</literal>, only if your instance has the role <literal>ReadOnlyReplica</literal>.
|
||||
While it is possible to change the domain later on, it requires extra steps!
|
||||
Please consider the warnings and execute the steps described
|
||||
<link xlink:href="https://kanidm.github.io/kanidm/stable/administrivia.html#rename-the-domain">in the documentation</link>.
|
||||
'';
|
||||
example = "example.org";
|
||||
default = null;
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
};
|
||||
db_path = lib.mkOption {
|
||||
description = "Path to Kanidm database.";
|
||||
default = "/var/lib/kanidm/kanidm.db";
|
||||
readOnly = true;
|
||||
type = lib.types.path;
|
||||
};
|
||||
log_level = lib.mkOption {
|
||||
description = "Log level of the server.";
|
||||
default = "default";
|
||||
type = lib.types.enum [ "default" "verbose" "perfbasic" "perffull" ];
|
||||
};
|
||||
role = lib.mkOption {
|
||||
description = "The role of this server. This affects the replication relationship and thereby available features.";
|
||||
default = "WriteReplica";
|
||||
type = lib.types.enum [ "WriteReplica" "WriteReplicaNoUI" "ReadOnlyReplica" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
description = ''
|
||||
Settings for Kanidm, see
|
||||
<link xlink:href="https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/server_configuration.md">the documentation</link>
|
||||
and <link xlink:href="https://github.com/kanidm/kanidm/blob/master/examples/server.toml">example configuration</link>
|
||||
for possible values.
|
||||
'';
|
||||
};
|
||||
|
||||
clientSettings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options.uri = lib.mkOption {
|
||||
description = "Address of the Kanidm server.";
|
||||
example = "http://127.0.0.1:8080";
|
||||
type = lib.types.str;
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Configure Kanidm clients, needed for the PAM daemon. See
|
||||
<link xlink:href="https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/client_tools.md#kanidm-configuration">the documentation</link>
|
||||
and <link xlink:href="https://github.com/kanidm/kanidm/blob/master/examples/config">example configuration</link>
|
||||
for possible values.
|
||||
'';
|
||||
};
|
||||
|
||||
unixSettings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options.pam_allowed_login_groups = lib.mkOption {
|
||||
description = "Kanidm groups that are allowed to login using PAM.";
|
||||
example = "my_pam_group";
|
||||
type = lib.types.listOf lib.types.str;
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
Configure Kanidm unix daemon.
|
||||
See <link xlink:href="https://github.com/kanidm/kanidm/blob/master/kanidm_book/src/pam_and_nsswitch.md#the-unix-daemon">the documentation</link>
|
||||
and <link xlink:href="https://github.com/kanidm/kanidm/blob/master/examples/unixd">example configuration</link>
|
||||
for possible values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.enableClient || cfg.enableServer || cfg.enablePam) {
|
||||
assertions =
|
||||
[
|
||||
{
|
||||
assertion = !cfg.enableServer || ((cfg.serverSettings.tls_chain or null) == null) || (!lib.isStorePath cfg.serverSettings.tls_chain);
|
||||
message = ''
|
||||
<option>services.kanidm.serverSettings.tls_chain</option> points to
|
||||
a file in the Nix store. You should use a quoted absolute path to
|
||||
prevent this.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !cfg.enableServer || ((cfg.serverSettings.tls_key or null) == null) || (!lib.isStorePath cfg.serverSettings.tls_key);
|
||||
message = ''
|
||||
<option>services.kanidm.serverSettings.tls_key</option> points to
|
||||
a file in the Nix store. You should use a quoted absolute path to
|
||||
prevent this.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !cfg.enableClient || options.services.kanidm.clientSettings.isDefined;
|
||||
message = ''
|
||||
<option>services.kanidm.clientSettings</option> needs to be configured
|
||||
if the client is enabled.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !cfg.enablePam || options.services.kanidm.clientSettings.isDefined;
|
||||
message = ''
|
||||
<option>services.kanidm.clientSettings</option> needs to be configured
|
||||
for the PAM daemon to connect to the Kanidm server.
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = !cfg.enableServer || (cfg.serverSettings.domain == null
|
||||
-> cfg.serverSettings.role == "WriteReplica" || cfg.serverSettings.role == "WriteReplicaNoUI");
|
||||
message = ''
|
||||
<option>services.kanidm.serverSettings.domain</option> can only be set if this instance
|
||||
is not a ReadOnlyReplica. Otherwise the db would inherit it from
|
||||
the instance it follows.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = lib.mkIf cfg.enableClient [ pkgs.kanidm ];
|
||||
|
||||
systemd.services.kanidm = lib.mkIf cfg.enableServer {
|
||||
description = "kanidm identity management daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
serviceConfig = defaultServiceConfig // {
|
||||
StateDirectory = "kanidm";
|
||||
StateDirectoryMode = "0700";
|
||||
ExecStart = "${pkgs.kanidm}/bin/kanidmd server -c ${serverConfigFile}";
|
||||
User = "kanidm";
|
||||
Group = "kanidm";
|
||||
|
||||
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
|
||||
# This would otherwise override the CAP_NET_BIND_SERVICE capability.
|
||||
PrivateUsers = false;
|
||||
# Port needs to be exposed to the host network
|
||||
PrivateNetwork = false;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
|
||||
TemporaryFileSystem = "/:ro";
|
||||
};
|
||||
environment.RUST_LOG = "info";
|
||||
};
|
||||
|
||||
systemd.services.kanidm-unixd = lib.mkIf cfg.enablePam {
|
||||
description = "Kanidm PAM daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
restartTriggers = [ unixConfigFile clientConfigFile ];
|
||||
serviceConfig = defaultServiceConfig // {
|
||||
CacheDirectory = "kanidm-unixd";
|
||||
CacheDirectoryMode = "0700";
|
||||
RuntimeDirectory = "kanidm-unixd";
|
||||
ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd";
|
||||
User = "kanidm-unixd";
|
||||
Group = "kanidm-unixd";
|
||||
|
||||
BindReadOnlyPaths = [
|
||||
"/nix/store"
|
||||
"-/etc/resolv.conf"
|
||||
"-/etc/nsswitch.conf"
|
||||
"-/etc/hosts"
|
||||
"-/etc/localtime"
|
||||
"-/etc/kanidm"
|
||||
"-/etc/static/kanidm"
|
||||
];
|
||||
BindPaths = [
|
||||
# To create the socket
|
||||
"/run/kanidm-unixd:/var/run/kanidm-unixd"
|
||||
];
|
||||
# Needs to connect to kanidmd
|
||||
PrivateNetwork = false;
|
||||
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
|
||||
TemporaryFileSystem = "/:ro";
|
||||
};
|
||||
environment.RUST_LOG = "info";
|
||||
};
|
||||
|
||||
systemd.services.kanidm-unixd-tasks = lib.mkIf cfg.enablePam {
|
||||
description = "Kanidm PAM home management daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "kanidm-unixd.service" ];
|
||||
partOf = [ "kanidm-unixd.service" ];
|
||||
restartTriggers = [ unixConfigFile clientConfigFile ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.kanidm}/bin/kanidm_unixd_tasks";
|
||||
|
||||
BindReadOnlyPaths = [
|
||||
"/nix/store"
|
||||
"-/etc/resolv.conf"
|
||||
"-/etc/nsswitch.conf"
|
||||
"-/etc/hosts"
|
||||
"-/etc/localtime"
|
||||
"-/etc/kanidm"
|
||||
"-/etc/static/kanidm"
|
||||
];
|
||||
BindPaths = [
|
||||
# To manage home directories
|
||||
"/home"
|
||||
# To connect to kanidm-unixd
|
||||
"/run/kanidm-unixd:/var/run/kanidm-unixd"
|
||||
];
|
||||
# CAP_DAC_OVERRIDE is needed to ignore ownership of unixd socket
|
||||
CapabilityBoundingSet = [ "CAP_CHOWN" "CAP_FOWNER" "CAP_DAC_OVERRIDE" "CAP_DAC_READ_SEARCH" ];
|
||||
IPAddressDeny = "any";
|
||||
# Need access to users
|
||||
PrivateUsers = false;
|
||||
# Need access to home directories
|
||||
ProtectHome = false;
|
||||
RestrictAddressFamilies = [ "AF_UNIX" ];
|
||||
TemporaryFileSystem = "/:ro";
|
||||
};
|
||||
environment.RUST_LOG = "info";
|
||||
};
|
||||
|
||||
# These paths are hardcoded
|
||||
environment.etc = lib.mkMerge [
|
||||
(lib.mkIf options.services.kanidm.clientSettings.isDefined {
|
||||
"kanidm/config".source = clientConfigFile;
|
||||
})
|
||||
(lib.mkIf cfg.enablePam {
|
||||
"kanidm/unixd".source = unixConfigFile;
|
||||
})
|
||||
];
|
||||
|
||||
system.nssModules = lib.mkIf cfg.enablePam [ pkgs.kanidm ];
|
||||
|
||||
system.nssDatabases.group = lib.optional cfg.enablePam "kanidm";
|
||||
system.nssDatabases.passwd = lib.optional cfg.enablePam "kanidm";
|
||||
|
||||
users.groups = lib.mkMerge [
|
||||
(lib.mkIf cfg.enableServer {
|
||||
kanidm = { };
|
||||
})
|
||||
(lib.mkIf cfg.enablePam {
|
||||
kanidm-unixd = { };
|
||||
})
|
||||
];
|
||||
users.users = lib.mkMerge [
|
||||
(lib.mkIf cfg.enableServer {
|
||||
kanidm = {
|
||||
description = "Kanidm server";
|
||||
isSystemUser = true;
|
||||
group = "kanidm";
|
||||
packages = with pkgs; [ kanidm ];
|
||||
};
|
||||
})
|
||||
(lib.mkIf cfg.enablePam {
|
||||
kanidm-unixd = {
|
||||
description = "Kanidm PAM daemon";
|
||||
isSystemUser = true;
|
||||
group = "kanidm-unixd";
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ erictapen Flakebi ];
|
||||
meta.buildDocsInSandbox = false;
|
||||
}
|
||||
68
nixos/modules/services/security/munge.nix
Normal file
68
nixos/modules/services/security/munge.nix
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.munge;
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.munge = {
|
||||
enable = mkEnableOption "munge service";
|
||||
|
||||
password = mkOption {
|
||||
default = "/etc/munge/munge.key";
|
||||
type = types.path;
|
||||
description = ''
|
||||
The path to a daemon's secret key.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = [ pkgs.munge ];
|
||||
|
||||
users.users.munge = {
|
||||
description = "Munge daemon user";
|
||||
isSystemUser = true;
|
||||
group = "munge";
|
||||
};
|
||||
|
||||
users.groups.munge = {};
|
||||
|
||||
systemd.services.munged = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
path = [ pkgs.munge pkgs.coreutils ];
|
||||
|
||||
serviceConfig = {
|
||||
ExecStartPre = "+${pkgs.coreutils}/bin/chmod 0400 ${cfg.password}";
|
||||
ExecStart = "${pkgs.munge}/bin/munged --syslog --key-file ${cfg.password}";
|
||||
PIDFile = "/run/munge/munged.pid";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
User = "munge";
|
||||
Group = "munge";
|
||||
StateDirectory = "munge";
|
||||
StateDirectoryMode = "0711";
|
||||
RuntimeDirectory = "munge";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
67
nixos/modules/services/security/nginx-sso.nix
Normal file
67
nixos/modules/services/security/nginx-sso.nix
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.nginx.sso;
|
||||
pkg = getBin cfg.package;
|
||||
configYml = pkgs.writeText "nginx-sso.yml" (builtins.toJSON cfg.configuration);
|
||||
in {
|
||||
options.services.nginx.sso = {
|
||||
enable = mkEnableOption "nginx-sso service";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.nginx-sso;
|
||||
defaultText = literalExpression "pkgs.nginx-sso";
|
||||
description = ''
|
||||
The nginx-sso package that should be used.
|
||||
'';
|
||||
};
|
||||
|
||||
configuration = mkOption {
|
||||
type = types.attrsOf types.unspecified;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
listen = { addr = "127.0.0.1"; port = 8080; };
|
||||
|
||||
providers.token.tokens = {
|
||||
myuser = "MyToken";
|
||||
};
|
||||
|
||||
acl = {
|
||||
rule_sets = [
|
||||
{
|
||||
rules = [ { field = "x-application"; equals = "MyApp"; } ];
|
||||
allow = [ "myuser" ];
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
nginx-sso configuration
|
||||
(<link xlink:href="https://github.com/Luzifer/nginx-sso/wiki/Main-Configuration">documentation</link>)
|
||||
as a Nix attribute set.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.nginx-sso = {
|
||||
description = "Nginx SSO Backend";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${pkg}/bin/nginx-sso \
|
||||
--config ${configYml} \
|
||||
--frontend-dir ${pkg}/share/frontend
|
||||
'';
|
||||
Restart = "always";
|
||||
DynamicUser = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
594
nixos/modules/services/security/oauth2_proxy.nix
Normal file
594
nixos/modules/services/security/oauth2_proxy.nix
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
# NixOS module for oauth2_proxy.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.oauth2_proxy;
|
||||
|
||||
# oauth2_proxy provides many options that are only relevant if you are using
|
||||
# a certain provider. This set maps from provider name to a function that
|
||||
# takes the configuration and returns a string that can be inserted into the
|
||||
# command-line to launch oauth2_proxy.
|
||||
providerSpecificOptions = {
|
||||
azure = cfg: {
|
||||
azure-tenant = cfg.azure.tenant;
|
||||
resource = cfg.azure.resource;
|
||||
};
|
||||
|
||||
github = cfg: { github = {
|
||||
inherit (cfg.github) org team;
|
||||
}; };
|
||||
|
||||
google = cfg: { google = with cfg.google; optionalAttrs (groups != []) {
|
||||
admin-email = adminEmail;
|
||||
service-account = serviceAccountJSON;
|
||||
group = groups;
|
||||
}; };
|
||||
};
|
||||
|
||||
authenticatedEmailsFile = pkgs.writeText "authenticated-emails" cfg.email.addresses;
|
||||
|
||||
getProviderOptions = cfg: provider: providerSpecificOptions.${provider} or (_: {}) cfg;
|
||||
|
||||
allConfig = with cfg; {
|
||||
inherit (cfg) provider scope upstream;
|
||||
approval-prompt = approvalPrompt;
|
||||
basic-auth-password = basicAuthPassword;
|
||||
client-id = clientID;
|
||||
client-secret = clientSecret;
|
||||
custom-templates-dir = customTemplatesDir;
|
||||
email-domain = email.domains;
|
||||
http-address = httpAddress;
|
||||
login-url = loginURL;
|
||||
pass-access-token = passAccessToken;
|
||||
pass-basic-auth = passBasicAuth;
|
||||
pass-host-header = passHostHeader;
|
||||
reverse-proxy = reverseProxy;
|
||||
proxy-prefix = proxyPrefix;
|
||||
profile-url = profileURL;
|
||||
redeem-url = redeemURL;
|
||||
redirect-url = redirectURL;
|
||||
request-logging = requestLogging;
|
||||
skip-auth-regex = skipAuthRegexes;
|
||||
signature-key = signatureKey;
|
||||
validate-url = validateURL;
|
||||
htpasswd-file = htpasswd.file;
|
||||
cookie = {
|
||||
inherit (cookie) domain secure expire name secret refresh;
|
||||
httponly = cookie.httpOnly;
|
||||
};
|
||||
set-xauthrequest = setXauthrequest;
|
||||
} // lib.optionalAttrs (cfg.email.addresses != null) {
|
||||
authenticated-emails-file = authenticatedEmailsFile;
|
||||
} // lib.optionalAttrs (cfg.passBasicAuth) {
|
||||
basic-auth-password = cfg.basicAuthPassword;
|
||||
} // lib.optionalAttrs (cfg.htpasswd.file != null) {
|
||||
display-htpasswd-file = cfg.htpasswd.displayForm;
|
||||
} // lib.optionalAttrs tls.enable {
|
||||
tls-cert-file = tls.certificate;
|
||||
tls-key-file = tls.key;
|
||||
https-address = tls.httpsAddress;
|
||||
} // (getProviderOptions cfg cfg.provider) // cfg.extraConfig;
|
||||
|
||||
mapConfig = key: attr:
|
||||
if attr != null && attr != [] then (
|
||||
if isDerivation attr then mapConfig key (toString attr) else
|
||||
if (builtins.typeOf attr) == "set" then concatStringsSep " "
|
||||
(mapAttrsToList (name: value: mapConfig (key + "-" + name) value) attr) else
|
||||
if (builtins.typeOf attr) == "list" then concatMapStringsSep " " (mapConfig key) attr else
|
||||
if (builtins.typeOf attr) == "bool" then "--${key}=${boolToString attr}" else
|
||||
if (builtins.typeOf attr) == "string" then "--${key}='${attr}'" else
|
||||
"--${key}=${toString attr}")
|
||||
else "";
|
||||
|
||||
configString = concatStringsSep " " (mapAttrsToList mapConfig allConfig);
|
||||
in
|
||||
{
|
||||
options.services.oauth2_proxy = {
|
||||
enable = mkEnableOption "oauth2_proxy";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.oauth2-proxy;
|
||||
defaultText = literalExpression "pkgs.oauth2-proxy";
|
||||
description = ''
|
||||
The package that provides oauth2-proxy.
|
||||
'';
|
||||
};
|
||||
|
||||
##############################################
|
||||
# PROVIDER configuration
|
||||
# Taken from: https://github.com/oauth2-proxy/oauth2-proxy/blob/master/providers/providers.go
|
||||
provider = mkOption {
|
||||
type = types.enum [
|
||||
"adfs"
|
||||
"azure"
|
||||
"bitbucket"
|
||||
"digitalocean"
|
||||
"facebook"
|
||||
"github"
|
||||
"gitlab"
|
||||
"google"
|
||||
"keycloak"
|
||||
"keycloak-oidc"
|
||||
"linkedin"
|
||||
"login.gov"
|
||||
"nextcloud"
|
||||
"oidc"
|
||||
];
|
||||
default = "google";
|
||||
description = ''
|
||||
OAuth provider.
|
||||
'';
|
||||
};
|
||||
|
||||
approvalPrompt = mkOption {
|
||||
type = types.enum ["force" "auto"];
|
||||
default = "force";
|
||||
description = ''
|
||||
OAuth approval_prompt.
|
||||
'';
|
||||
};
|
||||
|
||||
clientID = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
The OAuth Client ID.
|
||||
'';
|
||||
example = "123456.apps.googleusercontent.com";
|
||||
};
|
||||
|
||||
clientSecret = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
The OAuth Client Secret.
|
||||
'';
|
||||
};
|
||||
|
||||
skipAuthRegexes = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Skip authentication for requests matching any of these regular
|
||||
expressions.
|
||||
'';
|
||||
};
|
||||
|
||||
# XXX: Not clear whether these two options are mutually exclusive or not.
|
||||
email = {
|
||||
domains = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Authenticate emails with the specified domains. Use
|
||||
<literal>*</literal> to authenticate any email.
|
||||
'';
|
||||
};
|
||||
|
||||
addresses = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
description = ''
|
||||
Line-separated email addresses that are allowed to authenticate.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
loginURL = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Authentication endpoint.
|
||||
|
||||
You only need to set this if you are using a self-hosted provider (e.g.
|
||||
Github Enterprise). If you're using a publicly hosted provider
|
||||
(e.g github.com), then the default works.
|
||||
'';
|
||||
example = "https://provider.example.com/oauth/authorize";
|
||||
};
|
||||
|
||||
redeemURL = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Token redemption endpoint.
|
||||
|
||||
You only need to set this if you are using a self-hosted provider (e.g.
|
||||
Github Enterprise). If you're using a publicly hosted provider
|
||||
(e.g github.com), then the default works.
|
||||
'';
|
||||
example = "https://provider.example.com/oauth/token";
|
||||
};
|
||||
|
||||
validateURL = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Access token validation endpoint.
|
||||
|
||||
You only need to set this if you are using a self-hosted provider (e.g.
|
||||
Github Enterprise). If you're using a publicly hosted provider
|
||||
(e.g github.com), then the default works.
|
||||
'';
|
||||
example = "https://provider.example.com/user/emails";
|
||||
};
|
||||
|
||||
redirectURL = mkOption {
|
||||
# XXX: jml suspects this is always necessary, but the command-line
|
||||
# doesn't require it so making it optional.
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The OAuth2 redirect URL.
|
||||
'';
|
||||
example = "https://internalapp.yourcompany.com/oauth2/callback";
|
||||
};
|
||||
|
||||
azure = {
|
||||
tenant = mkOption {
|
||||
type = types.str;
|
||||
default = "common";
|
||||
description = ''
|
||||
Go to a tenant-specific or common (tenant-independent) endpoint.
|
||||
'';
|
||||
};
|
||||
|
||||
resource = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The resource that is protected.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
google = {
|
||||
adminEmail = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
The Google Admin to impersonate for API calls.
|
||||
|
||||
Only users with access to the Admin APIs can access the Admin SDK
|
||||
Directory API, thus the service account needs to impersonate one of
|
||||
those users to access the Admin SDK Directory API.
|
||||
|
||||
See <link xlink:href="https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account" />.
|
||||
'';
|
||||
};
|
||||
|
||||
groups = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Restrict logins to members of these Google groups.
|
||||
'';
|
||||
};
|
||||
|
||||
serviceAccountJSON = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
The path to the service account JSON credentials.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
github = {
|
||||
org = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Restrict logins to members of this organisation.
|
||||
'';
|
||||
};
|
||||
|
||||
team = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Restrict logins to members of this team.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
####################################################
|
||||
# UPSTREAM Configuration
|
||||
upstream = mkOption {
|
||||
type = with types; coercedTo str (x: [x]) (listOf str);
|
||||
default = [];
|
||||
description = ''
|
||||
The http url(s) of the upstream endpoint or <literal>file://</literal>
|
||||
paths for static files. Routing is based on the path.
|
||||
'';
|
||||
};
|
||||
|
||||
passAccessToken = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Pass OAuth access_token to upstream via X-Forwarded-Access-Token header.
|
||||
'';
|
||||
};
|
||||
|
||||
passBasicAuth = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream.
|
||||
'';
|
||||
};
|
||||
|
||||
basicAuthPassword = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
The password to set when passing the HTTP Basic Auth header.
|
||||
'';
|
||||
};
|
||||
|
||||
passHostHeader = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Pass the request Host Header to upstream.
|
||||
'';
|
||||
};
|
||||
|
||||
signatureKey = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
GAP-Signature request signature key.
|
||||
'';
|
||||
example = "sha1:secret0";
|
||||
};
|
||||
|
||||
cookie = {
|
||||
domain = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Optional cookie domains to force cookies to (ie: `.yourcompany.com`).
|
||||
The longest domain matching the request's host will be used (or the shortest
|
||||
cookie domain if there is no match).
|
||||
'';
|
||||
example = ".yourcompany.com";
|
||||
};
|
||||
|
||||
expire = mkOption {
|
||||
type = types.str;
|
||||
default = "168h0m0s";
|
||||
description = ''
|
||||
Expire timeframe for cookie.
|
||||
'';
|
||||
};
|
||||
|
||||
httpOnly = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Set HttpOnly cookie flag.
|
||||
'';
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
type = types.str;
|
||||
default = "_oauth2_proxy";
|
||||
description = ''
|
||||
The name of the cookie that the oauth_proxy creates.
|
||||
'';
|
||||
};
|
||||
|
||||
refresh = mkOption {
|
||||
# XXX: Unclear what the behavior is when this is not specified.
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Refresh the cookie after this duration; 0 to disable.
|
||||
'';
|
||||
example = "168h0m0s";
|
||||
};
|
||||
|
||||
secret = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
description = ''
|
||||
The seed string for secure cookies.
|
||||
'';
|
||||
};
|
||||
|
||||
secure = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Set secure (HTTPS) cookie flag.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
####################################################
|
||||
# OAUTH2 PROXY configuration
|
||||
|
||||
httpAddress = mkOption {
|
||||
type = types.str;
|
||||
default = "http://127.0.0.1:4180";
|
||||
description = ''
|
||||
HTTPS listening address. This module does not expose the port by
|
||||
default. If you want this URL to be accessible to other machines, please
|
||||
add the port to <literal>networking.firewall.allowedTCPPorts</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
htpasswd = {
|
||||
file = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Additionally authenticate against a htpasswd file. Entries must be
|
||||
created with <literal>htpasswd -s</literal> for SHA encryption.
|
||||
'';
|
||||
};
|
||||
|
||||
displayForm = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Display username / password login form if an htpasswd file is provided.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
customTemplatesDir = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to custom HTML templates.
|
||||
'';
|
||||
};
|
||||
|
||||
reverseProxy = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
In case when running behind a reverse proxy, controls whether headers
|
||||
like <literal>X-Real-Ip</literal> are accepted. Usage behind a reverse
|
||||
proxy will require this flag to be set to avoid logging the reverse
|
||||
proxy IP address.
|
||||
'';
|
||||
};
|
||||
|
||||
proxyPrefix = mkOption {
|
||||
type = types.str;
|
||||
default = "/oauth2";
|
||||
description = ''
|
||||
The url root path that this proxy should be nested under.
|
||||
'';
|
||||
};
|
||||
|
||||
tls = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to serve over TLS.
|
||||
'';
|
||||
};
|
||||
|
||||
certificate = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to certificate file.
|
||||
'';
|
||||
};
|
||||
|
||||
key = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to private key file.
|
||||
'';
|
||||
};
|
||||
|
||||
httpsAddress = mkOption {
|
||||
type = types.str;
|
||||
default = ":443";
|
||||
description = ''
|
||||
<literal>addr:port</literal> to listen on for HTTPS clients.
|
||||
|
||||
Remember to add <literal>port</literal> to
|
||||
<literal>allowedTCPPorts</literal> if you want other machines to be
|
||||
able to connect to it.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
requestLogging = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Log requests to stdout.
|
||||
'';
|
||||
};
|
||||
|
||||
####################################################
|
||||
# UNKNOWN
|
||||
|
||||
# XXX: Is this mandatory? Is it part of another group? Is it part of the provider specification?
|
||||
scope = mkOption {
|
||||
# XXX: jml suspects this is always necessary, but the command-line
|
||||
# doesn't require it so making it optional.
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
OAuth scope specification.
|
||||
'';
|
||||
};
|
||||
|
||||
profileURL = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Profile access endpoint.
|
||||
'';
|
||||
};
|
||||
|
||||
setXauthrequest = mkOption {
|
||||
type = types.nullOr types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Set X-Auth-Request-User and X-Auth-Request-Email response headers (useful in Nginx auth_request mode). Setting this to 'null' means using the upstream default (false).
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
default = {};
|
||||
type = types.attrsOf types.anything;
|
||||
description = ''
|
||||
Extra config to pass to oauth2-proxy.
|
||||
'';
|
||||
};
|
||||
|
||||
keyFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
oauth2-proxy allows passing sensitive configuration via environment variables.
|
||||
Make a file that contains lines like
|
||||
OAUTH2_PROXY_CLIENT_SECRET=asdfasdfasdf.apps.googleuserscontent.com
|
||||
and specify the path here.
|
||||
'';
|
||||
example = "/run/keys/oauth2_proxy";
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.oauth2_proxy = mkIf (cfg.keyFile != null) {
|
||||
clientID = mkDefault null;
|
||||
clientSecret = mkDefault null;
|
||||
cookie.secret = mkDefault null;
|
||||
};
|
||||
|
||||
users.users.oauth2_proxy = {
|
||||
description = "OAuth2 Proxy";
|
||||
isSystemUser = true;
|
||||
group = "oauth2_proxy";
|
||||
};
|
||||
|
||||
users.groups.oauth2_proxy = {};
|
||||
|
||||
systemd.services.oauth2_proxy = {
|
||||
description = "OAuth2 Proxy";
|
||||
path = [ cfg.package ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
User = "oauth2_proxy";
|
||||
Restart = "always";
|
||||
ExecStart = "${cfg.package}/bin/oauth2-proxy ${configString}";
|
||||
EnvironmentFile = mkIf (cfg.keyFile != null) cfg.keyFile;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
66
nixos/modules/services/security/oauth2_proxy_nginx.nix
Normal file
66
nixos/modules/services/security/oauth2_proxy_nginx.nix
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
{ config, lib, ... }:
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.oauth2_proxy.nginx;
|
||||
in
|
||||
{
|
||||
options.services.oauth2_proxy.nginx = {
|
||||
proxy = mkOption {
|
||||
type = types.str;
|
||||
default = config.services.oauth2_proxy.httpAddress;
|
||||
defaultText = literalExpression "config.services.oauth2_proxy.httpAddress";
|
||||
description = ''
|
||||
The address of the reverse proxy endpoint for oauth2_proxy
|
||||
'';
|
||||
};
|
||||
virtualHosts = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
A list of nginx virtual hosts to put behind the oauth2 proxy
|
||||
'';
|
||||
};
|
||||
};
|
||||
config.services.oauth2_proxy = mkIf (cfg.virtualHosts != [] && (hasPrefix "127.0.0.1:" cfg.proxy)) {
|
||||
enable = true;
|
||||
};
|
||||
config.services.nginx = mkIf config.services.oauth2_proxy.enable (mkMerge
|
||||
((optional (cfg.virtualHosts != []) {
|
||||
recommendedProxySettings = true; # needed because duplicate headers
|
||||
}) ++ (map (vhost: {
|
||||
virtualHosts.${vhost} = {
|
||||
locations."/oauth2/" = {
|
||||
proxyPass = cfg.proxy;
|
||||
extraConfig = ''
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
proxy_set_header X-Auth-Request-Redirect $scheme://$host$request_uri;
|
||||
'';
|
||||
};
|
||||
locations."/oauth2/auth" = {
|
||||
proxyPass = cfg.proxy;
|
||||
extraConfig = ''
|
||||
proxy_set_header X-Scheme $scheme;
|
||||
# nginx auth_request includes headers but not body
|
||||
proxy_set_header Content-Length "";
|
||||
proxy_pass_request_body off;
|
||||
'';
|
||||
};
|
||||
locations."/".extraConfig = ''
|
||||
auth_request /oauth2/auth;
|
||||
error_page 401 = /oauth2/sign_in;
|
||||
|
||||
# pass information via X-User and X-Email headers to backend,
|
||||
# requires running with --set-xauthrequest flag
|
||||
auth_request_set $user $upstream_http_x_auth_request_user;
|
||||
auth_request_set $email $upstream_http_x_auth_request_email;
|
||||
proxy_set_header X-User $user;
|
||||
proxy_set_header X-Email $email;
|
||||
|
||||
# if you enabled --cookie-refresh, this is needed for it to work with auth_request
|
||||
auth_request_set $auth_cookie $upstream_http_set_cookie;
|
||||
add_header Set-Cookie $auth_cookie;
|
||||
'';
|
||||
|
||||
};
|
||||
}) cfg.virtualHosts)));
|
||||
}
|
||||
125
nixos/modules/services/security/opensnitch.nix
Normal file
125
nixos/modules/services/security/opensnitch.nix
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.opensnitch;
|
||||
format = pkgs.formats.json {};
|
||||
in {
|
||||
options = {
|
||||
services.opensnitch = {
|
||||
enable = mkEnableOption "Opensnitch application firewall";
|
||||
settings = mkOption {
|
||||
type = types.submodule {
|
||||
freeformType = format.type;
|
||||
|
||||
options = {
|
||||
Server = {
|
||||
|
||||
Address = mkOption {
|
||||
type = types.str;
|
||||
description = ''
|
||||
Unix socket path (unix:///tmp/osui.sock, the "unix:///" part is
|
||||
mandatory) or TCP socket (192.168.1.100:50051).
|
||||
'';
|
||||
};
|
||||
|
||||
LogFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
File to write logs to (use /dev/stdout to write logs to standard
|
||||
output).
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
DefaultAction = mkOption {
|
||||
type = types.enum [ "allow" "deny" ];
|
||||
description = ''
|
||||
Default action whether to block or allow application internet
|
||||
access.
|
||||
'';
|
||||
};
|
||||
|
||||
DefaultDuration = mkOption {
|
||||
type = types.enum [
|
||||
"once" "always" "until restart" "30s" "5m" "15m" "30m" "1h"
|
||||
];
|
||||
description = ''
|
||||
Default duration of firewall rule.
|
||||
'';
|
||||
};
|
||||
|
||||
InterceptUnknown = mkOption {
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Wheter to intercept spare connections.
|
||||
'';
|
||||
};
|
||||
|
||||
ProcMonitorMethod = mkOption {
|
||||
type = types.enum [ "ebpf" "proc" "ftrace" "audit" ];
|
||||
description = ''
|
||||
Which process monitoring method to use.
|
||||
'';
|
||||
};
|
||||
|
||||
LogLevel = mkOption {
|
||||
type = types.enum [ 0 1 2 3 4 ];
|
||||
description = ''
|
||||
Default log level from 0 to 4 (debug, info, important, warning,
|
||||
error).
|
||||
'';
|
||||
};
|
||||
|
||||
Firewall = mkOption {
|
||||
type = types.enum [ "iptables" "nftables" ];
|
||||
description = ''
|
||||
Which firewall backend to use.
|
||||
'';
|
||||
};
|
||||
|
||||
Stats = {
|
||||
|
||||
MaxEvents = mkOption {
|
||||
type = types.int;
|
||||
description = ''
|
||||
Max events to send to the GUI.
|
||||
'';
|
||||
};
|
||||
|
||||
MaxStats = mkOption {
|
||||
type = types.int;
|
||||
description = ''
|
||||
Max stats per item to keep in backlog.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
description = ''
|
||||
opensnitchd configuration. Refer to
|
||||
<link xlink:href="https://github.com/evilsocket/opensnitch/wiki/Configurations"/>
|
||||
for details on supported values.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
# pkg.opensnitch is referred to elsewhere in the module so we don't need to worry about it being garbage collected
|
||||
services.opensnitch.settings = mapAttrs (_: v: mkDefault v) (builtins.fromJSON (builtins.unsafeDiscardStringContext (builtins.readFile "${pkgs.opensnitch}/etc/default-config.json")));
|
||||
|
||||
systemd = {
|
||||
packages = [ pkgs.opensnitch ];
|
||||
services.opensnitchd.wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
environment.etc."opensnitchd/default-config.json".source = format.generate "default-config.json" cfg.settings;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
139
nixos/modules/services/security/physlock.nix
Normal file
139
nixos/modules/services/security/physlock.nix
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.physlock;
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.physlock = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to enable the <command>physlock</command> screen locking mechanism.
|
||||
|
||||
Enable this and then run <command>systemctl start physlock</command>
|
||||
to securely lock the screen.
|
||||
|
||||
This will switch to a new virtual terminal, turn off console
|
||||
switching and disable SysRq mechanism (when
|
||||
<option>services.physlock.disableSysRq</option> is set)
|
||||
until the root or user password is given.
|
||||
'';
|
||||
};
|
||||
|
||||
allowAnyUser = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to allow any user to lock the screen. This will install a
|
||||
setuid wrapper to allow any user to start physlock as root, which
|
||||
is a minor security risk. Call the physlock binary to use this instead
|
||||
of using the systemd service.
|
||||
'';
|
||||
};
|
||||
|
||||
disableSysRq = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to disable SysRq when locked with physlock.
|
||||
'';
|
||||
};
|
||||
|
||||
lockMessage = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Message to show on physlock login terminal.
|
||||
'';
|
||||
};
|
||||
|
||||
lockOn = {
|
||||
|
||||
suspend = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to lock screen with physlock just before suspend.
|
||||
'';
|
||||
};
|
||||
|
||||
hibernate = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to lock screen with physlock just before hibernate.
|
||||
'';
|
||||
};
|
||||
|
||||
extraTargets = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "display-manager.service" ];
|
||||
description = ''
|
||||
Other targets to lock the screen just before.
|
||||
|
||||
Useful if you want to e.g. both autologin to X11 so that
|
||||
your <filename>~/.xsession</filename> gets executed and
|
||||
still to have the screen locked so that the system can be
|
||||
booted relatively unattended.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable (mkMerge [
|
||||
{
|
||||
|
||||
# for physlock -l and physlock -L
|
||||
environment.systemPackages = [ pkgs.physlock ];
|
||||
|
||||
systemd.services.physlock = {
|
||||
enable = true;
|
||||
description = "Physlock";
|
||||
wantedBy = optional cfg.lockOn.suspend "suspend.target"
|
||||
++ optional cfg.lockOn.hibernate "hibernate.target"
|
||||
++ cfg.lockOn.extraTargets;
|
||||
before = optional cfg.lockOn.suspend "systemd-suspend.service"
|
||||
++ optional cfg.lockOn.hibernate "systemd-hibernate.service"
|
||||
++ optional (cfg.lockOn.hibernate || cfg.lockOn.suspend) "systemd-suspend-then-hibernate.service"
|
||||
++ cfg.lockOn.extraTargets;
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = "${pkgs.physlock}/bin/physlock -d${optionalString cfg.disableSysRq "s"}${optionalString (cfg.lockMessage != "") " -p \"${cfg.lockMessage}\""}";
|
||||
};
|
||||
};
|
||||
|
||||
security.pam.services.physlock = {};
|
||||
|
||||
}
|
||||
|
||||
(mkIf cfg.allowAnyUser {
|
||||
|
||||
security.wrappers.physlock =
|
||||
{ setuid = true;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
source = "${pkgs.physlock}/bin/physlock";
|
||||
};
|
||||
|
||||
})
|
||||
]);
|
||||
|
||||
}
|
||||
309
nixos/modules/services/security/privacyidea.nix
Normal file
309
nixos/modules/services/security/privacyidea.nix
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.privacyidea;
|
||||
opt = options.services.privacyidea;
|
||||
|
||||
uwsgi = pkgs.uwsgi.override { plugins = [ "python3" ]; };
|
||||
python = uwsgi.python3;
|
||||
penv = python.withPackages (const [ pkgs.privacyidea ]);
|
||||
logCfg = pkgs.writeText "privacyidea-log.cfg" ''
|
||||
[formatters]
|
||||
keys=detail
|
||||
|
||||
[handlers]
|
||||
keys=stream
|
||||
|
||||
[formatter_detail]
|
||||
class=privacyidea.lib.log.SecureFormatter
|
||||
format=[%(asctime)s][%(process)d][%(thread)d][%(levelname)s][%(name)s:%(lineno)d] %(message)s
|
||||
|
||||
[handler_stream]
|
||||
class=StreamHandler
|
||||
level=NOTSET
|
||||
formatter=detail
|
||||
args=(sys.stdout,)
|
||||
|
||||
[loggers]
|
||||
keys=root,privacyidea
|
||||
|
||||
[logger_privacyidea]
|
||||
handlers=stream
|
||||
qualname=privacyidea
|
||||
level=INFO
|
||||
|
||||
[logger_root]
|
||||
handlers=stream
|
||||
level=ERROR
|
||||
'';
|
||||
|
||||
piCfgFile = pkgs.writeText "privacyidea.cfg" ''
|
||||
SUPERUSER_REALM = [ '${concatStringsSep "', '" cfg.superuserRealm}' ]
|
||||
SQLALCHEMY_DATABASE_URI = 'postgresql:///privacyidea'
|
||||
SECRET_KEY = '${cfg.secretKey}'
|
||||
PI_PEPPER = '${cfg.pepper}'
|
||||
PI_ENCFILE = '${cfg.encFile}'
|
||||
PI_AUDIT_KEY_PRIVATE = '${cfg.auditKeyPrivate}'
|
||||
PI_AUDIT_KEY_PUBLIC = '${cfg.auditKeyPublic}'
|
||||
PI_LOGCONFIG = '${logCfg}'
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.privacyidea = {
|
||||
enable = mkEnableOption "PrivacyIDEA";
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
example = "/root/privacyidea.env";
|
||||
description = ''
|
||||
File to load as environment file. Environment variables
|
||||
from this file will be interpolated into the config file
|
||||
using <package>envsubst</package> which is helpful for specifying
|
||||
secrets:
|
||||
<programlisting>
|
||||
{ <xref linkend="opt-services.privacyidea.secretKey" /> = "$SECRET"; }
|
||||
</programlisting>
|
||||
|
||||
The environment-file can now specify the actual secret key:
|
||||
<programlisting>
|
||||
SECRET=veryverytopsecret
|
||||
</programlisting>
|
||||
'';
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/privacyidea";
|
||||
description = ''
|
||||
Directory where all PrivacyIDEA files will be placed by default.
|
||||
'';
|
||||
};
|
||||
|
||||
superuserRealm = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "super" "administrators" ];
|
||||
description = ''
|
||||
The realm where users are allowed to login as administrators.
|
||||
'';
|
||||
};
|
||||
|
||||
secretKey = mkOption {
|
||||
type = types.str;
|
||||
example = "t0p s3cr3t";
|
||||
description = ''
|
||||
This is used to encrypt the auth_token.
|
||||
'';
|
||||
};
|
||||
|
||||
pepper = mkOption {
|
||||
type = types.str;
|
||||
example = "Never know...";
|
||||
description = ''
|
||||
This is used to encrypt the admin passwords.
|
||||
'';
|
||||
};
|
||||
|
||||
encFile = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/enckey";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/enckey"'';
|
||||
description = ''
|
||||
This is used to encrypt the token data and token passwords
|
||||
'';
|
||||
};
|
||||
|
||||
auditKeyPrivate = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/private.pem";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/private.pem"'';
|
||||
description = ''
|
||||
Private Key for signing the audit log.
|
||||
'';
|
||||
};
|
||||
|
||||
auditKeyPublic = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.stateDir}/public.pem";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/public.pem"'';
|
||||
description = ''
|
||||
Public key for checking signatures of the audit log.
|
||||
'';
|
||||
};
|
||||
|
||||
adminPasswordFile = mkOption {
|
||||
type = types.path;
|
||||
description = "File containing password for the admin user";
|
||||
};
|
||||
|
||||
adminEmail = mkOption {
|
||||
type = types.str;
|
||||
example = "admin@example.com";
|
||||
description = "Mail address for the admin user";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra configuration options for pi.cfg.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "privacyidea";
|
||||
description = "User account under which PrivacyIDEA runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "privacyidea";
|
||||
description = "Group account under which PrivacyIDEA runs.";
|
||||
};
|
||||
|
||||
ldap-proxy = {
|
||||
enable = mkEnableOption "PrivacyIDEA LDAP Proxy";
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to PrivacyIDEA LDAP Proxy configuration (proxy.ini).
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "pi-ldap-proxy";
|
||||
description = "User account under which PrivacyIDEA LDAP proxy runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "pi-ldap-proxy";
|
||||
description = "Group account under which PrivacyIDEA LDAP proxy runs.";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
|
||||
(mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = [ pkgs.privacyidea ];
|
||||
|
||||
services.postgresql.enable = mkDefault true;
|
||||
|
||||
systemd.services.privacyidea = let
|
||||
piuwsgi = pkgs.writeText "uwsgi.json" (builtins.toJSON {
|
||||
uwsgi = {
|
||||
buffer-size = 8192;
|
||||
plugins = [ "python3" ];
|
||||
pythonpath = "${penv}/${uwsgi.python3.sitePackages}";
|
||||
socket = "/run/privacyidea/socket";
|
||||
uid = cfg.user;
|
||||
gid = cfg.group;
|
||||
chmod-socket = 770;
|
||||
chown-socket = "${cfg.user}:nginx";
|
||||
chdir = cfg.stateDir;
|
||||
wsgi-file = "${penv}/etc/privacyidea/privacyideaapp.wsgi";
|
||||
processes = 4;
|
||||
harakiri = 60;
|
||||
reload-mercy = 8;
|
||||
stats = "/run/privacyidea/stats.socket";
|
||||
max-requests = 2000;
|
||||
limit-as = 1024;
|
||||
reload-on-as = 512;
|
||||
reload-on-rss = 256;
|
||||
no-orphans = true;
|
||||
vacuum = true;
|
||||
};
|
||||
});
|
||||
in {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "postgresql.service" ];
|
||||
path = with pkgs; [ openssl ];
|
||||
environment.PRIVACYIDEA_CONFIGFILE = "${cfg.stateDir}/privacyidea.cfg";
|
||||
preStart = let
|
||||
pi-manage = "${config.security.sudo.package}/bin/sudo -u privacyidea -HE ${penv}/bin/pi-manage";
|
||||
pgsu = config.services.postgresql.superUser;
|
||||
psql = config.services.postgresql.package;
|
||||
in ''
|
||||
mkdir -p ${cfg.stateDir} /run/privacyidea
|
||||
chown ${cfg.user}:${cfg.group} -R ${cfg.stateDir} /run/privacyidea
|
||||
umask 077
|
||||
${lib.getBin pkgs.envsubst}/bin/envsubst -o ${cfg.stateDir}/privacyidea.cfg \
|
||||
-i "${piCfgFile}"
|
||||
chown ${cfg.user}:${cfg.group} ${cfg.stateDir}/privacyidea.cfg
|
||||
if ! test -e "${cfg.stateDir}/db-created"; then
|
||||
${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createuser --no-superuser --no-createdb --no-createrole ${cfg.user}
|
||||
${config.security.sudo.package}/bin/sudo -u ${pgsu} ${psql}/bin/createdb --owner ${cfg.user} privacyidea
|
||||
${pi-manage} create_enckey
|
||||
${pi-manage} create_audit_keys
|
||||
${pi-manage} createdb
|
||||
${pi-manage} admin add admin -e ${cfg.adminEmail} -p "$(cat ${cfg.adminPasswordFile})"
|
||||
${pi-manage} db stamp head -d ${penv}/lib/privacyidea/migrations
|
||||
touch "${cfg.stateDir}/db-created"
|
||||
chmod g+r "${cfg.stateDir}/enckey" "${cfg.stateDir}/private.pem"
|
||||
fi
|
||||
${pi-manage} db upgrade -d ${penv}/lib/privacyidea/migrations
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "notify";
|
||||
ExecStart = "${uwsgi}/bin/uwsgi --json ${piuwsgi}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
EnvironmentFile = lib.mkIf (cfg.environmentFile != null) cfg.environmentFile;
|
||||
ExecStop = "${pkgs.coreutils}/bin/kill -INT $MAINPID";
|
||||
NotifyAccess = "main";
|
||||
KillSignal = "SIGQUIT";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.privacyidea = mkIf (cfg.user == "privacyidea") {
|
||||
group = cfg.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.privacyidea = mkIf (cfg.group == "privacyidea") {};
|
||||
})
|
||||
|
||||
(mkIf cfg.ldap-proxy.enable {
|
||||
|
||||
systemd.services.privacyidea-ldap-proxy = let
|
||||
ldap-proxy-env = pkgs.python3.withPackages (ps: [ ps.privacyidea-ldap-proxy ]);
|
||||
in {
|
||||
description = "privacyIDEA LDAP proxy";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.ldap-proxy.user;
|
||||
Group = cfg.ldap-proxy.group;
|
||||
ExecStart = ''
|
||||
${ldap-proxy-env}/bin/twistd \
|
||||
--nodaemon \
|
||||
--pidfile= \
|
||||
-u ${cfg.ldap-proxy.user} \
|
||||
-g ${cfg.ldap-proxy.group} \
|
||||
ldap-proxy \
|
||||
-c ${cfg.ldap-proxy.configFile}
|
||||
'';
|
||||
Restart = "always";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.pi-ldap-proxy = mkIf (cfg.ldap-proxy.user == "pi-ldap-proxy") {
|
||||
group = cfg.ldap-proxy.group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.pi-ldap-proxy = mkIf (cfg.ldap-proxy.group == "pi-ldap-proxy") {};
|
||||
})
|
||||
];
|
||||
|
||||
}
|
||||
75
nixos/modules/services/security/shibboleth-sp.nix
Normal file
75
nixos/modules/services/security/shibboleth-sp.nix
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
{pkgs, config, lib, ...}:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.shibboleth-sp;
|
||||
in {
|
||||
options = {
|
||||
services.shibboleth-sp = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to enable the shibboleth service";
|
||||
};
|
||||
|
||||
configFile = mkOption {
|
||||
type = types.path;
|
||||
example = literalExpression ''"''${pkgs.shibboleth-sp}/etc/shibboleth/shibboleth2.xml"'';
|
||||
description = "Path to shibboleth config file";
|
||||
};
|
||||
|
||||
fastcgi.enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Whether to include the shibauthorizer and shibresponder FastCGI processes";
|
||||
};
|
||||
|
||||
fastcgi.shibAuthorizerPort = mkOption {
|
||||
type = types.int;
|
||||
default = 9100;
|
||||
description = "Port for shibauthorizer FastCGI proccess to bind to";
|
||||
};
|
||||
|
||||
fastcgi.shibResponderPort = mkOption {
|
||||
type = types.int;
|
||||
default = 9101;
|
||||
description = "Port for shibauthorizer FastCGI proccess to bind to";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.services.shibboleth-sp = {
|
||||
description = "Provides SSO and federation for web applications";
|
||||
after = lib.optionals cfg.fastcgi.enable [ "shibresponder.service" "shibauthorizer.service" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.shibboleth-sp}/bin/shibd -F -d ${pkgs.shibboleth-sp} -c ${cfg.configFile}";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.shibresponder = mkIf cfg.fastcgi.enable {
|
||||
description = "Provides SSO through Shibboleth via FastCGI";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ "${pkgs.spawn_fcgi}" ];
|
||||
environment.SHIBSP_CONFIG = "${cfg.configFile}";
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibResponderPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibresponder";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.shibauthorizer = mkIf cfg.fastcgi.enable {
|
||||
description = "Provides SSO through Shibboleth via FastCGI";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ "${pkgs.spawn_fcgi}" ];
|
||||
environment.SHIBSP_CONFIG = "${cfg.configFile}";
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.spawn_fcgi}/bin/spawn-fcgi -n -p ${toString cfg.fastcgi.shibAuthorizerPort} ${pkgs.shibboleth-sp}/lib/shibboleth/shibauthorizer";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ jammerful ];
|
||||
}
|
||||
146
nixos/modules/services/security/sks.nix
Normal file
146
nixos/modules/services/security/sks.nix
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.sks;
|
||||
sksPkg = cfg.package;
|
||||
dbConfig = pkgs.writeText "DB_CONFIG" ''
|
||||
${cfg.extraDbConfig}
|
||||
'';
|
||||
|
||||
in {
|
||||
meta.maintainers = with maintainers; [ primeos calbrecht jcumming ];
|
||||
|
||||
options = {
|
||||
|
||||
services.sks = {
|
||||
|
||||
enable = mkEnableOption ''
|
||||
SKS (synchronizing key server for OpenPGP) and start the database
|
||||
server. You need to create "''${dataDir}/dump/*.gpg" for the initial
|
||||
import'';
|
||||
|
||||
package = mkOption {
|
||||
default = pkgs.sks;
|
||||
defaultText = literalExpression "pkgs.sks";
|
||||
type = types.package;
|
||||
description = "Which SKS derivation to use.";
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/db/sks";
|
||||
example = "/var/lib/sks";
|
||||
# TODO: The default might change to "/var/lib/sks" as this is more
|
||||
# common. There's also https://github.com/NixOS/nixpkgs/issues/26256
|
||||
# and "/var/db" is not FHS compliant (seems to come from BSD).
|
||||
description = ''
|
||||
Data directory (-basedir) for SKS, where the database and all
|
||||
configuration files are located (e.g. KDB, PTree, membership and
|
||||
sksconf).
|
||||
'';
|
||||
};
|
||||
|
||||
extraDbConfig = mkOption {
|
||||
type = types.str;
|
||||
default = "";
|
||||
description = ''
|
||||
Set contents of the files "KDB/DB_CONFIG" and "PTree/DB_CONFIG" within
|
||||
the ''${dataDir} directory. This is used to configure options for the
|
||||
database for the sks key server.
|
||||
|
||||
Documentation of available options are available in the file named
|
||||
"sampleConfig/DB_CONFIG" in the following repository:
|
||||
https://bitbucket.org/skskeyserver/sks-keyserver/src
|
||||
'';
|
||||
};
|
||||
|
||||
hkpAddress = mkOption {
|
||||
default = [ "127.0.0.1" "::1" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Domain names, IPv4 and/or IPv6 addresses to listen on for HKP
|
||||
requests.
|
||||
'';
|
||||
};
|
||||
|
||||
hkpPort = mkOption {
|
||||
default = 11371;
|
||||
type = types.ints.u16;
|
||||
description = "HKP port to listen on.";
|
||||
};
|
||||
|
||||
webroot = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = "${sksPkg.webSamples}/OpenPKG";
|
||||
defaultText = literalExpression ''"''${package.webSamples}/OpenPKG"'';
|
||||
description = ''
|
||||
Source directory (will be symlinked, if not null) for the files the
|
||||
built-in webserver should serve. SKS (''${pkgs.sks.webSamples})
|
||||
provides the following examples: "HTML5", "OpenPKG", and "XHTML+ES".
|
||||
The index file can be named index.html, index.htm, index.xhtm, or
|
||||
index.xhtml. Files with the extensions .css, .es, .js, .jpg, .jpeg,
|
||||
.png, or .gif are supported. Subdirectories and filenames with
|
||||
anything other than alphanumeric characters and the '.' character
|
||||
will be ignored.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users = {
|
||||
users.sks = {
|
||||
isSystemUser = true;
|
||||
description = "SKS user";
|
||||
home = cfg.dataDir;
|
||||
createHome = true;
|
||||
group = "sks";
|
||||
useDefaultShell = true;
|
||||
packages = [ sksPkg pkgs.db ];
|
||||
};
|
||||
groups.sks = { };
|
||||
};
|
||||
|
||||
systemd.services = let
|
||||
hkpAddress = "'" + (builtins.concatStringsSep " " cfg.hkpAddress) + "'" ;
|
||||
hkpPort = builtins.toString cfg.hkpPort;
|
||||
in {
|
||||
sks-db = {
|
||||
description = "SKS database server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
preStart = ''
|
||||
${lib.optionalString (cfg.webroot != null)
|
||||
"ln -sfT \"${cfg.webroot}\" web"}
|
||||
mkdir -p dump
|
||||
${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/
|
||||
${sksPkg}/bin/sks cleandb || true
|
||||
${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true
|
||||
# Check that both database configs are symlinks before overwriting them
|
||||
# TODO: The initial build will be without DB_CONFIG, but this will
|
||||
# hopefully not cause any significant problems. It might be better to
|
||||
# create both directories manually but we have to check that this does
|
||||
# not affect the initial build of the DB.
|
||||
for CONFIG_FILE in KDB/DB_CONFIG PTree/DB_CONFIG; do
|
||||
if [ -e $CONFIG_FILE ] && [ ! -L $CONFIG_FILE ]; then
|
||||
echo "$CONFIG_FILE exists but is not a symlink." >&2
|
||||
echo "Please remove $PWD/$CONFIG_FILE manually to continue." >&2
|
||||
exit 1
|
||||
fi
|
||||
ln -sf ${dbConfig} $CONFIG_FILE
|
||||
done
|
||||
'';
|
||||
serviceConfig = {
|
||||
WorkingDirectory = "~";
|
||||
User = "sks";
|
||||
Group = "sks";
|
||||
Restart = "always";
|
||||
ExecStart = "${sksPkg}/bin/sks db -hkp_address ${hkpAddress} -hkp_port ${hkpPort}";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
161
nixos/modules/services/security/sshguard.nix
Normal file
161
nixos/modules/services/security/sshguard.nix
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.sshguard;
|
||||
|
||||
configFile = let
|
||||
args = lib.concatStringsSep " " ([
|
||||
"-afb"
|
||||
"-p info"
|
||||
"-o cat"
|
||||
"-n1"
|
||||
] ++ (map (name: "-t ${escapeShellArg name}") cfg.services));
|
||||
backend = if config.networking.nftables.enable
|
||||
then "sshg-fw-nft-sets"
|
||||
else "sshg-fw-ipset";
|
||||
in pkgs.writeText "sshguard.conf" ''
|
||||
BACKEND="${pkgs.sshguard}/libexec/${backend}"
|
||||
LOGREADER="LANG=C ${config.systemd.package}/bin/journalctl ${args}"
|
||||
'';
|
||||
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.sshguard = {
|
||||
enable = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Whether to enable the sshguard service.";
|
||||
};
|
||||
|
||||
attack_threshold = mkOption {
|
||||
default = 30;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Block attackers when their cumulative attack score exceeds threshold. Most attacks have a score of 10.
|
||||
'';
|
||||
};
|
||||
|
||||
blacklist_threshold = mkOption {
|
||||
default = null;
|
||||
example = 120;
|
||||
type = types.nullOr types.int;
|
||||
description = ''
|
||||
Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
|
||||
'';
|
||||
};
|
||||
|
||||
blacklist_file = mkOption {
|
||||
default = "/var/lib/sshguard/blacklist.db";
|
||||
type = types.path;
|
||||
description = ''
|
||||
Blacklist an attacker when its score exceeds threshold. Blacklisted addresses are loaded from and added to blacklist-file.
|
||||
'';
|
||||
};
|
||||
|
||||
blocktime = mkOption {
|
||||
default = 120;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Block attackers for initially blocktime seconds after exceeding threshold. Subsequent blocks increase by a factor of 1.5.
|
||||
|
||||
sshguard unblocks attacks at random intervals, so actual block times will be longer.
|
||||
'';
|
||||
};
|
||||
|
||||
detection_time = mkOption {
|
||||
default = 1800;
|
||||
type = types.int;
|
||||
description = ''
|
||||
Remember potential attackers for up to detection_time seconds before resetting their score.
|
||||
'';
|
||||
};
|
||||
|
||||
whitelist = mkOption {
|
||||
default = [ ];
|
||||
example = [ "198.51.100.56" "198.51.100.2" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Whitelist a list of addresses, hostnames, or address blocks.
|
||||
'';
|
||||
};
|
||||
|
||||
services = mkOption {
|
||||
default = [ "sshd" ];
|
||||
example = [ "sshd" "exim" ];
|
||||
type = types.listOf types.str;
|
||||
description = ''
|
||||
Systemd services sshguard should receive logs of.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.etc."sshguard.conf".source = configFile;
|
||||
|
||||
systemd.services.sshguard = {
|
||||
description = "SSHGuard brute-force attacks protection system";
|
||||
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
partOf = optional config.networking.firewall.enable "firewall.service";
|
||||
|
||||
restartTriggers = [ configFile ];
|
||||
|
||||
path = with pkgs; if config.networking.nftables.enable
|
||||
then [ nftables iproute2 systemd ]
|
||||
else [ iptables ipset iproute2 systemd ];
|
||||
|
||||
# The sshguard ipsets must exist before we invoke
|
||||
# iptables. sshguard creates the ipsets after startup if
|
||||
# necessary, but if we let sshguard do it, we can't reliably add
|
||||
# the iptables rules because postStart races with the creation
|
||||
# of the ipsets. So instead, we create both the ipsets and
|
||||
# firewall rules before sshguard starts.
|
||||
preStart = optionalString config.networking.firewall.enable ''
|
||||
${pkgs.ipset}/bin/ipset -quiet create -exist sshguard4 hash:net family inet
|
||||
${pkgs.iptables}/bin/iptables -I INPUT -m set --match-set sshguard4 src -j DROP
|
||||
'' + optionalString (config.networking.firewall.enable && config.networking.enableIPv6) ''
|
||||
${pkgs.ipset}/bin/ipset -quiet create -exist sshguard6 hash:net family inet6
|
||||
${pkgs.iptables}/bin/ip6tables -I INPUT -m set --match-set sshguard6 src -j DROP
|
||||
'';
|
||||
|
||||
postStop = optionalString config.networking.firewall.enable ''
|
||||
${pkgs.iptables}/bin/iptables -D INPUT -m set --match-set sshguard4 src -j DROP
|
||||
${pkgs.ipset}/bin/ipset -quiet destroy sshguard4
|
||||
'' + optionalString (config.networking.firewall.enable && config.networking.enableIPv6) ''
|
||||
${pkgs.iptables}/bin/ip6tables -D INPUT -m set --match-set sshguard6 src -j DROP
|
||||
${pkgs.ipset}/bin/ipset -quiet destroy sshguard6
|
||||
'';
|
||||
|
||||
unitConfig.Documentation = "man:sshguard(8)";
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = let
|
||||
args = lib.concatStringsSep " " ([
|
||||
"-a ${toString cfg.attack_threshold}"
|
||||
"-p ${toString cfg.blocktime}"
|
||||
"-s ${toString cfg.detection_time}"
|
||||
(optionalString (cfg.blacklist_threshold != null) "-b ${toString cfg.blacklist_threshold}:${cfg.blacklist_file}")
|
||||
] ++ (map (name: "-w ${escapeShellArg name}") cfg.whitelist));
|
||||
in "${pkgs.sshguard}/bin/sshguard ${args}";
|
||||
Restart = "always";
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = "tmpfs";
|
||||
RuntimeDirectory = "sshguard";
|
||||
StateDirectory = "sshguard";
|
||||
CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
32
nixos/modules/services/security/sslmate-agent.nix
Normal file
32
nixos/modules/services/security/sslmate-agent.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.sslmate-agent;
|
||||
|
||||
in {
|
||||
meta.maintainers = with maintainers; [ wolfangaukang ];
|
||||
|
||||
options = {
|
||||
services.sslmate-agent = {
|
||||
enable = mkEnableOption "sslmate-agent, a daemon for managing SSL/TLS certificates on a server";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = with pkgs; [ sslmate-agent ];
|
||||
|
||||
systemd = {
|
||||
packages = [ pkgs.sslmate-agent ];
|
||||
services.sslmate-agent = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
ConfigurationDirectory = "sslmate-agent";
|
||||
LogsDirectory = "sslmate-agent";
|
||||
StateDirectory = "sslmate-agent";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
146
nixos/modules/services/security/step-ca.nix
Normal file
146
nixos/modules/services/security/step-ca.nix
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.services.step-ca;
|
||||
settingsFormat = (pkgs.formats.json { });
|
||||
in
|
||||
{
|
||||
meta.maintainers = with lib.maintainers; [ mohe2015 ];
|
||||
|
||||
options = {
|
||||
services.step-ca = {
|
||||
enable = lib.mkEnableOption "the smallstep certificate authority server";
|
||||
openFirewall = lib.mkEnableOption "opening the certificate authority server port";
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.step-ca;
|
||||
defaultText = lib.literalExpression "pkgs.step-ca";
|
||||
description = "Which step-ca package to use.";
|
||||
};
|
||||
address = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
example = "127.0.0.1";
|
||||
description = ''
|
||||
The address (without port) the certificate authority should listen at.
|
||||
This combined with <option>services.step-ca.port</option> overrides <option>services.step-ca.settings.address</option>.
|
||||
'';
|
||||
};
|
||||
port = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
example = 8443;
|
||||
description = ''
|
||||
The port the certificate authority should listen on.
|
||||
This combined with <option>services.step-ca.address</option> overrides <option>services.step-ca.settings.address</option>.
|
||||
'';
|
||||
};
|
||||
settings = lib.mkOption {
|
||||
type = with lib.types; attrsOf anything;
|
||||
description = ''
|
||||
Settings that go into <filename>ca.json</filename>. See
|
||||
<link xlink:href="https://smallstep.com/docs/step-ca/configuration">
|
||||
the step-ca manual</link> for more information. The easiest way to
|
||||
configure this module would be to run <literal>step ca init</literal>
|
||||
to generate <filename>ca.json</filename> and then import it using
|
||||
<literal>builtins.fromJSON</literal>.
|
||||
<link xlink:href="https://smallstep.com/docs/step-cli/basic-crypto-operations#run-an-offline-x509-certificate-authority">This article</link>
|
||||
may also be useful if you want to customize certain aspects of
|
||||
certificate generation for your CA.
|
||||
You need to change the database storage path to <filename>/var/lib/step-ca/db</filename>.
|
||||
|
||||
<warning>
|
||||
<para>
|
||||
The <option>services.step-ca.settings.address</option> option
|
||||
will be ignored and overwritten by
|
||||
<option>services.step-ca.address</option> and
|
||||
<option>services.step-ca.port</option>.
|
||||
</para>
|
||||
</warning>
|
||||
'';
|
||||
};
|
||||
intermediatePasswordFile = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
example = "/run/keys/smallstep-password";
|
||||
description = ''
|
||||
Path to the file containing the password for the intermediate
|
||||
certificate private key.
|
||||
|
||||
<warning>
|
||||
<para>
|
||||
Make sure to use a quoted absolute path instead of a path literal
|
||||
to prevent it from being copied to the globally readable Nix
|
||||
store.
|
||||
</para>
|
||||
</warning>
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.services.step-ca.enable (
|
||||
let
|
||||
configFile = settingsFormat.generate "ca.json" (cfg.settings // {
|
||||
address = cfg.address + ":" + toString cfg.port;
|
||||
});
|
||||
in
|
||||
{
|
||||
assertions =
|
||||
[
|
||||
{
|
||||
assertion = !lib.isStorePath cfg.intermediatePasswordFile;
|
||||
message = ''
|
||||
<option>services.step-ca.intermediatePasswordFile</option> points to
|
||||
a file in the Nix store. You should use a quoted absolute path to
|
||||
prevent this.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
systemd.packages = [ cfg.package ];
|
||||
|
||||
# configuration file indirection is needed to support reloading
|
||||
environment.etc."smallstep/ca.json".source = configFile;
|
||||
|
||||
systemd.services."step-ca" = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartTriggers = [ configFile ];
|
||||
unitConfig = {
|
||||
ConditionFileNotEmpty = ""; # override upstream
|
||||
};
|
||||
serviceConfig = {
|
||||
User = "step-ca";
|
||||
Group = "step-ca";
|
||||
UMask = "0077";
|
||||
Environment = "HOME=%S/step-ca";
|
||||
WorkingDirectory = ""; # override upstream
|
||||
ReadWriteDirectories = ""; # override upstream
|
||||
|
||||
# LocalCredential handles file permission problems arising from the use of DynamicUser.
|
||||
LoadCredential = "intermediate_password:${cfg.intermediatePasswordFile}";
|
||||
|
||||
ExecStart = [
|
||||
"" # override upstream
|
||||
"${cfg.package}/bin/step-ca /etc/smallstep/ca.json --password-file \${CREDENTIALS_DIRECTORY}/intermediate_password"
|
||||
];
|
||||
|
||||
# ProtectProc = "invisible"; # not supported by upstream yet
|
||||
# ProcSubset = "pid"; # not supported by upstream yet
|
||||
# PrivateUsers = true; # doesn't work with privileged ports therefore not supported by upstream
|
||||
|
||||
DynamicUser = true;
|
||||
StateDirectory = "step-ca";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.step-ca = {
|
||||
home = "/var/lib/step-ca";
|
||||
group = "step-ca";
|
||||
isSystemUser = true;
|
||||
};
|
||||
|
||||
users.groups.step-ca = {};
|
||||
|
||||
networking.firewall = lib.mkIf cfg.openFirewall {
|
||||
allowedTCPPorts = [ cfg.port ];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
1067
nixos/modules/services/security/tor.nix
Normal file
1067
nixos/modules/services/security/tor.nix
Normal file
File diff suppressed because it is too large
Load diff
80
nixos/modules/services/security/torify.nix
Normal file
80
nixos/modules/services/security/torify.nix
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
with lib;
|
||||
let
|
||||
|
||||
cfg = config.services.tor;
|
||||
|
||||
torify = pkgs.writeTextFile {
|
||||
name = "tsocks";
|
||||
text = ''
|
||||
#!${pkgs.runtimeShell}
|
||||
TSOCKS_CONF_FILE=${pkgs.writeText "tsocks.conf" cfg.tsocks.config} LD_PRELOAD="${pkgs.tsocks}/lib/libtsocks.so $LD_PRELOAD" "$@"
|
||||
'';
|
||||
executable = true;
|
||||
destination = "/bin/tsocks";
|
||||
};
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
|
||||
services.tor.tsocks = {
|
||||
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to build tsocks wrapper script to relay application traffic via Tor.
|
||||
|
||||
<important>
|
||||
<para>You shouldn't use this unless you know what you're
|
||||
doing because your installation of Tor already comes with
|
||||
its own superior (doesn't leak DNS queries)
|
||||
<literal>torsocks</literal> wrapper which does pretty much
|
||||
exactly the same thing as this.</para>
|
||||
</important>
|
||||
'';
|
||||
};
|
||||
|
||||
server = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost:9050";
|
||||
example = "192.168.0.20";
|
||||
description = ''
|
||||
IP address of TOR client to use.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
Extra configuration. Contents will be added verbatim to TSocks
|
||||
configuration file.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.tsocks.enable {
|
||||
|
||||
environment.systemPackages = [ torify ]; # expose it to the users
|
||||
|
||||
services.tor.tsocks.config = ''
|
||||
server = ${toString(head (splitString ":" cfg.tsocks.server))}
|
||||
server_port = ${toString(tail (splitString ":" cfg.tsocks.server))}
|
||||
|
||||
local = 127.0.0.0/255.128.0.0
|
||||
local = 127.128.0.0/255.192.0.0
|
||||
'';
|
||||
};
|
||||
|
||||
}
|
||||
121
nixos/modules/services/security/torsocks.nix
Normal file
121
nixos/modules/services/security/torsocks.nix
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.tor.torsocks;
|
||||
optionalNullStr = b: v: optionalString (b != null) v;
|
||||
|
||||
configFile = server: ''
|
||||
TorAddress ${toString (head (splitString ":" server))}
|
||||
TorPort ${toString (tail (splitString ":" server))}
|
||||
|
||||
OnionAddrRange ${cfg.onionAddrRange}
|
||||
|
||||
${optionalNullStr cfg.socks5Username
|
||||
"SOCKS5Username ${cfg.socks5Username}"}
|
||||
${optionalNullStr cfg.socks5Password
|
||||
"SOCKS5Password ${cfg.socks5Password}"}
|
||||
|
||||
AllowInbound ${if cfg.allowInbound then "1" else "0"}
|
||||
'';
|
||||
|
||||
wrapTorsocks = name: server: pkgs.writeTextFile {
|
||||
name = name;
|
||||
text = ''
|
||||
#!${pkgs.runtimeShell}
|
||||
TORSOCKS_CONF_FILE=${pkgs.writeText "torsocks.conf" (configFile server)} ${pkgs.torsocks}/bin/torsocks "$@"
|
||||
'';
|
||||
executable = true;
|
||||
destination = "/bin/${name}";
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
options = {
|
||||
services.tor.torsocks = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = config.services.tor.enable && config.services.tor.client.enable;
|
||||
defaultText = literalExpression "config.services.tor.enable && config.services.tor.client.enable";
|
||||
description = ''
|
||||
Whether to build <literal>/etc/tor/torsocks.conf</literal>
|
||||
containing the specified global torsocks configuration.
|
||||
'';
|
||||
};
|
||||
|
||||
server = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1:9050";
|
||||
example = "192.168.0.20:1234";
|
||||
description = ''
|
||||
IP/Port of the Tor SOCKS server. Currently, hostnames are
|
||||
NOT supported by torsocks.
|
||||
'';
|
||||
};
|
||||
|
||||
fasterServer = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1:9063";
|
||||
example = "192.168.0.20:1234";
|
||||
description = ''
|
||||
IP/Port of the Tor SOCKS server for torsocks-faster wrapper suitable for HTTP.
|
||||
Currently, hostnames are NOT supported by torsocks.
|
||||
'';
|
||||
};
|
||||
|
||||
onionAddrRange = mkOption {
|
||||
type = types.str;
|
||||
default = "127.42.42.0/24";
|
||||
description = ''
|
||||
Tor hidden sites do not have real IP addresses. This
|
||||
specifies what range of IP addresses will be handed to the
|
||||
application as "cookies" for .onion names. Of course, you
|
||||
should pick a block of addresses which you aren't going to
|
||||
ever need to actually connect to. This is similar to the
|
||||
MapAddress feature of the main tor daemon.
|
||||
'';
|
||||
};
|
||||
|
||||
socks5Username = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "bob";
|
||||
description = ''
|
||||
SOCKS5 username. The <literal>TORSOCKS_USERNAME</literal>
|
||||
environment variable overrides this option if it is set.
|
||||
'';
|
||||
};
|
||||
|
||||
socks5Password = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "sekret";
|
||||
description = ''
|
||||
SOCKS5 password. The <literal>TORSOCKS_PASSWORD</literal>
|
||||
environment variable overrides this option if it is set.
|
||||
'';
|
||||
};
|
||||
|
||||
allowInbound = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Set Torsocks to accept inbound connections. If set to
|
||||
<literal>true</literal>, listen() and accept() will be
|
||||
allowed to be used with non localhost address.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ pkgs.torsocks (wrapTorsocks "torsocks-faster" cfg.fasterServer) ];
|
||||
|
||||
environment.etc."tor/torsocks.conf" =
|
||||
{
|
||||
source = pkgs.writeText "torsocks.conf" (configFile cfg.server);
|
||||
};
|
||||
};
|
||||
}
|
||||
214
nixos/modules/services/security/usbguard.nix
Normal file
214
nixos/modules/services/security/usbguard.nix
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.usbguard;
|
||||
|
||||
# valid policy options
|
||||
policy = (types.enum [ "allow" "block" "reject" "keep" "apply-policy" ]);
|
||||
|
||||
defaultRuleFile = "/var/lib/usbguard/rules.conf";
|
||||
|
||||
# decide what file to use for rules
|
||||
ruleFile = if cfg.rules != null then pkgs.writeText "usbguard-rules" cfg.rules else defaultRuleFile;
|
||||
|
||||
daemonConf = ''
|
||||
# generated by nixos/modules/services/security/usbguard.nix
|
||||
RuleFile=${ruleFile}
|
||||
ImplicitPolicyTarget=${cfg.implictPolicyTarget}
|
||||
PresentDevicePolicy=${cfg.presentDevicePolicy}
|
||||
PresentControllerPolicy=${cfg.presentControllerPolicy}
|
||||
InsertedDevicePolicy=${cfg.insertedDevicePolicy}
|
||||
RestoreControllerDeviceState=${boolToString cfg.restoreControllerDeviceState}
|
||||
# this does not seem useful for endusers to change
|
||||
DeviceManagerBackend=uevent
|
||||
IPCAllowedUsers=${concatStringsSep " " cfg.IPCAllowedUsers}
|
||||
IPCAllowedGroups=${concatStringsSep " " cfg.IPCAllowedGroups}
|
||||
IPCAccessControlFiles=/var/lib/usbguard/IPCAccessControl.d/
|
||||
DeviceRulesWithPort=${boolToString cfg.deviceRulesWithPort}
|
||||
# HACK: that way audit logs still land in the journal
|
||||
AuditFilePath=/dev/null
|
||||
'';
|
||||
|
||||
daemonConfFile = pkgs.writeText "usbguard-daemon-conf" daemonConf;
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.usbguard = {
|
||||
enable = mkEnableOption "USBGuard daemon";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.usbguard;
|
||||
defaultText = literalExpression "pkgs.usbguard";
|
||||
description = ''
|
||||
The usbguard package to use. If you do not need the Qt GUI, use
|
||||
<literal>pkgs.usbguard-nox</literal> to save disk space.
|
||||
'';
|
||||
};
|
||||
|
||||
rules = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
example = ''
|
||||
allow with-interface equals { 08:*:* }
|
||||
'';
|
||||
description = ''
|
||||
The USBGuard daemon will load this as the policy rule set.
|
||||
As these rules are NixOS managed they are immutable and can't
|
||||
be changed by the IPC interface.
|
||||
|
||||
If you do not set this option, the USBGuard daemon will load
|
||||
it's policy rule set from <literal>${defaultRuleFile}</literal>.
|
||||
This file can be changed manually or via the IPC interface.
|
||||
|
||||
Running <literal>usbguard generate-policy</literal> as root will
|
||||
generate a config for your currently plugged in devices.
|
||||
|
||||
For more details see <citerefentry>
|
||||
<refentrytitle>usbguard-rules.conf</refentrytitle>
|
||||
<manvolnum>5</manvolnum></citerefentry>.
|
||||
'';
|
||||
};
|
||||
|
||||
implictPolicyTarget = mkOption {
|
||||
type = policy;
|
||||
default = "block";
|
||||
description = ''
|
||||
How to treat USB devices that don't match any rule in the policy.
|
||||
Target should be one of allow, block or reject (logically remove the
|
||||
device node from the system).
|
||||
'';
|
||||
};
|
||||
|
||||
presentDevicePolicy = mkOption {
|
||||
type = policy;
|
||||
default = "apply-policy";
|
||||
description = ''
|
||||
How to treat USB devices that are already connected when the daemon
|
||||
starts. Policy should be one of allow, block, reject, keep (keep
|
||||
whatever state the device is currently in) or apply-policy (evaluate
|
||||
the rule set for every present device).
|
||||
'';
|
||||
};
|
||||
|
||||
presentControllerPolicy = mkOption {
|
||||
type = policy;
|
||||
default = "keep";
|
||||
description = ''
|
||||
How to treat USB controller devices that are already connected when
|
||||
the daemon starts. One of allow, block, reject, keep or apply-policy.
|
||||
'';
|
||||
};
|
||||
|
||||
insertedDevicePolicy = mkOption {
|
||||
type = policy;
|
||||
default = "apply-policy";
|
||||
description = ''
|
||||
How to treat USB devices that are already connected after the daemon
|
||||
starts. One of block, reject, apply-policy.
|
||||
'';
|
||||
};
|
||||
|
||||
restoreControllerDeviceState = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
The USBGuard daemon modifies some attributes of controller
|
||||
devices like the default authorization state of new child device
|
||||
instances. Using this setting, you can controll whether the daemon
|
||||
will try to restore the attribute values to the state before
|
||||
modificaton on shutdown.
|
||||
'';
|
||||
};
|
||||
|
||||
IPCAllowedUsers = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ "root" ];
|
||||
example = [ "root" "yourusername" ];
|
||||
description = ''
|
||||
A list of usernames that the daemon will accept IPC connections from.
|
||||
'';
|
||||
};
|
||||
|
||||
IPCAllowedGroups = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
example = [ "wheel" ];
|
||||
description = ''
|
||||
A list of groupnames that the daemon will accept IPC connections
|
||||
from.
|
||||
'';
|
||||
};
|
||||
|
||||
deviceRulesWithPort = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Generate device specific rules including the "via-port" attribute.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
systemd.services.usbguard = {
|
||||
description = "USBGuard daemon";
|
||||
|
||||
wantedBy = [ "basic.target" ];
|
||||
wants = [ "systemd-udevd.service" ];
|
||||
|
||||
# make sure an empty rule file exists
|
||||
preStart = ''[ -f "${ruleFile}" ] || touch ${ruleFile}'';
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
ExecStart = "${cfg.package}/bin/usbguard-daemon -P -k -c ${daemonConfFile}";
|
||||
Restart = "on-failure";
|
||||
|
||||
StateDirectory = [
|
||||
"usbguard"
|
||||
"usbguard/IPCAccessControl.d"
|
||||
];
|
||||
|
||||
AmbientCapabilities = "";
|
||||
CapabilityBoundingSet = "CAP_CHOWN CAP_FOWNER";
|
||||
DeviceAllow = "/dev/null rw";
|
||||
DevicePolicy = "strict";
|
||||
IPAddressDeny = "any";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
ProtectControlGroups = true;
|
||||
ProtectHome = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectSystem = true;
|
||||
ReadOnlyPaths = "-/";
|
||||
ReadWritePaths = "-/dev/shm -/tmp";
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_NETLINK" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
SystemCallArchitectures = "native";
|
||||
SystemCallFilter = "@system-service";
|
||||
UMask = "0077";
|
||||
};
|
||||
};
|
||||
};
|
||||
imports = [
|
||||
(mkRemovedOptionModule [ "services" "usbguard" "ruleFile" ] "The usbguard module now uses ${defaultRuleFile} as ruleFile. Alternatively, use services.usbguard.rules to configure rules.")
|
||||
(mkRemovedOptionModule [ "services" "usbguard" "IPCAccessControlFiles" ] "The usbguard module now hardcodes IPCAccessControlFiles to /var/lib/usbguard/IPCAccessControl.d.")
|
||||
(mkRemovedOptionModule [ "services" "usbguard" "auditFilePath" ] "Removed usbguard module audit log files. Audit logs can be found in the systemd journal.")
|
||||
];
|
||||
}
|
||||
204
nixos/modules/services/security/vault.nix
Normal file
204
nixos/modules/services/security/vault.nix
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.vault;
|
||||
opt = options.services.vault;
|
||||
|
||||
configFile = pkgs.writeText "vault.hcl" ''
|
||||
listener "tcp" {
|
||||
address = "${cfg.address}"
|
||||
${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
|
||||
tls_disable = "true"
|
||||
'' else ''
|
||||
tls_cert_file = "${cfg.tlsCertFile}"
|
||||
tls_key_file = "${cfg.tlsKeyFile}"
|
||||
''}
|
||||
${cfg.listenerExtraConfig}
|
||||
}
|
||||
storage "${cfg.storageBackend}" {
|
||||
${optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''}
|
||||
${optionalString (cfg.storageConfig != null) cfg.storageConfig}
|
||||
}
|
||||
${optionalString (cfg.telemetryConfig != "") ''
|
||||
telemetry {
|
||||
${cfg.telemetryConfig}
|
||||
}
|
||||
''}
|
||||
${cfg.extraConfig}
|
||||
'';
|
||||
|
||||
allConfigPaths = [configFile] ++ cfg.extraSettingsPaths;
|
||||
|
||||
configOptions = escapeShellArgs (concatMap (p: ["-config" p]) allConfigPaths);
|
||||
|
||||
in
|
||||
|
||||
{
|
||||
options = {
|
||||
services.vault = {
|
||||
enable = mkEnableOption "Vault daemon";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.vault;
|
||||
defaultText = literalExpression "pkgs.vault";
|
||||
description = "This option specifies the vault package to use.";
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1:8200";
|
||||
description = "The name of the ip interface to listen to";
|
||||
};
|
||||
|
||||
tlsCertFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/path/to/your/cert.pem";
|
||||
description = "TLS certificate file. TLS will be disabled unless this option is set";
|
||||
};
|
||||
|
||||
tlsKeyFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
example = "/path/to/your/key.pem";
|
||||
description = "TLS private key file. TLS will be disabled unless this option is set";
|
||||
};
|
||||
|
||||
listenerExtraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = ''
|
||||
tls_min_version = "tls12"
|
||||
'';
|
||||
description = "Extra text appended to the listener section.";
|
||||
};
|
||||
|
||||
storageBackend = mkOption {
|
||||
type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ];
|
||||
default = "inmem";
|
||||
description = "The name of the type of storage backend";
|
||||
};
|
||||
|
||||
storagePath = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = if cfg.storageBackend == "file" then "/var/lib/vault" else null;
|
||||
defaultText = literalExpression ''
|
||||
if config.${opt.storageBackend} == "file"
|
||||
then "/var/lib/vault"
|
||||
else null
|
||||
'';
|
||||
description = "Data directory for file backend";
|
||||
};
|
||||
|
||||
storageConfig = mkOption {
|
||||
type = types.nullOr types.lines;
|
||||
default = null;
|
||||
description = ''
|
||||
HCL configuration to insert in the storageBackend section.
|
||||
|
||||
Confidential values should not be specified here because this option's
|
||||
value is written to the Nix store, which is publicly readable.
|
||||
Provide credentials and such in a separate file using
|
||||
<xref linkend="opt-services.vault.extraSettingsPaths"/>.
|
||||
'';
|
||||
};
|
||||
|
||||
telemetryConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Telemetry configuration";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = "Extra text appended to <filename>vault.hcl</filename>.";
|
||||
};
|
||||
|
||||
extraSettingsPaths = mkOption {
|
||||
type = types.listOf types.path;
|
||||
default = [];
|
||||
description = ''
|
||||
Configuration files to load besides the immutable one defined by the NixOS module.
|
||||
This can be used to avoid putting credentials in the Nix store, which can be read by any user.
|
||||
|
||||
Each path can point to a JSON- or HCL-formatted file, or a directory
|
||||
to be scanned for files with <literal>.hcl</literal> or
|
||||
<literal>.json</literal> extensions.
|
||||
|
||||
To upload the confidential file with NixOps, use for example:
|
||||
|
||||
<programlisting><![CDATA[
|
||||
# https://releases.nixos.org/nixops/latest/manual/manual.html#opt-deployment.keys
|
||||
deployment.keys."vault.hcl" = let db = import ./db-credentials.nix; in {
|
||||
text = ${"''"}
|
||||
storage "postgresql" {
|
||||
connection_url = "postgres://''${db.username}:''${db.password}@host.example.com/exampledb?sslmode=verify-ca"
|
||||
}
|
||||
${"''"};
|
||||
user = "vault";
|
||||
};
|
||||
services.vault.extraSettingsPaths = ["/run/keys/vault.hcl"];
|
||||
services.vault.storageBackend = "postgresql";
|
||||
users.users.vault.extraGroups = ["keys"];
|
||||
]]></programlisting>
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{ assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
|
||||
message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
|
||||
}
|
||||
{ assertion = (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) && (cfg.storagePath != null -> cfg.storageBackend == "file");
|
||||
message = ''You must set services.vault.storagePath only when using the "file" backend'';
|
||||
}
|
||||
];
|
||||
|
||||
users.users.vault = {
|
||||
name = "vault";
|
||||
group = "vault";
|
||||
uid = config.ids.uids.vault;
|
||||
description = "Vault daemon user";
|
||||
};
|
||||
users.groups.vault.gid = config.ids.gids.vault;
|
||||
|
||||
systemd.tmpfiles.rules = optional (cfg.storagePath != null)
|
||||
"d '${cfg.storagePath}' 0700 vault vault - -";
|
||||
|
||||
systemd.services.vault = {
|
||||
description = "Vault server daemon";
|
||||
|
||||
wantedBy = ["multi-user.target"];
|
||||
after = [ "network.target" ]
|
||||
++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
|
||||
|
||||
restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
|
||||
|
||||
startLimitIntervalSec = 60;
|
||||
startLimitBurst = 3;
|
||||
serviceConfig = {
|
||||
User = "vault";
|
||||
Group = "vault";
|
||||
ExecStart = "${cfg.package}/bin/vault server ${configOptions}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
|
||||
PrivateDevices = true;
|
||||
PrivateTmp = true;
|
||||
ProtectSystem = "full";
|
||||
ProtectHome = "read-only";
|
||||
AmbientCapabilities = "cap_ipc_lock";
|
||||
NoNewPrivileges = true;
|
||||
KillSignal = "SIGINT";
|
||||
TimeoutStopSec = "30s";
|
||||
Restart = "on-failure";
|
||||
};
|
||||
|
||||
unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
17
nixos/modules/services/security/vaultwarden/backup.sh
Normal file
17
nixos/modules/services/security/vaultwarden/backup.sh
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
# Based on: https://github.com/dani-garcia/vaultwarden/wiki/Backing-up-your-vault
|
||||
if ! mkdir -p "$BACKUP_FOLDER"; then
|
||||
echo "Could not create backup folder '$BACKUP_FOLDER'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$DATA_FOLDER"/db.sqlite3 ]]; then
|
||||
echo "Could not find SQLite database file '$DATA_FOLDER/db.sqlite3'" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
sqlite3 "$DATA_FOLDER"/db.sqlite3 ".backup '$BACKUP_FOLDER/db.sqlite3'"
|
||||
cp "$DATA_FOLDER"/rsa_key.{der,pem,pub.der} "$BACKUP_FOLDER"
|
||||
cp -r "$DATA_FOLDER"/attachments "$BACKUP_FOLDER"
|
||||
cp -r "$DATA_FOLDER"/icon_cache "$BACKUP_FOLDER"
|
||||
185
nixos/modules/services/security/vaultwarden/default.nix
Normal file
185
nixos/modules/services/security/vaultwarden/default.nix
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.vaultwarden;
|
||||
user = config.users.users.vaultwarden.name;
|
||||
group = config.users.groups.vaultwarden.name;
|
||||
|
||||
# Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
|
||||
nameToEnvVar = name:
|
||||
let
|
||||
parts = builtins.split "([A-Z0-9]+)" name;
|
||||
partsToEnvVar = parts: foldl' (key: x: let last = stringLength key - 1; in
|
||||
if isList x then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x
|
||||
else if key != "" && elem (substring 0 1 x) lowerChars then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ]
|
||||
substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x
|
||||
else key + toUpper x) "" parts;
|
||||
in if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts;
|
||||
|
||||
# Due to the different naming schemes allowed for config keys,
|
||||
# we can only check for values consistently after converting them to their corresponding environment variable name.
|
||||
configEnv =
|
||||
let
|
||||
configEnv = listToAttrs (concatLists (mapAttrsToList (name: value:
|
||||
if value != null then [ (nameValuePair (nameToEnvVar name) (if isBool value then boolToString value else toString value)) ] else []
|
||||
) cfg.config));
|
||||
in { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
|
||||
WEB_VAULT_FOLDER = "${cfg.webVaultPackage}/share/vaultwarden/vault";
|
||||
} // configEnv;
|
||||
|
||||
configFile = pkgs.writeText "vaultwarden.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
|
||||
|
||||
vaultwarden = cfg.package.override { inherit (cfg) dbBackend; };
|
||||
|
||||
in {
|
||||
imports = [
|
||||
(mkRenamedOptionModule [ "services" "bitwarden_rs" ] [ "services" "vaultwarden" ])
|
||||
];
|
||||
|
||||
options.services.vaultwarden = with types; {
|
||||
enable = mkEnableOption "vaultwarden";
|
||||
|
||||
dbBackend = mkOption {
|
||||
type = enum [ "sqlite" "mysql" "postgresql" ];
|
||||
default = "sqlite";
|
||||
description = ''
|
||||
Which database backend vaultwarden will be using.
|
||||
'';
|
||||
};
|
||||
|
||||
backupDir = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = ''
|
||||
The directory under which vaultwarden will backup its persistent data.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = attrsOf (nullOr (oneOf [ bool int str ]));
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
domain = "https://bw.domain.tld:8443";
|
||||
signupsAllowed = true;
|
||||
rocketPort = 8222;
|
||||
rocketLog = "critical";
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
The configuration of vaultwarden is done through environment variables,
|
||||
therefore the names are converted from camel case (e.g. disable2FARemember)
|
||||
to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
|
||||
In this conversion digits (0-9) are handled just like upper case characters,
|
||||
so foo2 would be converted to FOO_2.
|
||||
Names already in this format remain unchanged, so FOO2 remains FOO2 if passed as such,
|
||||
even though foo2 would have been converted to FOO_2.
|
||||
This allows working around any potential future conflicting naming conventions.
|
||||
|
||||
Based on the attributes passed to this config option an environment file will be generated
|
||||
that is passed to vaultwarden's systemd service.
|
||||
|
||||
The available configuration options can be found in
|
||||
<link xlink:href="https://github.com/dani-garcia/vaultwarden/blob/${vaultwarden.version}/.env.template">the environment template file</link>.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = with types; nullOr path;
|
||||
default = null;
|
||||
example = "/root/vaultwarden.env";
|
||||
description = ''
|
||||
Additional environment file as defined in <citerefentry>
|
||||
<refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
|
||||
</citerefentry>.
|
||||
|
||||
Secrets like <envar>ADMIN_TOKEN</envar> and <envar>SMTP_PASSWORD</envar>
|
||||
may be passed to the service without adding them to the world-readable Nix store.
|
||||
|
||||
Note that this file needs to be available on the host on which
|
||||
<literal>vaultwarden</literal> is running.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = package;
|
||||
default = pkgs.vaultwarden;
|
||||
defaultText = literalExpression "pkgs.vaultwarden";
|
||||
description = "Vaultwarden package to use.";
|
||||
};
|
||||
|
||||
webVaultPackage = mkOption {
|
||||
type = package;
|
||||
default = pkgs.vaultwarden-vault;
|
||||
defaultText = literalExpression "pkgs.vaultwarden-vault";
|
||||
description = "Web vault package to use.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [ {
|
||||
assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite";
|
||||
message = "Backups for database backends other than sqlite will need customization";
|
||||
} ];
|
||||
|
||||
users.users.vaultwarden = {
|
||||
inherit group;
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.vaultwarden = { };
|
||||
|
||||
systemd.services.vaultwarden = {
|
||||
aliases = [ "bitwarden_rs.service" ];
|
||||
after = [ "network.target" ];
|
||||
path = with pkgs; [ openssl ];
|
||||
serviceConfig = {
|
||||
User = user;
|
||||
Group = group;
|
||||
EnvironmentFile = [ configFile ] ++ optional (cfg.environmentFile != null) cfg.environmentFile;
|
||||
ExecStart = "${vaultwarden}/bin/vaultwarden";
|
||||
LimitNOFILE = "1048576";
|
||||
PrivateTmp = "true";
|
||||
PrivateDevices = "true";
|
||||
ProtectHome = "true";
|
||||
ProtectSystem = "strict";
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
StateDirectory = "bitwarden_rs";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
systemd.services.backup-vaultwarden = mkIf (cfg.backupDir != null) {
|
||||
aliases = [ "backup-bitwarden_rs.service" ];
|
||||
description = "Backup vaultwarden";
|
||||
environment = {
|
||||
DATA_FOLDER = "/var/lib/bitwarden_rs";
|
||||
BACKUP_FOLDER = cfg.backupDir;
|
||||
};
|
||||
path = with pkgs; [ sqlite ];
|
||||
serviceConfig = {
|
||||
SyslogIdentifier = "backup-vaultwarden";
|
||||
Type = "oneshot";
|
||||
User = mkDefault user;
|
||||
Group = mkDefault group;
|
||||
ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
systemd.timers.backup-vaultwarden = mkIf (cfg.backupDir != null) {
|
||||
aliases = [ "backup-bitwarden_rs.service" ];
|
||||
description = "Backup vaultwarden on time";
|
||||
timerConfig = {
|
||||
OnCalendar = mkDefault "23:00";
|
||||
Persistent = "true";
|
||||
Unit = "backup-vaultwarden.service";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
};
|
||||
|
||||
# uses attributes of the linked package
|
||||
meta.buildDocsInSandbox = false;
|
||||
}
|
||||
66
nixos/modules/services/security/yubikey-agent.nix
Normal file
66
nixos/modules/services/security/yubikey-agent.nix
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Global configuration for yubikey-agent.
|
||||
|
||||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.yubikey-agent;
|
||||
|
||||
# reuse the pinentryFlavor option from the gnupg module
|
||||
pinentryFlavor = config.programs.gnupg.agent.pinentryFlavor;
|
||||
in
|
||||
{
|
||||
###### interface
|
||||
|
||||
meta.maintainers = with maintainers; [ philandstuff rawkode jwoudenberg ];
|
||||
|
||||
options = {
|
||||
|
||||
services.yubikey-agent = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to start yubikey-agent when you log in. Also sets
|
||||
SSH_AUTH_SOCK to point at yubikey-agent.
|
||||
|
||||
Note that yubikey-agent will use whatever pinentry is
|
||||
specified in programs.gnupg.agent.pinentryFlavor.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.yubikey-agent;
|
||||
defaultText = literalExpression "pkgs.yubikey-agent";
|
||||
description = ''
|
||||
The package used for the yubikey-agent daemon.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
systemd.packages = [ cfg.package ];
|
||||
|
||||
# This overrides the systemd user unit shipped with the
|
||||
# yubikey-agent package
|
||||
systemd.user.services.yubikey-agent = mkIf (pinentryFlavor != null) {
|
||||
path = [ pkgs.pinentry.${pinentryFlavor} ];
|
||||
wantedBy = [
|
||||
(if pinentryFlavor == "tty" || pinentryFlavor == "curses" then
|
||||
"default.target"
|
||||
else
|
||||
"graphical-session.target")
|
||||
];
|
||||
};
|
||||
|
||||
environment.extraInit = ''
|
||||
if [ -z "$SSH_AUTH_SOCK" -a -n "$XDG_RUNTIME_DIR" ]; then
|
||||
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/yubikey-agent/yubikey-agent.sock"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue