5 คะแนน โดย GN⁺ 2025-08-20 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • systemd มี ความสามารถในการจัดการบริการ ที่ทรงพลัง แต่ค่าตั้งต้นถูกปรับให้เหมาะกับการใช้งานมากกว่าความปลอดภัย จึงควรมีการใช้ ตัวเลือก hardening เพิ่มเติม
  • สามารถใช้คำสั่ง systemd-analyze security เพื่อวิเคราะห์ ตัวชี้วัดการเปิดเผยด้านความปลอดภัย ของทุกบริการหรือบริการเฉพาะ และใช้จัดลำดับความสำคัญได้
  • มี ตัวเลือกด้านความปลอดภัย หลายแบบที่ใช้ได้ในระดับ service unit และสามารถแก้ไขแยกเป็นรายตัวผ่าน /etc/systemd/system/ServiceName.service.d/override.conf เป็นต้น
  • ตัวเลือกสำคัญมีทั้ง ProtectSystem, PrivateTmp, NoNewPrivileges, SystemCallFilter, MemoryDenyWriteExecute เป็นต้น ซึ่งใช้จำกัดสิทธิ์ของโปรเซสและการเข้าถึงทรัพยากร
  • แทนที่จะมุ่งเป้าไปที่ความปลอดภัยสมบูรณ์แบบ ควร harden บริการที่เปิดเผยสู่ภายนอก ก่อนเพื่อลดความเสี่ยง และยังให้ผลดีมากในสภาพแวดล้อมแบบ self-hosting

ภาพรวมของ systemd

  • systemd มอบแนวทางการจัดการบริการที่สมบูรณ์และแข็งแกร่งมาก
  • แต่เนื่องจากให้ความสำคัญกับความพร้อมใช้งานทันทีมากกว่าความปลอดภัย ค่าตั้งต้นจึงค่อนข้างผ่อนปรน
  • เอกสารนี้แนะนำตัวเลือกเสริมความปลอดภัยหลายแบบที่ใช้กับ systemd service unit และ podman quadlet เพื่อลดโอกาสถูกเจาะ และลดขอบเขตความเสียหายหากเกิดการเจาะระบบ
  • นี่ไม่ใช่คู่มือสำหรับนำไปใช้แบบเหมารวมกับทุกบริการ แต่ต้องมีการทดลอง ตรวจสอบล็อก และปรับแต่งเป็นรายบริการตามลักษณะและความต้องการของแต่ละตัว
  • ความรับผิดชอบด้านความปลอดภัยของโครงสร้างพื้นฐานเป็นของผู้ดูแลทั้งหมด และเอกสารนี้เป็นเพียงเครื่องมืออ้างอิง

การวิเคราะห์ความปลอดภัยของ systemd

  • ใช้คำสั่ง systemd-analyze security เพื่อตรวจสอบสถานะความปลอดภัยของทุกบริการ หรือวิเคราะห์การตั้งค่าอย่างละเอียดของบริการเฉพาะ (เช่น sshd.service) ได้
    • เอาต์พุตจะมีสถานะการตรวจสอบ ชื่อฟีเจอร์ คำอธิบาย และ คะแนน Exposure โดยยิ่ง Exposure สูง ความเสี่ยงก็ยิ่งมาก
  • สามารถเพิ่มการตั้งค่าตัวเลือกความปลอดภัยในส่วน [Service] (systemd) หรือ [Container] (podman quadlet) ได้
  • แนะนำให้ใช้ systemctl edit ServiceName.service เพื่อสร้างไฟล์ override และหากล้มเหลวควรตรวจสอบสิทธิ์ที่จำเป็นแล้วปรับแก้

