Merge pull request 'clanModule: Extend dyndns support for most dns hosters. Improve security.' (#1871) from Qubasa/clan-core:Qubasa-main into main
This commit is contained in:
@@ -14,18 +14,31 @@ let
|
|||||||
secret_path =
|
secret_path =
|
||||||
opt: config.clan.core.facts.services."${secret_id opt}".secret."${secret_id opt}".path;
|
opt: config.clan.core.facts.services."${secret_id opt}".secret."${secret_id opt}".path;
|
||||||
|
|
||||||
|
# We check that a secret has not been set in extraSettings.
|
||||||
|
extraSettingsSafe =
|
||||||
|
opt:
|
||||||
|
if (builtins.hasAttr opt.secret_field_name opt.extraSettings) then
|
||||||
|
throw "Please do not set ${opt.secret_field_name} in extraSettings, it is automatically set by the dyndns module."
|
||||||
|
else
|
||||||
|
opt.extraSettings;
|
||||||
/*
|
/*
|
||||||
We go from:
|
We go from:
|
||||||
{home.gchq.icu:{value:{domain:gchq.icu,host:home, provider:namecheap}}}
|
{home.example.com:{value:{domain:example.com,host:home, provider:namecheap}}}
|
||||||
To:
|
To:
|
||||||
{settings: [{domain: gchq.icu, host: home, provider: namecheap, password: dyndns-namecheap-gchq.icu}]}
|
{settings: [{domain: example.com, host: home, provider: namecheap, password: dyndns-namecheap-example.com}]}
|
||||||
*/
|
*/
|
||||||
service_config = {
|
service_config = {
|
||||||
settings = builtins.catAttrs "value" (
|
settings = builtins.catAttrs "value" (
|
||||||
builtins.attrValues (
|
builtins.attrValues (
|
||||||
lib.mapAttrs (_: opt: {
|
lib.mapAttrs (_: opt: {
|
||||||
value = opt // {
|
value =
|
||||||
password = secret_id opt;
|
(extraSettingsSafe opt)
|
||||||
|
// {
|
||||||
|
domain = opt.domain;
|
||||||
|
provider = opt.provider;
|
||||||
|
}
|
||||||
|
// {
|
||||||
|
"${opt.secret_field_name}" = secret_id opt;
|
||||||
};
|
};
|
||||||
}) cfg.settings
|
}) cfg.settings
|
||||||
)
|
)
|
||||||
@@ -57,6 +70,25 @@ in
|
|||||||
description = "Group to run the service as";
|
description = "Group to run the service as";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
server = {
|
||||||
|
enable = lib.mkEnableOption "dyndns webserver";
|
||||||
|
domain = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Domain to serve the webservice on";
|
||||||
|
};
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 54805;
|
||||||
|
description = "Port to listen on";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
period = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 5;
|
||||||
|
description = "Domain update period in minutes";
|
||||||
|
};
|
||||||
|
|
||||||
settings = lib.mkOption {
|
settings = lib.mkOption {
|
||||||
type = lib.types.attrsOf (
|
type = lib.types.attrsOf (
|
||||||
lib.types.submodule (
|
lib.types.submodule (
|
||||||
@@ -64,24 +96,44 @@ in
|
|||||||
{
|
{
|
||||||
options = {
|
options = {
|
||||||
provider = lib.mkOption {
|
provider = lib.mkOption {
|
||||||
|
example = "namecheap";
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
description = "The dyndns provider to use";
|
description = "The dyndns provider to use";
|
||||||
};
|
};
|
||||||
domain = lib.mkOption {
|
domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
description = "The top level domain to update. For example 'example.com'.
|
example = "example.com";
|
||||||
If you want to update a subdomain, add the 'subdomain' option";
|
description = "The top level domain to update.";
|
||||||
};
|
};
|
||||||
host = lib.mkOption {
|
secret_field_name = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
example = [
|
||||||
description = "The subdomain to update of the tld";
|
"password"
|
||||||
|
"api_key"
|
||||||
|
];
|
||||||
|
type = lib.types.enum [
|
||||||
|
"password"
|
||||||
|
"token"
|
||||||
|
"api_key"
|
||||||
|
];
|
||||||
|
default = "password";
|
||||||
|
description = "The field name for the secret";
|
||||||
|
};
|
||||||
|
# TODO: Ideally we would create a gigantic list of all possible settings / types
|
||||||
|
# optimally we would have a way to generate the options from the source code
|
||||||
|
extraSettings = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf lib.types.str;
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Extra settings for the provider.
|
||||||
|
Provider specific settings: https://github.com/qdm12/ddns-updater#configuration
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = "Wifi networks to predefine";
|
description = "Configuration for which domains to update";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -106,6 +158,24 @@ in
|
|||||||
createHome = true;
|
createHome = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = lib.mkIf cfg.server.enable [
|
||||||
|
80
|
||||||
|
443
|
||||||
|
];
|
||||||
|
|
||||||
|
services.nginx = lib.mkIf cfg.server.enable {
|
||||||
|
enable = true;
|
||||||
|
virtualHosts = {
|
||||||
|
"${cfg.server.domain}" = {
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
locations."/" = {
|
||||||
|
proxyPass = "http://localhost:${toString cfg.server.port}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
systemd.services.${name} = {
|
systemd.services.${name} = {
|
||||||
path = [ ];
|
path = [ ];
|
||||||
description = "Dynamic DNS updater";
|
description = "Dynamic DNS updater";
|
||||||
@@ -113,6 +183,9 @@ in
|
|||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
environment = {
|
environment = {
|
||||||
MYCONFIG = "${builtins.toJSON service_config}";
|
MYCONFIG = "${builtins.toJSON service_config}";
|
||||||
|
SERVER_ENABLED = if cfg.server.enable then "yes" else "no";
|
||||||
|
PERIOD = "${toString cfg.period}m";
|
||||||
|
LISTENING_ADDRESS = ":${toString cfg.server.port}";
|
||||||
};
|
};
|
||||||
|
|
||||||
serviceConfig =
|
serviceConfig =
|
||||||
@@ -135,7 +208,14 @@ in
|
|||||||
config = json.loads(config_str)
|
config = json.loads(config_str)
|
||||||
print(f"Config: {config}")
|
print(f"Config: {config}")
|
||||||
for attrset in config["settings"]:
|
for attrset in config["settings"]:
|
||||||
|
if "password" in attrset:
|
||||||
attrset['password'] = get_credential(attrset['password'])
|
attrset['password'] = get_credential(attrset['password'])
|
||||||
|
elif "token" in attrset:
|
||||||
|
attrset['token'] = get_credential(attrset['token'])
|
||||||
|
elif "api_key" in attrset:
|
||||||
|
attrset['api_key'] = get_credential(attrset['api_key'])
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Missing secret field in {attrset}")
|
||||||
|
|
||||||
# create directory data if it does not exist
|
# create directory data if it does not exist
|
||||||
data_dir = Path('data')
|
data_dir = Path('data')
|
||||||
@@ -162,7 +242,19 @@ in
|
|||||||
LoadCredential = lib.mapAttrsToList (_: opt: "${secret_id opt}:${secret_path opt}") cfg.settings;
|
LoadCredential = lib.mapAttrsToList (_: opt: "${secret_id opt}:${secret_path opt}") cfg.settings;
|
||||||
User = cfg.user;
|
User = cfg.user;
|
||||||
Group = cfg.group;
|
Group = cfg.group;
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ReadOnlyPaths = "/";
|
||||||
|
PrivateDevices = "yes";
|
||||||
|
ProtectKernelModules = "yes";
|
||||||
|
ProtectKernelTunables = "yes";
|
||||||
|
|
||||||
WorkingDirectory = "/var/lib/${name}";
|
WorkingDirectory = "/var/lib/${name}";
|
||||||
|
ReadWritePaths = [
|
||||||
|
"/proc/self"
|
||||||
|
"/var/lib/${name}"
|
||||||
|
];
|
||||||
|
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
RestartSec = 60;
|
RestartSec = 60;
|
||||||
|
|||||||
@@ -169,6 +169,11 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
80
|
||||||
|
443
|
||||||
|
];
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
virtualHosts = {
|
virtualHosts = {
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ def deploy_machine(machines: MachineGroup) -> None:
|
|||||||
cmd = [
|
cmd = [
|
||||||
"nixos-rebuild",
|
"nixos-rebuild",
|
||||||
"switch",
|
"switch",
|
||||||
|
"--show-trace",
|
||||||
"--fast",
|
"--fast",
|
||||||
"--option",
|
"--option",
|
||||||
"keep-going",
|
"keep-going",
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ def nix_build(flags: list[str], gcroot: Path | None = None) -> list[str]:
|
|||||||
str(gcroot),
|
str(gcroot),
|
||||||
"--print-out-paths",
|
"--print-out-paths",
|
||||||
"--no-write-lock-file",
|
"--no-write-lock-file",
|
||||||
|
"--show-trace",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
+ flags
|
+ flags
|
||||||
@@ -47,6 +48,7 @@ def nix_build(flags: list[str], gcroot: Path | None = None) -> list[str]:
|
|||||||
"--no-link",
|
"--no-link",
|
||||||
"--print-out-paths",
|
"--print-out-paths",
|
||||||
"--no-write-lock-file",
|
"--no-write-lock-file",
|
||||||
|
"--show-trace",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
+ flags
|
+ flags
|
||||||
|
|||||||
Reference in New Issue
Block a user