OpenFGA: Giải pháp ưu việt cho phân quyền truy cập dữ liệu
OpenFGA là một giải pháp phân quyền mạnh mẽ với khả năng xử lý nhanh chóng và linh hoạt. Hãy cùng tìm hiểu về OpenFGA và ưu điểm của nó khi xây dựng theo mô hình kiểm soát truy cập dựa trên mối quan hệ (ReBAC) qua bài viết sau.
This post is also available in English
ReBAC
Trong bảo mật kỹ thuật số, các mô hình phân quyền đóng vai trò quan trọng trong việc xác định “ai” có “quyền gì” trên “tài nguyên nào”. Các mô hình truyền thống như Roled-based Access Control (RBAC) - Kiểm soát truy cập dựa trên vai trò và Attribute-based Access Control (ABAC) - Kiểm soát truy cập dựa trên thuộc tính là những giải pháp thông dụng trong nhiều năm. Tuy nhiên, khi độ phức tạp và quy mô của các hệ thống tăng lên, nhu cầu về tính linh hoạt và độ chi tiết cũng cao hơn. Trong bối cảnh đó, một mô hình phân quyền mới ra đời: Relationship-Based Access Control (ReBAC) - Kiểm soát truy cập dựa trên mối quan hệ.
Sơ đồ các mô hình phân quyền ABAC, RBAC và ReBAC
ReBAC là mô hình phân quyền xem xét mối quan hệ giữa chủ thể và đối tượng khi xác định quyền truy cập. Khác với “attribute” trong ABAC hay “role” trong RBAC, mô hình ReBAC quan tâm đến “relationship” của người dùng trong tổ chức, xã hội, cấp bậc, bên cạnh những quan hệ thông dụng giữa người dùng và tài nguyên. Ví dụ như trong trường hợp cần cấp phép cho người quản lý truy cập dữ liệu của cấp dưới trong nhóm của họ, hoặc cho phép các thành viên của nhóm dự án truy cập các tài nguyên được liên kết với dự án, ReBAC có thể cung cấp một cơ chế kiểm soát truy cập linh hoạt, phản ánh cấu trúc quan hệ trong thế giới thực tốt hơn và có thể giúp các tổ chức thực thi các chính sách bảo mật hiệu quả hơn.
Google Zanzibar là một ví dụ điển hình về ReBAC. Zanzibar là hệ thống được Google sử dụng để xử lý phân quyền cho hàng trăm dịch vụ và sản phẩm như YouTube, Drive, Calendar, Cloud, Maps,… Với khả năng xử lý các chính sách phân quyền phức tạp ở quy mô toàn cầu, Zanzibar có khả năng xử lý hàng tỷ truy vấn mỗi giây.
OpenFGA
Lấy ý tưởng từ Zanzibar, OpenFGA ra đời như một mô hình phân quyền vừa mạnh mẽ cho các kỹ sư, vừa thân thiện với các stakeholder trong dự án. OpenFGA - với khả năng xử lý nhanh chóng, linh hoạt - là công cụ phù hợp với dự án ở mọi quy mô.
Fine-Grained Authorization (FGA) là khả năng chỉ định quyền truy cập vào các tài nguyên hoặc đối tượng cụ thể trong hệ thống cho từng người dùng. Fine-Grained Authorization đặc biệt phù hợp trong các hệ thống quy mô lớn với hàng triệu đối tượng, người dùng, mối quan hệ và các quyền truy cập được cập nhật thường xuyên. Một ví dụ về hệ thống sử dụng Fine-Grained Authorization chính là Google Drive, nơi quyền truy cập thường xuyên thay đổi khi người dùng chia sẻ tài liệu, thư mục.
OpenFGA trả lời kiểm tra phân quyền bằng cách xem xét mối quan hệ giữa người dùng và đối tượng được yêu cầu. Việc kiểm tra sẽ dựa vào mô hình ủy quyền (authorization model) của hệ thống và các bộ dữ liệu quan hệ (relationship tuple) đang có tại thời điểm để đưa ra quyết định.
Một số khái niệm trong OpenFGA
Sơ đồ quan hệ giữa các khái niệm trong OpenFGA
- Store - “Kho” lưu trữ: mỗi Store chứa một hay nhiều phiên bản Authorization Model và các Relationship Tuple. Khi thực hiện kiểm tra phân quyền, ta sẽ cần cung cấp StoreID để chỉ định store chứa dữ liệu của model và các tuple cần kiểm tra. Dữ liệu trong một store không thể chia sẻ với các store khác.
- Authorization model - Mô hình phân quyền.
OpenFGA giới thiệu hai loại cú pháp để định nghĩa authorization model là JSON và DSL. Đây là một ví dụ về authorization model được định nghĩa bằng OpenFGA DSL:
model
schema 1.1
type document
relations
define viewer: [domain#member,user]
define commenter: [domain#member,user]
define editor: [domain#member,user]
define owner: [domain#member,user]
type domain
relations
define member: [user]
type user
Mỗi authorization model được định nghĩa bằng:
-
Type: định nghĩa một tập các đối tượng có tính chất giống nhau (ví dụ: document, repository, user, admin, ….)
-
Relations: định nghĩa mối quan hệ giữa user và object
-
User-to-object
type document relations define viewer: [user:hungtgq1]
-
Object-to-object
type document relations define editor: [application:myapp]a
-
Userset-to-object
type document relations define editor: [organization:myteam#member]
-
Everyone-to-object
type document relations define editor: [user:*]
-
-
Relationship Tuple - Bộ dữ liệu quan hệ: bao gồm user (người), relation (quan hệ) và object (đối tượng) được lưu trữ trong OpenFGA. Tập hợp các Relationship Tuple thể hiện mối quan hệ giữa người và các đối tượng cụ thể trong hệ thống, đồng thời là căn cứ để OpenFGA thực hiện xác thực.
[ { "user": "user:hungtgq1", "relation": "owner", "object": "board:myfirstboard", }, ]
OpenFGA Playground
OpenFGA hỗ trợ Playground để người dùng có thể làm quen với ReBAC và OpenFGA.
Với Playground, ta có thể định nghĩa Authorization bằng DSL và sao chép phiên bản JSON tương ứng để sử dụng trong source code. Hiện tại OpenFGA server chỉ hỗ trợ trực tiếp model dưới dạng JSON, để sử dụng DSL, ta cần sử dụng parser hoặc chuyển đổi bằng playground.
Giao diện OpenFGA Playground
Người dùng có thể sử dụng Playground tại OpenFGA Playground hoặc sử dụng Playground tích hợp sẵn trong server tự cài đặt. Ta sẽ tìm hiểu cách cài đặt OpenFGA thông qua phần demo kế tiếp.
Demo
Cài đặt OpenFGA
OpenFGA hỗ trợ cài đặt nhanh và đơn giản bằng Docker và Kubernetes, chi tiết tại Setup OpenFGA. Trong bài viết này, ta sẽ sử dụng bản mẫu Docker Compose có sẵn để cài đặt OpenFGA.
Sau khi chạy docker compose theo hướng dẫn, ta có thể kiểm tra kết quả bằng cách access vào api /healthz
.
Kết quả cài đặt OpenFGA
Mô tả bài toán
Architecture Diagram Platform là nền tảng cho phép người dùng tạo các bảng vẽ kiến trúc hệ thống và chia sẻ với những người dùng khác. Các bảng vẽ có thể được nhóm trong thư mục. Chỉ có người tạo ra bảng vẽ mới có thể sửa và chia sẻ bảng vẽ, người dùng được chia sẻ chỉ có thể xem các bảng vẽ.
Với bài toán như trên, ta có thể tìm được các đối tượng và quan hệ như sau:
-
User (người dùng)
-
Board (bảng vẽ)
- Owner (người tạo ra bảng vẽ)
- Viewer (người được chia sẻ)
- Parent (thư mục cha)
- can_view (quyền xem của người tạo bảng vẽ và người được chia sẻ)
- can_share (quyền chia sẻ của người tạo bảng vẽ)
- can_edit (quyền sửa của người tạo bảng vẽ)
-
Folder (thư mục)
- Owner (người tạo thư mục)
- can_create (quyền tạo bảng vẽ của người tạo thư mục)
Ta có mô hình phân quyền tương ứng:
Mô hình phân quyền của bài toán Architecture Diagram Platform
go
model
schema 1.1
type user
type folder
relations
define can_create: owner
define owner: [user]
type board
relations
define can_view: viewer or owner
define can_share: owner
define can_edit: owner
define owner: [user]
define parent: [folder]
define viewer: [user]
Hiện thực hóa
Hiện tại, OpenFGA hỗ trợ SDK cho các ngôn ngữ Node.js, Go, .NET và Python. Trong demo này, chúng ta sẽ sử dụng ngôn ngữ Go Để cài đặt Golang SDK và làm theo hướng dẫn Install SDK Client.
Ta sẽ lần lượt thực hiện các thao tác: tạo Store → thêm Authorization Model → thêm các Relationship Tuple → tiến hành Check kiểm tra phân quyền.
- Tạo Store
Để tạo Store, ta cần tạo configuration object chứa ApiScheme và ApiHost trỏ tới OpenFGA local server như sau:
configuration, err := openfga.NewConfiguration(openfga.Configuration{
ApiScheme: os.Getenv("FGA_API_SCHEME"), // "http"
ApiHost: os.Getenv("FGA_API_HOST"), // "localhost:8080"
})
Sau đó, ta tạo Store bằng hàm CreateStore như sau:
apiClient := openfga.NewAPIClient(configuration)
resp, _, err := apiClient.OpenFgaApi.CreateStore(context.Background()).Body(
openfga.CreateStoreRequest{
Name: "Architecture Diagram Platform",
}).Execute()
Khi thực thi thành công, Store mới được tạo ra như hình sau:
Chúng ta sẽ ghi lại id để sử dụng làm Store Id (FGA_STORE_ID) trong những bước kế tiếp.
- Thêm Authorization Model
Để thực hiện thêm Authorization Model, ta sẽ cần cấu hình StoreId với giá trị lấy được từ bước trước thêm vào configuration object:
StoreId: os.Getenv("FGA_STORE_ID"),
Ta sẽ dùng playground để convert model thành JSON và sử dụng trong source code như sau:
var writeAuthorizationModelRequestString = "{\"type_definitions\":[{\"type\":\"user\",\"relations\":{}},{\"type\":\"folder\",\"relations\":{\"can_create\":{\"computedUserset\":{\"object\":\"\",\"relation\":\"owner\"}},\"owner\":{\"this\":{}}},\"metadata\":{\"relations\":{\"can_create\":{\"directly_related_user_types\":[]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}},{\"type\":\"board\",\"relations\":{\"can_view\":{\"union\":{\"child\":[{\"computedUserset\":{\"object\":\"\",\"relation\":\"viewer\"}},{\"computedUserset\":{\"object\":\"\",\"relation\":\"owner\"}}]}},\"can_share\":{\"computedUserset\":{\"object\":\"\",\"relation\":\"owner\"}},\"can_edit\":{\"computedUserset\":{\"object\":\"\",\"relation\":\"owner\"}},\"owner\":{\"this\":{}},\"parent\":{\"this\":{}},\"viewer\":{\"this\":{}}},\"metadata\":{\"relations\":{\"can_view\":{\"directly_related_user_types\":[]},\"can_share\":{\"directly_related_user_types\":[]},\"can_edit\":{\"directly_related_user_types\":[]},\"owner\":{\"directly_related_user_types\":[{\"type\":\"user\"}]},\"parent\":{\"directly_related_user_types\":[{\"type\":\"folder\"}]},\"viewer\":{\"directly_related_user_types\":[{\"type\":\"user\"}]}}}}],\"schema_version\":\"1.1\"}"
var body openfga.WriteAuthorizationModelRequest
if err := json.Unmarshal([]byte(writeAuthorizationModelRequestString), &body); err != nil {
// ...
}
data, response, err := apiClient.OpenFgaApi.WriteAuthorizationModel(context.Background()).Body(body).Execute()
...
Khi thực thi thành công, Authorization Model mới được tạo ra như hình:
Chúng ta sẽ ghi lại authorization_model_id để sử dụng làm Authoirzation Model Id (FGA_AUTH_MODEL_ID) trong những bước kế tiếp.
- Thêm Relationship Tuple
Để thêm Relationship Tuple, ta sử dụng đoạn code như sau:
body := openfga.WriteRequest{
Writes: &openfga.TupleKeys{
TupleKeys: []openfga.TupleKey{
{
User: openfga.PtrString("user:hungtgq1"),
Relation: openfga.PtrString("owner"),
Object: openfga.PtrString("folder:myfirstfolder"),
},
},
},
AuthorizationModelId: openfga.PtrString(os.Getenv("FGA_AUTH_MODEL_ID"))}
_, response, err := apiClient.OpenFgaApi.Write(context.Background()).Body(body).Execute()
Kết quả khi thành công:
Ở bước này, ta sẽ lần lượt thêm vào các Tuple như sau:
- Người dùng 'hungtgq1' tạo thư mục 'myfirstfolder'
- Người dùng 'hungtgq1' tạo bảng vẽ 'myfirstboard' trong thư mục 'myfirstfolder'
- Người dùng 'hungtgq1' chia sẻ bảng vẽ với người dùng 'manlm1'
Sau khi thêm các tuple, ta sẽ có dữ liệu cơ sở dữ liệu của OpenFGA như sau:
- Thực hiện kiểm tra phân quyền
Để kiểm tra phân quyền, ta dùng đoạn code sau:
body := openfga.CheckRequest{
AuthorizationModelId: openfga.PtrString(os.Getenv("FGA_AUTH_MODEL_ID")),
TupleKey: openfga.TupleKey{
User: openfga.PtrString("user:hungtgq1"),
Relation: openfga.PtrString("can_view"),
Object: openfga.PtrString("board:myfirstboard"),
},
}
data, _, err := apiClient.OpenFgaApi.Check(context.Background()).Body(body).Execute()
// Print for demo
fmt.Println(*data.Allowed)
Ta sẽ được kết quả True hoặc False. Trong đoạn code trên, người dùng 'hungtgq1' là owner của bảng vẽ 'myfirstboard' nên sẽ có quyền can_view như ảnh dưới:
Tương tự, khi thực hiện các kiểm tra sau:
- Quyền can_view của người dùng 'manlm1': True
- Quyền can_edit của người dùng 'manlm1': False
- Quyền can_share của người dùng 'hungtgq1': True
Những điểm cần chú ý khi sử dụng OpenFGA
OpenFGA đưa ra một số best practice khi cài đặt OpenFGA cho môi trường production, trong đó có một số điểm đáng chú ý như sau:
Về hiện thực hóa:
- Không lưu dữ liệu nhạy cảm hay thông tin cá nhân trong Relationship Tuple.
- Luôn khai báo Authorization Model ID trong request để tăng tốc độ xử lí của OpenFGA và đảm bảo tính nhất quán của hệ thống trong thời gian thay đổi model (do OpenFGA mặc định tìm model mới nhất nếu không khai báo Model ID).
Về triển khai:
Trong demo, chúng ta gọi trực tiếp OpenFGA mà không cần thông qua xác thực. Trong thực tế, bên cạnh no-authentication (mặc định), ta có thể cấu hình cho OpenFGA sử dụng các phương thức xác thực như:
- Pre-shared Key Authentication: sử dụng secret key và đặt “Authorization” header trong các request gọi đến OpenFGA.
- OIDC: Cấu hình issuer và audience từ provider.
Về cơ sở dữ liệu:
- Sử dụng một cơ sở dữ liệu riêng biệt, không chia sẻ với các ứng dụng khác để có thể mở rộng độc lập và tránh xung đột với cơ sở dữ liệu của ứng dụng khác.
- Cơ sở dữ liệu được thiết lập và quản lý bằng công cụ “openfga migrate” để đảm bảo các chỉ mục cần thiết cho bất kỳ phiên bản nào của OpenFGA.
Kết luận
OpenFGA nổi lên như một giải pháp kiểm soát truy cập mạnh mẽ, thúc đẩy khái niệm ReBAC - Kiểm soát truy cập dựa trên mối quan hệ. Việc triển khai OpenFGA giúp hệ thống có khả năng mở rộng quy mô tốt và độ trễ thấp, là lựa chọn đáng tin cậy cho các hệ thống phức tạp. OpenFGA có những điểm mạnh nổi bật trong việc quản lý các mối quan hệ giữa các đối tượng để linh hoạt và linh động kiểm soát truy cập trong các ứng dụng hiện đại.