Xin chào. Tôi là Mrinal Wadhwa, CTO tại Ockam . Trong những ngày đầu của Ockam, chúng tôi đang phát triển một thư viện C. Đây là câu chuyện về lý do tại sao, trong nhiều tháng, chúng tôi quyết định từ bỏ hàng chục nghìn dòng chữ C và viết lại bằng Rust .
Trước khi chúng tôi bắt đầu, tôi đã tham gia một hội thảo trên web được ghi lại vào tuần này cùng với Paul Dix, CTO của InfluxData, nơi cả hai chúng tôi đã thảo luận về các bản viết lại của InfluxDB và Ockam trong Rust. Tại sao hai dự án nguồn mở chọn viết lại, tại sao chúng tôi chọn Rust làm ngôn ngữ mới, các bài học chúng tôi đã học được trong quá trình thực hiện, v.v. Hãy kiểm tra các ghi âm . Đó là một cuộc thảo luận sâu sắc.
Ockam cho phép các nhà phát triển xây dựng các ứng dụng có thể tin cậy vào dữ liệu đang chuyển động. Chúng tôi cung cấp cho bạn các công cụ đơn giản để thêm giao tiếp được mã hóa đầu cuối và xác thực lẫn nhau vào - bất kỳ ứng dụng nào chạy trong mọi môi trường. Các ứng dụng của bạn nhận được sự đảm bảo từ đầu đến cuối về tính toàn vẹn, tính xác thực và tính bảo mật của dữ liệu… trên các mạng riêng, giữa nhiều đám mây, thông qua các luồng tin nhắn trong Kafka – qua bất kỳ cấu trúc liên kết đa giao thức, đa bước nào. Tất cả thông tin liên lạc trở nên xác thực từ đầu đến cuối và riêng tư.
Chúng tôi cũng làm cho các phần cứng trở nên cực kỳ dễ mở rộng quy mô - mối quan hệ tin cậy bootstrap, quản lý khóa an toàn, xoay vòng/thu hồi thông tin xác thực tồn tại trong thời gian ngắn, thực thi chính sách ủy quyền dựa trên thuộc tính, v.v. Kết quả cuối cùng là - bạn có thể tạo ứng dụng có quyền kiểm soát chi tiết đối với mọi quyết định về quyền truy cập và tin cậy - các ứng dụng riêng tư và bảo mật theo thiết kế.
Vào năm 2019, chúng tôi đã bắt đầu xây dựng tất cả những thứ này bằng C. Chúng tôi muốn Ockam chạy ở mọi nơi - từ các thiết bị biên bị hạn chế đến các máy chủ đám mây mạnh mẽ. Chúng tôi cũng muốn Ockam có thể sử dụng được trong bất kỳ loại ứng dụng nào - bất kể ngôn ngữ mà ứng dụng được tích hợp sẵn.
Điều này làm cho C trở thành một ứng cử viên rõ ràng. Nó có thể được biên dịch cho 99% máy tính và hầu như chạy ở mọi nơi (một khi bạn tìm ra cách xử lý tất cả các chuỗi công cụ dành riêng cho mục tiêu). Và tất cả các ngôn ngữ phổ biến khác có thể gọi các thư viện C thông qua một số dạng giao diện chức năng gốc - vì vậy sau này chúng tôi có thể cung cấp các trình bao bọc thành ngữ ngôn ngữ cho mọi ngôn ngữ khác: TypeScript, Python, Elixir, Java, v.v.
Ý tưởng là chúng tôi sẽ giữ cho cốt lõi của các giao thức tập trung vào giao tiếp của chúng tôi được tách biệt khỏi bất kỳ hành vi cụ thể nào của phần cứng và có các bộ điều hợp có thể cắm được cho phần cứng mà chúng tôi muốn hỗ trợ. Ví dụ, sẽ có các bộ điều hợp để lưu trữ các khóa bí mật trong các HSM khác nhau, các bộ điều hợp cho các giao thức vận chuyển khác nhau, v.v.
Kế hoạch của chúng tôi là triển khai cốt lõi của chúng tôi dưới dạng thư viện C. Sau đó, chúng tôi sẽ bọc thư viện C này bằng các trình bao bọc cho các ngôn ngữ khác và chạy nó ở mọi nơi với sự trợ giúp của bộ điều hợp phần cứng có thể cắm được.
Tuy nhiên, chúng tôi cũng quan tâm sâu sắc đến sự đơn giản - đó là tên của chúng tôi. Chúng tôi muốn Ockam trở nên đơn giản để sử dụng, đơn giản để xây dựng và đơn giản để bảo trì.
Cốt lõi của Ockam là một tập hợp nhiều lớp gồm các giao thức mật mã và dựa trên thông báo như Kênh bảo mật Ockam và Định tuyến Ockam. Đây là các giao thức truyền thông không đồng bộ, nhiều bước, có trạng thái và chúng tôi muốn loại bỏ tất cả các chi tiết của các giao thức này khỏi các nhà phát triển ứng dụng. Chúng tôi tưởng tượng trải nghiệm người dùng là một lệnh gọi hàm một dòng duy nhất để tạo một kênh bảo mật được mã hóa và xác thực từ đầu đến cuối.
Mã liên quan đến mật mã cũng có xu hướng chứa nhiều yếu tố nguy hiểm, chỉ cần một chút sai sót nhỏ, hệ thống của bạn sẽ trở nên không an toàn. Vì vậy, sự đơn giản không chỉ là một lý tưởng thẩm mỹ đối với chúng tôi, chúng tôi nghĩ rằng đó là một yêu cầu quan trọng để đảm bảo rằng chúng tôi có thể trao quyền cho mọi người để xây dựng các hệ thống an toàn. Không cần thiết phải biết bản chất của mọi giao thức liên quan. Chúng tôi muốn ẩn những khẩu súng ngắn này đi và cung cấp các giao diện dành cho nhà phát triển dễ sử dụng một cách chính xác và không thể/khó sử dụng theo cách sẽ bắn vào chân ứng dụng của bạn.
Đó là nơi C bị thiếu trầm trọng.
Những nỗ lực của chúng tôi trong việc hiển thị các giao diện đơn giản và an toàn, trong C, đã không thành công. Trong mỗi lần lặp lại, chúng tôi nhận thấy rằng các nhà phát triển ứng dụng sẽ cần biết quá nhiều chi tiết về trạng thái giao thức và quá trình chuyển đổi trạng thái.
Vào khoảng thời gian đó, tôi đã viết một nguyên mẫu tạo Kênh bảo mật Ockam qua Định tuyến Ockam trong Elixir.
Các chương trình Elixir chạy trên BEAM, Máy ảo Erlang. BEAM cung cấp các Quy trình Erlang. Các quy trình Erlang là các tác nhân đồng thời có trạng thái nhẹ. Vì các tác nhân có thể chạy đồng thời trong khi vẫn duy trì trạng thái bên trong, nên việc chạy đồng thời một chồng giao thức có trạng thái Ockam Transports + Ockam Routing + Ockam Secure Channels trở nên dễ dàng.
Tôi đã có thể ẩn tất cả các lớp trạng thái và tạo một chức năng một dòng đơn giản mà ai đó có thể gọi để tạo một kênh bảo mật được mã hóa đầu cuối qua nhiều tuyến đường đa giao thức, đa chặng.
{:ok, channel} = Ockam.SecureChannel.create(route, vault, keypair)
Một nhà phát triển ứng dụng sẽ gọi chức năng đơn giản này và nhiều tác nhân đồng thời sẽ chạy các lớp cơ bản của giao thức trạng thái. Chức năng sẽ trở lại khi kênh được thiết lập hoặc nếu có lỗi. Đây chính xác là những gì chúng tôi muốn trong giao diện của mình.
Nhưng Elixir không giống như C. Nó không chạy tốt trên các máy tính nhỏ/bị hạn chế và nó không phải là một lựa chọn tốt để được bao bọc trong các hàm bao thành ngữ dành riêng cho ngôn ngữ.
Tại thời điểm này, chúng tôi biết rằng chúng tôi muốn triển khai các diễn viên hạng nhẹ nhưng chúng tôi cũng biết rằng C sẽ không dễ dàng thực hiện điều đó. Đây là lúc tôi bắt đầu tìm hiểu về Rust và nhanh chóng bắt gặp một vài điều khiến Rust trở nên rất hấp dẫn:
Các thư viện Rust có thể xuất giao diện tương thích với quy ước gọi của C. Điều này có nghĩa là bất kỳ ngôn ngữ hoặc thời gian chạy nào có thể liên kết tĩnh hoặc động và gọi các hàm trong thư viện C cũng có thể liên kết và gọi các hàm trong thư viện Rust - theo cách chính xác. Vì hầu hết các ngôn ngữ đều hỗ trợ các hàm gốc trong C nên chúng cũng đã hỗ trợ các hàm gốc trong Rust. Điều này làm cho Rust ngang hàng với C từ góc độ yêu cầu của chúng tôi về việc có các trình bao bọc dành riêng cho ngôn ngữ xung quanh thư viện cốt lõi của chúng tôi.
Rust biên dịch bằng LLVM, điều đó có nghĩa là nó có thể nhắm mục tiêu vào một số lượng lớn máy tính. Tập hợp này có thể không lớn bằng mọi thứ mà C có thể nhắm mục tiêu với GCC và các nhánh GCC độc quyền khác nhau nhưng vẫn là một tập hợp con rất lớn và công việc đang diễn ra để làm cho Rust biên dịch với GCC. Với sự hỗ trợ ngày càng tăng của các mục tiêu LLVM mới và hỗ trợ GCC tiềm năng trong Rust, có vẻ như đó là một lựa chọn tốt từ góc độ yêu cầu của chúng tôi về khả năng chạy ở mọi nơi.
Hệ thống kiểu của Rust cho phép chúng ta biến các bất biến thành các lỗi thời gian biên dịch. Điều này làm giảm tập hợp các lỗi có thể xảy ra có thể được chuyển sang sản xuất bằng cách làm cho chúng dễ phát hiện hơn tại thời điểm phát triển. Nhóm của chúng tôi và người dùng thư viện Rust của chúng tôi ít có khả năng đưa các lỗi hành vi hoặc lỗ hổng bảo mật vào quá trình sản xuất.
Các tính năng an toàn bộ nhớ của Rust loại bỏ khả năng sử dụng sau khi giải phóng, giải phóng gấp đôi, tràn, truy cập ngoài giới hạn, chạy đua dữ liệu và nhiều lỗi phổ biến khác được biết là nguyên nhân gây ra 60-70% lỗ hổng nghiêm trọng trong C lớn hoặc cơ sở mã C ++. Rust cung cấp sự an toàn này tại thời điểm biên dịch mà không làm phát sinh chi phí hiệu năng của việc quản lý bộ nhớ một cách an toàn trong thời gian chạy bằng trình thu gom rác. Điều này mang lại cho Rust một lợi thế lớn để viết mã cần có hiệu suất cao, chạy trong môi trường hạn chế và có tính bảo mật cao.
Phần cuối cùng thuyết phục tôi rằng Rust rất phù hợp với Ockam là async/await
. Chúng tôi đã xác định rằng chúng tôi cần các tác nhân nhẹ để tạo giao diện đơn giản và an toàn cho chồng giao thức của Ockam. async/await
có nghĩa là rất nhiều công việc khó khăn để tạo ra các diễn viên đã được thực hiện trong các dự án như tokio và async-std. Chúng tôi có thể xây dựng triển khai diễn viên của Ockam trên nền tảng này.
Một khía cạnh quan trọng khác nổi bật là async/await
trong Rust có một điểm khác biệt đáng kể so với async/await
trong các ngôn ngữ khác như Javascript. Trong Javascript, một công cụ trình duyệt hoặc nodejs chọn cách nó sẽ chạy các chức năng không đồng bộ. Nhưng trong Rust, bạn có thể bổ sung một cơ chế do chính mình lựa chọn. Chúng được gọi là thời gian chạy không đồng bộ - tokio là một ví dụ phổ biến về thời gian chạy như vậy được tối ưu hóa cho các hệ thống có khả năng mở rộng cao. Nhưng không phải lúc nào chúng ta cũng phải sử dụng tokio, thay vào đó, chúng ta có thể chọn thời gian chạy không đồng bộ được tối ưu hóa cho các thiết bị nhúng hoặc bộ vi điều khiển nhỏ.
Điều này có nghĩa là việc triển khai diễn viên của Ockam, mà sau này chúng tôi gọi là Công nhân Ockam, nếu chúng tôi dựa trên async/await
của Rust có thể hiển thị chính xác cùng một giao diện cho người dùng của chúng tôi bất kể nó đang chạy ở đâu - máy tính lớn hay máy tính nhỏ. Tất cả các giao diện giao thức của chúng tôi nằm trên Ockam Worker cũng có thể hiển thị cùng một giao diện đơn giản - bất kể chúng đang chạy ở đâu.
Tại thời điểm này, chúng tôi đã bị thuyết phục rằng chúng tôi nên viết lại Ockam trong Rust. Trong cuộc trò chuyện hội thảo trên web mà tôi đã đề cập trước đó, Paul Dix và tôi đã thảo luận về quá trình chuyển đổi đối với các nhóm của chúng tôi tại Ockam và InfluxDB sau khi mỗi dự án đã quyết định chuyển sang Rust. Chúng tôi đã thảo luận về cách InfluxDB chuyển từ Go sang Rust và cách Ockam chuyển từ C sang Rust. Trong trường hợp bạn quan tâm, trong phần hành trình đó của chúng ta, hãy nghe đoạn ghi âm .
Nhiều lần lặp lại sau này, giờ đây bất kỳ ai cũng có thể sử dụng thùng Ockam bị rỉ sét để tạo kênh bảo mật được mã hóa đầu cuối và xác thực lẫn nhau bằng một lệnh gọi hàm đơn giản.
Đây là một dòng duy nhất, chúng tôi đã tưởng tượng khi chúng tôi bắt đầu:
let channel = node.create_secure_channel(&identity, route, options).await?;
Nó tạo ra một kênh được xác thực và mã hóa qua các tuyến đường đa giao thức, đa giao thức tùy ý có thể trải rộng trên các mạng riêng và đám mây. Chúng tôi có thể che giấu tất cả sự phức tạp tiềm ẩn và súng ngắn đằng sau lệnh gọi hàm đơn giản và an toàn này. Mã vẫn giữ nguyên bất kể bạn sử dụng nó như thế nào - trên các máy chủ có khả năng mở rộng hoặc các bộ vi điều khiển nhỏ.
Để tìm hiểu thêm, hãy xem kout Ockam trên GitHub hoặc thử hướng dẫn từng bước của thư viện Ockam Rust và Ockam Command .
Cũng được xuất bản ở đây.
Nếu bạn là một phần của dự án được viết lại bằng Rust, hãy chia sẻ câu chuyện của nhóm bạn với chúng tôi trên máy chủ bất hòa của chúng tôi. Chúng tôi cũng đang tuyển dụng cho cả hai vai trò Rust và Elixir, hãy tham gia vào nhóm các nhà xây dựng của chúng tôi - chúng tôi đang thực hiện nhiệm vụ làm cho phần mềm trở nên an toàn và riêng tư hơn theo thiết kế.