2 điểm bởi GN⁺ 2024-03-27 | 1 bình luận | Chia sẻ qua WhatsApp
  • Trong hệ sinh thái Rust, ngay khoảnh khắc một dependency không còn được bảo trì bị đưa vào RUSTSEC, ngay cả thư viện vốn không gặp vấn đề trực tiếp cũng trở thành nợ kỹ thuật thông qua người dùng và CI
  • insta phụ thuộc vào yaml-rust; sau khi tác giả ban đầu giảm quan tâm, yêu cầu tính năng và lỗi đã chất đống
  • Sau khi được đăng ký vào RUSTSEC, CI của người dùng trực tiếp và gián tiếp bắt đầu thất bại; ví theo tài chính thì giống như đã xảy ra hạ bậc tín nhiệmmargin call
  • Thư viện thay thế hay fork cũng không phải lời giải rõ ràng, nên dù đổi dependency vẫn còn gánh nặng bảo trì và rủi ro từ dependency mới
  • Cách xử lý cuối cùng là vendoryaml-rust vào trong insta, dẫn đến lời phê phán rằng điều này giống một CDO gói nợ kỹ thuật xấu thành AAA

Quá trình dependency yaml-rust lộ rõ là nợ kỹ thuật

  • insta đang phụ thuộc vào yaml-rust, và sau khi tác giả ban đầu mất dần hứng thú, các issue trong yaml-rust tiếp tục tích tụ
    • Một số là yêu cầu tính năng, một số là lỗi thật sự
    • Maintainer của insta không trực tiếp gặp các vấn đề đó, nhưng xét việc đây là một dependency không còn được bảo trì thì đó là nợ kỹ thuật
  • Tình hình thay đổi khi yaml-rust được đưa ra thảo luận để thêm vào cơ sở dữ liệu RUSTSEC
    • Trong phép ví tài chính, RUSTSEC đóng vai trò tổ chức xếp hạng tín nhiệm
    • Sau khi được đăng ký, CI của nhiều dự án sử dụng yaml-rust trực tiếp hoặc gián tiếp bắt đầu thất bại chỉ trong vài phút
    • Khi người dùng chỉ ra vấn đề sử dụng yaml-rust với maintainer của insta, theo phép ví tài chính thì một margin call đã xảy ra

Các lựa chọn và cách xử lý thực tế

  • Chuyển sang phương án thay thế không hấp dẫn
    • Một phương án là fork của yaml-rust, chỉ có 1 maintainer và thêm 3 dependency
    • Một trong số đó đã bị xếp hạng “B-”
    • Một lựa chọn khác trong hệ sinh thái đã quyết định thay đổi giá trị mặc định trước khi bị nêu vấn đề
  • Tự fork cũng không phải giải pháp căn cơ
    • Thư viện được fork vẫn kéo theo cùng các yêu cầu bảo trì
    • Nếu không phản hồi báo cáo lỗi, cuối cùng nó cũng sẽ bị chỉ ra như yaml-rust trước đó
    • Vì vậy fork có thể giúp mua thêm thời gian, nhưng không loại bỏ vấn đề
  • Cách xử lý thực tế là vendoryaml-rust vào trong insta
    • insta giờ trở thành dạng kết hợp giữa mã instayaml-rust
    • Cấu trúc này giống như nâng hạng nợ kỹ thuật xấu lên AAA
    • CDO trong tiêu đề chỉ chứng khoán nợ có bảo đảm, thứ trở nên khét tiếng trong khủng hoảng tài chính 2007
  • Đánh giá cuối cùng gần với “không ai thắng”
    • Đoạn mã có vấn đề không biến mất, chỉ được chuyển vị trí vào bên trong insta
    • Áp lực khi nó xuất hiện như dependency bên ngoài đã giảm, nhưng bản thân gánh nặng bảo trì vẫn còn

