Odoo.sh không cho biến môi trường cũng không cho file cấu hình với tới được, nên API key chỉ còn cách nằm trong repository hoặc trong database. Các phương án và cái giá của chúng.
Odoo.sh build mỗi nhánh thành một container riêng từ một lần git push, không có bước nào ở giữa để bạn định nghĩa thứ gì trước khi container đó khởi động. Điều này va phải một nhu cầu vận hành cơ bản: đưa secret (khóa API, thông tin đăng nhập SMTP, token của bên thứ ba) vào một instance đang chạy mà không phải để chúng trong mã nguồn hay gõ tay vào database sau mỗi lần deploy.
Bài này xem Odoo.sh cho sẵn những gì cho việc đó, vì sao chừng đó là chưa đủ khi bạn có nhiều hơn một môi trường, và những cách đi vòng đáng cân nhắc. Bài không kết thúc bằng một giải pháp được khuyến nghị. Chúng tôi chưa tìm được cách nào thỏa đáng trọn vẹn, và mục đích ở đây là bày các đánh đổi ra một cách thẳng thắn thay vì làm như đã có lời giải.
1. Cách tiêu chuẩn: config parameter và neutralize.sql
Có hai cơ chế trong bản lõi Odoo chạm tới chuyện này, và không cơ chế nào thật sự là một trình quản lý secret.
Tham số hệ thống (ir.config_parameter)
Kho khóa/giá trị duy nhất ghi được và bền vững mà Odoo.sh cho bạn khi không có file cấu hình, và nó chỉ là một bảng trong database. Mọi thứ lưu ở đó đều ở dạng văn bản thuần, ai có quyền truy cập backend hoặc một bản dump database đều đọc được, và được mang sang mọi bản sao staging hay dev mà Odoo.sh nhân bản từ production, trừ khi có thứ gì đó cố tình chà sạch nó đi.
neutralize.sql
Bất kỳ module nào đã cài cũng có thể mang theo một file
<module_name>/data/neutralize.sql. Lệnh CLI odoo neutralize gom mỗi module
đã cài một file như vậy rồi chạy tất cả trong một transaction. Hai ví dụ từ bản
lõi:
- base: vô hiệu hóa các mail server, tắt các cron không thiết yếu, đánh dấu các webhook action là đã trung hòa.
- payment_authorize: đặt
authorize_login,authorize_transaction_key,authorize_signature_keyvàauthorize_client_keyvề null trên mọi bản ghi nhà cung cấp thanh toán.
Khuôn mẫu ở mọi addon có mang file này đều giống nhau: đặt về null các cột đang
giữ thông tin đăng nhập. Đó là một lần chà sạch, không phải một kho lưu. Nó chạy
sau khi bản sao database đã tồn tại, để tước đi các tác dụng phụ (bản staging
không gửi email, không có cron nào gọi vào API production, không có lời gọi
thanh toán nào dùng khóa thật), chứ không phải để cấp phát một secret ngay từ
đầu. Nó chỉ xóa những gì tác giả module nhớ mà liệt kê ra; một trường không có
dòng nào trong neutralize.sql sẽ sống sót nguyên vẹn.
2. Cách sửa cho on-premise: config parameter theo từng môi trường
Tham số hệ thống là một bảng khóa/giá trị phẳng cho mỗi database, không có khái
niệm sẵn có nào về “giá trị cho staging so với giá trị cho production”. Module
server_environment_ir_config_parameter của OCA lấp đúng khoảng
trống đó cho các hệ thống on-prem: nó ghi đè get_param/create/write để một
mục [ir.config_parameter] trong file riêng của từng môi trường thắng database
và chặn các chỉnh sửa từ giao diện khỏi bám lại, dù giá trị vẫn được cache vào
database ở lần đọc đầu tiên, nên nó được làm cho mất thẩm quyền ở đó chứ không
phải bị gỡ khỏi đó.
3. Các ràng buộc của Odoo.sh
Cách sửa ở trên phụ thuộc vào đúng cái kênh mà Odoo.sh không cho bạn: một file
cấu hình để viết mục [ir.config_parameter] vào. Có hai thứ không dùng được,
đáng nêu chính xác vì Odoo.sh có hỗ trợ những dạng tùy biến lúc build khác, chỉ
là không phải hai thứ này:
- Không có biến môi trường tùy chỉnh trong build. Không được ghi trong tài
liệu, nhưng đúng trên thực tế: container chỉ phơi ra các biến do chính Odoo.sh
bơm vào, không phải các biến do một dự án định nghĩa. Nó bơm vào hai biến:
ODOO_STAGE(dev,staginghoặcproduction) vàODOO_VERSION, cả hai đọc được từos.environ. Hữu ích để biết code đang chạy ở môi trường nào, nhưng chỉ một chiều. Không có đường tương ứng để bơm một giá trị vào. - Không có cách nào được hỗ trợ để đổi file cấu hình Odoo. Nó nằm ở một
đường dẫn dotfile bên trong container build, chẳng hạn
~/.config/odoo/odoo.conftrên một bản build chúng tôi kiểm tra. Trình Editor của Odoo.sh (giao diện web) không với tới được. Một phiên shell thì có, nếu bạn quen dùng terminal, nhưng các sửa đổi theo cách đó không sống sót qua lần rebuild kế tiếp, và một số tùy chọn bị bỏ qua ngay cả khi với tới được file.
Ngược lại, một file requirements.txt ở gốc repository (hoặc trong thư mục
submodule chứa module Odoo) là một kênh khai báo được hỗ trợ cho các phụ thuộc
Python bổ sung, áp dụng tự động lúc build. Còn giá trị cấu hình và secret thì
chẳng có gì tương tự.
4. Vì sao chuyện này chưa thỏa đáng
Riêng trên Odoo.sh, cách sửa theo từng môi trường ở mục 2 không có chỗ mà
gắn vào: không file cấu hình nào để chứa mục [ir.config_parameter], cũng không
biến môi trường nào để mang nó thay thế. Thứ còn lại là commit secret vào
repository, hoặc lưu nó thành tham số hệ thống hay một trường của model, mà như
vậy là rơi lại vào đúng cái bảng database mô tả ở mục 1, được sao nguyên
sang mọi database staging và dev trừ khi có một dòng neutralize.sql cho đúng
trường đó. Không cách nào là một câu chuyện quản lý secret cả. Cả hai chỉ là thứ
còn lại khi nền tảng không cho bạn chỗ nào khác để đặt giá trị vào.
5. Cách đi vòng chúng tôi đang dùng
Ở một dự án, cách đi vòng hiện tại là module đồng hành tiêu chuẩn
server_environment_files, như chính tài liệu của
server_environment mô tả: một addon chứa các file .conf theo
từng môi trường dưới default/, staging/, dev/, được đọc lúc khởi động và
trộn đè lên cấu hình nền. Production được cố ý để ra ngoài và xử lý riêng.
- Khép lại khoảng trống ở mục 2 mà không cần file cấu hình, cho phép có giá trị theo phạm vi môi trường mà không đụng tới database.
- Cái giá thật: các file
.confnằm ngay trong repository Git mà Odoo.sh build từ đó, tức đổi “secret trong database” lấy “secret trong mã nguồn”, chỉ là có phân theo phạm vi và không lộ trên giao diện.
6. Những hướng chúng tôi chưa thử
Một workflow GitHub Actions cập nhật cấu hình sau khi deploy
Ý tưởng: chờ build xong, kết nối qua SSH, cập nhật cấu hình, khởi động lại worker.
- Không có webhook hay sự kiện báo build xong, chỉ có cách thăm dò liên tục.
- Mỗi bản build có endpoint SSH riêng gắn với chính bản build đó chứ không phải một địa chỉ cố định theo dự án hay theo nhánh, nên workflow phải phân giải xem cần kết nối tới host nào trước khi kết nối được.
- Ngay cả khi giải xong, secret vẫn phải được đặt sẵn ở đâu đó để workflow đọc được lúc deploy. Vẫn câu hỏi đó, chỉ lùi ra một cấp.
Mã hóa giá trị khi lưu trữ
Module data_encryption của OCA cho Odoo một kho mã hóa khi
lưu trữ đúng nghĩa: một model encrypted.data, đánh khóa theo tên và môi
trường, mỗi bản ghi giữ một khối dữ liệu mã hóa bằng Fernet. Khóa giải mã được
đọc từ file cấu hình, mỗi môi trường một khóa (encryption_key_<env>), không
bao giờ từ database. server_environment_data_encryption
nối cơ chế này vào chính server.env.mixin mà server_environment_files dùng,
nên các trường do môi trường quản lý được lưu mã hóa trong database thay vì nằm
ở một trường mặc định dạng thuần.
Đây là câu chuyện mã hóa khi lưu trữ sạch sẽ nhất chúng tôi tìm được: phần dữ liệu mã hóa nằm an toàn trong database, không đọc được nếu không có khóa. Nhưng bản thân cái khóa lại phải đến từ file cấu hình, đúng cái kênh mà mục 3 đã loại trừ.
Một middleware quản lý secret
Ý tưởng: một dịch vụ nhỏ đặt bên ngoài Odoo.sh, mà mỗi database gọi tới lúc chạy
để lấy secret cho stage của mình, thay vì lưu cục bộ. ODOO_STAGE vốn đã cho
instance biết nó đang chạy ở stage nào; có thể ghi đè ir.config_parameter
(cùng khuôn mẫu với server_environment_ir_config_parameter) để khi cache trượt
một khóa thì kích hoạt một lời gọi tới middleware.
Cách này không xóa bỏ câu hỏi “đặt nó ở đâu”, nó thu nhỏ câu hỏi lại: thay vì mọi secret đều cần một kênh vào Odoo.sh, chỉ còn một thứ cần, là thông tin đăng nhập instance dùng để xác thực với chính middleware. Mà thông tin đăng nhập đó vẫn cần một kênh mà Odoo.sh không cho, còn middleware thì trở thành một điểm hỏng đơn lẻ mới mà mọi request đều phụ thuộc vào.
Từ chối khởi động trên một bản sao chưa từng được trung hòa
Ý tưởng: một module nạp ở phạm vi toàn máy chủ (--load) kiểm tra
database.is_neutralized trước khi registry mở, và từ chối nạp database nếu
instance không phải production mà cờ kia chưa được bật.
Làm cho giai đoạn trung hòa cấu hình được
Ý tưởng: khai báo những gì một bản sao không phải production được phép giữ dưới dạng bản ghi trong một bảng, thay vì mỗi lần cần chà một trường lại phải đưa vào một file SQL tĩnh mới.
Một file mà bản lõi vốn đã đọc cho mỗi module đã cài, và nó không nhất thiết phải
là một danh sách câu lệnh UPDATE: một khối PL/pgSQL đọc một bảng cấu hình rồi
tự sinh ra các câu lệnh đó khi chạy. File thì không bao giờ đổi, các dòng dữ liệu
mới đổi.
- Với tới được mà không cần nền tảng hợp tác gì, vì nó đi nhờ chính cơ chế gom file mà bản lõi vốn đã làm.
- Các quy tắc đi ngay bên trong database đang được trung hòa, điều đó vừa là cái làm cho phép gián tiếp này chạy được, vừa là cái giới hạn nó: một giá trị thay thế lưu ở đó là lại nằm trong đúng cái bảng database ở mục 1, và được sao đi khắp nơi. Hữu ích cho các endpoint sandbox và những định danh giả một cách hiển nhiên, không dùng được cho thông tin đăng nhập thật.
- Đáng kết hợp với middleware ở trên: quy tắc đặt một dấu hiệu, còn instance phân giải giá trị thật lúc chạy dựa trên stage nó đang chạy.
Ba hướng đầu hoặc giữ secret trong repository, hoặc giữ chúng trong database, hoặc thêm một thành phần mà bản thân nó lại phải được tin cậy để biết chúng nằm ở đâu. Hai hướng cuối không cấp phát gì cả; chúng thu hẹp quãng đường một giá trị production đi được một khi nó đã nằm trong một database, tức nửa còn lại của cùng bài toán.