1 คะแนน โดย GN⁺ 3 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • การโจมตีซัพพลายเชน กลายเป็นปัญหาใหญ่ขึ้น เพราะต้นทุนในการแจกจ่ายซอฟต์แวร์ต่ำมาก และมีการใช้งานระบบ build/การ deploy แบบอัตโนมัติอย่างแพร่หลาย
  • ในยุค 1970 เคยมี วิกฤตซอฟต์แวร์ ที่ทำให้การสร้างซอฟต์แวร์ที่นำกลับมาใช้ซ้ำได้เป็นเรื่องยาก แต่ปัจจุบัน package repository และ package manager สามารถดึงโค้ดและ build ได้ด้วยแค่ชื่อกับเวอร์ชัน
  • การอัปเดต dependency อัตโนมัติทำให้การเปลี่ยนแปลงที่เป็นอันตรายแพร่กระจายได้รวดเร็วผ่าน CI และการโจมตีซัพพลายเชนที่ดีจะแพร่ลามได้เร็วเท่ากับความเร็วที่ CI runner ทำงาน
  • การ vendor dependency ทั้งหมดมาไว้ใน repository ของโปรเจ็กต์จะทำให้ repository ใหญ่ขึ้น แต่ช่วยกันการเปลี่ยนแปลงอัตโนมัติ และทำให้มองเห็นขนาดกับต้นทุนของ dependency ได้ชัดขึ้น
  • แม้จะไม่ใช่คำตอบสำหรับซอฟต์แวร์ทุกประเภท แต่ซอฟต์แวร์ขนาดเล็กจำนวนมากอาจได้ประโยชน์จากการลด dependency ที่อาจเปลี่ยนจากภายนอกอย่างกะทันหันให้เหลือเพียง ระดับ 2~3 ตัว

ปัญหา

  • การโจมตีซัพพลายเชนกลายเป็นปัญหาใหญ่ขึ้นเรื่อย ๆ ไม่ใช่เพราะธรรมชาติของซอฟต์แวร์หรือการบำรุงรักษาเปลี่ยนไปเท่านั้น แต่เพราะโมเดลต้นทุนของการแชร์และแจกจ่ายซอฟต์แวร์ต่ำลงมาก
  • ต้นทุนการแจกจ่ายต่ำมากจนแม้จะมีความสิ้นเปลืองก็ยังมีการใช้อัตโนมัติอย่างหนัก ซึ่งตัวระบบอัตโนมัตินั้นก็มีประโยชน์
  • ทุกไม่กี่เดือนจะมีการโจมตีซัพพลายเชนครั้งใหม่ที่ทำให้โค้ดจำนวนมากทั่วโลกเสียหาย