1 bình luận

 
GN⁺ 2024-03-27
Ý kiến trên Hacker News
  • Nói thẳng ra, tác giả của serde_yaml(https://lib.rs/crates/serde_yaml), parser YAML phổ biến nhất, đã đột ngột rút lui mà không báo trước hay chỉ định người quản lý kế nhiệm, rồi đánh dấu nó là deprecated và unmaintained.
    Không hoàn toàn giống left-pad. Gói vẫn hoạt động và crates.io cũng không cho phép xóa, nhưng gói này đang được 4.000 crate khác sử dụng.
    Các công cụ audit và tự động cập nhật sẽ bắt đầu coi việc dùng crate không còn được bảo trì là một vấn đề.

    • Không chính xác. Bài gốc nói nó phụ thuộc vào yaml-rust(https://github.com/chyh1990/yaml-rust), và dự án này giờ đã không còn được bảo trì.
      Đồng thời, để ngăn mọi người ồ ạt chuyển sang phía đó, serde_yaml cũng được đánh dấu là unmaintained.
    • Trường hợp này thì không phải, nhưng đôi khi một thư viện không có dependency thực sự có thể ở trạng thái đã hoàn thiện về tính năng, đến mức ngoài các bản vá bảo mật thì hầu như không cần thay đổi gì.
      Mong rằng các công cụ audit và chính sách công ty đủ thông minh để phân biệt những trường hợp như vậy, thay vì cứ cảnh báo “không được bảo trì” chỉ vì gần đây không có commit.
    • Tác giả đó chẳng phải là người khá năng suất trong hệ sinh thái thư viện Rust sao? Với các thư viện khác thì không làm động thái như vậy.
      Không biết có thông tin chi tiết hơn không.
  • Tôi không hiểu ngay từ viết tắt CDO xuất hiện mà không giải thích, nhưng vì bài viết nhiều lần dùng từ collateralized, có lẽ đó là nghĩa vụ nợ được thế chấp (collateralized debt obligation).
    https://en.wikipedia.org/wiki/Collateralized_debt_obligation
    Ban đầu tôi nghĩ đến chief data officer.

    • Phép ví von ở đây nằm ở chỗ CDO là một sản phẩm tài chính được tạo ra từ các khoản nợ khác.
      Cụ thể, vào năm 2008, nó là thứ giống như quyền sở hữu một phần đối với nhiều khoản vay thế chấp nhà, và khi các khoản vay thế chấp nhà kém chất lượng vỡ nợ, CDO cũng sụp theo [1].
      Dù sao thì điều tác giả nghĩ đến không hẳn là phép ví von dựa trên nợ, mà gần với rủi ro hệ thống kiểu leftpad hoặc sự cố DNS dây chuyền hơn. Lý do lớn khiến năm 2008 trở thành hỗn loạn như vậy cũng chủ yếu là vấn đề mang tính hệ thống, hơn là bản thân việc đóng gói nợ thành sản phẩm [2].
      1. Một sự thật thú vị là trước năm 2008, nhu cầu đối với các sản phẩm tài chính dựa trên thế chấp nhà tăng vô hạn đến mức người ta bắt đầu tạo cả CDO làm từ CDO. Cho người không có việc làm vay thế chấp nhà, rồi gói nó vào sản phẩm được xếp hạng tín dụng AAA, thế là thành một sản phẩm sinh lời dễ bán.
      2. Cũng còn chuyện tỷ lệ đòn bẩy của Lehman và Bear Stearns là 30~40 lần. Mức đó là điên rồ.
    • Đúng vậy. Hàm ý là nếu tạo động lực để gói nợ kỹ thuật tệ hại vào một package có danh tiếng tốt, tức package AAA, thì hệ sinh thái Rust có thể đi vào bong bóng và cuối cùng sụp đổ.
      Điều đó cũng ngụ ý rằng các tổ chức đánh giá đáng lẽ phải đóng vai trò quản lý đã bị thao túng rồi.
    • Nên xem The Big Short. Tôi học được CDO là gì từ đó.
    • Tôi nhận ra giờ đã có một thế hệ quá trẻ để từng đọc các tiêu đề thời CDO làm sụp đổ kinh tế thế giới. Thấy mình già thật.
    • Tôi đã nghĩ đến https://en.wikipedia.org/wiki/Collaboration_Data_Objects ;)
  • Tôi không muốn tranh luận liệu đây có phải là “chiến thắng” hay không, vì nó phụ thuộc quá nhiều vào định nghĩa của “chiến thắng”, nhưng rõ ràng là có lợi điểm. Một đường đi mã dễ tổn thương vốn không bao giờ được thực thi và cũng không thể được chạm tới từ thư viện bên ngoài giờ trở thành một đường đi mã an toàn.
    Tất nhiên trạng thái đó vẫn đáng bận tâm, nhưng là an toàn.
    Kiểu vendor hóa như thế này còn có lợi điểm khác. Nếu thư viện của bạn có độ phủ kiểm thử vững chắc, bạn có thể chạy công cụ đo code coverage trên thư viện mới được mang vào.
    Việc sửa thư viện có thể khó, nhưng bạn cũng có thể tương đối dễ dàng xóa dần những phần mà mã của bạn không đụng tới. Tùy vào cấu trúc, nếu toàn bộ mã dễ tổn thương bị xóa thì đó rõ ràng là lợi ích; và nếu bạn phát hiện mình thực ra có dùng một phần trong đó thì có khi còn là lợi ích rõ ràng hơn.
    Nói cách khác, fork một thư viện để bảo trì công khai là trách nhiệm lớn, nhưng vendor chỉ phần cần thiết và chỉnh sửa nó là gánh nặng nhỏ hơn nhiều. Ngay cả khi không thực sự cắt tỉa, bản thân việc làm cho chuyện đó trở nên dễ dàng cũng đã là tiến bộ.
    Rõ ràng cũng có nhược điểm, nhưng không phải mọi thứ đều xấu.

    • Thực ra đây là một kết quả rất tốt. Mọi dependency đều là rủi ro bảo mật, và lập luận chống lại việc vendor mọi dependency nằm ở chỗ để tránh các vấn đề bảo mật đã biết thì phải giữ dependency bên ngoài luôn cập nhật.
      Nhưng nếu không còn ai theo dõi dependency bên ngoài đó nữa, lập luận ấy biến mất, và dependency đó trở thành gánh nặng.
      Việc dependency bị đánh dấu abandoned và bắt đầu xuất hiện trong báo cáo bảo mật là hành vi mong muốn. Nhờ đó ta có thể đưa ra quyết định dựa trên thông tin: vendor nó, tức loại bỏ rủi ro “kẻ xấu lén nhét gì đó vào”, hay chọn phương án khác.
      Việc hệ thống build của Rust cho phép làm điều này dễ dàng cũng là điểm tốt.
    • Cũng đáng lưu ý rằng tại Google, theo chính sách, mọi dependency bên thứ ba đều phải được vendor hóa.
      Thường thì trong toàn bộ monorepo khổng lồ chỉ cho phép một phiên bản của dependency đó. Có thể điều này đã thay đổi sau thời tôi còn ở đó.
      Và mỗi dependency trong third_party đều có người chịu trách nhiệm hoặc OWNERS được chỉ định.
      Nhờ vậy mà mọi thứ được duy trì khá trật tự.
      Tuy nhiên kiểu kỷ luật bị áp đặt này có thể phù hợp với một tổ chức như Google, nơi có đủ thời gian và tiền bạc và không bị trói vào triết lý “di chuyển nhanh và phá vỡ mọi thứ”. Tôi không biết nó sẽ phù hợp đến mức nào ở startup.
    • Tôi thắc mắc liệu có công cụ nào cho phép maintainer thư viện tính độ phủ mã bắc cầu của thư viện mình hay không, chẳng hạn như test suite của thư viện nào khác đang sử dụng nó.
      Theo một nghĩa nào đó, các thư viện khác cung cấp những lời gọi gần như ngẫu nhiên, giống một bài fuzz test có hướng, nên đây có thể là một chiến lược kiểm thử thú vị.
  • Cũng thấy mô hình tương tự trong hệ sinh thái JS npm
    npm audit nhìn chung hành xử như cậu bé chăn cừu trong các vấn đề bảo mật, và nếu giấy phép cho phép thì kéo mã vào bên trong là một trong những cách ổn định nhất để không bị nhấn chìm bởi các issue giả từ người dùng
    Nhiều khi người dùng không hiểu bối cảnh, hoặc họ chẳng quan tâm vì chính sách của chủ lao động được tạo ra ở nơi xa rời thực tế
    Không phải regex được dùng trong một phần codebase của phụ thuộc bắc cầu trong pipeline build là thực sự có thể bị lợi dụng để tấn công từ chối dịch vụ
    Các “issue” trong phụ thuộc bắc cầu sâu đặc biệt có thể rất phiền khi phải né tránh. Do cấu trúc, thường khó chứng minh về mặt kỹ thuật những sự thật như “chúng ta tuyệt đối không đi vào đường dẫn mã đó nên không bị ảnh hưởng bởi lỗi” hoặc “trường hợp duy nhất đi vào đường dẫn đó là input đáng tin cậy trong môi trường offline”

    • Issue của phụ thuộc phát triển trong thế giới JS thật sự là một cơn đau đầu
      Đúng là có những kịch bản mà các vấn đề như vậy quan trọng. Ví dụ, một công cụ build bị xâm nhập có thể chèn mã độc vào thư viện đang được build
      Nhưng các trường hợp đó cực kỳ hiếm, và bị chôn vùi trong làn sóng các regex có khả năng gây từ chối dịch vụ nhưng thực tế không quan trọng vì chúng chỉ được gọi trong quá trình build
      Thêm vào đó việc các công cụ build phổ biến có cây phụ thuộc bắc cầu khoảng 50 tỷ gói nữa thì đúng là rất vất vả
      Tôi nghĩ các công cụ báo cáo những issue kiểu này cần phân biệt giữa “có thể khai thác nếu tái phân phối” và “có thể khai thác nếu dùng trong pipeline build”
    • Ngay cả khi hiện tại không đi vào đường dẫn có vấn đề đó, về sau khi phụ thuộc được cập nhật thì có thể sẽ đi vào đường dẫn có vấn đề
  • Trong câu “giờ khoản nợ kỹ thuật xấu bỗng nhiên được xếp hạng AAA”, cách nói “bỗng nhiên” có vẻ muốn nói rằng thật vô lý khi cùng một đoạn mã lại nhận hạng nợ tốt hơn trước chỉ vì nó được vendor
    Nhưng như vậy chỉ nhìn vào giá trị của bản thân đoạn mã, và bỏ lỡ phần quan trọng nhất trong toàn bộ đề xuất giá trị
    Khi maintainer đưa mã vào bên trong, đoạn mã đó giờ thuộc quyền sở hữu của maintainer ấy. Nếu một maintainer đang hoạt động vendor mã của một dự án đã chết, thì giá trị của đoạn mã tăng lên vì giờ có một người đang hoạt động có thể phản hồi issue, review pull request và sửa bug
    Ví von cách khác, cũng giống như một thú cưng bị bỏ mặc được đưa cho chủ mới thì giá trị của nó tăng lên vì nó có thể được chăm sóc tốt hơn, khỏe mạnh hơn và sống lâu hơn

    • Điều đó chỉ đúng với những người dùng phụ thuộc ấy một cách gián tiếp. Trong trường hợp này, khả năng là sẽ có rất ít con mắt xem xét để tìm bug
      Maintainer cũng phải làm quen với một codebase lớn và xa lạ, nên sẽ có rào cản gia nhập đối với việc triển khai hoặc review bản sửa
  • Hơi lạc đề và là một ý nghĩ gây tranh cãi, nhưng tôi cho rằng rất khó tránh khỏi các vấn đề khủng khiếp nếu trình quản lý gói dựa trên mã nguồn không đảm bảo quyền pháp lý để registry cưỡng chế tiếp quản việc bảo trì các gói công khai
    Đó là các vấn đề như bị bỏ mặc, thay đổi ác ý, gỡ bỏ ác ý, mạo danh
    Nếu một gói được đánh giá là đủ quan trọng với cộng đồng lớn hơn, cần có cách tước mục đăng ký gói khỏi tay chủ sở hữu ban đầu và trỏ nó sang một fork
    Tất nhiên biện pháp như vậy sẽ kéo theo rất nhiều drama, nhưng nó có thể chủ động bảo vệ những người dùng hạ nguồn

    • Tôi nghĩ đây không phải vấn đề lớn. Vẻ đẹp của mã nguồn mở là có thể fork
      Vấn đề thực sự là việc bảo trì một dự án nguồn mở cần thời gian và công sức, và không dễ tìm được người khác có thời gian rảnh
    • Một trình quản lý gói dựa trên mã nguồn có thể tước quyền sở hữu gói khi registry thấy cần thì đáng lẽ sẽ khó thu hút đóng góp
      Nó còn kém hấp dẫn hơn cả mô hình bản quyền trong đó mọi đóng góp tự động trở thành tài sản của một nhóm cụ thể
      Có thể lập luận rằng GitHub, khi hosting phần mềm tự do, là một nơi chuyên biệt cho việc xâm phạm bản quyền của phần mềm đó, nhưng ít nhất hiện tại họ không cố tước quyền sở hữu danh nghĩa của các gói
    • Nếu lý do là tầm quan trọng ở cấp cộng đồng, thì tốt nhất cũng là việc nên làm ở cấp gói, không phải cấp registry
      Vì đã nói về cộng đồng thì đó không phải quan hệ khách hàng-nhà cung cấp, và phần lớn các gói chỉ quan trọng đến mức được cung cấp miễn phí
      Nó cũng có thể trở thành những khoản tiền nhục nhã kiểu “trả 10 đô/tháng, hãy quản lý 100.000 gói cho chúng tôi”
    • Các vấn đề này không cùng một loại. Một gói bị đóng băng có nghĩa là các phụ thuộc hạ nguồn phải đưa ra quyết định, nhưng có đủ nhiều lựa chọn, bao gồm cả vendor
      Trên Internet có rất nhiều mã nguồn không được bảo trì, chỉ đơn giản là từng được công khai nguyên trạng. Không ai được đảm bảo sẽ nhận cập nhật cho mã miễn phí
    • Phương án thay thế là vấn đề Kik NPM. Dù chọn bên nào cũng tệ
  • Việc đã có người fork yaml-rust thành yaml-rust2(https://github.com/Ethiraric/yaml-rust2/blob/master/document...) là khá may mắn
    Cũng rất hay khi fork đó vượt qua hoàn toàn bộ test YAML và nhanh hơn trong benchmark. Việc migration có vẻ cũng đơn giản
    Rốt cuộc vấn đề vẫn còn đó. Hiện tại chúng ta dựa vào công sức của những người khác sẵn lòng lao động miễn phí, nhưng điều đó có thể không kéo dài mãi
    Tôi không biết có cách né tránh nào ngoài việc trả công cho thời gian, công sức của họ và hy vọng họ tiếp tục làm tốt hay không

    • yaml-rust vốn là bản triển khai Rust thuần và tagline của nó viết đúng như vậy
      “A pure rust YAML implementation.”
      Ngược lại, serde_yaml khó có thể xem là Rust thuần hơn, vì nó phụ thuộc vào unsafe-libyaml, vốn là libyaml được chuyển đổi bằng c2rust
    • Dự án có bus factor 1 về bản chất là rủi ro
      Trên một khung thời gian đủ dài, xác suất maintainer nguồn mở rời dự án là 1
      Cách duy nhất để tránh điều này là coi các dự án do một người bảo trì là điều cấm kỵ
    • Cách giải quyết mặc định của tôi cho vấn đề này là tránh phụ thuộc bên thứ ba nhiều nhất có thể
      Với các dự án Rust thì có lẽ không phải lựa chọn, vì những người làm thư viện chuẩn Rust muốn giữ thư viện chuẩn ở mức nghèo nàn
      Nhưng khi dùng các ngôn ngữ khác có thư viện chuẩn đủ tính năng thì chắc chắn đó là một lựa chọn khả thi
      Vì tôi chủ ý dùng những ngôn ngữ có sẵn những thứ mình cần, nên ngoài driver cơ sở dữ liệu ra tôi hầu như không dùng thư viện ngoài
  • Toàn bộ tình huống này trông hơi lố bịch. Nếu code vẫn chạy và đã như vậy suốt nhiều năm, tôi không hiểu vì sao việc nó không được bảo trì lại là vấn đề
    Nếu không cần sửa, và bạn biết các giới hạn cũng như chức năng của nó, thì ổn thôi
    Code không tự nhiên hỏng đi. Tôi đã nhiều lần mượn hoặc tích hợp code từ vài chục năm trước và dùng tốt
    Cá nhân tôi có lẽ sẽ cứ bỏ qua mọi lời phàn nàn về thư viện đó

    • Câu trả lời hiển nhiên là nó không “hoạt động” theo nhiều nghĩa
      Đã có nhiều bug tích tụ, và dù bản sửa tồn tại, chúng cũng sẽ không bao giờ được patch. Xem https://github.com/chyh1990/yaml-rust/issues và /pulls là thấy
    • Việc mượn hoặc tích hợp code từ vài chục năm trước chẳng phải là tác giả đã vendor thư viện vào trong dự án của mình sao?
      Tức là giống như tự gánh việc bảo trì phần code mình dùng
      Đôi khi việc đó có thể không tốt. Vì nó có thể trở thành kiểu code copy-paste hoặc nỗ lực trùng lặp
      Nhưng nếu một thành phần bên thứ ba hoàn toàn không được bảo trì nữa thì điều đó khá dễ hiểu
  • Đúng vậy, dependency thì cứ vendor là được. Với những dependency gần như “đã hoàn thiện” và việc phát triển, bảo trì chậm lại, suốt 20 năm qua về cơ bản tôi vẫn làm như vậy
    Chỉ là tôi chưa từng làm việc với ngôn ngữ kiểu “batteries are not included”

  • Liệu có cách nào tạo thứ như cargo vendor --aggressive để, dựa trên crate của tôi, cắt tỉa toàn bộ dead code bên trong các dependency không?
    Tôi tò mò liệu có thể làm cho vấn đề “hãy review dependency” trở nên dễ xử lý hơn không
    Dù hơi lệch khỏi ý chính của bài, nhưng nó vẫn liên quan ở chỗ cuối cùng việc chọn dependency và mọi trách nhiệm đi kèm là của chúng ta
    Có vẻ vẫn còn chỗ cho các công cụ giúp ta chịu trách nhiệm tốt hơn về những gì thực sự được biên dịch vào crate

    • Vậy có copy toàn bộ bug report của thư viện đó sang bug tracker của mình không?
      Nếu không thì tôi không rõ có gì được cải thiện