ตัวเลือกความปลอดภัยของบริการ

  • systemd มี คีย์เวิร์ดตัวเลือกความปลอดภัย หลายแบบ และสามารถดูรายละเอียดได้จาก man systemd.exec 5, man capabilities 7 เป็นต้น
  • ตัวเลือกด้านความปลอดภัยที่พบได้บ่อย
    • ProtectSystem → ตัวเลือกสำหรับจำกัดไฟล์ซิสเต็มให้อ่านได้อย่างเดียว
    • ProtectHome → ตัวเลือกสำหรับบล็อกการเข้าถึง /home, /root, /run/user
    • PrivateDevices → ตัวเลือกสำหรับบล็อกการเข้าถึงอุปกรณ์จริง และอนุญาตเฉพาะอุปกรณ์เสมือนอย่าง /dev/null
    • ProtectKernelTunables, ProtectKernelModules, ProtectKernelLogs → ตัวเลือกสำหรับบล็อกการเข้าถึงทรัพยากรของเคอร์เนล
    • NoNewPrivileges → ตัวเลือกสำหรับป้องกันการได้สิทธิ์ใหม่ผ่าน setuid/setgid เป็นต้น
    • MemoryDenyWriteExecute → บล็อกการใช้หน่วยความจำที่เขียนและรันได้พร้อมกัน ซึ่งอาจมีปัญหากับภาษาแบบ JIT บางตัว
    • SystemCallFilter → ตัวเลือกสำหรับจำกัด system call ที่อนุญาต โดยเมื่อฝ่าฝืนอาจจบโปรเซสหรือคืนค่า EPERM ได้

คำอธิบายของแต่ละตัวเลือก

  • ProtectSystem: หากตั้งเป็น strict จะเมานต์ทั้งไฟล์ซิสเต็มเป็นแบบอ่านอย่างเดียว โดย /dev, /proc, /sys ต้องใช้ตัวเลือกป้องกันเพิ่มเติมแยกต่างหาก
  • ReadWritePaths: ตั้งค่าให้บางพาธกลับมาเขียนได้อีกครั้ง
  • ProtectHome: บล็อกการเข้าถึง /home, /root, /run/user
  • PrivateDevices: ปิดการเข้าถึงอุปกรณ์จริง และอนุญาตเฉพาะอุปกรณ์ pseudo อย่าง /dev/null
  • ProtectKernelTunables: ทำให้ /proc, /sys เป็นแบบอ่านอย่างเดียว
  • ProtectControlGroups: อนุญาตเฉพาะการเข้าถึง cgroup แบบอ่านอย่างเดียว
  • ProtectKernelModules: ห้ามโหลดโมดูลเคอร์เนลแบบชัดแจ้ง
  • ProtectKernelLogs: จำกัดการเข้าถึงบัฟเฟอร์ล็อกของเคอร์เนล
  • ProtectProc: หากตั้งเป็น invisible จะซ่อนโปรเซสของผู้ใช้อื่นจาก /proc/
  • ProcSubset: บล็อกเนื้อหาใน /proc นอกเหนือจากรายการที่เกี่ยวข้องกับ PID ที่กำหนด
  • NoNewPrivileges: บล็อกการยกระดับสิทธิ์ใหม่ผ่าน setuid, setgid และ file system capability
  • ProtectClock: บล็อกการเขียนค่า system/hardware clock
  • SystemCallArchitectures: หากตั้งเป็น native จะอนุญาตเฉพาะ native syscall เช่น x86-64
  • RestrictNamespaces: จำกัด namespace ที่ใช้กับคอนเทนเนอร์โดยเฉพาะ
  • RestrictSUIDSGID: บล็อกการตั้งบิต setuid, setgid ให้กับไฟล์
  • LockPersonality: ป้องกันการเปลี่ยน execution domain (มักจำเป็นเฉพาะกับแอปพลิเคชันรุ่นเก่า)
  • RestrictRealtime: จำกัดการจัดตารางแบบ real-time (จำเป็นเฉพาะกับบริการเฉพาะทางบางตัว)
  • RestrictAddressFamilies: จำกัดตระกูล socket address ที่อนุญาต (เช่น ระบุ AF_INET, AF_INET6, AF_UNIX เป็นต้น)
  • MemoryDenyWriteExecute: บล็อกการสร้างพื้นที่หน่วยความจำใหม่ที่เขียนได้+รันได้ (บริการที่ใช้ JIT ต้องระวัง)
  • ProtectHostname: ห้ามใช้ syscall sethostname, setdomainname
  • SystemCallFilter: ตั้งค่าอนุญาต/บล็อก syscall รายบริการ และกรองได้ละเอียด
    • ปรับได้ทั้งแบบกลุ่ม, syscall รายตัว, วิธีอนุญาต/บล็อก เป็นต้น
    • รองรับการตั้งค่าให้คืนรหัสผิดพลาดอย่าง EPERM แทนการจบโปรเซสเมื่อมีการฝ่าฝืน
    • ดูรายการทั้งหมดได้จาก systemd-analyze syscall-filter หรือ man systemd.exec(5)
    • ใช้คำนำหน้า ~ เพื่อกลับความหมายของทั้งลิสต์ได้ (เช่น CapabilityBoundingSet=~CAP_SETUID เป็นต้น)

