This is an automated email from the ASF dual-hosted git repository.

sbp pushed a commit to branch sbp
in repository https://gitbox.apache.org/repos/asf/tooling-trusted-releases.git


The following commit(s) were added to refs/heads/sbp by this push:
     new 030b4fc3 Use the intersection of algorithms from asyncssh and ssh-audit
030b4fc3 is described below

commit 030b4fc39add34172696cc0c222fa4751db38d00
Author: Sean B. Palmer <[email protected]>
AuthorDate: Fri Feb 20 16:52:06 2026 +0000

    Use the intersection of algorithms from asyncssh and ssh-audit
---
 atr/ssh.py                     | 18 ++++++++++++++++++
 atr/util.py                    | 39 +++++++++++++++++++++++----------------
 pyproject.toml                 |  1 +
 requirements-for-pip-audit.txt |  4 +++-
 uv.lock                        | 19 +++++++++++++++----
 5 files changed, 60 insertions(+), 21 deletions(-)

diff --git a/atr/ssh.py b/atr/ssh.py
index 78876cba..6f9d1433 100644
--- a/atr/ssh.py
+++ b/atr/ssh.py
@@ -31,6 +31,10 @@ from typing import Any, Final
 import aiofiles
 import aiofiles.os
 import asyncssh
+import asyncssh.encryption as encryption
+import asyncssh.kex as kex
+import asyncssh.mac as mac
+import ssh_audit.builtin_policies as builtin_policies
 
 import atr.attestable as attestable
 import atr.config as config
@@ -44,6 +48,17 @@ import atr.util as util
 
 _CONFIG: Final = config.get()
 
+_SSH_AUDIT_POLICY: Final = builtin_policies.BUILTIN_POLICIES["Hardened OpenSSH 
Server v9.9 (version 1)"]
+
+_ASYNCSSH_SUPPORTED_ENC: Final = {bytes(a) for a in 
encryption.get_encryption_algs()}
+_ASYNCSSH_SUPPORTED_KEX: Final = {bytes(a) for a in kex.get_kex_algs()}
+_ASYNCSSH_SUPPORTED_MAC: Final = {bytes(a) for a in mac.get_mac_algs()}
+
+
+_APPROVED_CIPHERS: Final = util.intersect_algs(_SSH_AUDIT_POLICY, "ciphers", 
_ASYNCSSH_SUPPORTED_ENC)
+_APPROVED_KEX: Final = util.intersect_algs(_SSH_AUDIT_POLICY, "kex", 
_ASYNCSSH_SUPPORTED_KEX)
+_APPROVED_MACS: Final = util.intersect_algs(_SSH_AUDIT_POLICY, "macs", 
_ASYNCSSH_SUPPORTED_MAC)
+
 
 class RsyncArgsError(Exception):
     """Exception raised when the rsync arguments are invalid."""
@@ -179,6 +194,9 @@ async def server_start() -> asyncssh.SSHAcceptor:
         host=_CONFIG.SSH_HOST,
         port=_CONFIG.SSH_PORT,
         encoding=None,
+        encryption_algs=_APPROVED_CIPHERS,
+        kex_algs=_APPROVED_KEX,
+        mac_algs=_APPROVED_MACS,
     )
 
     log.info(f"SSH server started on {_CONFIG.SSH_HOST}:{_CONFIG.SSH_PORT}")
