# Quản lý secret trên Odoo.sh

> 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.

**Date:** 2026-08-11
**Source:** <https://trobz.com/vi/insights/secrets-management-on-odoo-sh/>

---



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` {#s1}

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][base-neutralize]: 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][payment_authorize-neutralize]: đặt `authorize_login`,
  `authorize_transaction_key`, `authorize_signature_key` và
  `authorize_client_key` về 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 {#s2}

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`][server-env] 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 {#s3}

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`, `staging` hoặc `production`) 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.conf` trê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](#s2) 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](#s1), đượ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`][server-env] 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](#s2) 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 `.conf` nằ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`][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`][server-env-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](#s3)
đã 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](#s1), 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.

[server-env]: https://github.com/OCA/server-env
[base-neutralize]: https://github.com/odoo/odoo/blob/18.0/odoo/addons/base/data/neutralize.sql
[payment_authorize-neutralize]: https://github.com/odoo/odoo/blob/18.0/addons/payment_authorize/data/neutralize.sql
[data-encryption]: https://github.com/OCA/server-env/tree/18.0/data_encryption
[server-env-data-encryption]: https://github.com/OCA/server-env/tree/18.0/server_environment_data_encryption