ขั้นตอนการปรับข้อจำกัดของ SystemCallFilter

  • ใช้ auditd เพื่อตรวจดูล็อกได้ว่าเมื่อบริการล้มเหลว มี syscall ใดถูกบล็อก
    • รัน sudo ausearch -i -m SECCOMP -ts recent แล้วตรวจค่าของ syscall
    • จากนั้นเพิ่ม syscall นั้นหรือกลุ่มที่เกี่ยวข้องลงใน SystemCallFilter เพื่อแก้ปัญหาไปทีละขั้น

ลำดับความสำคัญในการทำ hardening และเคล็ดลับการใช้งาน

  • ไม่จำเป็นต้องใช้กับทุกบริการทั้งหมด
  • threat model และการจัดการความเสี่ยงคือหัวใจสำคัญ โดยเฉพาะ บริการที่เปิดเผยสู่ภายนอก (httpd, nginx, ssh เป็นต้น) ควรพิจารณาเป็นพิเศษ
  • คำสั่งแบบกำหนดเอง, timer unit (ตัวแทน cron แบบเดิม) เป็นต้น ก็ให้ผลดีหากนำไปใช้เชิงรุก
  • ยิ่งเป็นบริการที่ไม่ซับซ้อนมาก ก็ยิ่งมีโอกาสปรับจูนแบบละเอียดได้มาก

เช็กลิสต์: ชุดตัวเลือกความปลอดภัยที่แนะนำ (ลำดับความสำคัญเริ่มต้น)

  • ProtectSystem=strict
  • PrivateTmp=yes
  • ProtectHome=yes หรือ ProtectHome=tmpfs
  • ProtectClock=yes, ProtectKernelLogs=yes, ProtectKernelModules=yes
  • RestrictSUIDGUID=yes
  • UMask=0077
  • LockPersonality=yes
  • RestrictRealtime=yes
  • MemoryDenyWriteExecute=yes
  • DynamicUser=yes หรือระบุ User เป็นผู้ใช้เฉพาะที่ไม่ใช่ root
  • รายการข้างต้นโดยทั่วไปเป็นชุดที่มักใช้ได้โดยแทบไม่กระทบต่อบริการ
  • หากต้องการเพิ่มการกรอง syscall (SystemCallFilter) ด้วย จะต้องมีการทดสอบอย่างละเอียด

ตัวอย่างการตั้งค่า Traefik

  • เป็นกรณีตัวอย่างของบริการ Traefik แบบคอนเทนเนอร์ที่รันด้วย systemd quadlet และใช้ตัวเลือกด้านความปลอดภัยหลายรายการ
    • ใช้ ProtectSystem=full, ProtectHome=yes, SystemCallFilter=@system-service @mount @privileged เป็นต้น
    • ลบสิทธิ์บางอย่างด้วย CapabilityBoundingSet=~CAP_SETUID CAP_SETPCAP
    • ใช้ข้อจำกัดการเข้าถึงเครือข่าย เช่น RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK

บทสรุป

  • ตัวเลือกเสริมความปลอดภัยของ systemd เป็นวิธีที่ใช้งานได้จริงและควรมีติดกล่องเครื่องมือไว้สำหรับผู้ดูแลระบบสายยูนิกซ์
  • ไม่ใช่มาตรการความปลอดภัยที่สมบูรณ์แบบ แต่ควรใช้เป็น เครื่องมือเพื่อลดความเสี่ยง และไม่จำเป็นต้องยัดการตั้งค่าความปลอดภัยใส่ทุกบริการแบบไม่เลือก
  • โดยเฉพาะผู้ดูแลในสภาพแวดล้อมแบบ self-hosting จะได้ประโยชน์มากในการยกระดับความปลอดภัย
  • ให้ความสำคัญกับ “ความใช้งานได้จริงมากกว่าความสมบูรณ์แบบ” และแนะนำให้นำไปใช้แบบบางส่วนตามงานและสภาพแวดล้อมที่เหมาะสม