เรามาถึงจุดนี้ได้อย่างไร

  • ช่วงปลายทศวรรษ 1960 ถึงต้นทศวรรษ 1970 ผู้คนยังไม่ค่อยรู้วิธีสร้างซอฟต์แวร์ที่นำกลับมาใช้ซ้ำได้ และเรียกสิ่งนี้ว่า วิกฤตซอฟต์แวร์
  • ความต้องการซอฟต์แวร์เพิ่มขึ้นแบบเอ็กซ์โปเนนเชียล แต่ความสามารถในการสร้างซอฟต์แวร์ใหม่ให้ทันกับความซับซ้อนที่ต้องการเพิ่มขึ้นช้ากว่านั้น
  • ช่วงเวลานี้นำไปสู่งานวิจัยด้าน modularity, structured programming และระบบโมดูลของภาษาโปรแกรมแทบทั้งหมดที่สร้างหลังปี 1990 ก็สามารถสืบสายย้อนกลับไปถึง Modula-2 ได้
  • ในทศวรรษ 1990 และ 2000 อินเทอร์เน็ตได้สร้างทางออกที่ทรงพลังกว่า ทำให้การ build และแจกจ่ายซอฟต์แวร์มีต้นทุนต่ำลง และซอฟต์แวร์จำนวนมากที่ผู้คนอยากใช้จริงก็เป็นโอเพนซอร์ส
  • จาก CPAN, CTAN และ Linux distribution ได้เกิด package repository และ package manager จำนวนมากขึ้นมา และเครื่องมือเหล่านี้ใช้เพียงไฟล์ manifest, ชื่อ และส่วนใหญ่คือเลขเวอร์ชันที่ค่อนข้างกำหนดกันเอง เพื่อค้นหา ดึง และ build ซอฟต์แวร์
  • จากการรวมระบบด้วยมือสู่ dependency อัตโนมัติ

    • ในอดีต วิธีที่ดีในการสร้างระบบซอฟต์แวร์ซับซ้อนคือการนำชิ้นส่วนที่ใช้งานได้มาประกอบกันด้วยมืออย่างระมัดระวัง ซึ่ง Linux distribution ก็ทำสิ่งนี้เป็นหลัก
    • ในปี 2003 การ build SDL พร้อมฟีเจอร์ทั้งหมดนั้นยุ่งยากถึงขั้นใช้เวลาหลายวัน และก็ไม่จำเป็นต้องโหยหายุคนั้น
    • เมื่อมี Linux distribution เป็นสภาพแวดล้อมพื้นฐานที่รู้จักแน่ชัด ซอฟต์แวร์แบบปรับแต่งจำนวนมากก็สามารถทำงานในโลกของตัวเองได้ โดยไม่ต้องกังวลกับส่วนอื่นของระบบมากนัก
    • เมื่อสื่อสารกับซอฟต์แวร์อื่น ก็มักทำผ่านไฟล์หรือ network socket ที่ใช้ protocol ที่รู้จักกันดี
    • ปัจจุบันมีซอฟต์แวร์ดี ๆ จำนวนมากที่ build ตั้งแต่ต้นด้วย Rust หรือ Go หรือ deploy เป็น Docker container และซอฟต์แวร์เหล่านี้แทบไม่โต้ตอบกับ system library เลย
    • แทนที่จะต้องปรับให้เข้ากับชุดซอฟต์แวร์ที่ OS distribution จัดมาให้ วิธีที่ระบบ build ดึงไลบรารีที่ต้องใช้มาเองได้กลายเป็นแนวทางที่แพร่หลาย
  • วิกฤตในทิศทางตรงกันข้าม

    • ตอนนี้เรากลับเจอวิกฤตตรงข้ามกับยุค 1970 คือผู้คนใช้ซอฟต์แวร์ซ้ำมากเกินไปจนโปรแกรมแย่ลง
    • การแจกจ่ายซอฟต์แวร์ยังคงราคาถูกมาก แต่การใช้งานซอฟต์แวร์ยังคงมีต้นทุน
    • เป็นเวลานานที่ต้นทุนใหญ่ที่สุดคือความซับซ้อนของการ build ซอฟต์แวร์และทำให้มันรันได้บนคอมพิวเตอร์ แต่ปัญหานั้นส่วนใหญ่ถูกทำให้หายไปด้วยระบบอัตโนมัติแล้ว
    • ตอนนี้เราจึง build, deploy และใช้ซอฟต์แวร์มากขึ้นมาก และต้นทุนก็ปรากฏในรูปของ dependency hell, ความพองตัว, เวลา build ที่ยาวนาน, และการหายไปของแพ็กเกจหรือ package manager
    • ปัญหาที่ใหญ่ที่สุดคือ การโจมตีซัพพลายเชน
  • โครงสร้างการแพร่กระจายของการโจมตีซัพพลายเชน

    • การโจมตีซัพพลายเชนเป็นปัญหาที่มีมานานพอ ๆ กับโอเพนซอร์สซอฟต์แวร์เอง
    • ในอดีต เคยมีความพยายามส่งแพตช์อันตรายเข้า Linux kernel โดยใส่ uid = 0 แทน uid == 0 ซึ่งเป็นหนึ่งในความพยายามแก้ไขเคอร์เนลแบบประสงค์ร้ายที่ถูกพบจริงเป็นครั้งแรก และนับเป็นความพยายามโจมตีซัพพลายเชน
    • เหตุผลที่ในช่วง 10 ปีหลังการโจมตีซัพพลายเชนใหญ่ขึ้นและเป็นปัญหามากขึ้น คือระบบ build ถูกทำให้ดึง source code และนำไปแจกจ่ายโดยอัตโนมัติ
    • ระบบ CI มักจะทำงานกับทุกการเปลี่ยนแปลงของโค้ดหรือการเปลี่ยนแปลงใหญ่ ๆ และการเปลี่ยนแปลงเหล่านี้ก็จะพร้อมให้ทุกคนที่พึ่งพาโค้ดนั้นใช้งานได้โดยอัตโนมัติ
    • ระบบ CI ของฝั่งที่พึ่งพาก็จะดึงการเปลี่ยนแปลงนั้นมา รวมโค้ดอันตรายที่เพิ่งถูกเพิ่มเข้าไป และการโจมตีซัพพลายเชนที่ทำได้ดีจะลามเหมือนไฟป่าด้วยความเร็วเท่ากับที่ CI runner ทำงาน
    • มีวิธีชะลอการโจมตีซัพพลายเชน เช่น dependency cooldown แต่ก็จะเกิดข้อถกเถียงเรื่องนโยบายและความรับผิดชอบ

