From f6b942023c1531757aab2ce999242ec5998c7aac Mon Sep 17 00:00:00 2001 From: Daniel Frank Date: Sat, 22 Jan 2022 14:31:29 +0100 Subject: [PATCH] Initial commit --- .envrc | 1 + .gitignore | 2 + README.md | 18 ++++++++ acme.nix | 9 ++++ collector.nix | 29 +++++++++++++ configuration.nix | 104 ++++++++++++++++++++++++++++++++++++++++++++++ grafana.nix | 53 +++++++++++++++++++++++ influxdb.nix | 80 +++++++++++++++++++++++++++++++++++ nginx.nix | 17 ++++++++ sshusers.nix | 32 ++++++++++++++ 10 files changed, 345 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 README.md create mode 100644 acme.nix create mode 100644 collector.nix create mode 100644 configuration.nix create mode 100644 grafana.nix create mode 100644 influxdb.nix create mode 100644 nginx.nix create mode 100644 sshusers.nix 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 ; +}