1 ความคิดเห็น

 
GN⁺ 2025-08-20
ความคิดเห็นจาก Hacker News
  • คิดว่าน่าสนใจที่สามารถทำ automated systemd service hardening ได้ด้วยการทำ strace profiling
    https://github.com/desbma/shh

    • มีวิธีที่ผมเจอแล้วค่อนข้างดี คือในตัวอย่างไม่ได้ใช้ ProtectSystem=
      แต่ถ้าใช้ TemporaryFileSystem=/:ro, BindReadOnly=/usr/bin/binary /lib /lib64 /usr/lib usr/lib64
      ก็จะใส่เฉพาะไบนารีที่ต้องการและพาธที่ต้องการให้อ่านได้เท่านั้น
      ตอนนี้ ProtectSystem= ยังไม่รองรับการทำงานแบบนี้
      รายละเอียดเพิ่มเติมดูได้ที่นี่

    • คิดว่าวิธีนี้อาจมีปัญหากับ service ที่ต้องทำงานเพิ่มเติมเวลาเกิด error เช่น ส่งอีเมล

  • เทียบกับโพสต์เรื่อง systemd hardening เมื่อวาน โพสต์นี้ใช้งานได้จริงกว่าเยอะและมีทิปที่เอาไปใช้ได้ทันทีหลายอย่าง
    เมื่อวานผมพยายามยกตัวอย่างที่ practical กว่าไว้ในคอมเมนต์ของโพสต์นั้น แต่โพสต์วันนี้สรุปเนื้อหาเชิงปฏิบัติได้ดีมาก และอธิบายวิธีเสริม isolation กับ security ด้วย systemd ได้อย่างรวดเร็วและง่ายดาย
    คิดว่าเป็นบทความที่ยอดเยี่ยม
    แปะของเมื่อวานไว้เป็นข้อมูลอ้างอิงด้วย
    https://us.jlcarveth.dev/post/hardening-systemd.md
    https://news.ycombinator.com/item?id=44928504

    • อยากให้แก้ปัญหา certificate ของเว็บหน่อย
      บางเบราว์เซอร์เข้าไม่ได้เลยเพราะ certificate error
  • ขอบคุณที่แชร์
    ถ้าใช้ systemd-analyze พร้อมแฟล็ก --user ก็สามารถตรวจความปลอดภัยของ systemd user unit ได้ ("systemd-analyze --user security")
    ช่วงที่ย้าย container ไปใช้ Podman ผมเริ่มใช้ systemd มากขึ้น และคิดว่าเครื่องมือนี้จะช่วยยกระดับความปลอดภัยของ systemd unit/container service ได้มาก

  • สมัยก่อน init script ต่างคนต่างทำกันไป จึงทำ hardening แบบสม่ำเสมออย่างนี้ไม่ได้

    • แน่นอนว่าทำ hardening แบบนี้กับ init script แบบเดิมก็พอทำได้ แต่ systemd ช่วยให้ใช้ความสามารถดี ๆ ของ kernel ได้ง่ายขึ้นในแบบมาตรฐานและสม่ำเสมอ
      ผมเข้ามาใช้ Linux ค่อนข้างช้าเลยนึกภาพระบบที่ไม่มี systemd แทบไม่ออก และระบบที่ไม่มี systemd ก็ใช้งานลำบากมาก
      ช่วงหลังผมเพิ่งเจอเครื่องมือชื่อ "unshare" ทำให้ทดลองอย่าง remount ทั้ง /nix เป็น RW ได้โดยไม่กระทบ process อื่น
      systemd อาจใช้งานไม่ค่อยลื่นนัก แต่พูดตรง ๆ สำหรับผม ทางเลือกอื่นมีแค่ Windows
  • สงสัยว่าทำไม Linux distro ถึงไม่เปิด security switch พวกนี้เป็นค่าเริ่มต้นให้มากกว่านี้
    เลยคิดว่าการ harden แบบ conservative มันมีข้อเสียอะไรหรือเปล่า
    สำหรับผู้ใช้จำนวนมาก การตั้งค่าอาจเยอะและซับซ้อนเกินไป

    • ถ้าปรับค่าแบบ aggressive เกินไป ก็อาจทำให้ค่าที่มีอยู่เดิมพังโดยไม่ตั้งใจ
      ตัวอย่างเช่น ถ้า harden NetworkManager ก็ต้องไล่ตรวจเองว่าทั้ง IPv4 และ IPv6 ยังเชื่อมต่อได้ไหม, โหมด dns=systemd-resolved กับ dns=default ยังทำงานปกติไหม, การเชื่อมต่อกับ ModemManager และ cellular, ปลั๊กอิน openvpn หรือ cisco anyconnect, รวมถึง NetworkManager-dispatcher hook ทำงานครบหรือไม่
      อีกประเด็นคือจะมี maintainer ของ distro สักกี่คนที่มั่นใจได้ว่าปรับ switch ของแพ็กเกจที่ตัวเองดูแลได้มากแค่ไหน โดยที่ไม่ทำให้ environment ของผู้ใช้เกิน 0.01% พัง
      ถ้า distro เป็นคนดูแลแฟล็กพวกนี้เอง ก็ต้องแบก compatibility issue เพิ่มทุกครั้งที่มี upstream release และถ้า upstream เป็นคนตั้งค่าเอง ก็เลี่ยงไม่ได้ที่จะต้องระวังเรื่อง backward compatibility มากเป็นพิเศษ

    • คำถามนี้คล้ายกับคำถามว่า "ทำไม distro ถึงไม่เปิด MAC (อย่าง SELinux) แบบเข้มงวดเป็นค่าเริ่มต้น?"
      แม้แต่ sshd เองก็ควรถูกจำกัดให้มากกว่านี้

      1. ต้นทุนการพัฒนาเริ่มต้นเพื่อเอาไปใช้
      2. ต้นทุนจัดการ bug report ที่เกิดขึ้นใน environment ผู้ใช้สารพัดแบบ
      3. ต้นทุนการดูแลต่อเนื่องให้ทันการเปลี่ยนแปลงของ distro/upstream
        ด้วยเหตุผลพวกนี้ distro หลัก ๆ เลยรับภาระค่อนข้างหนัก
        SELinux กับ AppArmor ก็คล้ายกัน คือ maintainer หลายคนมองว่าผลตอบแทนต่อการลงทุนไม่ค่อยสูง
    • อีกเหตุผลใหญ่คือไม่มีศักยภาพหรือทรัพยากรพอจะทำ integration test ทีละพารามิเตอร์เพื่อให้มั่นใจว่า system service หลักยังทำงานปกติ
      บทสนทนาที่เกี่ยวข้อง
      https://news.ycombinator.com/item?id=29995566
      ผลของ systemd-analyze security ก็แตกต่างกันไปในแต่ละ distro
      desbma/shh จะสร้างกฎรายหน่วยอย่าง SyscallFilter เป็นต้นแบบอัตโนมัติจากข้อมูลที่เก็บด้วย strace ซึ่งคล้ายกับ audit2allow ของ SELinux
      แต่การติดตั้ง strace ลงใน production environment ก็อาจเป็นเรื่องที่ถกเถียงกันได้
      https://github.com/desbma/shh

    • ผมก็ไม่แน่ใจเหมือนกัน แต่บาง setting เป็นของที่เพิ่งเพิ่มเข้ามา ผู้ใช้จำนวนมากอาจยังไม่รู้จัก
      ไม่ใช่ว่าทุกคนจะเป็น systemd power user และถ้าเปิด setting เหล่านี้ ก็มีความเสี่ยงว่า systemd เวอร์ชันเก่าอาจทำงานไม่ได้ตามปกติ
      ถึงจะมีความสามารถอย่าง SELinux, AppArmor และอื่น ๆ อยู่ แต่ distro, นักพัฒนา และผู้ใช้จำนวนมากก็ไม่ได้รู้สึกว่าจำเป็นขนาดนั้น จึงทำให้เปิดใช้อัตโนมัติได้ยาก

  • มีตัวเลือกสำหรับ hardening เยอะมาก จนคิดว่าน่าจะมี repository ที่รวบรวมตัวอย่าง hardening ทั่วไปแยกตาม service ไว้ให้ดู
    ผู้ใช้มักนำสคริปต์ hardening ที่คนอื่นใช้กันทั่วไปมาปรับใช้ แต่สุดท้ายกลับพบว่าต้องตั้งสิทธิ์กว้างกว่าที่คิดเพื่อไม่ให้เกิดเคสยกเว้น

    • เวลาแพ็กเกจบน distro ที่ upstream support น้อยอย่าง nixpkgs
      วิธีที่มีประโยชน์ที่สุดคือดูว่า distro กระแสหลักแพ็กเกจและ harden กันอย่างไร
      hardening เหล่านั้นมักผ่านการทดสอบมาค่อนข้างดีแล้ว ดังนั้นถ้าอยากดูตัวอย่างของ postgresql หรือบริการอื่น ๆ ก็ควรเริ่มจากแพ็กเกจของ Debian, Ubuntu, RHEL
  • หนึ่งในความสามารถด้าน security ที่ยอดเยี่ยมของ systemd คือการจัดการ credentials
    มันช่วยส่ง credential ให้แอปพลิเคชันได้อย่างปลอดภัยกว่าการเก็บไว้ใน environment variable หรือไฟล์บน filesystem
    ใน environment ที่ไม่มี Vault หรือระบบลักษณะนั้น เช่น โปรเจกต์ส่วนตัว ผมเลือกวิธีนี้ตลอด
    ผมยังทำ Go package ที่เชื่อมกับความสามารถนี้ไว้เองด้วย
    credentials ใน systemd
    แพ็กเกจ credential-go

    • ฟังดูเหมือนวัฒนธรรมแบบ nodejs หรือ npm ที่ทำโค้ด 2 บรรทัดให้กลายเป็นแพ็กเกจ
      จริง ๆ แล้วก็แค่

      dir, err := os.Getenv("CREDENTIALS_DIRECTORY")
      cred, err := os.ReadFile(filepath.Join(dir, "name"))
      

      ไม่ได้ซับซ้อนไปกว่า left-pad เลย
      เท่าที่ผมเข้าใจ ชุมชน Go เดิมทีถือว่าการลด dependency และหลีกเลี่ยง abstraction ที่ไม่จำเป็น (เช่น การเรียกฟังก์ชันเพิ่ม) เป็นสิ่งที่ดี
      การทำงานง่าย ๆ แบบนี้เมื่อก่อนคนก็มักเขียนกันสด ๆ ตรงนั้นเลย

    • สงสัยว่ารูปแบบการส่ง credential แบบนี้ป้องกันไม่ให้ process ลูกที่ถูก fork สืบทอด credential ต่อได้อย่างไร

  • เป็นบทความที่มีประโยชน์มาก
    ชอบทั้งรายการตัวเลือกต่าง ๆ ของ systemd และคำแนะนำแนว "เปิด man แล้วขอให้โชคดี"
    systemd ยอดเยี่ยมมากจนผมอยาก deploy ใช้กับเซิร์ฟเวอร์ของตัวเองให้หนักขึ้น

  • ทิปเล็ก ๆ คือการเขียนชื่อ systemd ที่ถูกต้องคือ systemd
    ไม่ใช่ SystemD, system D หรือ system d
    เพราะมันย่อมาจาก system daemon จึงตั้งชื่อตามธรรมเนียม Unix/Linux ที่ลงท้ายด้วย d ตัวเล็ก

    • น่าสนใจดี
      ปกติผมเห็นคนใช้ systemD บ่อยกว่า เลยสงสัยว่าทำไมรูปแบบนั้นถึงแพร่หลายมาก
  • ทิปการดีบักปัญหา syscall ใน systemd มีประโยชน์มากจริง ๆ