diff --git a/hosts/skydick/datapool.nix b/hosts/skydick/datapool.nix index 4722286..9613927 100644 --- a/hosts/skydick/datapool.nix +++ b/hosts/skydick/datapool.nix @@ -75,12 +75,6 @@ # dick/system/vm /srv/system/vm 64K zstd central VM filesystem root / parent for zvol children # dick/templates/vm /srv/templates/vm 64K zstd shared read-only VM base images # -# LEGACY (active migration — destroy after cutover) -# dick/share /srv/share 128K zstd → dick/public -# dick/torrent /srv/torrent 1M zstd → dick/media/data -# dick/backup /srv/backup 1M zstd-3 → dick/system/backup -# dick/vm /srv/vm 64K zstd → dick/system/vm -# # Design rule: dataset boundary = hardlink domain = quota/tuning domain. # dick/media keeps payload (data/) and library in ONE dataset so *arr # hardlinks work. Per-user trees hold private state only, not payload — @@ -93,7 +87,7 @@ # zfs create -o mountpoint=/srv/public -o recordsize=128K dick/public # chown root:storage /srv/public && chmod 2775 /srv/public # -# dick/media already exists (recordsize=1M, compression=off). +# zfs create -o mountpoint=/srv/media -o recordsize=1M -o compression=off dick/media # mkdir -p /srv/media/{data,library} # chown qbittorrent:storage /srv/media/{data,library} # @@ -111,16 +105,16 @@ # # File-backed VM images live under /srv/users/ldx/vm/files. # # Block LUNs are zvol children of dick/users/ldx/vm/. # -# # ylw (UID 1002, primary GID 100 = users) — same pattern, s/ldx/ylw/ -# zfs create -o mountpoint=/srv/users/ylw -o quota=10T dick/users/ylw -# zfs create -o recordsize=128K -o mountpoint=/srv/users/ylw/files dick/users/ylw/files -# zfs create -o recordsize=16K -o mountpoint=/srv/users/ylw/bt-state dick/users/ylw/bt-state -# zfs create -o recordsize=64K -o mountpoint=/srv/users/ylw/vm dick/users/ylw/vm -# mkdir -p /srv/users/ylw/vm/files -# chown ylw:users /srv/users/ylw && chmod 0700 /srv/users/ylw -# for d in files bt-state vm vm/files; do chown ylw:users /srv/users/ylw/$d && chmod 0750 /srv/users/ylw/$d; done -# # File-backed VM images live under /srv/users/ylw/vm/files. -# # Block LUNs are zvol children of dick/users/ylw/vm/. +# # ye-lw21 (local+LDAP account, local UID 1002/GID 100 on skydick) +# zfs create -o mountpoint=/srv/users/ye-lw21 -o quota=10T dick/users/ye-lw21 +# zfs create -o recordsize=128K -o mountpoint=/srv/users/ye-lw21/files dick/users/ye-lw21/files +# zfs create -o recordsize=16K -o mountpoint=/srv/users/ye-lw21/bt-state dick/users/ye-lw21/bt-state +# zfs create -o recordsize=64K -o mountpoint=/srv/users/ye-lw21/vm dick/users/ye-lw21/vm +# mkdir -p /srv/users/ye-lw21/vm/files +# chown ye-lw21:users /srv/users/ye-lw21 && chmod 0700 /srv/users/ye-lw21 +# for d in files bt-state vm vm/files; do chown ye-lw21:users /srv/users/ye-lw21/$d && chmod 0750 /srv/users/ye-lw21/$d; done +# # File-backed VM images live under /srv/users/ye-lw21/vm/files. +# # Block LUNs are zvol children of dick/users/ye-lw21/vm/. # # System: # zfs create -o mountpoint=none -o canmount=off dick/system @@ -166,16 +160,21 @@ # NFS all_squash provides UID mapping, not authentication. Per-user NFS here # uses one explicit export per user, which is acceptable for a small fixed set # but scales linearly with user count and client ACL maintenance. -# Samba [homes] valid users = %S gives real per-user auth via tdbsam. -# LDAP already has posixAccount users and Samba schema loaded, but no live -# sambaSamAccount/sambaDomain entries on skydick yet — unified SMB auth is a -# separate integration step. For stronger NFS isolation: use sec=krb5 or +# Samba [homes] valid users = %S gives real per-user auth via LDAP-backed +# Samba accounts (ldapsam). The intended model is LDAP-only SMB users; if a +# same-name local account exists on skydick, local NSS still wins for the +# final Unix authorization step. For stronger NFS isolation: use sec=krb5 or # tighter per-client IP restrictions. { config, pkgs, ... }: { - users.groups.storage = {}; + # Keep a fixed local GID for on-disk ownership and the qbittorrent service + # account. Shared-user membership policy is carried by the matching LDAP + # posixGroup cn=storage,ou=posix_groups,dc=skyw,dc=top. + users.groups.storage = { + gid = 997; + }; # Service account for the shared media writer (qbittorrent + *arr stack) users.users.qbittorrent = { @@ -201,11 +200,11 @@ "d /srv/users/ldx/bt-state 0750 ldx users -" "d /srv/users/ldx/vm 0750 ldx users -" "d /srv/users/ldx/vm/files 0750 ldx users -" - "d /srv/users/ylw 0700 ylw users -" - "d /srv/users/ylw/files 0750 ylw users -" - "d /srv/users/ylw/bt-state 0750 ylw users -" - "d /srv/users/ylw/vm 0750 ylw users -" - "d /srv/users/ylw/vm/files 0750 ylw users -" + "d /srv/users/ye-lw21 0700 ye-lw21 users -" + "d /srv/users/ye-lw21/files 0750 ye-lw21 users -" + "d /srv/users/ye-lw21/bt-state 0750 ye-lw21 users -" + "d /srv/users/ye-lw21/vm 0750 ye-lw21 users -" + "d /srv/users/ye-lw21/vm/files 0750 ye-lw21 users -" # System "d /srv/system 0700 root root -" @@ -215,13 +214,29 @@ "d /srv/templates 0755 root root -" "d /srv/templates/vm 0755 root root -" - # Legacy (keep until migration complete) - "d /srv/share 2775 root storage -" - "d /srv/backup 0700 root root -" - "d /srv/torrent 2775 root storage -" - "d /srv/vm 0700 root root -" ]; + systemd.services.samba-ldap-admin-password = { + description = "Seed Samba LDAP admin password into secrets.tdb"; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + before = [ "samba-nmbd.service" "samba-winbindd.service" "samba-smbd.service" ]; + requiredBy = [ "samba-nmbd.service" "samba-winbindd.service" "samba-smbd.service" ]; + restartTriggers = [ config.environment.etc."samba/smb.conf".source ]; + + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ConditionPathExists = config.age.secrets.skydick-samba-ldap-admin.path; + }; + + script = '' + set -euo pipefail + password="$(${pkgs.coreutils}/bin/cat ${config.age.secrets.skydick-samba-ldap-admin.path})" + ${pkgs.coreutils}/bin/printf '%s\n%s\n' "$password" "$password" | ${config.services.samba.package}/bin/smbpasswd -s -W + ''; + }; + # NFS — primary protocol for all datasets services.rpcbind.enable = true; @@ -242,18 +257,13 @@ # Per-user — explicit exports; all_squash maps every client UID to the owner /srv/users/ldx 10.0.0.0/16(rw,sync,no_subtree_check,all_squash,anonuid=1000,anongid=100) - /srv/users/ylw 10.0.0.0/16(rw,sync,no_subtree_check,all_squash,anonuid=1002,anongid=100) + /srv/users/ye-lw21 10.0.0.0/16(rw,sync,no_subtree_check,all_squash,anonuid=1002,anongid=100) # System /srv/system/backup 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash) /srv/system/vm 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash) /srv/templates/vm 10.0.0.0/16(ro,sync,no_subtree_check,root_squash) - # Legacy (remove after cutover) - /srv/share 10.0.0.0/16(rw,sync,no_subtree_check,root_squash) - /srv/backup 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash) - /srv/torrent 10.0.0.0/16(rw,sync,no_subtree_check,all_squash,anonuid=900,anongid=997) - /srv/vm 10.0.0.0/16(rw,sync,no_subtree_check,no_root_squash) ''; }; @@ -268,6 +278,7 @@ # Samba — file-level access (SMB + NFS share the same datasets) services.samba = { enable = true; + package = pkgs.sambaFull; openFirewall = false; settings = { @@ -276,6 +287,18 @@ "server string" = "Skydick Storage"; "netbios name" = "SKYDICK"; security = "user"; + "passdb backend" = "ldapsam:ldap://10.0.0.1"; + "ldap admin dn" = "cn=admin,dc=skyw,dc=top"; + "ldap suffix" = "dc=skyw,dc=top"; + "ldap user suffix" = "ou=people"; + "ldap group suffix" = "ou=posix_groups"; + "ldap machine suffix" = "ou=machines"; + "ldap delete dn" = "no"; + "ldap passwd sync" = "no"; + "ldap ssl" = "off"; + "ldap server require strong auth" = "no"; + "ldap connection timeout" = "5"; + "ldap timeout" = "5"; "hosts allow" = "10.0. 127."; "hosts deny" = "ALL"; diff --git a/hosts/skydick/default.nix b/hosts/skydick/default.nix index 6bc5de9..efebb5f 100644 --- a/hosts/skydick/default.nix +++ b/hosts/skydick/default.nix @@ -194,7 +194,8 @@ hashedPassword = "$y$j9T$hHcj2QYj1.AXbLEALbvr/.$WuDsa.hRDcBWN5s.dJX.KHm9rgkgP/NpNlp3bs2vvs3"; }; - users.users.ylw = { + users.users."ye-lw21" = { + uid = 1002; extraGroups = [ "storage" ]; hashedPassword = "$y$j9T$hia.9h7L/5q7G4QdKFHOA1$fAFFSpJRf57ZEvCVjDjwM1WH8UPR5E1Xy28KeJQ.gfD"; }; @@ -209,8 +210,17 @@ mode = "0400"; }; - # LDAP is used here for POSIX identity lookups only. SMB still uses Samba's - # local passdb until sambaSamAccount objects are provisioned in LDAP. + age.secrets.skydick-samba-ldap-admin = { + file = ../../secrets/skydick-samba-ldap-admin.age; + owner = "root"; + group = "root"; + mode = "0400"; + }; + + # LDAP is used for POSIX identity lookups. SMB account data is also stored in + # LDAP via Samba's ldapsam backend. Same-name local admin overlays still win + # for the final Unix UID/GID on skydick, while SMB password data remains in + # LDAP. users.ldap = { enable = true; loginPam = false; diff --git a/hosts/skydick/samba-ldap-bootstrap.ldif b/hosts/skydick/samba-ldap-bootstrap.ldif new file mode 100644 index 0000000..72fe168 --- /dev/null +++ b/hosts/skydick/samba-ldap-bootstrap.ldif @@ -0,0 +1,45 @@ +# Bootstrap LDAP objects required for skydick's Samba ldapsam backend and the +# shared storage access group. Apply once on 10.0.0.1, then maintain users with +# normal LDAP tools and `smbpasswd -a ` on skydick. +# +# Example: +# ldapadd -x -H ldap://127.0.0.1:389 \ +# -D "cn=admin,dc=skyw,dc=top" -W \ +# -f hosts/skydick/samba-ldap-bootstrap.ldif +# +# After bootstrap, add or update Samba passwords from skydick: +# smbpasswd -a ldx +# smbpasswd -a ye-lw21 +# +dn: ou=machines,dc=skyw,dc=top +objectClass: organizationalUnit +objectClass: top +ou: machines + +dn: cn=storage,ou=posix_groups,dc=skyw,dc=top +objectClass: posixGroup +objectClass: top +cn: storage +gidNumber: 997 +memberUid: ldx +memberUid: ye-lw21 + +dn: sambaDomainName=SKYDICK,dc=skyw,dc=top +objectClass: sambaDomain +objectClass: top +sambaDomainName: SKYDICK +sambaSID: S-1-5-21-217057769-2756971674-166300659 +sambaAlgorithmicRidBase: 1000 +sambaMinPwdLength: 5 +sambaPwdHistoryLength: 0 +sambaLogonToChgPwd: 0 +sambaMaxPwdAge: -1 +sambaMinPwdAge: 0 +sambaLockoutDuration: 30 +sambaLockoutObservationWindow: 30 +sambaLockoutThreshold: 0 +sambaForceLogoff: -1 +sambaRefuseMachinePwdChange: 0 +sambaNextRid: 1000 +sambaNextUserRid: 1000 +sambaNextGroupRid: 1000 diff --git a/hosts/xlab-gateway/default.nix b/hosts/xlab-gateway/default.nix index 2459ff6..a6edc47 100644 --- a/hosts/xlab-gateway/default.nix +++ b/hosts/xlab-gateway/default.nix @@ -33,7 +33,7 @@ hashedPassword = "$y$j9T$R0XBoDSGk700h7UdglfaJ1$mjRwpJir/Tno1.fCbjet0cp/JPb1DW.JILvmE5.NJuD"; }; - users.users.ylw = { + users.users."ye-lw21" = { extraGroups = [ "networkmanager" ]; hashedPassword = "$y$j9T$jiLKGLB/gJKEYYn2zaoUw/$9mfwEUo5z2sH9OXwioLnbAVpCMOg2lUpA3ph9Vqx228"; }; diff --git a/modules/users.nix b/modules/users.nix index 4656c5b..d7fb8f6 100644 --- a/modules/users.nix +++ b/modules/users.nix @@ -10,11 +10,12 @@ ]; }; - users.users.ylw = { + users.users."ye-lw21" = { isNormalUser = true; + uid = 1002; extraGroups = [ "wheel" ]; openssh.authorizedKeys.keys = [ - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGMOAuXpGQcwZDOiAbqnVOkBB4jjSs+awGa0WHpDhuZFo61mQNIoE3lozAO58HikY49+JwJccL+oJgA7VNzpy1hl711wbqj8CV3wjJflpnTla86XnXI30w6dlx7qd07JDoXYr6JvCOCk0nbwomXeekxm8Yw1hwjb476ryvZJDvFhIpSx+7j8oP+qlsUDKOhaMdpiDN71b8YihS7jTZvyw9958XB2AH1WjjV1foWk1ux0KIfkqTTZTPkvbkKBsuHFhHzp08OVXTNaROBz+iDcOm8JUodAO/Qbjcaw2pwENjThxwtuXDCYwYv9CN1rQ3lTXo5zC+8MqvGCZBbnhgIvTV8C5W1nRTpOC1d07r0CrNqq4PVPPW1FKBkhUhbuhJCawNjxgQvK2fwP9hZYstrYiXHVgGNmnv9Utg8ENB4fXYKHgFktGhWXMb8oYJkV9wSQuh86hnr3WAjmQu7dIRT02Sp+KPYskFIGQkDoC79PjUVGJC0HZAFLPIBozliF0u/58= ylw-laptop@YLW-LAPTOP" + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDGMOAuXpGQcwZDOiAbqnVOkBB4jjSs+awGa0WHpDhuZFo61mQNIoE3lozAO58HikY49+JwJccL+oJgA7VNzpy1hl711wbqj8CV3wjJflpnTla86XnXI30w6dlx7qd07JDoXYr6JvCOCk0nbwomXeekxm8Yw1hwjb476ryvZJDvFhIpSx+7j8oP+qlsUDKOhaMdpiDN71b8YihS7jTZvyw9958XB2AH1WjjV1foWk1ux0KIfkqTTZTPkvbkKBsuHFhHzp08OVXTNaROBz+iDcOm8JUodAO/Qbjcaw2pwENjThxwtuXDCYwYv9CN1rQ3lTXo5zC+8MqvGCZBbnhgIvTV8C5W1nRTpOC1d07r0CrNqq4PVPPW1FKBkhUhbuhJCawNjxgQvK2fwP9hZYstrYiXHVgGNmnv9Utg8ENB4fXYKHgFktGhWXMb8oYJkV9wSQuh86hnr3WAjmQu7dIRT02Sp+KPYskFIGQkDoC79PjUVGJC0HZAFLPIBozliF0u/58= ye-lw21-laptop@YLW-LAPTOP" ]; }; @@ -24,7 +25,7 @@ # switch-to-configuration, and confirmation commands via non-interactive SSH security.sudo.extraRules = [ { - users = [ "ldx" "ylw" ]; + users = [ "ldx" "ye-lw21" ]; commands = [ { command = "ALL"; options = [ "NOPASSWD" ]; } ]; diff --git a/secrets/secrets.nix b/secrets/secrets.nix index 613b2f7..80050ef 100644 --- a/secrets/secrets.nix +++ b/secrets/secrets.nix @@ -13,4 +13,5 @@ "xlab-wg-warp.age".publicKeys = [ admin xlab-gateway ]; "influxdb-token.age".publicKeys = [ admin skydick ]; "skydick-ldap-bind.age".publicKeys = [ admin skydick ]; + "skydick-samba-ldap-admin.age".publicKeys = [ admin skydick ]; } diff --git a/secrets/skydick-samba-ldap-admin.age b/secrets/skydick-samba-ldap-admin.age new file mode 100644 index 0000000..4eb23ba --- /dev/null +++ b/secrets/skydick-samba-ldap-admin.age @@ -0,0 +1,10 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IHA2dHpqdyAxSHNI +MEhIR2c2MjZyOVZ4aUg0ZnI5UGsyMkYyREhHRVZrSTNtclp1V2lrCmhIUVo0L2NZ +TktZMEE4czRpdVZFdWpOMHVseTh5OTY5SzlVRGZoQW9HbmcKLT4gc3NoLWVkMjU1 +MTkgSE0yOW13IHFNUXd5WG1rU1hzYXRwMVlSVEc1UzljSlUxQUtZZWVhM3UvRXZs +L0lCMncKZ0tzWmlnOTlZUHo1aFZFTnR2ajRtZi9IcTBwNWZlSXhMZXNObG90Mm9l +SQotLS0gL1RPdWpzNjYzRGFKQjBtbDMyeTc0dWkzSGFBRXhYQUZsSW1pcSt4cCtw +QQqkG6AODvy/s10+M50PT3N2Sv6q8NhU1dAv7p6SX2Fsxf0GNjNKrMMu/vbq0ZSI +JXWWkbIi4NnGA2ft +-----END AGE ENCRYPTED FILE-----