ทางแก้

  • แกนสำคัญคือไม่ปล่อยให้ระบบ build อย่าง npm, cargo ดึง dependency จากตำแหน่งบนเครือข่ายโดยอัตโนมัติทุกครั้ง แต่ให้เก็บ dependency ทั้งหมดไว้พร้อมกับซอฟต์แวร์
  • ทำ vendor dependency ทั้งหมดเข้ามาในโปรเจ็กต์ แล้วคัดลอกเนื้อหาจาก upstream source control มาใส่ใน git repository และ commit ไว้
  • เมื่อมีการอัปเดตจาก upstream ก็แค่ดาวน์โหลดมาแล้วคัดลอกเข้ามาใหม่ และถ้าการทำด้วยมือเริ่มน่าเบื่อก็ค่อยให้เครื่องมือ build ช่วยทำอัตโนมัติ
  • ถ้ามี lockfile อยู่แล้ว ก็ทำให้มันผูกกับ source tree ทั้งชุดที่อยู่ใน source control ได้เลย
  • เป็นการถือครองทุกบรรทัดของ source code ด้วยการควบคุมอย่างเข้มงวด
  • ต้นทุนและ trade-off

    • repository จะใหญ่ขึ้น แต่พื้นที่ดิสก์มีราคาถูก
    • ต้นทุนการส่งข้อมูลไม่ได้ถูกเท่าดิสก์ แต่ในบริบทนี้ยังเป็นสิ่งที่ต้องยอมรับ
    • เวลา build อาจดูเหมือนจะเพิ่มขึ้น แต่เพราะยังไงก็ต้อง build dependency พวกนั้นใหม่อยู่แล้ว จึงไม่ได้เพิ่มขึ้นเสมอไป
    • การนำโค้ดกลับมาใช้ซ้ำอาจยากขึ้น และอาจเป็นปัญหาจริงสำหรับโปรแกรมอย่าง client และ server ที่ใช้ไลบรารี protocol ร่วมกัน
    • โปรแกรมแบบนั้นมีปัญหาเรื่องเวอร์ชันไม่ตรงกันอยู่แล้วและจำเป็นต้องรับมือ ดังนั้นการทำให้ต้องใส่ใจมากขึ้นจริง ๆ ก็ไม่ได้แย่กว่าเดิมในระยะยาว
  • แนวกันไฟของการโจมตีซัพพลายเชน

    • หากไม่อัปเดต dependency โดยอัตโนมัติ ทุกแพ็กเกจใน ecosystem ก็จะกลายเป็น แนวกันไฟ สำหรับการโจมตีซัพพลายเชน
    • วิธีเดียวกันนี้ยังขัดขวางการกระจายของ bug fix และแพตช์ด้วย แต่ถ้าเป็นการแก้ไขสำคัญ มนุษย์ก็มักต้องไปตามดูเองอยู่ดี
    • การแก้ไขที่ไม่มีใครไปตามดู มักไม่ใช่เรื่องสำคัญมากนัก
    • หากเลิกใช้แนวคิดเรื่อง semver หรือแนวคิดว่า “โค้ดสองชุดที่ต่างกันควรทำงานแบบเดียวกัน” ในระบบ build และมองหมายเลขเวอร์ชันทุกตัวเป็นเอกลักษณ์ที่ไม่เกี่ยวข้องกัน ก็อาจได้ผลคล้ายกัน
    • ปัญหาของ semver คือมันสะท้อนเจตนาของคน ไม่ใช่ความจริงในโลกจริง และยังใช้ได้ก็ต่อเมื่อถูกใช้อย่างถูกต้องในระดับหนึ่งเท่านั้น
    • การมองหมายเลขเวอร์ชันเป็นเอกลักษณ์แยกกัน ไม่ได้ช่วยแก้ปัญหา dependency หายไป ถูกแก้ไขแทรกแซง หรือเนื้อหาแพ็กเกจเสียหายในรูปแบบอื่น
  • การมองเห็น dependency

    • การ vendor dependency ทั้งหมด นอกจากจะช่วยชะลอการเปลี่ยนแปลงอัตโนมัติแล้ว ยังเพิ่มต้นทุนของการใช้ dependency ขึ้นเล็กน้อย
    • ต้นทุนที่เพิ่มขึ้นไม่ได้รุนแรงจนรับไม่ได้ แต่ทำให้ต้องคิดเพิ่มอีกนิดเวลาใช้โค้ดจาก upstream
    • มันเป็นกลไกแบบนุ่มนวลที่ทำให้ถามตัวเองอีกครั้งว่า “จำเป็นจริงหรือไม่” ตอนจะเพิ่ม dependency ใหม่
    • การมองเห็น ของ dependency สูงขึ้น และความพองตัวที่ซ่อนอยู่หลัง dependency ก็ซ่อนได้ยากขึ้น
    • ถ้าเพิ่มไลบรารีง่าย ๆ ที่คิดว่าน่าจะมีแค่ราว 200 บรรทัด แต่จริง ๆ กลับมี 50,000 บรรทัด ก็จะชัดขึ้นว่าควรหยุดและถามเหตุผล
    • ความรู้สึกมหัศจรรย์ของ dependency จะลดลง และทำให้ตามรอยเส้นทางที่บั๊กใน codebase เชื่อมไปสู่โค้ดของคนอื่นได้ง่ายขึ้น
  • ต้นไม้ dependency และปัญหาการแชร์

    • ถ้า vendor ทุกอย่างเป็นค่าเริ่มต้น อาจนำไปสู่ต้นไม้ dependency ที่แบนและกว้างขึ้น
    • การไปถึงระดับไลบรารียักษ์อย่าง Boost หรือ Qt ในโลก C++ ไม่ใช่สิ่งที่พึงปรารถนา
    • ไลบรารียักษ์เหล่านั้นมีอยู่เพราะการสร้างและใช้งานไลบรารี C/C++ ขนาดเล็กนั้นยากเกินไป
    • มีสมมติฐานว่าการให้ system integrator อย่าง Linux distribution ทำเรื่องการ build เหล่านี้ให้ครั้งเดียว จะดีกว่าการที่แต่ละคนต้องไปทำความเข้าใจวิธี build ของ Boost หรือ Qt เอง
    • ข้อเสียจริงคือ dependency แบบ transitive จะไม่ถูกแชร์ร่วมกัน
    • ถ้า lib A และ lib B ต่างก็พึ่งพา Z การ deduplicate ไม่ใช่ว่าทำไม่ได้ แต่จะยากขึ้น และต้องให้คนทำเองหรือใช้เครื่องมือที่ซับซ้อนขึ้น
    • แม้ตอนที่ dependency แบบ transitive ถูกแชร์ร่วมกันก็ยังมีปัญหาอยู่ และการมี dependency แบบ transitive เองก็เป็นส่วนหนึ่งของปัญหา
    • การอนุญาตให้ไลบรารีระบุ dependency แบบ transitive ได้ เท่ากับมอบการควบคุมโปรแกรมของเราให้คนอื่น

