Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions auto_backup/README.rst
Original file line number Diff line number Diff line change
@@ -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
====================
Expand All @@ -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
Expand Down Expand Up @@ -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
~~~~~~~~~~~~~~~

Expand Down
78 changes: 61 additions & 17 deletions auto_backup/models/db_backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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,
)
12 changes: 12 additions & 0 deletions auto_backup/readme/USAGE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
~~~~~~~~~~~~~~~

Expand Down
Loading