nixos/warpgate: init
This commit is contained in:
@@ -80,6 +80,8 @@
|
|||||||
|
|
||||||
- [Corteza](https://cortezaproject.org/), a low-code platform. Available as [services.corteza](#opt-services.corteza.enable).
|
- [Corteza](https://cortezaproject.org/), a low-code platform. Available as [services.corteza](#opt-services.corteza.enable).
|
||||||
|
|
||||||
|
- [Warpgate](https://warpgate.null.page), a SSH, HTTPS, MySQL and Postgres bastion. Available as [services.warpgate](#opt-services.warpgate.enable). Note that you need to run `warpgate recover-access` to recover builtin admin account, as the initialisation script uses a throwaway value to initialise its database.
|
||||||
|
|
||||||
- [TuneD](https://tuned-project.org/), a system tuning service for Linux. Available as [services.tuned](#opt-services.tuned.enable).
|
- [TuneD](https://tuned-project.org/), a system tuning service for Linux. Available as [services.tuned](#opt-services.tuned.enable).
|
||||||
|
|
||||||
- [yubikey-manager](https://github.com/Yubico/yubikey-manager), a tool for configuring YubiKey devices. Available as [programs.yubikey-manager](#opt-programs.yubikey-manager.enable).
|
- [yubikey-manager](https://github.com/Yubico/yubikey-manager), a tool for configuring YubiKey devices. Available as [programs.yubikey-manager](#opt-programs.yubikey-manager.enable).
|
||||||
|
|||||||
@@ -1497,6 +1497,7 @@
|
|||||||
./services/security/vault-agent.nix
|
./services/security/vault-agent.nix
|
||||||
./services/security/vault.nix
|
./services/security/vault.nix
|
||||||
./services/security/vaultwarden/default.nix
|
./services/security/vaultwarden/default.nix
|
||||||
|
./services/security/warpgate.nix
|
||||||
./services/security/yubikey-agent.nix
|
./services/security/yubikey-agent.nix
|
||||||
./services/system/automatic-timezoned.nix
|
./services/system/automatic-timezoned.nix
|
||||||
./services/system/bpftune.nix
|
./services/system/bpftune.nix
|
||||||
|
|||||||
444
nixos/modules/services/security/warpgate.nix
Normal file
444
nixos/modules/services/security/warpgate.nix
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
cfg = config.services.warpgate;
|
||||||
|
yaml = pkgs.formats.yaml { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.warpgate =
|
||||||
|
let
|
||||||
|
inherit (lib.types)
|
||||||
|
nullOr
|
||||||
|
enum
|
||||||
|
str
|
||||||
|
bool
|
||||||
|
port
|
||||||
|
listOf
|
||||||
|
attrsOf
|
||||||
|
submodule
|
||||||
|
;
|
||||||
|
inherit (lib.options) mkOption mkPackageOption literalExpression;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
enable = mkOption {
|
||||||
|
description = ''
|
||||||
|
Whether to enable Warpgate.
|
||||||
|
This module will initialize Warpgate base on your config automatically. Please run `warpgate recover-access` to gain access.
|
||||||
|
'';
|
||||||
|
type = bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
package = mkPackageOption pkgs "warpgate" { };
|
||||||
|
|
||||||
|
databaseUrlFile = mkOption {
|
||||||
|
description = ''
|
||||||
|
Path to file containing database connection string with credentials.
|
||||||
|
Should be a one line file with: `database_url: <protocol>://<username>:<password>@<host>/<database>`.
|
||||||
|
See [SeaORM documentation](https://www.sea-ql.org/SeaORM/docs/install-and-config/connection/).
|
||||||
|
'';
|
||||||
|
type = nullOr str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = mkOption {
|
||||||
|
description = "Warpgate configuration.";
|
||||||
|
type = submodule {
|
||||||
|
freeformType = yaml.type;
|
||||||
|
options = {
|
||||||
|
sso_providers = mkOption {
|
||||||
|
description = "Configure OIDC single sign-on providers.";
|
||||||
|
default = [ ];
|
||||||
|
type = listOf (submodule {
|
||||||
|
freeformType = yaml.type;
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "Internal identifier of SSO provider.";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
label = mkOption {
|
||||||
|
description = "SSO provider name displayed on login page.";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
provider = mkOption {
|
||||||
|
description = "SSO provider configurations.";
|
||||||
|
type = attrsOf yaml.type;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
example = literalExpression ''
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name = "3rd party SSO";
|
||||||
|
label = "ACME SSO";
|
||||||
|
provider = {
|
||||||
|
type = "custom";
|
||||||
|
client_id = "123...";
|
||||||
|
client_secret = "BC...";
|
||||||
|
issuer_url = "https://sso.acme.inc";
|
||||||
|
scopes = ["email"];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
recordings = {
|
||||||
|
enable = mkOption {
|
||||||
|
description = "Whether to enable session recording.";
|
||||||
|
default = true;
|
||||||
|
type = bool;
|
||||||
|
};
|
||||||
|
path = mkOption {
|
||||||
|
description = "Path to store session recordings.";
|
||||||
|
default = "/var/lib/warpgate/recordings";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
external_host = mkOption {
|
||||||
|
description = ''
|
||||||
|
Configure the domain name of this Warpgate instance.
|
||||||
|
See [HTTP domain binding](https://warpgate.null.page/http-domain-binding/).
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
type = nullOr str;
|
||||||
|
};
|
||||||
|
database_url = mkOption {
|
||||||
|
description = ''
|
||||||
|
Database connection string.
|
||||||
|
See [SeaORM documentation](https://www.sea-ql.org/SeaORM/docs/install-and-config/connection/).
|
||||||
|
'';
|
||||||
|
default = "sqlite:/var/lib/warpgate/db";
|
||||||
|
type = nullOr str;
|
||||||
|
};
|
||||||
|
ssh = {
|
||||||
|
enable = mkOption {
|
||||||
|
description = "Whether to enable SSH listener.";
|
||||||
|
default = false;
|
||||||
|
type = bool;
|
||||||
|
};
|
||||||
|
listen = mkOption {
|
||||||
|
description = "Listen endpoint of SSH listener.";
|
||||||
|
default = "[::]:2222";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
external_port = mkOption {
|
||||||
|
description = "The SSH listener is reachable via this port externally.";
|
||||||
|
default = null;
|
||||||
|
type = nullOr port;
|
||||||
|
};
|
||||||
|
keys = mkOption {
|
||||||
|
description = "Path to store SSH host & client keys.";
|
||||||
|
default = "/var/lib/warpgate/ssh-keys";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
host_key_verification = mkOption {
|
||||||
|
description = "Specify host key verification action when connecting to a SSH target with unknown/differing host key.";
|
||||||
|
default = "prompt";
|
||||||
|
type = enum [
|
||||||
|
"prompt"
|
||||||
|
"auto_accept"
|
||||||
|
"auto_reject"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
inactivity_timeout = mkOption {
|
||||||
|
description = "How long can user be inactive until Warpgate terminates the connection.";
|
||||||
|
default = "5m";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
keepalive_interval = mkOption {
|
||||||
|
description = "If nothing is received from the client for this amount of time, server will send a keepalive message.";
|
||||||
|
default = null;
|
||||||
|
type = nullOr str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
http = {
|
||||||
|
listen = mkOption {
|
||||||
|
description = "Listen endpoint of HTTP listener.";
|
||||||
|
default = "[::]:8888";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
external_port = mkOption {
|
||||||
|
description = "The HTTP listener is reachable via this port externally.";
|
||||||
|
default = null;
|
||||||
|
type = nullOr port;
|
||||||
|
};
|
||||||
|
certificate = mkOption {
|
||||||
|
description = "Path to HTTPS listener certificate.";
|
||||||
|
default = "/var/lib/warpgate/tls.certificate.pem";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
key = mkOption {
|
||||||
|
description = "Path to HTTPS listener private key.";
|
||||||
|
default = "/var/lib/warpgate/tls.key.pem";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
sni_certificates = mkOption {
|
||||||
|
description = "Certificates for additional domains.";
|
||||||
|
default = [ ];
|
||||||
|
type = listOf (submodule {
|
||||||
|
freeformType = yaml.type;
|
||||||
|
options = {
|
||||||
|
certificate = mkOption {
|
||||||
|
description = "Path to certificate.";
|
||||||
|
default = "";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
key = mkOption {
|
||||||
|
description = "Path to private key.";
|
||||||
|
default = "";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
example = literalExpression ''
|
||||||
|
[
|
||||||
|
{
|
||||||
|
certificate = "/var/lib/warpgate/example.tld.pem";
|
||||||
|
key = "/var/lib/warpgate/example.tld.key.pem";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
trust_x_forwarded_headers = mkOption {
|
||||||
|
description = ''
|
||||||
|
Trust X-Forwarded-* headers. Required when being reverse proxied.
|
||||||
|
See [Running behind a reverse proxy](https://warpgate.null.page/reverse-proxy/).
|
||||||
|
'';
|
||||||
|
default = false;
|
||||||
|
type = bool;
|
||||||
|
};
|
||||||
|
session_max_age = mkOption {
|
||||||
|
description = "How long until a logged in session expires.";
|
||||||
|
default = "30m";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
cookie_max_age = mkOption {
|
||||||
|
description = "How long until logged in cookie expires.";
|
||||||
|
default = "1day";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
mysql = {
|
||||||
|
enable = mkOption {
|
||||||
|
description = "Whether to enable MySQL listener.";
|
||||||
|
default = false;
|
||||||
|
type = bool;
|
||||||
|
};
|
||||||
|
listen = mkOption {
|
||||||
|
description = "Listen endpoint of MySQL listener.";
|
||||||
|
default = "[::]:33306";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
external_port = mkOption {
|
||||||
|
description = "The MySQL listener is reachable via this port externally.";
|
||||||
|
default = null;
|
||||||
|
type = nullOr port;
|
||||||
|
};
|
||||||
|
certificate = mkOption {
|
||||||
|
description = "Path to MySQL listener certificate.";
|
||||||
|
default = "/var/lib/warpgate/tls.certificate.pem";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
key = mkOption {
|
||||||
|
description = "Path to MySQL listener private key.";
|
||||||
|
default = "/var/lib/warpgate/tls.key.pem";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
postgres = {
|
||||||
|
enable = mkOption {
|
||||||
|
description = "Whether to enable PostgreSQL listener.";
|
||||||
|
default = false;
|
||||||
|
type = bool;
|
||||||
|
};
|
||||||
|
listen = mkOption {
|
||||||
|
description = "Listen endpoint of PostgreSQL listener.";
|
||||||
|
default = "[::]:55432";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
external_port = mkOption {
|
||||||
|
description = "The PostgreSQL listener is reachable via this port externally.";
|
||||||
|
default = null;
|
||||||
|
type = nullOr str;
|
||||||
|
};
|
||||||
|
certificate = mkOption {
|
||||||
|
description = "Path to PostgreSQL listener certificate.";
|
||||||
|
default = "/var/lib/warpgate/tls.certificate.pem";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
key = mkOption {
|
||||||
|
description = "Path to PostgreSQL listener private key.";
|
||||||
|
default = "/var/lib/warpgate/tls.key.pem";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
log = {
|
||||||
|
retention = mkOption {
|
||||||
|
description = "How long Warpgate keep its logs.";
|
||||||
|
default = "7days";
|
||||||
|
type = str;
|
||||||
|
};
|
||||||
|
send_to = mkOption {
|
||||||
|
description = ''
|
||||||
|
Path of UNIX socket of log forwarder.
|
||||||
|
See [Log forwarding](https://warpgate.null.page/log-forwarding/);
|
||||||
|
'';
|
||||||
|
default = null;
|
||||||
|
type = nullOr str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
config_provider = mkOption {
|
||||||
|
description = ''
|
||||||
|
Source of truth of users.
|
||||||
|
DO NOT change this, Warpgate only implemented database provider.
|
||||||
|
'';
|
||||||
|
default = "database";
|
||||||
|
type = enum [
|
||||||
|
"file"
|
||||||
|
"database"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
example = {
|
||||||
|
ssh = {
|
||||||
|
enable = true;
|
||||||
|
listen = "[::]:2211";
|
||||||
|
};
|
||||||
|
http = {
|
||||||
|
listen = "[::]:8011";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config =
|
||||||
|
let
|
||||||
|
inherit (lib.lists)
|
||||||
|
any
|
||||||
|
map
|
||||||
|
head
|
||||||
|
reverseList
|
||||||
|
;
|
||||||
|
inherit (lib.strings) splitString toIntBase10;
|
||||||
|
|
||||||
|
preStartScript = pkgs.writers.writeBash "warpgate-init" ''
|
||||||
|
CFGFILE=/var/lib/warpgate/config.yaml
|
||||||
|
if [ ! -O $CFGFILE ] || [ ! -s $CFGFILE ]; then
|
||||||
|
INITPWD=$(tr -dc 'A-Za-z0-9!?%=' </dev/urandom 2>/dev/null | head -c 16)
|
||||||
|
${lib.getExe cfg.package} \
|
||||||
|
--config $CFGFILE unattended-setup \
|
||||||
|
--data-path /var/lib/warpgate \
|
||||||
|
--http-port 8888 \
|
||||||
|
--admin-password $INITPWD
|
||||||
|
fi
|
||||||
|
${
|
||||||
|
if cfg.databaseUrlFile != null then
|
||||||
|
''
|
||||||
|
sed -e '/^database_url: null/d' ${yaml.generate "warpgate-config" cfg.settings} > $CFGFILE
|
||||||
|
cat /run/credentials/warpgate.service/databaseUrl >> $CFGFILE
|
||||||
|
''
|
||||||
|
else
|
||||||
|
"cp --no-preserve=ownership ${yaml.generate "warpgate-config" cfg.settings} $CFGFILE"
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
bindOnPrivilegedPorts = any (x: toIntBase10 x < 1025) (
|
||||||
|
map (x: head (reverseList (splitString ":" x))) (
|
||||||
|
[ cfg.settings.http.listen ]
|
||||||
|
++ lib.optional cfg.settings.ssh.enable cfg.settings.ssh.listen
|
||||||
|
++ lib.optional cfg.settings.mysql.enable cfg.settings.mysql.listen
|
||||||
|
++ lib.optional cfg.settings.postgres.enable cfg.settings.postgres.listen
|
||||||
|
)
|
||||||
|
);
|
||||||
|
in
|
||||||
|
lib.mkIf cfg.enable {
|
||||||
|
assertions = [
|
||||||
|
{
|
||||||
|
assertion = !((cfg.databaseUrlFile != null) && (cfg.settings.database_url != null));
|
||||||
|
message = "You cannot configure databaseUrlFile and settings.database_url at the same time.";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
assertion = !((cfg.databaseUrlFile == null) && (cfg.settings.database_url == null));
|
||||||
|
message = "Either databaseUrlFile or settings.database_url must be set; Set the other to null.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
environment.systemPackages = [ cfg.package ];
|
||||||
|
|
||||||
|
systemd.services.warpgate = {
|
||||||
|
description = "Warpgate smart bastion";
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
after = [ "network.target" ];
|
||||||
|
startLimitBurst = 5;
|
||||||
|
serviceConfig = {
|
||||||
|
LoadCredential = "${
|
||||||
|
if cfg.databaseUrlFile != null then "databaseUrl:${cfg.databaseUrlFile}" else ""
|
||||||
|
}";
|
||||||
|
ExecStartPre = preStartScript;
|
||||||
|
ExecStart = "${lib.getExe cfg.package} --config /var/lib/warpgate/config.yaml run";
|
||||||
|
DynamicUser = true;
|
||||||
|
RestartSec = 3;
|
||||||
|
Restart = "on-failure";
|
||||||
|
StateDirectory = "warpgate";
|
||||||
|
StateDirectoryMode = "0700";
|
||||||
|
LockPersonality = true;
|
||||||
|
MemoryDenyWriteExecute = true;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
PrivateDevices = true;
|
||||||
|
DeviceAllow = [
|
||||||
|
"/dev/null rw"
|
||||||
|
"/dev/urandom r"
|
||||||
|
];
|
||||||
|
DevicePolicy = "strict";
|
||||||
|
ProtectHostname = true;
|
||||||
|
ProtectKernelLogs = true;
|
||||||
|
ProtectKernelModules = true;
|
||||||
|
ProtectKernelTunables = true;
|
||||||
|
RestrictNamespaces = true;
|
||||||
|
ProtectProc = "invisible";
|
||||||
|
ProtectSystem = "full";
|
||||||
|
ProtectClock = true;
|
||||||
|
ProtectControlGroups = true;
|
||||||
|
RestrictRealtime = true;
|
||||||
|
RestrictSUIDSGID = true;
|
||||||
|
RemoveIPC = true;
|
||||||
|
RestrictAddressFamilies = [
|
||||||
|
"AF_INET"
|
||||||
|
"AF_INET6"
|
||||||
|
"AF_UNIX"
|
||||||
|
];
|
||||||
|
SystemCallArchitectures = "native";
|
||||||
|
SystemCallFilter = [
|
||||||
|
"@system-service"
|
||||||
|
"~@privileged"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// (
|
||||||
|
if bindOnPrivilegedPorts then
|
||||||
|
{
|
||||||
|
AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
|
||||||
|
CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrivateUsers = true;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
meta.maintainers = with lib.maintainers; [ alemonmk ];
|
||||||
|
}
|
||||||
@@ -1609,6 +1609,7 @@ in
|
|||||||
vsftpd = runTest ./vsftpd.nix;
|
vsftpd = runTest ./vsftpd.nix;
|
||||||
waagent = runTest ./waagent.nix;
|
waagent = runTest ./waagent.nix;
|
||||||
wakapi = runTest ./wakapi.nix;
|
wakapi = runTest ./wakapi.nix;
|
||||||
|
warpgate = runTest ./warpgate.nix;
|
||||||
warzone2100 = runTest ./warzone2100.nix;
|
warzone2100 = runTest ./warzone2100.nix;
|
||||||
wasabibackend = runTest ./wasabibackend.nix;
|
wasabibackend = runTest ./wasabibackend.nix;
|
||||||
wastebin = runTest ./wastebin.nix;
|
wastebin = runTest ./wastebin.nix;
|
||||||
|
|||||||
49
nixos/tests/warpgate.nix
Normal file
49
nixos/tests/warpgate.nix
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
name = "warpgate";
|
||||||
|
|
||||||
|
nodes = {
|
||||||
|
machine = {
|
||||||
|
services.warpgate = {
|
||||||
|
enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
machine2 = {
|
||||||
|
environment.etc."warpgate-db-url".text = "database: sqlite:/var/lib/warpgate/db/";
|
||||||
|
services.warpgate = {
|
||||||
|
enable = true;
|
||||||
|
databaseUrlFile = "/etc/warpgate-db-url";
|
||||||
|
settings = {
|
||||||
|
database_url = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
machine3 = {
|
||||||
|
services.warpgate = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
http.listen = "[::]:443";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
machine.wait_for_unit("warpgate.service")
|
||||||
|
machine.wait_for_open_port(8888)
|
||||||
|
machine.succeed("stat /var/lib/warpgate/db/db.sqlite3")
|
||||||
|
machine.succeed("curl -k --fail https://localhost:8888/@warpgate")
|
||||||
|
machine.shutdown()
|
||||||
|
|
||||||
|
machine2.wait_for_unit("warpgate.service")
|
||||||
|
machine2.wait_for_open_port(8888)
|
||||||
|
machine2.succeed("curl -k --fail https://localhost:8888/@warpgate")
|
||||||
|
machine2.shutdown()
|
||||||
|
|
||||||
|
machine3.wait_for_unit("warpgate.service")
|
||||||
|
machine3.wait_for_open_port(443)
|
||||||
|
machine3.succeed("curl -k --fail https://localhost/@warpgate")
|
||||||
|
machine3.shutdown()
|
||||||
|
'';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user