การวิเคราะห์

  • ไม่ใช่ว่าซอฟต์แวร์ทุกชนิดจะใช้วิธีนี้ได้
  • การ vendor และ build Redis ทั้งตัวเป็นส่วนหนึ่งของการ deploy เว็บแอป backend ไม่ใช่สิ่งที่สมเหตุสมผลเป็นพิเศษ
  • อย่างไรก็ตาม ถ้าการ deploy ถูกทำให้เป็นอัตโนมัติด้วย Ansible หรือ Docker image อยู่แล้ว ก็มีโอกาสสูงว่าคุณกำลังทำสิ่งคล้ายกันอยู่โดยพฤตินัย
  • วิธีนี้มีขีดจำกัดของความซับซ้อนที่รับไหว แต่บริษัทที่ใช้ monorepo ขนาดมหึมาอย่าง Google และ Facebook แสดงให้เห็นว่าขีดจำกัดนั้นอาจสูงกว่าที่คิด
  • ถึงจุดหนึ่ง dependency ก็ต้องไปเจอกับระบบปฏิบัติการ และตัวระบบปฏิบัติการเองก็เป็น dependency ขนาดใหญ่ที่มีปัญหาในตัวมากมาย
  • แนวคิด unikernel สำหรับเว็บแบ็กเอนด์ฟังดูน่าสนใจ แต่ในทางปฏิบัติยังมีปัญหาเรื่องเครื่องมือ และเรายังไปไม่ถึงจุดนั้น
  • Linux distribution และสภาพแวดล้อมการ build

    • วิธีนี้ไม่ใช่แนวทางสำหรับสร้างระบบที่มีปฏิสัมพันธ์กันครบชุดแบบ Linux distribution หรือ BSD
    • ระบบลักษณะนั้นมีโปรแกรมและไลบรารีจำนวนมากที่ต้องทำงานร่วมกัน จึงเป็นอีกปัญหาหนึ่ง
    • หากผลักหลักการนี้ไปจนสุด ก็จะเข้าใกล้แนวทางแบบ Nix หรือ Guix
    • แนวคิดที่ว่าต้องประกอบ “สภาพแวดล้อมการ build” ให้ถูกต้องนั้น ใกล้เคียงกับการแก้ปัญหา “จะ build ซอฟต์แวร์อย่างไร” แบบขี้เกียจและไม่เพียงพอ
    • แนวคิดนี้เป็นเศษตกค้างจากยุคที่ build ซอฟต์แวร์บน minicomputer เครื่องหนึ่งครั้งเดียว แล้วแจกจ่ายเป็นไบนารีในวงกว้าง
    • ทุกวันนี้เราทำการ build ซอฟต์แวร์สด ๆ มากกว่ายุค 1970 มาก
  • ขอบเขตที่นำไปใช้ได้

    • วิธีนี้ไม่ใช่คำตอบสารพัดนึก แต่มีซอฟต์แวร์จำนวนมากที่นำไปใช้ได้และได้รับประโยชน์
    • ซอฟต์แวร์ส่วนใหญ่มีขนาดเล็ก ส่วนโปรเจ็กต์ใหญ่ ๆ ก็มักต้องแก้ปัญหาแบบนี้อยู่แล้วจำนวนมาก
    • มีไลบรารีมากมายที่ทำงานคำนวณล้วน ๆ หรือแตะโลกภายนอกผ่าน I/O พื้นฐานและพกพาได้ เช่นไฟล์และ network socket เท่านั้น
    • ตัวอย่างอย่าง compression library, libcurl, TUI library, หรือ Django สามารถถือเป็นเป้าหมายของการ vendor ได้
    • การ vendor ช่วยหลีกเลี่ยงปัญหาที่เวลา deploy หรือ build บนระบบใหม่แล้วทุกอย่างพังแบบหาสาเหตุไม่ได้ เพราะเวอร์ชันชนกันหรือมีบั๊กจากแพตช์ที่เข้ามากะทันหัน
    • เป้าหมายคือการลด dependency ที่อาจเปลี่ยนจากภายนอกโดยไม่มีการแจ้งล่วงหน้า จากระดับ 200~300 ตัว ให้เหลืออย่างมากเพียง 2~3 ตัว

