Nợ kỹ thuật: Thư viện Rust của tôi giờ đã chuyển thành CDO
(lucumr.pocoo.org)- 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
instaphụ thuộc vàoyaml-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ệm và margin 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à vendor mã
yaml-rustvào tronginsta, 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ànhAAA
Quá trình dependency yaml-rust lộ rõ là nợ kỹ thuật
instađang phụ thuộc vàoyaml-rust, và sau khi tác giả ban đầu mất dần hứng thú, các issue trongyaml-rusttiế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
instakhô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-rusttrự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-rustvới maintainer củainsta, 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 đề
- Một phương án là fork của
- 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-rusttrướ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à vendor mã
yaml-rustvào tronginstainstagiờ trở thành dạng kết hợp giữa mãinstavàyaml-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
- Đoạn mã có vấn đề không biến mất, chỉ được chuyển vị trí vào bên trong
1 bình luận
Ý 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 đề.
Đồ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.
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.
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.
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].
Đ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.
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.
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.
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.
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”
Đú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”
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
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
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
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
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”
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í
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
“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
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ỵ
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ó 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
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
Nếu không thì tôi không rõ có gì được cải thiện