Odoo のセキュリティ強化: 認証、アクセス権、暗号化
エンタープライズ ERP システムは、サイバー攻撃の重要な標的となります。顧客の PII、財務記録、サプライヤー契約、および従業員データを含む Odoo インスタンスは、意図的な多層セキュリティ制御を必要とする重大な攻撃対象領域となります。
このガイドでは、認証の強化とアクセス権の設計から、ネットワークの強化、暗号化、監査ログ、インシデント対応の準備まで、Odoo 19 Enterprise のセキュリティ強化プロセス全体について説明します。
重要なポイント
- セキュリティの観点から、デフォルトの Odoo 構成は本番環境に対応していません
- 機密性の高いアクセスを持つすべてのユーザーに 2 要素認証 (2FA) を必須にする必要があります
- アクセス権は最小特権の原則に従います - ユーザーは必要なものだけを取得します
- レコード ルールは行レベルのセキュリティを提供し、データ分離を強制します
- すべてのサービス間通信は TLS を使用する必要があります。運用環境では決して HTTP を使用しない
- データベース認証情報は定期的にローテーションする必要があり、決してハードコーディングしないでください。
- 監査ログは改ざん防止され、コンプライアンス要件に合わせて保持される必要があります
- セキュリティ パッチは、Odoo SA の発表から 48 時間以内に適用する必要があります。
認証の強化
パスワードポリシー
Odoo 19 は、設定 → ユーザーと会社 → パスワード リセット ポリシー で構成可能なパスワード ポリシーをサポートしています。
推奨設定:
- 最小長: 12 文字
- 大文字、小文字、数字、特殊文字が必要です
- パスワードの有効期限: 90 日
- 過去 12 個のパスワードの再利用を防止する
- 5 回失敗するとアカウントがロックアウトされる (15 分間のロックアウト)
# Custom password validator in a security module
from odoo import api, models
from odoo.exceptions import UserError
import re
class ResUsers(models.Model):
_inherit = 'res.users'
@api.constrains('password')
def _check_password_strength(self):
for user in self:
if user.password:
pwd = user.password
if len(pwd) < 12:
raise UserError("Password must be at least 12 characters.")
if not re.search(r'[A-Z]', pwd):
raise UserError("Password must contain an uppercase letter.")
if not re.search(r'[a-z]', pwd):
raise UserError("Password must contain a lowercase letter.")
if not re.search(r'\d', pwd):
raise UserError("Password must contain a number.")
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', pwd):
raise UserError("Password must contain a special character.")
二要素認証 (2FA)
[設定] → [権限] → [2 要素認証] で 2FA を有効にします。
構成オプション:
- オプション: ユーザーは 2FA を有効にするかどうかを選択します
- 特定のグループに必須: 管理者と会計担当者に適用します
- すべてのユーザーに必須: 最大限のセキュリティ
エンタープライズ展開の場合、次の場合に 2FA が必要です。
- 管理者アカウント (
base.group_system) - 金融ユーザー (
account.group_account_manager) - 給与計算にアクセスできる人事マネージャー
- API キーにアクセスできるすべてのユーザー
OAuth と SSO の統合
既存の ID プロバイダーを持つ組織の場合は、SAML 2.0 または OAuth 2.0 を使用するように Odoo を構成します。
# In odoo.conf — configure OAuth
oauth_providers = [
{
'name': 'Your SSO Provider',
'client_id': 'odoo-client-id',
'auth_endpoint': 'https://sso.company.com/auth',
'validation_endpoint': 'https://sso.company.com/userinfo',
'scope': 'openid email profile',
}
]
これにより、アイデンティティ プロバイダーでの認証が一元化され、シングル サインオン、一元化された MFA、従業員の退職時の自動アカウント プロビジョニング解除などの機能が有効になります。
API キーのセキュリティ
# Restrict API key creation to administrators only
# In custom security module:
# ir.rule to prevent non-admin users from creating API keys
<record id="rule_api_key_admin_only" model="ir.rule">
<field name="name">API Keys: Admin Only</field>
<field name="model_id" ref="base.model_auth_api_key"/>
<field name="domain_force">[('create_uid', '=', user.id)]</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
ポリシーを強制する: API キーは 90 日後に期限切れになり、最小限の権限を持つ専用のサービス アカウントに関連付けられ、侵害の疑いがある場合はローテーションされます。
アクセス権アーキテクチャ
グループ階層の設計
Odoo のアクセス制御はグループに基づいています。組織構造を反映するグループ階層を設計します。
base.group_user (Internal User)
├── sales.group_sale_salesman (Salesperson)
│ └── sales.group_sale_manager (Sales Manager)
├── account.group_account_user (Accounting User)
│ └── account.group_account_manager (Accounting Manager)
├── stock.group_stock_user (Inventory User)
│ └── stock.group_stock_manager (Inventory Manager)
└── base.group_system (Administrator)
最小特権の原則: すべてのユーザーは、自分の仕事を実行するために必要な最小限のアクセス権を持っている必要があります。これにより、侵害されたアカウントの攻撃範囲が制限されます。
アクセス制御リスト (ACL)
ir.model.access の ACL エントリは、モデルレベルの CRUD 権限を定義します。
id,name,model_id,group_id,perm_read,perm_write,perm_create,perm_unlink
# Salesperson: can read/write own orders, cannot delete
access_sale_order_salesman,sale.order.salesman,sale.model_sale_order,sales.group_sale_salesman,1,1,1,0
# Manager: full CRUD
access_sale_order_manager,sale.order.manager,sale.model_sale_order,sales.group_sale_manager,1,1,1,1
# Accounting user: read-only on sales orders
access_sale_order_accountant,sale.order.accountant,sale.model_sale_order,account.group_account_user,1,0,0,0
レコード ルール (行レベルのセキュリティ)
モデル内でユーザーが表示できるレコードのレコード ルール フィルター:
<!-- Salesperson sees only their own opportunities -->
<record id="rule_crm_salesman" model="ir.rule">
<field name="name">CRM: Own Leads</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="domain_force">
['|', ('user_id', '=', user.id), ('user_id', '=', False)]
</field>
<field name="groups" eval="[(4, ref('sales.group_sale_salesman'))]"/>
</record>
<!-- Multi-company isolation: users see only their company's records -->
<record id="rule_sale_order_company" model="ir.rule">
<field name="name">Sale Order: Company</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="domain_force">
[('company_id', 'in', company_ids)]
</field>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
</record>
パフォーマンスに関する警告: 多くの search() または child_of 演算子を含む複雑なレコード ルールでは、リスト ビューの速度が大幅に低下する可能性があります。本番環境に展開する前に、大規模なデータセットに対してすべてのレコード ルールをテストします。
ネットワークセキュリティ
TLS/HTTPS の強制
運用環境ではプレーン HTTP 経由で Odoo を実行しないでください。すべてのトラフィックは暗号化する必要があります。
Nginx TLS 構成:
server {
listen 80;
server_name your-odoo.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name your-odoo.com;
ssl_certificate /etc/ssl/certs/your-odoo.com.pem;
ssl_certificate_key /etc/ssl/private/your-odoo.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
# HSTS: 2 years, include subdomains
add_header Strict-Transport-Security
"max-age=63072000; includeSubDomains; preload" always;
# Security headers
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header Permissions-Policy
"camera=(), microphone=(), geolocation=()" always;
}
ファイアウォール構成
# UFW firewall rules for Odoo server
ufw default deny incoming
ufw default allow outgoing
# Allow SSH from management IP only
ufw allow from 10.0.1.100 to any port 22
# Allow HTTPS from anywhere
ufw allow 443/tcp
# Allow HTTP (for Let's Encrypt renewal)
ufw allow 80/tcp
# Allow PostgreSQL only from Odoo server (if separate DB server)
ufw allow from 10.0.1.10 to any port 5432
# Block direct access to Odoo port (must go through Nginx)
ufw deny 8069/tcp
ufw deny 8072/tcp
ufw enable
レート制限
ブルート フォース攻撃を防ぐために Nginx でレート制限を構成します。
# Rate limiting zones
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;
server {
# Apply rate limiting to login endpoint
location /web/login {
limit_req zone=login burst=3 nodelay;
limit_req_status 429;
proxy_pass http://odoo;
}
# Apply rate limiting to API
location /web/dataset/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://odoo;
}
}
データの暗号化
データベースレベルの暗号化
機密性の高いデータの場合、Odoo フィールドを保存時に暗号化できます。
# Custom encrypted field using Fernet symmetric encryption
from cryptography.fernet import Fernet
from odoo import api, fields, models
import base64, os
class EncryptedField(models.Model):
_name = 'my.sensitive.model'
_fernet_key = os.environ.get('ODOO_FIELD_ENCRYPTION_KEY')
ssn_encrypted = fields.Char(string='SSN (Encrypted)', groups='hr.group_hr_user')
ssn_display = fields.Char(
string='SSN', compute='_compute_ssn_display',
inverse='_inverse_ssn_display'
)
def _get_fernet(self):
return Fernet(self._fernet_key.encode())
def _compute_ssn_display(self):
f = self._get_fernet()
for record in self:
if record.ssn_encrypted:
decrypted = f.decrypt(record.ssn_encrypted.encode()).decode()
record.ssn_display = f"***-**-{decrypted[-4:]}"
else:
record.ssn_display = False
def _inverse_ssn_display(self):
f = self._get_fernet()
for record in self:
if record.ssn_display:
encrypted = f.encrypt(record.ssn_display.encode()).decode()
record.ssn_encrypted = encrypted
環境変数のセキュリティ
シークレットを平文で odoo.conf に保存しないでください。環境変数を使用します。
# /etc/systemd/system/odoo.service
[Service]
Environment="ODOO_DB_PASSWORD=your_secure_password"
Environment="ODOO_ADMIN_PASSWD=your_master_password"
Environment="ODOO_FIELD_ENCRYPTION_KEY=your_fernet_key"
EnvironmentFile=/etc/odoo/secrets.env
# odoo.conf — reference env vars
[options]
db_password = ${ODOO_DB_PASSWORD}
admin_passwd = ${ODOO_ADMIN_PASSWD}
監査ログ
Odoo に組み込まれた監査ログは、すべてのモデルの変更を追跡します。機密性の高いモデルに対して有効にします。
# Enable audit logging via the Audit Trail module
# Or implement custom logging:
class AuditableMixin(models.AbstractModel):
_name = 'audit.mixin'
def write(self, vals):
# Log what changed before the write
for record in self:
old_vals = {f: record[f] for f in vals if f in record._fields}
self.env['audit.log'].create({
'model_name': self._name,
'record_id': record.id,
'user_id': self.env.uid,
'action': 'write',
'old_values': str(old_vals),
'new_values': str(vals),
'ip_address': self.env.context.get('remote_addr'),
})
return super().write(vals)
def unlink(self):
for record in self:
self.env['audit.log'].create({
'model_name': self._name,
'record_id': record.id,
'user_id': self.env.uid,
'action': 'unlink',
'old_values': str({f: record[f] for f in ['name', 'id']}),
})
return super().unlink()
監査対象となるコンプライアンス クリティカルなモデル:
res.users(ユーザーの作成、権限の変更)account.move(請求書の変更)hr.payslip(給与変更)res.partner(顧客/ベンダーの PII の変更)ir.model.access、ir.rule(権限の変更)ir.config_parameter(システム構成の変更)
セキュリティパッチ管理
Odoo セキュリティ アドバイザリを購読する:
Odoo SA は、https://github.com/odoo/odoo/security/advisories でセキュリティ アドバイザリを公開しています。このリポジトリの GitHub 通知をサブスクライブします。
パッチ適用プロセス:
# Check current Odoo version
python3 odoo-bin --version
# Pull latest security patches (within a minor version)
cd /opt/odoo
git fetch origin
git log origin/19.0..HEAD --oneline # See what's changed
# Apply patches on staging first
git merge origin/19.0
python3 odoo-bin -d staging_db -u all --stop-after-init
# Test critical workflows on staging
# If all clear, apply to production during maintenance window
対象: クリティカルまたは高重大度のアドバイザリから 48 時間以内にパッチを適用します。中程度の重症度: 1 週間以内。重大度が低い: 次に計画されているメンテナンス期間。
よくある質問
Odoo で誰がレコードを削除したかを監査するにはどうすればよいですか?
Odoo はデフォルトでは削除をログに記録しません。 Odoo Community Association (OCA) から auditlog モジュールをインストールするか、機密モデルにカスタム unlink() オーバーライドを実装します。 OCA 監査ログ モジュールは、ユーザー、タイムスタンプ、およびフィールド レベルの変更追跡を使用して、auditlog.log モデルへの作成、書き込み、リンク解除操作を記録します。
Odoo へのアクセスを IP アドレスで制限できますか?
Odoo にはネイティブではありません。 Nginx またはファイアウォール レベルで IP ホワイトリストを実装します。管理アクセスには、前提条件として VPN 接続が必要です。 Odoo の ir.config_parameter は web.base.url 制限をサポートしていますが、真の IP ベースのアクセス制御はネットワーク層に属します。
アクセス権 (ACL) とレコード ルールの違いは何ですか?
アクセス権 (ACL、ir.model.access に保存) は、ユーザー グループがモデルのレコードの読み取り、書き込み、作成、または削除を行えるかどうかを制御します。これはモデル レベルの制御です。レコード ルールは、ドメイン式を使用してモデル内でユーザーがアクセスできる特定のレコードをフィルターします。これは行レベルの制御です。どちらも連携して機能します。ユーザーは ACL 読み取り権限が必要であり、レコードを表示するにはレコード ルール ドメイン フィルターを渡す必要があります。
Odoo マスター パスワード (admin_passwd) はどのように処理すればよいですか?
odoo.conf の admin_passwd は、/web/database/manager のデータベース管理インターフェイスへのアクセスを制御します。運用環境では、長いランダム文字列 (32 文字以上) に設定し、パスワード マネージャーに保存し、絶対に共有しないでください。PostgreSQL への直接アクセスを介してデータベースを管理する場合は、odoo.conf の list_db = False を使用してデータベース マネージャーを完全に無効にすることを検討してください。
Odoo を root として実行する必要がありますか?
一度もない。シェルアクセス権がなく、ファイルシステム権限が制限された専用の odoo システム ユーザーを作成します。 odoo ユーザーには、Odoo コードベースへの読み取りアクセスが必要で、ファイルストアとログ ディレクトリへの書き込みアクセスのみが必要です。このユーザーとして systemd サービスを実行します。 root として実行すると、最小特権の原則に違反し、コード実行の脆弱性による被害が最大化されます。
次のステップ
セキュリティの強化は 1 回限りのタスクではなく、継続的に行われます。新しい脆弱性が出現し、ユーザーのアクセス要件が変化し、コンプライアンスのフレームワークが進化します。 Odoo を安全に導入するには、定期的なセキュリティ レビュー、パッチ管理規律、およびアクセス権の監査が必要です。
ECOSIRE は、構成ミス、過剰な権限、欠落しているコントロール、コンプライアンスのギャップを特定する Odoo セキュリティ強化評価を提供します。当社のセキュリティ チームは、Odoo 環境における ISO 27001、SOC 2、および GDPR 要件の経験があります。
ECOSIRE に Odoo セキュリティ評価をリクエスト →
現在の構成を業界標準と照らし合わせて確認し、実装サポートを備えた優先順位の高い修復計画を提供します。
執筆者
ECOSIRE Research and Development Team
ECOSIREでエンタープライズグレードのデジタル製品を開発。Odoo統合、eコマース自動化、AI搭載ビジネスソリューションに関するインサイトを共有しています。
関連記事
Odoo Accounting vs QuickBooks: Detailed Comparison 2026
In-depth 2026 comparison of Odoo Accounting vs QuickBooks covering features, pricing, integrations, scalability, and which platform fits your business needs.
API Rate Limiting: Patterns and Best Practices
Master API rate limiting with token bucket, sliding window, and fixed counter patterns. Protect your backend with NestJS throttler, Redis, and real-world configuration examples.
Case Study: eCommerce Migration to Shopify with Odoo Backend
How a fashion retailer migrated from WooCommerce to Shopify and connected it to Odoo ERP, cutting order fulfillment time by 71% and growing revenue 43%.