บทสรุป

  • การลดการอัปเดต dependency อัตโนมัติ และให้โปรเจ็กต์ถือครอง source ของ dependency เอง จะช่วยชะลอการแพร่กระจายอัตโนมัติของการโจมตีซัพพลายเชนได้
  • การเพิ่มต้นทุนของการใช้ dependency ขึ้นเล็กน้อยและเพิ่มการมองเห็น จะช่วยให้พบการใช้ซ้ำที่ไม่จำเป็นและความพองตัวที่ซ่อนอยู่ได้ง่ายขึ้น
  • วิธีนี้ไม่เหมาะกับทุกระบบ แต่สำหรับซอฟต์แวร์ขนาดเล็กและไลบรารีจำนวนมาก มันมีข้อดีในทางปฏิบัติ

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

 
GN⁺ 3 시간 전
ความเห็นจาก Lobste.rs
  • ตัวจัดการแพ็กเกจของ Zig ดูเป็นทางประนีประนอมที่ค่อนข้างดี
    ทุกแพ็กเกจถูกตรึงด้วย content hash เลยเท่ากับมี lock file โดยปริยาย จึงหลีกเลี่ยงปัญหาแบบ “รีโพต้นทางจู่ ๆ ก็กลายเป็นของอันตราย” ได้ แต่ปัญหา “รีโพต้นทางหายไป” ยังมีอยู่
    แต่เพราะมีทั้งแคชแบบ global/local และอิงตาม content hash ถ้ารีโพต้นทางหายไป ก็แค่เอา tarball จากสำเนาในเครื่องไปวางไว้ตรงที่ต้องใช้
    ดูเป็นจุดกึ่งกลางที่ดีระหว่าง “vendoring ซอร์ส” กับ “ซอฟต์แวร์ที่เรียบง่ายและนำกลับมาใช้ซ้ำได้”

    • วิธีนั้นน่าจะขยายไปใช้กับซอฟต์แวร์ทั้งหมดได้ด้วย และก็ดูเจ๋งดี
      เอาซอร์สทั้งหมดไว้ใน content-addressed store แล้วให้แต่ละโปรแกรมถูกแฮชจากแฮชของอินพุตที่ใช้
    • โดยรวมเห็นด้วย แต่ก็สงสัยนิดหน่อยว่าจะโจมตีการจัดวางแบบนั้นได้ยังไง
      คงต้องแก้ lock file หรือไม่ก็หา hash collision ซึ่งทั้งสองอย่างก็ดูไม่ง่าย
      แต่เพราะคุ้นกับ ecosystem ของ cargo เลยยังไม่ชอบหมดใจนัก เวลาอัปเดต dependency ก็มักมีแนวโน้มที่ transitive dependency จะถูกอัปเดตตามไปเงียบ ๆ ด้วย และตัวอื่นที่อยู่ในช่วง semantic version ที่เข้ากันได้ก็จะเปลี่ยนตามไปด้วย
  • จะเรียกว่า “การโจมตีซัพพลายเชน” ก็ดูไม่ใช่ เพราะไม่มีสัญญาที่ลงนามพร้อมข้อเสนอและสิ่งตอบแทน จึงไม่นับว่าเป็นซัพพลายเชน
    แต่อีกมุมหนึ่ง ถ้าพูดถึงการรับประกันว่า dependency จะไม่เปลี่ยนจากข้างล่าง lock file ที่มี hash หรือแนวทาง minimal version selection ของ Go ก็เทียบได้กับการ vendor dependency
    เข้าใจว่าการ vendoring มีแรงเสียดทานเพิ่มขึ้น แต่ถ้าสุดโต่งไปก็จะกลายเป็นต้องเขียนเอง หรือแย่กว่านั้นคือทำ dependency เป็นโค้ดที่สร้างแบบสด ๆ ดังนั้นผมคิดว่าการใช้ซอฟต์แวร์ที่ผู้เชี่ยวชาญในโดเมนเขียนและผ่านการตรวจสอบมาดีแล้วดีกว่า
    ผมเคยทำงานด้านนี้ที่ Facebook และไม่อยากแนะนำการ จัดการ dependency ของ third-party ที่นั่นให้ใครเลย สำหรับ direct dependency ของ Rust crate ตัวหนึ่ง จะอนุญาตให้มีเวอร์ชันที่เข้ากันไม่ได้ตาม semantic version พร้อมกันได้มากสุดแค่สองเวอร์ชันใน fbsource ทั้งหมด ถ้าจะอัปเดต dependency คุณต้องแบกรับภาระการอัปเดตทั้ง fbsource ไปด้วย
    มันอาจเหมาะกับ Facebook แต่ผมไม่คิดว่ามันยอดเยี่ยมหรือยั่งยืนเป็นพิเศษ

    • สงสัยว่าทำไมถึงเป็น “มากสุดสองเวอร์ชัน” น่าจะเพื่อให้ ค่อย ๆ ย้ายระบบ จากเวอร์ชันเก่าไปเวอร์ชันใหม่หรือเปล่า?
      ผมสงสัยว่าที่ว่า “ไม่ได้ยอดเยี่ยมหรือยั่งยืนเป็นพิเศษ” น่าจะขึ้นกับขนาดมากกว่าตัวนโยบายเอง ถ้ายอมให้มีหลายเวอร์ชันก็จะเกิดปัญหาอีกแบบ เพราะภาษาสมัยใหม่ส่วนใหญ่ยกเว้น TypeScript ใช้ nominal typing เป็นหลักหรือเกือบทั้งหมด ดังนั้นทุกครั้งที่มี breaking change ก็จะนำ type ข้ามเวอร์ชันมาใช้ซ้ำไม่ได้ ถ้าไม่ใช้ “semver trick”
      ตอน Log4Shell ผมจำได้ชัดว่าบริษัทที่มีหลายเวอร์ชันกระจัดกระจายอยู่หลายจุด อัปเกรดยากกว่าบริษัทที่มีจำนวนน้อยหรือปักเวอร์ชันไว้
    • ใช่ งั้นเรียกว่า การโจมตี dependency ก็ได้ <3
  • ตาม The Third Networking Truth ที่ว่า “ด้วยแรงขับมากพอ หมูก็บินได้ แต่ไม่ได้แปลว่านั่นเป็นความคิดที่ดี”
    แนวปฏิบัติจำนวนมากที่ถูกยกมาจาก Google/Facebook ใช้ได้ก็เพราะบริษัทเหล่านั้นทุ่ม แรงขับมากพอ ได้
    อย่างเช่น ผมรู้ว่าบางแห่งตั้งทีมที่ใหญ่กว่าจำนวนพนักงานทั้งบริษัทของผมเพื่อรองรับ monorepo และตัวเลือกด้าน dependency แบบนั้น พวกเขารับไหว แต่พวกเราส่วนใหญ่รับไม่ไหว

  • มุมมองดีมาก ผมเห็นด้วยแรง ๆ กับประเด็นที่ว่า “ถ้า vendor dependency ทั้งหมด ต้นทุนของการใช้ dependency ก็จะสูงขึ้น”
    แต่ก็ไม่ควรคัดลอก libcurl มาแปะตรง ๆ นะ สำหรับไลบรารีส่วนใหญ่ก็เป็นกลยุทธ์ที่โอเค แต่ไม่ใช่คำแนะนำที่ดีสำหรับโปรแกรม C ที่ต้องรับมือกับอินพุตที่เป็นปฏิปักษ์ ระบบปฏิบัติการไม่น่าจะมีใครดูแลให้ libcurl ปลอดภัยได้ดีกว่า
    จุดหนึ่งที่ผมไม่เคยคิดมาก่อนคือ มันแปลกอยู่นิดหน่อยที่ตัวจัดการแพ็กเกจสำหรับผู้ใช้ปลายทางอย่าง apt มาก่อน แล้วตัวจัดการแพ็กเกจระดับภาษาค่อยตามมาทีหลัง
    ผมคิดว่านี่ทำให้เกิดปัญหาจริงเยอะมาก ถ้าดู rubygems ช่วงต้นยุค 2000 จะเห็นค่อนข้างชัดว่ามันพยายามทำ “apt สำหรับ Ruby” โดยมีค่าเริ่มต้นเป็นการติดตั้งทั้งระบบ ไม่ใช่การจัดการรายโปรเจกต์ การย้อนแก้ผลเสียจากความผิดพลาดนั้นต้องใช้เวลาหลายสิบปีและต้องเพิ่ม bundler เข้ามา แต่ถ้ายอมรับตั้งแต่แรกว่าต้องมี การแยกระดับโปรเจกต์ ก็คงไม่ต้องมี bundler
    Python ยังเก็บกวาดความสับสนนี้อยู่ และ Perl ก็น่าจะยังเหมือนกัน แต่ผมไม่ค่อยรู้รายละเอียด

    • ก็แปลว่ามันมีขีดจำกัดอยู่ดี :-) แค่ยากตรงที่จะขีดเส้นตรงไหน
      ในเชิงประวัติศาสตร์ ตัวจัดการแพ็กเกจเดิมทีคือวิธี สร้างระบบ และระบบพวกนั้นมีผู้ใช้หลายคน มีเดสก์ท็อปหลายแบบ และมีซอฟต์แวร์จำนวนมากที่ต้องทำงานร่วมกัน
      การ build ซอฟต์แวร์กินเวลาและหน่วยความจำมาก และเมื่อเทียบกับดิสก์กับ RAM แล้ว ซอฟต์แวร์ก็มีเยอะมาก การใช้ไลบรารีซ้ำจึงสำคัญ
      เมื่อเว็บแอปเริ่มมีบทบาท คอมพิวเตอร์สำคัญส่วนใหญ่ก็กลายเป็นเซิร์ฟเวอร์ที่ตลอดอายุใช้งานรันโปรแกรมอยู่ไม่กี่ตัว และดิสก์กับ RAM ก็ถูกพอที่ขนาดไบนารีของโค้ดสำคัญน้อยลง
      เครื่องมือสำหรับสร้างระบบตามการเปลี่ยนแปลงของยุคสมัยไม่ทันนัก ทำให้คนส่วนใหญ่ที่สร้างซอฟต์แวร์ต้องการแค่เครื่องมือที่ช่วยทำโปรแกรมเดี่ยวให้ดี ไม่ใช่ระบบเชื่อมโยงขนาดมหึมาที่มี shared library เต็มไปหมด
      ควบคู่กับประวัติศาสตร์นี้ก็มีอีกสายหนึ่งคือ “C ไม่มีระบบโมดูลที่ดีพอ” แต่ที่นี่ไม่ค่อยสำคัญเท่าไร
  • ผมอาจจะเข้าใจผิด แต่การคัดลอก dependency มาไว้เองน่าจะมีข้อเสียคือ ถึงจะมีบั๊กอยู่ในนั้น สแกนเนอร์ก็ตรวจไม่เจอ
    ถ้าอย่างนั้นปัญหาที่ตามปกติควรได้รับการแจ้งเตือนอาจค้างอยู่เงียบ ๆ

    • พอดูจำนวน false positive ที่สแกนเนอร์พวกนี้สร้างขึ้นมา มันอาจจะเป็นข้อดีก็ได้
      สแกนเนอร์มีประโยชน์มากในการชี้ว่าอะไร “อาจ” เป็นปัญหา แต่เวลามันทำให้คุณต้องเลื่อนงานที่วางแผนไว้กะทันหันเพื่อไปแก้สิ่งที่มันคิดว่าเป็นปัญหาแต่จริง ๆ ไม่ใช่ นั่นแหละที่ปวดหัวมาก
  • ถ้าทำตามข้อเสนอ คือรวม dependency ทั้งหมดเข้าไปในซอฟต์แวร์ คัดลอกการจัดการซอร์สต้นทางมาใส่ในรีโพ git แล้ว commit ไว้ และถ้าเบื่องานมือก็ให้เครื่องมือ build ทำให้อัตโนมัติ สุดท้ายมันก็วนกลับมาเป็นการรวม ซอฟต์แวร์ third-party เข้ามาโดยที่ไม่ได้ดูมันจริง ๆ อยู่ดีไม่ใช่เหรอ?

    • ถ้าอ่านต่อไป เขาบอกว่าสามารถได้ผลแบบเดียวกันโดยให้ระบบ build เลิกใช้ semantic version หรือแนวคิดที่ว่า “โค้ดสองชุดที่ต่างกันควรทำงานเท่ากัน” แล้วปฏิบัติกับหมายเลขเวอร์ชันทุกตัวว่าเป็นสิ่งที่เอกเทศและไม่เกี่ยวกัน
      แต่แนวทางนั้นก็ยังไม่แก้ปัญหา dependency หายหรือถูกแก้ไข หรือปัญหาที่ใครบางคนไปยุ่งกับเนื้อหาแพ็กเกจด้วยวิธีอื่น มันใกล้เคียงกับการปรับแต่งประสิทธิภาพมากกว่า และในความเห็นผมเป็นการปรับแต่งก่อนเวลาอันควร วันหนึ่งอาจไปถึงจุดนั้นได้ แต่ไม่ควรใช้เป็นจุดตั้งต้น