ntfy crowdsec
This commit is contained in:
@@ -13,34 +13,20 @@ let
|
|||||||
ntfyServer = "https://ntfy.mjallen.dev";
|
ntfyServer = "https://ntfy.mjallen.dev";
|
||||||
ntfyTopic = "crowdsec";
|
ntfyTopic = "crowdsec";
|
||||||
|
|
||||||
# CrowdSec HTTP notification plugin config — written to
|
# Build the notification-http plugin binary from the crowdsec source.
|
||||||
# /etc/crowdsec/notifications/ntfy.yaml at runtime. Credentials are
|
# The nixpkgs crowdsec package omits all notification plugin binaries;
|
||||||
# injected via EnvironmentFile so the plugin can reference them with
|
# we build just the http one we need.
|
||||||
# {{env "NTFY_USER"}} / {{env "NTFY_PASSWORD"}} in the URL.
|
crowdsecHttpPlugin = pkgs.buildGoModule {
|
||||||
ntfyPluginConfig = pkgs.writeText "crowdsec-ntfy.yaml" ''
|
pname = "crowdsec-notification-http";
|
||||||
type: http
|
inherit (pkgs.crowdsec) version src;
|
||||||
name: ntfy_plugin
|
vendorHash = pkgs.crowdsec.vendorHash or null;
|
||||||
log_level: info
|
subPackages = [ "cmd/notification-http" ];
|
||||||
format: |
|
ldflags = [
|
||||||
{{range . -}}
|
"-s"
|
||||||
CrowdSec blocked: {{.Scenario}}
|
"-w"
|
||||||
Source IP: {{.Source.Value}}
|
];
|
||||||
Country: {{.Source.Cn}}
|
meta.description = "CrowdSec HTTP notification plugin";
|
||||||
Decisions: {{.Decisions | len}}
|
};
|
||||||
{{range .Decisions -}}
|
|
||||||
Action: {{.Type}} for {{.Duration}}
|
|
||||||
{{end}}
|
|
||||||
{{- end}}
|
|
||||||
url: ${ntfyServer}/${ntfyTopic}
|
|
||||||
method: POST
|
|
||||||
headers:
|
|
||||||
Title: "CrowdSec: {{(index . 0).Scenario}}"
|
|
||||||
Priority: "high"
|
|
||||||
Tags: "rotating_light,shield"
|
|
||||||
Authorization: "Basic {{b64enc (print (env "NTFY_USER") ":" (env "NTFY_PASSWORD"))}}"
|
|
||||||
skip_tls_verify: false
|
|
||||||
timeout: 10s
|
|
||||||
'';
|
|
||||||
|
|
||||||
crowdsecConfig = lib.${namespace}.mkModule {
|
crowdsecConfig = lib.${namespace}.mkModule {
|
||||||
inherit config name;
|
inherit config name;
|
||||||
@@ -144,7 +130,8 @@ let
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
settings = {
|
settings = {
|
||||||
general.api = {
|
general = {
|
||||||
|
api = {
|
||||||
server = {
|
server = {
|
||||||
enable = true;
|
enable = true;
|
||||||
listen_uri = "${cfg.listenAddress}:${toString cfg.port}";
|
listen_uri = "${cfg.listenAddress}:${toString cfg.port}";
|
||||||
@@ -153,8 +140,16 @@ let
|
|||||||
credentials_path = lib.mkForce "${cfg.configDir}/crowdsec/client.yaml";
|
credentials_path = lib.mkForce "${cfg.configDir}/crowdsec/client.yaml";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
# plugin_config must be in settings.general so it ends up in the
|
||||||
|
# NixOS-generated crowdsec.yaml that the daemon reads via -c.
|
||||||
|
plugin_config = lib.mkIf cfg.ntfy.enable {
|
||||||
|
user = "crowdsec";
|
||||||
|
group = "crowdsec";
|
||||||
|
};
|
||||||
|
};
|
||||||
capi.credentialsFile = lib.mkDefault "${cfg.configDir}/crowdsec/capi.yaml";
|
capi.credentialsFile = lib.mkDefault "${cfg.configDir}/crowdsec/capi.yaml";
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
crowdsec-firewall-bouncer = {
|
crowdsec-firewall-bouncer = {
|
||||||
@@ -175,7 +170,12 @@ let
|
|||||||
# but /var/lib/crowdsec already exists as a real dir. Disabling DynamicUser on
|
# but /var/lib/crowdsec already exists as a real dir. Disabling DynamicUser on
|
||||||
# those two services lets them use the real crowdsec user/group instead, which is
|
# those two services lets them use the real crowdsec user/group instead, which is
|
||||||
# consistent with how crowdsec.service itself runs.
|
# consistent with how crowdsec.service itself runs.
|
||||||
systemd.services.crowdsec.serviceConfig.DynamicUser = lib.mkForce false;
|
systemd.services.crowdsec.serviceConfig = lib.mkMerge [
|
||||||
|
{ DynamicUser = lib.mkForce false; }
|
||||||
|
(lib.mkIf (cfg.ntfy.enable && cfg.ntfy.envFile != "") {
|
||||||
|
EnvironmentFile = [ cfg.ntfy.envFile ];
|
||||||
|
})
|
||||||
|
];
|
||||||
systemd.services.crowdsec-firewall-bouncer.serviceConfig.DynamicUser = lib.mkForce false;
|
systemd.services.crowdsec-firewall-bouncer.serviceConfig.DynamicUser = lib.mkForce false;
|
||||||
systemd.services.crowdsec-firewall-bouncer-register.serviceConfig.DynamicUser = lib.mkForce false;
|
systemd.services.crowdsec-firewall-bouncer-register.serviceConfig.DynamicUser = lib.mkForce false;
|
||||||
|
|
||||||
@@ -220,12 +220,11 @@ let
|
|||||||
|
|
||||||
# crowdsec-firewall-bouncer-register calls cscli without -c, so cscli
|
# crowdsec-firewall-bouncer-register calls cscli without -c, so cscli
|
||||||
# looks for /etc/crowdsec/config.yaml. The upstream crowdsec.service uses
|
# looks for /etc/crowdsec/config.yaml. The upstream crowdsec.service uses
|
||||||
# a nix store path via -c and never creates that file. Expose the config
|
# a nix store path via -c and never creates that file. Expose the full
|
||||||
# at /etc/crowdsec/config.yaml by extracting the store path from the
|
# NixOS-generated config (which includes plugin_config via
|
||||||
# crowdsec service's ExecStart list at NixOS eval time.
|
# settings.general.plugin_config) at the well-known path.
|
||||||
environment.etc."crowdsec/config.yaml" =
|
environment.etc."crowdsec/config.yaml" =
|
||||||
let
|
let
|
||||||
# ExecStart is [ " " "<store>/crowdsec -c <config-file> -info" ]
|
|
||||||
execStart = builtins.elemAt config.systemd.services.crowdsec.serviceConfig.ExecStart 1;
|
execStart = builtins.elemAt config.systemd.services.crowdsec.serviceConfig.ExecStart 1;
|
||||||
configPath = builtins.head (builtins.match ".* -c ([^ ]+) .*" execStart);
|
configPath = builtins.head (builtins.match ".* -c ([^ ]+) .*" execStart);
|
||||||
in
|
in
|
||||||
@@ -240,16 +239,27 @@ let
|
|||||||
# ntfy notifications via the CrowdSec HTTP notification plugin
|
# ntfy notifications via the CrowdSec HTTP notification plugin
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
# Drop the plugin config YAML into /etc/crowdsec/notifications/.
|
# Place the notification-http binary at the path the NixOS crowdsec module
|
||||||
# CrowdSec scans this directory on startup and registers any plugin
|
# hardcodes for plugin_dir (/etc/crowdsec/plugins/). CrowdSec matches
|
||||||
# config files it finds.
|
# plugins by their filename — it expects "notification-http" for type=http.
|
||||||
environment.etc."crowdsec/notifications/ntfy.yaml" = lib.mkIf cfg.ntfy.enable {
|
environment.etc."crowdsec/plugins/notification-http" = lib.mkIf cfg.ntfy.enable {
|
||||||
source = ntfyPluginConfig;
|
source = "${crowdsecHttpPlugin}/bin/notification-http";
|
||||||
mode = "0440";
|
mode = "0550";
|
||||||
user = "crowdsec";
|
user = "crowdsec";
|
||||||
group = "crowdsec";
|
group = "crowdsec";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# The ntfy plugin config YAML (with credentials baked in) is managed as a
|
||||||
|
# SOPS template in sops.nix — it renders to /run/secrets/rendered/crowdsec/
|
||||||
|
# notifications/ntfy.yaml at runtime. We use a tmpfiles symlink to expose
|
||||||
|
# it at the path CrowdSec scans, since environment.etc can't reference
|
||||||
|
# /run paths as source.
|
||||||
|
systemd.tmpfiles.rules = lib.mkIf cfg.ntfy.enable [
|
||||||
|
"L /etc/crowdsec/notifications/ntfy.yaml - - - - ${
|
||||||
|
config.sops.templates."crowdsec/notifications/ntfy.yaml".path
|
||||||
|
}"
|
||||||
|
];
|
||||||
|
|
||||||
# CrowdSec profiles.yaml: route every alert to the ntfy plugin.
|
# CrowdSec profiles.yaml: route every alert to the ntfy plugin.
|
||||||
# This replaces the default "do nothing" profile.
|
# This replaces the default "do nothing" profile.
|
||||||
environment.etc."crowdsec/profiles.yaml" = lib.mkIf cfg.ntfy.enable {
|
environment.etc."crowdsec/profiles.yaml" = lib.mkIf cfg.ntfy.enable {
|
||||||
@@ -279,13 +289,6 @@ let
|
|||||||
group = "crowdsec";
|
group = "crowdsec";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Inject NTFY_USER and NTFY_PASSWORD into the crowdsec service so the
|
|
||||||
# HTTP plugin template can reference them. The plugin config uses
|
|
||||||
# {{env "NTFY_BASIC_AUTH"}} — a pre-encoded "user:pass" base64 string
|
|
||||||
# for the Authorization: Basic header — computed in ExecStartPre.
|
|
||||||
systemd.services.crowdsec.serviceConfig.EnvironmentFile = lib.mkIf cfg.ntfy.enable [
|
|
||||||
cfg.ntfy.envFile
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -259,10 +259,10 @@ in
|
|||||||
"jallen-nas/ntfy/auth-users" = {
|
"jallen-nas/ntfy/auth-users" = {
|
||||||
sopsFile = defaultSops;
|
sopsFile = defaultSops;
|
||||||
};
|
};
|
||||||
|
|
||||||
"jallen-nas/ntfy/user" = {
|
"jallen-nas/ntfy/user" = {
|
||||||
sopsFile = defaultSops;
|
sopsFile = defaultSops;
|
||||||
mode = "0440";
|
mode = "0440";
|
||||||
owner = "grafana";
|
|
||||||
group = "keys";
|
group = "keys";
|
||||||
restartUnits = [
|
restartUnits = [
|
||||||
"grafana.service"
|
"grafana.service"
|
||||||
@@ -273,7 +273,6 @@ in
|
|||||||
"jallen-nas/ntfy/password" = {
|
"jallen-nas/ntfy/password" = {
|
||||||
sopsFile = defaultSops;
|
sopsFile = defaultSops;
|
||||||
mode = "0440";
|
mode = "0440";
|
||||||
owner = "grafana";
|
|
||||||
group = "keys";
|
group = "keys";
|
||||||
restartUnits = [
|
restartUnits = [
|
||||||
"grafana.service"
|
"grafana.service"
|
||||||
@@ -357,7 +356,8 @@ in
|
|||||||
NTFY_USER=${config.sops.placeholder."jallen-nas/ntfy/user"}
|
NTFY_USER=${config.sops.placeholder."jallen-nas/ntfy/user"}
|
||||||
NTFY_PASSWORD=${config.sops.placeholder."jallen-nas/ntfy/password"}
|
NTFY_PASSWORD=${config.sops.placeholder."jallen-nas/ntfy/password"}
|
||||||
'';
|
'';
|
||||||
mode = "0600";
|
mode = "0640";
|
||||||
|
group = "keys";
|
||||||
restartUnits = [
|
restartUnits = [
|
||||||
"crowdsec.service"
|
"crowdsec.service"
|
||||||
"upsmon.service"
|
"upsmon.service"
|
||||||
@@ -366,6 +366,33 @@ in
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# CrowdSec HTTP notification plugin config with credentials baked in.
|
||||||
|
# The plugin process spawned by crowdsec/cscli reads this file directly.
|
||||||
|
# Credentials are embedded in the URL using HTTP basic auth so no
|
||||||
|
# base64 encoding or env var injection is needed.
|
||||||
|
"crowdsec/notifications/ntfy.yaml" = {
|
||||||
|
content = ''
|
||||||
|
type: http
|
||||||
|
name: ntfy_plugin
|
||||||
|
log_level: info
|
||||||
|
format: "{{range . -}}CrowdSec blocked: {{.Scenario}}\nSource IP: {{.Source.Value}}\nCountry: {{.Source.Cn}}\nDecisions: {{.Decisions | len}}{{range .Decisions}}\nAction: {{.Type}} for {{.Duration}}{{end}}\n{{end}}"
|
||||||
|
url: https://${config.sops.placeholder."jallen-nas/ntfy/user"}:${
|
||||||
|
config.sops.placeholder."jallen-nas/ntfy/password"
|
||||||
|
}@ntfy.mjallen.dev/crowdsec
|
||||||
|
method: POST
|
||||||
|
headers:
|
||||||
|
Title: "CrowdSec: {{(index . 0).Scenario}}"
|
||||||
|
Priority: "high"
|
||||||
|
Tags: "rotating_light,shield"
|
||||||
|
skip_tls_verify: false
|
||||||
|
timeout: 10s
|
||||||
|
'';
|
||||||
|
mode = "0440";
|
||||||
|
owner = "crowdsec";
|
||||||
|
group = "crowdsec";
|
||||||
|
restartUnits = [ "crowdsec.service" ];
|
||||||
|
};
|
||||||
|
|
||||||
"paperless.env" = {
|
"paperless.env" = {
|
||||||
content = ''
|
content = ''
|
||||||
PAPERLESS_ADMIN_USER = "mjallen"
|
PAPERLESS_ADMIN_USER = "mjallen"
|
||||||
|
|||||||
@@ -57,8 +57,16 @@ in
|
|||||||
prometheus = {
|
prometheus = {
|
||||||
extraGroups = [ "keys" ];
|
extraGroups = [ "keys" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# crowdsec needs to read the ntfy.env SOPS template for notifications.
|
||||||
|
crowdsec = {
|
||||||
|
isSystemUser = true;
|
||||||
|
group = "crowdsec";
|
||||||
|
extraGroups = [ "keys" ];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
groups.nextcloud-exporter = { };
|
groups.nextcloud-exporter = { };
|
||||||
|
groups.crowdsec = { };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user