diff --git a/atr/util.py b/atr/util.py
index 1c30d07b..f6f019ee 100644
--- a/atr/util.py
+++ b/atr/util.py
@@ -196,22 +196,6 @@ async def async_temporary_directory(
             log.exception(f"Failed to remove temporary directory 
{temp_dir_path}")
 
 
-async def atomic_write_file(file_path: pathlib.Path, content: str, encoding: 
str = "utf-8") -> None:
-    """Atomically write content to a file using a temporary file."""
-    await aiofiles.os.makedirs(file_path.parent, exist_ok=True)
-    temp_path = file_path.parent / f".{file_path.name}.{uuid.uuid4()}.tmp"
-    try:
-        async with aiofiles.open(temp_path, "w", encoding=encoding) as f:
-            await f.write(content)
-            await f.flush()
-            await asyncio.to_thread(os.fsync, f.fileno())
-        await aiofiles.os.rename(temp_path, file_path)
-    except Exception:
-        with contextlib.suppress(FileNotFoundError):
-            await aiofiles.os.remove(temp_path)
-        raise
-
-
 async def atomic_modify_file(
     file_path: pathlib.Path,
     modify: Callable[[str], str],
@@ -233,6 +217,22 @@ async def atomic_modify_file(
         await asyncio.to_thread(os.close, lock_fd)
 
 
+async def atomic_write_file(file_path: pathlib.Path, content: str, encoding: 
str = "utf-8") -> None:
+    """Atomically write content to a file using a temporary file."""
+    await aiofiles.os.makedirs(file_path.parent, exist_ok=True)
+    temp_path = file_path.parent / f".{file_path.name}.{uuid.uuid4()}.tmp"
+    try:
+        async with aiofiles.open(temp_path, "w", encoding=encoding) as f:
+            await f.write(content)
+            await f.flush()
+            await asyncio.to_thread(os.fsync, f.fileno())
+        await aiofiles.os.rename(temp_path, file_path)
+    except Exception:
+        with contextlib.suppress(FileNotFoundError):
+            await aiofiles.os.remove(temp_path)
+        raise
+
+
 def chmod_directories(path: pathlib.Path, permissions: int = 
DIRECTORY_PERMISSIONS) -> None:
     # codeql[py/overly-permissive-file]
     os.chmod(path, permissions)
@@ -635,6 +635,13 @@ async def has_files(release: sql.Release) -> bool:
     return False
 
 
+def intersect_algs(policy: dict[str, Any], policy_key: str, supported: 
set[bytes]) -> list[str]:
+    algs = policy[policy_key]
+    if not isinstance(algs, list):
+        raise TypeError(f"ssh-audit policy '{policy_key}' is not a list")
+    return [a for a in algs if isinstance(a, str) and (a.encode("ascii") in 
supported)]
+
+
 def is_dev_environment() -> bool:
     conf = config.get()
     for development_host in ("127.0.0.1", "atr", "atr-dev", 
"localhost.apache.org"):
diff --git a/pyproject.toml b/pyproject.toml
index 4e5e1e07..ad547edb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -50,6 +50,7 @@ dependencies = [
   "rich~=14.0.0",
   "semver>=3.0.4",
   "sqlmodel~=0.0.24",
+  "ssh-audit>=3.3.0",
   "standard-imghdr>=3.13.0",
   "strictyaml>=1.7.3",
   "structlog>=25.5.0",
diff --git a/requirements-for-pip-audit.txt b/requirements-for-pip-audit.txt
index 34aa6df2..9464aa0f 100644
--- a/requirements-for-pip-audit.txt
+++ b/requirements-for-pip-audit.txt
@@ -324,7 +324,9 @@ sqlalchemy==2.0.46
     # via
     #   alembic
     #   sqlmodel
-sqlmodel==0.0.34
+sqlmodel==0.0.35
+    # via tooling-trusted-releases
+ssh-audit==3.3.0
     # via tooling-trusted-releases
 standard-imghdr==3.13.0
     # via tooling-trusted-releases
diff --git a/uv.lock b/uv.lock
index f8650a0e..98d1be08 100644
--- a/uv.lock
+++ b/uv.lock
@@ -3,7 +3,7 @@ revision = 3
 requires-python = "==3.13.*"
 
 [options]
-exclude-newer = "2026-02-20T16:30:57Z"
+exclude-newer = "2026-02-20T16:45:38Z"
 
 [[package]]
 name = "aiofiles"
@@ -1826,15 +1826,24 @@ wheels = [
 
 [[package]]
 name = "sqlmodel"
-version = "0.0.34"
+version = "0.0.35"
 source = { registry = "https://pypi.org/simple"; }
 dependencies = [
     { name = "pydantic" },
     { name = "sqlalchemy" },
 ]
-sdist = { url = 
"https://files.pythonhosted.org/packages/3b/6a/b1b26d589063e53a08c10a2d7bc624cba63dec045a312758d68f550a4ea1/sqlmodel-0.0.34.tar.gz";,
 hash = 
"sha256:577e4aae1ba96ee5038e03d8b1404c642dad1a92e628988cdf4ce68d27abe982", size 
= 96236, upload-time = "2026-02-16T19:06:34.275Z" }
+sdist = { url = 
"https://files.pythonhosted.org/packages/a6/fd/6f468f52977b85f8b1af3f0d7d4396ed77804a59bf589f2f47c524383388/sqlmodel-0.0.35.tar.gz";,
 hash = 
"sha256:e0079a6ec569323587ffb7326bbbc9d9a1a92e9be271b18e83f54d4a4200d6ac", size 
= 86087, upload-time = "2026-02-20T16:42:21.254Z" }
 wheels = [
-    { url = 
"https://files.pythonhosted.org/packages/eb/ee/1910f4eee41af4268b0d8cd688a05fb8ea23e9e6c64b8710592df24a8c66/sqlmodel-0.0.34-py3-none-any.whl";,
 hash = 
"sha256:aeabc8f0de32076a0ed9216e88568459d737fca1e7133bfc6d1c657920789a2d", size 
= 27445, upload-time = "2026-02-16T19:06:35.709Z" },
+    { url = 
"https://files.pythonhosted.org/packages/8f/f3/90f7b2eb86e590b74cf33e37a5313c074092684666355201afe9a1ae7ef5/sqlmodel-0.0.35-py3-none-any.whl";,
 hash = 
"sha256:367c11719bc4967430d5aadc43ee1a6f7638b9c82ee7c8835401400e05ec9431", size 
= 27221, upload-time = "2026-02-20T16:42:20.301Z" },
+]
+
+[[package]]
+name = "ssh-audit"
+version = "3.3.0"
+source = { registry = "https://pypi.org/simple"; }
+sdist = { url = 
"https://files.pythonhosted.org/packages/3b/ec/e89fdfaaa6f08813e1a5cf926bc0dc155761144ebcac57191b4c8001aae3/ssh_audit-3.3.0.tar.gz";,
 hash = 
"sha256:b76e36ac9844f45d64986c9f293a4b46766a10412dc29fb43bd52d0f6661a5b0", size 
= 116958, upload-time = "2024-10-15T21:08:28.632Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/4f/21/2b3cd275bc3c09fc765691f6c005a6683edf47f47c18d2f9eb78c39ca7e6/ssh_audit-3.3.0-py3-none-any.whl";,
 hash = 
"sha256:8d2b22b7bb7c20c9d84452c9f13408015ec76eb025379cc70335e5315e4378d6", size 
= 121789, upload-time = "2024-10-15T21:08:27.182Z" },
 ]
 
 [[package]]
@@ -1920,6 +1929,7 @@ dependencies = [
     { name = "rich" },
     { name = "semver" },
     { name = "sqlmodel" },
+    { name = "ssh-audit" },
     { name = "standard-imghdr" },
     { name = "strictyaml" },
     { name = "structlog" },
@@ -1984,6 +1994,7 @@ requires-dist = [
     { name = "rich", specifier = "~=14.0.0" },
     { name = "semver", specifier = ">=3.0.4" },
     { name = "sqlmodel", specifier = "~=0.0.24" },
+    { name = "ssh-audit", specifier = ">=3.3.0" },
     { name = "standard-imghdr", specifier = ">=3.13.0" },
     { name = "strictyaml", specifier = ">=1.7.3" },
     { name = "structlog", specifier = ">=25.5.0" },


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to