commit f6b942023c1531757aab2ce999242ec5998c7aac Author: Daniel Frank Date: Sat Jan 22 14:31:29 2022 +0100 Initial commit diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..262736c --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +export NIX_PATH=nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=$(expand_path .)/configuration.nix:/nix/var/nix/profiles/per-user/root/channels diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..782c8d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +result +.*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..821355f --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +hamburg.freifunk.net Statistik +=============================== + +Initiales Setup +----- +1. System starten +2. Passwörter liegen nach dem Start des ersten Dienstes jeweils unter `/var/lib/*/*.pw` +3. Nginx konfigurieren um ACME zu benutzen +4. services.influxdb.extraConfig.http.auth-enabled auf true setzen +5. Config für Collector erstellen und hier ablegen: /var/lib/private/collector/ffhh.conf +6. Grafana konfigurieren + + +Development +----- +Starten des Systems: + QEMU_NET_OPTS="hostfwd=tcp:127.0.0.1:2222-:22,hostfwd=tcp:127.0.0.1:8080-:80" nixos-shell +Zugriff dann per SSH über 127.0.0.1:2222 und HTTP über 127.0.0.1:8080. diff --git a/acme.nix b/acme.nix new file mode 100644 index 0000000..b870f4e --- /dev/null +++ b/acme.nix @@ -0,0 +1,9 @@ +{ ... }: + +{ + security.acme.acceptTerms = true; + security.acme.email = "kontakt@hamburg.freifunk.net"; + users.groups.certs = { + members = [ "nginx" ]; + }; +} diff --git a/collector.nix b/collector.nix new file mode 100644 index 0000000..4d3d5dc --- /dev/null +++ b/collector.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: + +let + collector = pkgs.fetchFromGitHub { + owner = "tokudan"; + repo = "ffhh-stats"; + rev = "76c61a0c0f7d276fd79026b551780e318901adf6"; + sha256 = "0irnc8ffm413aq3sh64sd2457yp2ax4paaf0ss9r1pkbkb8q5dgx"; + + }; +in +{ + systemd.services.collector = { + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" ]; + requires = [ "influxdb.service" ]; + path = [ pkgs.wget ]; + serviceConfig = { + Type = "simple"; + Restart = "always"; + RestartSec = 65; + DynamicUser = true; + PrivateTmp = true; + StateDirectory = "collector"; + # The config file is actually in /var/lib/private/collector, systemd maps that path to /var/lib/collector + ExecStart = "${pkgs.ruby.withPackages (ps: with ps; [ json ])}/bin/ruby ${collector}/query-data.influx --config /var/lib/collector/ffhh.conf"; + }; + }; +} diff --git a/configuration.nix b/configuration.nix new file mode 100644 index 0000000..bd30b6f --- /dev/null +++ b/configuration.nix @@ -0,0 +1,104 @@ +# Edit this configuration file to define what should be installed on +# your system. Help is available in the configuration.nix(5) man page +# and in the NixOS manual (accessible by running ‘nixos-help’). + +{ config, pkgs, ... }: + +{ + imports = + [ + ./acme.nix + ./sshusers.nix + ./nginx.nix + ./grafana.nix + ./influxdb.nix + ./collector.nix + ]; + + # Use the GRUB 2 boot loader. + boot.loader.grub.enable = true; + boot.loader.grub.version = 2; + boot.loader.grub.device = "/dev/vda"; + + swapDevices = [{ device = "/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi0-0-0-0-part2"; randomEncryption.enable = true; randomEncryption.source = "/dev/random"; }]; + + networking = { + hostName = "stats"; + domain = "hamburg.freifunk.net"; + hostId = "7d7135dd"; + firewall.rejectPackets = true; + firewall.logRefusedConnections = false; + usePredictableInterfaceNames = false; + dhcpcd.enable = false; + nameservers = [ "213.133.99.99" "213.133.100.100" "213.133.98.98" ]; + interfaces.eth0 = { + ipv4.addresses = [ { address = "142.132.181.225"; prefixLength = 32; } ]; + ipv6.addresses = [ { address = "2a01:4f8:1c17:dbfb::1"; prefixLength = 64; } ]; + }; + defaultGateway = { address = "172.31.1.1"; interface = "eth0"; }; + defaultGateway6 = { address = "fe80::1"; interface = "eth0"; }; + }; + + # Automatic update each day + system.autoUpgrade.enable = true; + system.autoUpgrade.allowReboot = true; + nix = { + autoOptimiseStore = true; + gc.automatic = true; + gc.options = "--delete-older-than 14d"; + }; + + # Select internationalisation properties. + i18n.defaultLocale = "de_DE.UTF-8"; + + # Set your time zone. + time.timeZone = "Europe/Berlin"; + + # List packages installed in system profile. To search, run: + # $ nix search wget + environment.systemPackages = with pkgs; [ + git htop lsof mosh nano screen socat traceroute vim wget + ]; + + # Some programs need SUID wrappers, can be configured further or are + # started in user sessions. + programs.mtr.enable = true; + programs.screen.screenrc = '' + hardstatus alwayslastline + hardstatus string '%{= kG}[ %{G}%H %{g}][%= %{= kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][%{B} %m-%d %{W}%c:%s %{g}]' + defscrollback 1000 + ''; + + # List services that you want to enable: + + # Support mosh connections + programs.mosh.enable = true; + + # Open ports in the firewall. + # networking.firewall.allowedTCPPorts = [ ... ]; + # networking.firewall.allowedUDPPorts = [ ... ]; + + # User configuration for root. + # Other users are defined in sshusers.nix + users.extraUsers.root = { + hashedPassword = "!!"; + }; + users.motd = with config; '' + Welcome to ${networking.hostName}.${networking.domain} + + - This server is NixOS + - All changes must be done through the git repository at + /etc/nixos or https://github.com/freifunkhamburg/stats-nixos-config/ + - Other changes will be lost + + OS: NixOS ${system.nixos.release} (${system.nixos.codeName}) + Version: ${system.nixos.version} + Kernel: ${boot.kernelPackages.kernel.version} + ''; + + # This value determines the NixOS release with which your system is to be + # compatible, in order to avoid breaking some software such as database + # servers. You should change this only after NixOS release notes say you + # should. + system.stateVersion = "21.11"; # Did you read the comment? +} diff --git a/grafana.nix b/grafana.nix new file mode 100644 index 0000000..be47848 --- /dev/null +++ b/grafana.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, ... }: + +{ + services.grafana = { + enable = true; + analytics.reporting.enable = false; + protocol = "socket"; + rootUrl = "https://stats.besaid.de/"; + auth.anonymous.enable = false; + security = { + adminUser = "dfrank"; + adminPasswordFile = "/var/lib/grafana/admin.pw"; + secretKeyFile = "/var/lib/grafana/security.key"; + }; + }; + systemd.services.grafana.serviceConfig = { + # upstream module already defines most hardening options + IPAddressDeny = "any"; + IPAddressAllow = "localhost"; + MemoryDenyWriteExecute = true; + PrivateUsers = true; + ExecStartPost = [ + (pkgs.writeScript "grafana-socket-perms" '' + #!${pkgs.stdenv.shell} + until chmod -c 666 /run/grafana/grafana.sock ; do sleep 1; done + '') + ]; + }; + systemd.services.grafana-init = { + description = "Grafana Service Daemon - initialize files"; + wantedBy = [ "grafana.service" ]; + before = [ "grafana.service" ]; + serviceConfig.Type = "oneshot"; + script = '' + #!${pkgs.stdenv.shell} + set -euo pipefail + # Make sure everything but the password ends up on stderr + exec 3>&1 >&2 + mkdir -p /var/lib/grafana + if [ ! -s /var/lib/grafana/admin.pw ]; then + head -c 30 /dev/urandom | base64 > /var/lib/grafana/admin.pw + chmod 400 /var/lib/grafana/admin.pw + chown grafana:grafana /var/lib/grafana/admin.pw + fi + if [ ! -s /var/lib/grafana/security.key ]; then + head -c 30 /dev/urandom | base64 > /var/lib/grafana/security.key + chmod 400 /var/lib/grafana/security.key + chown grafana:grafana /var/lib/grafana/security.key + fi + ''; + + }; +} diff --git a/influxdb.nix b/influxdb.nix new file mode 100644 index 0000000..641c408 --- /dev/null +++ b/influxdb.nix @@ -0,0 +1,80 @@ +{ config, lib, pkgs, ... }: + +{ + environment.systemPackages = [ pkgs.influxdb ]; + services.influxdb = { + enable = true; + dataDir = "/var/lib/influxdb"; + extraConfig = { + meta.reporting-disabled = true; + data.query-log-enabled = false; + http.bind-address = "localhost:8086"; + http.auth-enabled = false; + http.log-enabled = false; + }; + }; + systemd.services.influxdb = { + before = [ "grafana.service" ]; + serviceConfig = { + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + LockPersonality = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + ReadWritePaths = "/var/lib/influxdb"; + RestrictAddressFamilies = [ "~AF_PACKET" "~AF_NETLINK" "~AF_UNIX" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + RemoveIPC = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ "~@clock" "~@cpu-emulation" "~@debug" "~@module" "~@mount" "~@obsolete" "~@privileged" "~@raw-io" "~@reboot" "~@resources" "~@swap" ]; + CapabilityBoundingSet = ""; + IPAddressDeny = "any"; + IPAddressAllow = "localhost"; + UMask = "077"; + RuntimeDirectory = "influxdb"; + ExecStartPost = lib.mkForce [ (pkgs.writeShellScript "influxdb-first-run" '' + #!${pkgs.stdenv.shell} + set -euo pipefail + if [ ! -s /var/lib/influxdb/admin.pw ]; then + INIT=1 + head -c 30 /dev/urandom | base64 > /var/lib/influxdb/admin.pw + chmod 400 /var/lib/influxdb/admin.pw + fi + if [ ! -s /var/lib/influxdb/knotendaten.pw ]; then + head -c 30 /dev/urandom | base64 > /var/lib/influxdb/knotendaten.pw + chmod 400 /var/lib/influxdb/knotendaten.pw + fi + if [ ! -s /var/lib/influxdb/grafana.pw ]; then + head -c 30 /dev/urandom | base64 > /var/lib/influxdb/grafana.pw + chmod 400 /var/lib/influxdb/grafana.pw + fi + until ${pkgs.curl}/bin/curl --connect-timeout 1 http://127.0.0.1:8086/ping; do + sleep 1 + done + if [ -v INIT ]; then + read -r adminpw < /var/lib/influxdb/admin.pw + read -r knotendatenpw < /var/lib/influxdb/knotendaten.pw + read -r grafanapw < /var/lib/influxdb/grafana.pw + ${config.services.influxdb.package}/bin/influx -execute 'create database freifunk' + ${config.services.influxdb.package}/bin/influx -database freifunk -execute "create user admin with password '$adminpw'" + ${config.services.influxdb.package}/bin/influx -database freifunk -execute "create user grafana with password '$grafanapw'" + ${config.services.influxdb.package}/bin/influx -database freifunk -execute "create user knotendaten with password '$knotendatenpw'" + ${config.services.influxdb.package}/bin/influx -database freifunk -execute "grant read on freifunk to grafana" + ${config.services.influxdb.package}/bin/influx -database freifunk -execute "grant all on freifunk to admin" + fi + '') ]; + }; + }; +} diff --git a/nginx.nix b/nginx.nix new file mode 100644 index 0000000..1fa0dc6 --- /dev/null +++ b/nginx.nix @@ -0,0 +1,17 @@ +{ config, lib, pkgs, ... }: + +{ + networking.firewall.allowedTCPPorts = [ 80 ]; + services.nginx = { + enable = true; + #logError = "/dev/null"; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + virtualHosts."stats" = { + default = true; + locations."/".proxyPass = "http://unix:${config.services.grafana.socket}:/"; + }; + }; +} diff --git a/sshusers.nix b/sshusers.nix new file mode 100644 index 0000000..f85b3b8 --- /dev/null +++ b/sshusers.nix @@ -0,0 +1,32 @@ +{ lib, pkgs, ... }: + +# Setup users. To add a new user: +# 1. Add the name of the user to the list in the second-to-last line +# 2. Make sure that the git repo contains the key as "$USER.pub" +# 3. Make sure that the commit ("rev") contains the latest commit hash. If it correct, jump to step 7. +# 4. If you changed the commit, manipulate the sha512 entry by changing the first character from 0 to 1 or 1 to 0. +# 5. Run "nixos-rebuild build" +# 6. Wait for a message about an invalid hash and replace the hash in this file with the new one. +# 7. Run "nixos-rebuild switch" +# 8. Let the user login and change their password + +let + sshkeys = pkgs.fetchFromGitHub { + owner = "freifunkhamburg"; + repo = "ssh-keys"; + rev = "286c324f0c0c9ddfd37eee286d064b36dc5e4c2c"; + sha512 = "034d5y75wr8vyz3r222hxar1wm0vmqryvgcji2lh1f8jxpgs3nchb0w2qv44msz085s9p4i92s96z9cb8zapmwj3anm0p8f156pf34c"; + }; + getpubkeys = user: builtins.readFile "${sshkeys}/${user}.pub"; + mkuser = user: { name = user; isNormalUser = true; extraGroups = [ "wheel" ]; initialPassword = "test1234"; openssh.authorizedKeys.keys = [ (getpubkeys user) ]; }; +in +{ + services.openssh = { + enable = true; + # Only allow login through pubkey + passwordAuthentication = false; + challengeResponseAuthentication = false; + }; + + users.users = lib.genAttrs [ "tokudan" ] mkuser ; +}