Maintainers
+Maintainers
This module is maintained by the OCA.
@@ -522,6 +529,5 @@ diff --git a/auto_backup/README.rst b/auto_backup/README.rst index 60c77d98a0a..6ec83d05dae 100644 --- a/auto_backup/README.rst +++ b/auto_backup/README.rst @@ -1,7 +1,3 @@ -.. image:: https://odoo-community.org/readme-banner-image - :target: https://odoo-community.org/get-involved?utm_source=readme - :alt: Odoo Community Association - ==================== Database Auto-Backup ==================== @@ -17,7 +13,7 @@ Database Auto-Backup .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png :target: https://odoo-community.org/page/development-status :alt: Beta -.. |badge2| image:: https://img.shields.io/badge/license-AGPL--3-blue.png +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html :alt: License: AGPL-3 .. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fserver--tools-lightgray.png?logo=github @@ -73,6 +69,18 @@ a path and everything will be backed up automatically. This is done through an SSH (encrypted) tunnel, thanks to pysftp, so your data is safe! +Secure SFTP connection with host key verification +------------------------------------------------- + +A new optional field **Host Public Key** has been added. + +- Paste the exact public key of your SFTP server (you can obtain it with + `ssh-keyscan -t rsa,ecdsa,ed25519 your.sftp.server`). +- When filled, Odoo will **verify the server identity** and refuse the connection if the key does not match → protects against man-in-the-middle attacks. +- Leave empty → old behaviour (no host key checking, backward compatible). + +The "Test SFTP Connection" button now also validates the host key. + Test connection ~~~~~~~~~~~~~~~ diff --git a/auto_backup/models/db_backup.py b/auto_backup/models/db_backup.py index b8517bae55a..6b688654378 100644 --- a/auto_backup/models/db_backup.py +++ b/auto_backup/models/db_backup.py @@ -7,11 +7,13 @@ import os import shutil import traceback +from base64 import decodebytes from contextlib import contextmanager from datetime import datetime, timedelta from glob import iglob import pysftp +from paramiko import DSSKey, ECDSAKey, Ed25519Key, HostKeys, RSAKey from odoo import _, api, exceptions, fields, models, tools from odoo.exceptions import UserError @@ -94,6 +96,17 @@ class DbBackup(models.Model): help="Choose the format for this backup.", ) + host_public_key = fields.Text( + help=( + "Public key of the remote SFTP server " + "(SSH RSA/ED25519 key in OpenSSH format). " + "If set, verifies the server identity to prevent " + "man-in-the-middle attacks. " + "Leave empty for no verification (less secure). " + "Can be obtained with e.g. 'ssh-keyscan 202.54.1.5'" + ), + ) + @api.model def _default_folder(self): """Default to ``backups`` folder inside current server datadir.""" @@ -283,21 +296,52 @@ def filename(when, ext="zip"): ) def sftp_connection(self): - """Return a new SFTP connection with found parameters.""" - self.ensure_one() - params = { - "host": self.sftp_host, - "username": self.sftp_user, - "port": self.sftp_port, - } - _logger.debug( - "Trying to connect to sftp://%(username)s@%(host)s:%(port)d", extra=params - ) - if self.sftp_private_key: - params["private_key"] = self.sftp_private_key - if self.sftp_password: - params["private_key_pass"] = self.sftp_password + cnopts = pysftp.CnOpts() + if self.host_public_key: + # Strict mode: Load only the provided key (no default known_hosts) + try: + parts = self.host_public_key.strip().split(None, 2) + if len(parts) < 2: + raise UserError( + _( + "Invalid host public key format. " + "Expected: 'ssh-rsa AAAAB3Nza...'" + ) + ) + key_type = parts[0] + key_b64 = parts[1] + data = decodebytes(key_b64.encode("ascii")) + if key_type == "ssh-rsa": + key = RSAKey(data=data) + elif key_type == "ssh-dss": + key = DSSKey(data=data) + elif key_type.startswith("ecdsa-sha2-nistp"): + key = ECDSAKey(data=data) + elif key_type == "ssh-ed25519": + key = Ed25519Key(data=data) + else: + raise ValueError(_("Unsupported key type: %s") % key_type) + # Use Paramiko's HostKeys directly (pysftp-compatible) + cnopts.hostkeys = HostKeys() + cnopts.hostkeys.add(self.sftp_host, key_type, key) + except Exception as e: + raise UserError(_("Error loading host public key: %s") % str(e)) from e else: - params["password"] = self.sftp_password - - return pysftp.Connection(**params) + # Disable checking explicitly to avoid known_hosts warnings + cnopts.hostkeys = None + if self.sftp_private_key: + return pysftp.Connection( + host=self.sftp_host, + username=self.sftp_user, + port=self.sftp_port or 22, + private_key=self.sftp_private_key, + private_key_pass=self.sftp_password or None, + cnopts=cnopts, + ) + return pysftp.Connection( + host=self.sftp_host, + username=self.sftp_user, + port=self.sftp_port or 22, + password=self.sftp_password, + cnopts=cnopts, + ) diff --git a/auto_backup/readme/USAGE.rst b/auto_backup/readme/USAGE.rst index bc3607e22a4..56dda90b52c 100644 --- a/auto_backup/readme/USAGE.rst +++ b/auto_backup/readme/USAGE.rst @@ -15,6 +15,18 @@ a path and everything will be backed up automatically. This is done through an SSH (encrypted) tunnel, thanks to pysftp, so your data is safe! +Secure SFTP connection with host key verification +------------------------------------------------- + +A new optional field **Host Public Key** has been added. + +- Paste the exact public key of your SFTP server (you can obtain it with + `ssh-keyscan -t rsa,ecdsa,ed25519 your.sftp.server`). +- When filled, Odoo will **verify the server identity** and refuse the connection if the key does not match → protects against man-in-the-middle attacks. +- Leave empty → old behaviour (no host key checking, backward compatible). + +The "Test SFTP Connection" button now also validates the host key. + Test connection ~~~~~~~~~~~~~~~ diff --git a/auto_backup/static/description/index.html b/auto_backup/static/description/index.html index ff75e85f681..0801fec30c3 100644 --- a/auto_backup/static/description/index.html +++ b/auto_backup/static/description/index.html @@ -3,7 +3,7 @@
-A tool for all your back-ups, internal and external!
Table of contents
Before installing this module, you need to execute:
pip3 install pysftp==0.2.9
Go to Settings -> Database Structure -> Automated Backup to create your configurations for each database that you needed to backups.
Keep your Odoo data safe with this module. Take automated back-ups, remove them automatically and even write them to an external server through an encrypted tunnel. You can even specify how long local backups and external backups should be kept, automatically!
Want to go even further and write your backups to an external server? You can with this module! Specify the credentials to the server, specify a path and everything will be backed up automatically. This is done through an SSH (encrypted) tunnel, thanks to pysftp, so your data is safe!
A new optional field Host Public Key has been added.
+The “Test SFTP Connection” button now also validates the host key.
+Want to make sure if the connection details are correct and if Odoo can automatically write them to the remote server? Simply click on the ‘Test SFTP Connection’ button and you will get message telling you if @@ -448,15 +455,15 @@
Do you want to know if the database backup succeeded or failed? Subscribe to the corresponding backup setting notification type.
From the backups configuration list, press More > Execute backup(s) to manually execute the selected processes.
@@ -465,7 +472,7 @@Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. If you spotted it first, help us to smash it by providing a detailed and welcomed @@ -483,9 +490,9 @@
Do not contact contributors directly about support or help with technical issues.