ec2patcher 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ec2patcher might be problematic. Click here for more details.

Files changed (108) hide show
  1. ec2patcher-1.0.0/LICENSE +21 -0
  2. ec2patcher-1.0.0/PKG-INFO +417 -0
  3. ec2patcher-1.0.0/README.md +376 -0
  4. ec2patcher-1.0.0/pyproject.toml +82 -0
  5. ec2patcher-1.0.0/setup.cfg +4 -0
  6. ec2patcher-1.0.0/src/ec2patcher/__init__.py +8 -0
  7. ec2patcher-1.0.0/src/ec2patcher/__main__.py +4 -0
  8. ec2patcher-1.0.0/src/ec2patcher/app.py +1022 -0
  9. ec2patcher-1.0.0/src/ec2patcher/config.py +100 -0
  10. ec2patcher-1.0.0/src/ec2patcher/database.py +1570 -0
  11. ec2patcher-1.0.0/src/ec2patcher/formatting.py +22 -0
  12. ec2patcher-1.0.0/src/ec2patcher/logging_setup.py +103 -0
  13. ec2patcher-1.0.0/src/ec2patcher/main.py +96 -0
  14. ec2patcher-1.0.0/src/ec2patcher/models.py +395 -0
  15. ec2patcher-1.0.0/src/ec2patcher/services/__init__.py +0 -0
  16. ec2patcher-1.0.0/src/ec2patcher/services/amazon_state.py +205 -0
  17. ec2patcher-1.0.0/src/ec2patcher/services/amazon_updateinfo.py +456 -0
  18. ec2patcher-1.0.0/src/ec2patcher/services/analysis_service.py +819 -0
  19. ec2patcher-1.0.0/src/ec2patcher/services/apt_planner.py +323 -0
  20. ec2patcher-1.0.0/src/ec2patcher/services/cache_settings.py +240 -0
  21. ec2patcher-1.0.0/src/ec2patcher/services/cve_resolver.py +642 -0
  22. ec2patcher-1.0.0/src/ec2patcher/services/debversion.py +94 -0
  23. ec2patcher-1.0.0/src/ec2patcher/services/downloader.py +117 -0
  24. ec2patcher-1.0.0/src/ec2patcher/services/excel_export.py +307 -0
  25. ec2patcher-1.0.0/src/ec2patcher/services/local_apt.py +353 -0
  26. ec2patcher-1.0.0/src/ec2patcher/services/nvd.py +635 -0
  27. ec2patcher-1.0.0/src/ec2patcher/services/os_adapters/__init__.py +92 -0
  28. ec2patcher-1.0.0/src/ec2patcher/services/os_adapters/amazon_linux.py +374 -0
  29. ec2patcher-1.0.0/src/ec2patcher/services/os_adapters/base.py +146 -0
  30. ec2patcher-1.0.0/src/ec2patcher/services/os_adapters/ubuntu.py +246 -0
  31. ec2patcher-1.0.0/src/ec2patcher/services/patch_remote.py +432 -0
  32. ec2patcher-1.0.0/src/ec2patcher/services/patch_service.py +1457 -0
  33. ec2patcher-1.0.0/src/ec2patcher/services/patch_state.py +176 -0
  34. ec2patcher-1.0.0/src/ec2patcher/services/report_service.py +152 -0
  35. ec2patcher-1.0.0/src/ec2patcher/services/rpmversion.py +138 -0
  36. ec2patcher-1.0.0/src/ec2patcher/services/secret_store.py +144 -0
  37. ec2patcher-1.0.0/src/ec2patcher/services/security_metadata.py +542 -0
  38. ec2patcher-1.0.0/src/ec2patcher/services/server_state.py +255 -0
  39. ec2patcher-1.0.0/src/ec2patcher/services/severity.py +39 -0
  40. ec2patcher-1.0.0/src/ec2patcher/services/ssh_service.py +431 -0
  41. ec2patcher-1.0.0/src/ec2patcher/services/staging.py +222 -0
  42. ec2patcher-1.0.0/src/ec2patcher/static/app.js +113 -0
  43. ec2patcher-1.0.0/src/ec2patcher/static/style.css +395 -0
  44. ec2patcher-1.0.0/src/ec2patcher/templates/_cache_badges.html +2 -0
  45. ec2patcher-1.0.0/src/ec2patcher/templates/_cache_stats.html +3 -0
  46. ec2patcher-1.0.0/src/ec2patcher/templates/_metadata_status.html +8 -0
  47. ec2patcher-1.0.0/src/ec2patcher/templates/_nvd_key_badge.html +13 -0
  48. ec2patcher-1.0.0/src/ec2patcher/templates/_run_status.html +5 -0
  49. ec2patcher-1.0.0/src/ec2patcher/templates/_ssh_result.html +19 -0
  50. ec2patcher-1.0.0/src/ec2patcher/templates/analysis_run.html +97 -0
  51. ec2patcher-1.0.0/src/ec2patcher/templates/base.html +42 -0
  52. ec2patcher-1.0.0/src/ec2patcher/templates/dashboard.html +36 -0
  53. ec2patcher-1.0.0/src/ec2patcher/templates/error.html +9 -0
  54. ec2patcher-1.0.0/src/ec2patcher/templates/history.html +35 -0
  55. ec2patcher-1.0.0/src/ec2patcher/templates/patch_all.html +48 -0
  56. ec2patcher-1.0.0/src/ec2patcher/templates/patch_all_confirm.html +52 -0
  57. ec2patcher-1.0.0/src/ec2patcher/templates/patch_confirm.html +40 -0
  58. ec2patcher-1.0.0/src/ec2patcher/templates/patch_execution.html +169 -0
  59. ec2patcher-1.0.0/src/ec2patcher/templates/reports.html +142 -0
  60. ec2patcher-1.0.0/src/ec2patcher/templates/server_form.html +100 -0
  61. ec2patcher-1.0.0/src/ec2patcher/templates/server_report.html +283 -0
  62. ec2patcher-1.0.0/src/ec2patcher/templates/servers.html +79 -0
  63. ec2patcher-1.0.0/src/ec2patcher/templates/settings.html +137 -0
  64. ec2patcher-1.0.0/src/ec2patcher/templates/shutdown_confirm.html +16 -0
  65. ec2patcher-1.0.0/src/ec2patcher/templates/shutdown_done.html +33 -0
  66. ec2patcher-1.0.0/src/ec2patcher/validation.py +222 -0
  67. ec2patcher-1.0.0/src/ec2patcher.egg-info/PKG-INFO +417 -0
  68. ec2patcher-1.0.0/src/ec2patcher.egg-info/SOURCES.txt +106 -0
  69. ec2patcher-1.0.0/src/ec2patcher.egg-info/dependency_links.txt +1 -0
  70. ec2patcher-1.0.0/src/ec2patcher.egg-info/entry_points.txt +2 -0
  71. ec2patcher-1.0.0/src/ec2patcher.egg-info/requires.txt +14 -0
  72. ec2patcher-1.0.0/src/ec2patcher.egg-info/top_level.txt +1 -0
  73. ec2patcher-1.0.0/tests/test_amazon_linux.py +549 -0
  74. ec2patcher-1.0.0/tests/test_amazon_updateinfo.py +402 -0
  75. ec2patcher-1.0.0/tests/test_analysis.py +901 -0
  76. ec2patcher-1.0.0/tests/test_apt_planner.py +242 -0
  77. ec2patcher-1.0.0/tests/test_cache_ttl.py +690 -0
  78. ec2patcher-1.0.0/tests/test_cve_resolver.py +335 -0
  79. ec2patcher-1.0.0/tests/test_database.py +183 -0
  80. ec2patcher-1.0.0/tests/test_debversion.py +109 -0
  81. ec2patcher-1.0.0/tests/test_integration_e2e.py +423 -0
  82. ec2patcher-1.0.0/tests/test_integration_targets.py +152 -0
  83. ec2patcher-1.0.0/tests/test_local_apt.py +320 -0
  84. ec2patcher-1.0.0/tests/test_nvd_cache.py +242 -0
  85. ec2patcher-1.0.0/tests/test_nvd_key_check.py +350 -0
  86. ec2patcher-1.0.0/tests/test_os_adapters.py +259 -0
  87. ec2patcher-1.0.0/tests/test_patch_all.py +467 -0
  88. ec2patcher-1.0.0/tests/test_patch_execution.py +826 -0
  89. ec2patcher-1.0.0/tests/test_patch_resilience.py +205 -0
  90. ec2patcher-1.0.0/tests/test_patch_web.py +493 -0
  91. ec2patcher-1.0.0/tests/test_perl_report.py +150 -0
  92. ec2patcher-1.0.0/tests/test_phase21.py +555 -0
  93. ec2patcher-1.0.0/tests/test_phase22.py +702 -0
  94. ec2patcher-1.0.0/tests/test_phase4.py +128 -0
  95. ec2patcher-1.0.0/tests/test_planner_guards.py +148 -0
  96. ec2patcher-1.0.0/tests/test_remediation_groups.py +340 -0
  97. ec2patcher-1.0.0/tests/test_report_service.py +127 -0
  98. ec2patcher-1.0.0/tests/test_rpmversion.py +125 -0
  99. ec2patcher-1.0.0/tests/test_security_metadata.py +826 -0
  100. ec2patcher-1.0.0/tests/test_server_state.py +134 -0
  101. ec2patcher-1.0.0/tests/test_ssh_service.py +142 -0
  102. ec2patcher-1.0.0/tests/test_ssh_user.py +204 -0
  103. ec2patcher-1.0.0/tests/test_stale_state.py +356 -0
  104. ec2patcher-1.0.0/tests/test_stored_credentials.py +609 -0
  105. ec2patcher-1.0.0/tests/test_tags.py +376 -0
  106. ec2patcher-1.0.0/tests/test_ui_polish.py +378 -0
  107. ec2patcher-1.0.0/tests/test_validation.py +91 -0
  108. ec2patcher-1.0.0/tests/test_web.py +409 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amir Amiri
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,417 @@
1
+ Metadata-Version: 2.4
2
+ Name: ec2patcher
3
+ Version: 1.0.0
4
+ Summary: Local web GUI that analyzes Ubuntu and Amazon Linux 2023 EC2 servers for CVE fixes and patches Ubuntu servers over SSH from an approved .deb plan
5
+ Author: Amir Amiri
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Amiri83/EC2Patcher
8
+ Project-URL: Source, https://github.com/Amiri83/EC2Patcher
9
+ Project-URL: Issues, https://github.com/Amiri83/EC2Patcher/issues
10
+ Keywords: ec2,aws,ubuntu,amazon-linux,cve,security,patching,ssh,apt
11
+ Classifier: Development Status :: 5 - Production/Stable
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Framework :: FastAPI
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Security
23
+ Classifier: Topic :: System :: Systems Administration
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: fastapi>=0.110
28
+ Requires-Dist: uvicorn>=0.27
29
+ Requires-Dist: jinja2>=3.1
30
+ Requires-Dist: python-multipart>=0.0.9
31
+ Requires-Dist: platformdirs>=4.0
32
+ Requires-Dist: openpyxl>=3.1
33
+ Requires-Dist: cryptography>=42
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=8; extra == "dev"
36
+ Requires-Dist: httpx2; extra == "dev"
37
+ Requires-Dist: ruff>=0.5; extra == "dev"
38
+ Requires-Dist: build>=1.2; extra == "dev"
39
+ Requires-Dist: twine>=6.1; extra == "dev"
40
+ Dynamic: license-file
41
+
42
+ # EC2 Patcher
43
+
44
+ A small, local, single-user web GUI (FastAPI + Jinja2 + SQLite) for recurring security
45
+ patching of EC2 servers. You keep an inventory of servers, upload the security team's CVE
46
+ report, get a **read-only** per-server analysis, and then patch an approved plan on one server
47
+ or on all eligible servers of an analysis (**Patch All**), with an optional reboot afterwards.
48
+
49
+ - **Ubuntu**: analysis (Canonical security data) with an exact package / `.deb` plan, and
50
+ patching of the approved plan.
51
+ - **Amazon Linux 2023**: read-only analysis (Amazon's ALAS advisories); patching is not
52
+ supported yet.
53
+ - The OS is **detected automatically** from `/etc/os-release`; any other OS is listed as
54
+ *OS not supported yet*.
55
+
56
+ ```bash
57
+ pipx install ec2patcher
58
+ ec2patcher
59
+ ```
60
+
61
+ EC2Patcher never runs `apt upgrade` / `apt dist-upgrade`: it installs only the exact `.deb`
62
+ files of a plan you approved.
63
+
64
+ ## Workflow at a glance
65
+
66
+ 1. **Servers**: add each server (name, IP, SSH user, login type: PEM key or password,
67
+ optional tags) and run the SSH test.
68
+ 2. **Reports**: pick or drop the CVE report JSON; it is uploaded and validated automatically.
69
+ 3. **Analyze Report**: read-only analysis of every server in the report.
70
+ 4. Review each **server report** (Severity, CVSS, Ubuntu priority, package plan, reboot
71
+ expectation); export it to Excel if needed. **Re-analyze** or **Retry** where needed.
72
+ 5. **Approve & Patch** one server, or **Patch All** for the whole analysis. Optionally tick
73
+ **Skip reboot**.
74
+ 6. **History** keeps every decision, execution, verification and reboot result.
75
+
76
+ ## Target server requirements
77
+
78
+ - Ubuntu (analysis + patching) or Amazon Linux 2023 (analysis only), reachable over SSH as
79
+ the server's **SSH user** (per server, default `ubuntu`; `ec2-user` on Amazon Linux) with a
80
+ PEM key (default; the PEM *path* is stored, its contents are never read, stored or logged)
81
+ or with username + password (see below). Other operating systems (including end-of-life
82
+ Amazon Linux 2) are detected and listed as *OS not supported yet* / *OS not supported*.
83
+ - **Passwordless sudo** for that user for patching: every privileged command uses `sudo -n`,
84
+ and patching aborts if `sudo -n true` fails. Analysis needs no sudo at all.
85
+ - Ubuntu: `dpkg`, `apt-get`, `sha256sum` and write access to `/tmp` (the standard Ubuntu image
86
+ has them). Amazon Linux 2023: `rpm` (read-only queries; `needs-restarting` from
87
+ `dnf-utils` is optional and used only for the reboot status).
88
+
89
+ ## Features
90
+
91
+ ### Servers and tags
92
+
93
+ - Add, edit, delete and SSH-test servers. **Clear All Servers** requires typing
94
+ `DELETE SERVERS`.
95
+ - Names may contain letters, digits, `.`, `_` and `-` and are unique regardless of case. CVE
96
+ reports are always matched by **name**.
97
+ - Optional key/value **tags** per server (e.g. `display_name = Billing API`, `env = prod`):
98
+ up to 50 per server, keys unique per server (case-insensitive). `display_name` is shown in
99
+ reports but never used for matching.
100
+ - Each server has an **SSH user** (default `ubuntu`, e.g. `ec2-user` on Amazon Linux) used for
101
+ every ssh/scp to it. It must be a plain POSIX user name (`[a-z_][a-z0-9_.-]*`, max 32).
102
+ - **SSH test** runs `ssh -i <pem> <user>@<ip>` with `BatchMode=yes`, `ConnectTimeout=10` and a
103
+ 30 s overall limit, and shows hostname, OS release and architecture or a short error. New host
104
+ keys are accepted on first connect (`accept-new`); a changed host key is an error.
105
+ - **Login type** per server (dropdown on the server form): **PEM key** (default) or
106
+ **username + password**. The password is **stored per server, encrypted** (Fernet, see
107
+ [Secrets at rest](#secrets-at-rest)); it is never shown, logged or exported, and the form
108
+ field is always empty: when editing, leave it empty to keep the stored password. Switching a
109
+ server to PEM, deleting it, Clear All Servers or Reset Database removes its password.
110
+ Password login runs `sshpass -e ssh|scp ...`; the password reaches sshpass only through the
111
+ `SSHPASS` environment variable of the child process, never the command line. Install
112
+ `sshpass` on the workstation (`sudo apt install sshpass`); without it the connection fails
113
+ with a clear message. Analysis or patching of a password server without a usable stored
114
+ password is refused with a clear message. `sudo` on the server must still be passwordless
115
+ (`sudo -n`).
116
+
117
+ ### Report upload
118
+
119
+ On **Reports**, choosing or dropping a `.json` file uploads it immediately (no extra click; a
120
+ plain *Upload Report* button is shown without JavaScript). Max 1 MiB; structural validation
121
+ only. The latest accepted report is kept; a failed upload does not replace it.
122
+
123
+ ```json
124
+ {
125
+ "app-prod-01": ["CVE-2026-12345", "CVE-2026-67890"],
126
+ "database-prod-01": ["CVE-2026-22222"]
127
+ }
128
+ ```
129
+
130
+ Each key must be a configured server name (an unknown server rejects the whole report); each
131
+ value is an array of `CVE-YYYY-NNNN…` strings (case-insensitive, normalized and de-duplicated).
132
+
133
+ ### Analysis (read-only)
134
+
135
+ **Analyze Report** starts a background run; the run page refreshes itself and shows each
136
+ server as *Waiting*, *Analyzing*, *Complete*, *Failed* (with the reason) or *Not supported*.
137
+ Servers are analyzed one after another; one failure never affects the others.
138
+
139
+ - **On the server**: one fixed read-only command as the server's SSH user, **without sudo**:
140
+ hostname, `/etc/os-release`, architecture, running kernel, `/run/reboot-required(.pkgs)`,
141
+ `dpkg-query` (binary → source package and versions) and `dpkg --audit`. Nothing is
142
+ downloaded, copied, installed or restarted.
143
+ - **OS detection**: the OS is taken from `/etc/os-release`; everything OS-specific (inventory,
144
+ security data lookups, planning, install, reboot check) sits behind an `OsAdapter`
145
+ (`services/os_adapters/`). Adapters: **Ubuntu** (analysis + patching) and **Amazon Linux
146
+ 2023** (analysis only). For Amazon Linux a second fixed read-only command collects the `rpm`
147
+ inventory and the dnf releasever. Any other OS is shown as
148
+ `OS not supported yet: <name> <version>` (not a failure, never patchable).
149
+ - **On the workstation**: APT candidates and the `.deb` plan are resolved against a private
150
+ APT state per release and architecture (`<data dir>/apt/<codename>-<arch>/`, pockets
151
+ `<codename>`, `-updates`, `-security`). Every `apt-get` / `apt-cache` call overrides
152
+ `Dir::State`, `Dir::Cache` and `Dir::Etc`, so the workstation's own APT is never used or
153
+ changed. `apt-get -s` and `--print-uris` give exact versions, URIs, sizes and SHA256 without
154
+ downloading anything.
155
+ - **Canonical** (`https://ubuntu.com/security/cves/<CVE>.json`) decides applicability, fixed
156
+ version and status, per source package and Ubuntu release, with Debian version comparison.
157
+ Kernel CVEs are checked against the *running* kernel.
158
+ - **Amazon Linux 2023**: the repository `updateinfo.xml` (ALAS advisories) of the server's
159
+ releasever and of the latest release is fetched on the workstation (via
160
+ `cdn.amazonlinux.com` mirror lists) and compared with the installed rpm versions (EVR
161
+ comparison). Statuses: patch available, fix only in a newer releasever, already fixed,
162
+ package not installed, or no advisory (investigate). No plan is built and the server is
163
+ never patchable (*Patching not supported yet for Amazon Linux 2023*).
164
+ - **NVD** (CVE API 2.0) supplies only the CVSS **Severity** (Critical/High/Medium/Low/Unknown)
165
+ and score; it never changes a finding's status or plan. Canonical's priority is shown as
166
+ **Ubuntu Priority**.
167
+ - A server with unconfigured/half-installed packages (`dpkg --audit`) gets a blocker
168
+ ("run sudo dpkg --configure -a") instead of a plan. Packages already at or above their
169
+ target version are never planned (shown as an "already at target" warning).
170
+ - Findings are grouped into **Action required**, **Investigate** and **No action**. Anything
171
+ that cannot be decided is shown as such, never as safe.
172
+ - Every run is stored as a snapshot (report, server facts, findings, plan); older runs stay
173
+ viewable. **Export to Excel** builds an `.xlsx` (Summary, CVE Findings, Package Plan) from
174
+ that snapshot only, without any network or SSH access.
175
+
176
+ **Re-analyze and Retry**
177
+
178
+ - **Retry failed lookups** (run page): re-runs only the Canonical lookups that failed in that
179
+ run.
180
+ - **Retry these CVEs** (server report, Investigate bucket): fetches those CVEs again and
181
+ re-analyzes the server.
182
+ - **Re-analyze** (server report): analyzes the server again, fetching all of its CVEs from
183
+ Canonical again.
184
+
185
+ ### Patching one server
186
+
187
+ Each server report has **Reject** and **Approve & Patch** (after a confirmation page with a
188
+ **Skip reboot** checkbox, unchecked by default). Only the latest analysis of a server can be
189
+ executed, and each approved analysis only once. The pipeline stops at the first failing step:
190
+
191
+ 1. **Revalidate** hostname, release, architecture and installed versions against the analysis
192
+ (drift → *PATCH ABORTED — SERVER STATE CHANGED*); check `sudo -n true`. Packages already
193
+ at target are dropped; if nothing is left the result is **ALREADY PATCHED**.
194
+ 2. **Download** each approved `.deb` from its recorded URI; keep it only if size and SHA256
195
+ match the plan.
196
+ 3. **Transfer** with `scp` to `/tmp/<server name>` and verify size + `sha256sum` (one retry).
197
+ 4. **Simulate** `apt-get -s install <explicit .deb paths>`: must install exactly the approved
198
+ packages/versions, with no removal or downgrade.
199
+ 5. **Install** `sudo -n apt-get install -y <explicit .deb paths>` with no remote APT sources,
200
+ `--no-remove`, `DEBIAN_FRONTEND=noninteractive`, `NEEDRESTART_MODE=l`.
201
+ 6. **Verify** installed versions, `dpkg --audit`, each CVE against Canonical's fixed version,
202
+ and `/run/reboot-required`.
203
+ 7. **Clean up** local and remote staging.
204
+ 8. **Reboot**, only after a verified patch, only if `/run/reboot-required` exists and **Skip
205
+ reboot** is unchecked: `sudo -n reboot`, wait up to 10 minutes for a new boot id, record
206
+ uptime and kernel. The reboot result is recorded separately from the patch result.
207
+
208
+ A failed or interrupted install is never retried or rolled back; if the connection drops the
209
+ server is inspected once more and the result is proven or recorded as *EXECUTION STATE
210
+ UNKNOWN*. A new analysis is required before trying again.
211
+
212
+ ### Patch All
213
+
214
+ The analysis run page has **Patch All** with a **Skip reboot** checkbox. The confirmation page
215
+ lists the eligible servers in order and the **SKIPPED** ones with their reasons. A server
216
+ analyzed again after this run is patched from its latest analysis. Servers are patched **one
217
+ at a time** with the pipeline above; the queue **stops at the first failure** and the rest are
218
+ shown as **NOT RUN**. Only one patch execution or queue runs at a time.
219
+
220
+ ### Settings
221
+
222
+ - **Caches** (top of the page): one badge per cache (*NVD cache: <N> CVEs, oldest <age>, TTL
223
+ <ttl>*, same for Canonical and Amazon updateinfo; blue when the latest analysis answered
224
+ lookups from it, gray when unused or empty), the **Cache TTL** of each cache and **Clear
225
+ Security Cache**, which empties all three caches (`nvd_cache`, `cve_metadata_cache`,
226
+ `amazon_updateinfo_cache`) and the in-memory lookup state. TTLs are entered as hours (`24h`)
227
+ or days (`30d`), between 1 hour and 365 days, and stored in the database. Changing a TTL
228
+ deletes nothing; expired entries are refreshed on their next lookup. The same badges appear
229
+ next to the NVD API key badge on the Pre-Patch Analysis pages and link to this section.
230
+ - **Local Patch Download Directory**: template, default `/tmp/${server_name}`; must contain
231
+ `${server_name}` and resolve to a safe absolute path.
232
+ - **Logging**: the **Log directory** (default: the per-user log directory from platformdirs, or
233
+ `$EC2PATCHER_LOG_DIR`) and the current log file path. The directory is created if needed and
234
+ must be writable, or it is not saved. Logs rotate at 5 MB, keeping 5 old files.
235
+ - **Security Data**: Canonical request settings (timeout, pacing, retries, proxy).
236
+ - **Reset Database**: type `RESET` to remove all stored data (refused while an analysis or
237
+ patch is running).
238
+
239
+ ### Caching and NVD API key
240
+
241
+ All caches live in the application SQLite database; failed lookups are never cached and a
242
+ cache error never fails a lookup. The TTLs below are the defaults (**Settings → Caches**; a
243
+ saved Canonical TTL also overrides `EC2PATCHER_CANONICAL_CACHE_TTL_HOURS`). Each analysis run
244
+ records, per cache, how many lookups came from the cache and how many were live (*NVD: x from
245
+ cache / y live*, shown on the run page and the server report; re-analyses and retries add to
246
+ it).
247
+
248
+ - **Canonical** (`cve_metadata_cache`): reused for 24 h (1 h while a release is under
249
+ investigation); older entries are refreshed and used as a marked fallback when ubuntu.com is
250
+ unreachable.
251
+ - **Amazon updateinfo** (`amazon_updateinfo_cache`): per repository, reused for 24 h.
252
+ - **NVD** (`nvd_cache`): raw CVSS metrics per CVE, reused for **30 days**; older entries are
253
+ refreshed and used as a *stale cache* fallback when NVD is unreachable. Without any data the
254
+ severity is **Unknown**. (The old on-disk NVD cache under `~/.cache/ec2patcher/nvd/` is no
255
+ longer read or written and can be deleted.)
256
+
257
+ NVD requests are spaced 6 s apart (public limit). With an API key they are spaced 0.6 s apart.
258
+ Enter the key on **Settings → NVD API Key** (stored encrypted; shown only as its last 4
259
+ characters, with **Replace** / **Clear**), or provide it through the environment — never in a
260
+ file in this repository:
261
+
262
+ ```bash
263
+ export NVD_API_KEY=... # your own key; it is sent only in the apiKey request header
264
+ ```
265
+ Request a free key at https://nvd.nist.gov/developers/request-an-api-key.
266
+
267
+ A key saved in Settings **overrides** `NVD_API_KEY`; Clear falls back to the environment. The
268
+ key is never logged, exported or shown in full. Saving a key, and the **Test key** button, send
269
+ one keyed request to NVD right away (cached CVSS lookups send none). The result and its time are
270
+ kept in the settings table (never the key itself) and updated by every keyed lookup, so they
271
+ survive restarts. The Pre-Patch Analysis pages show only the state and source: *NVD API key: not
272
+ set*, *unknown* (not checked yet, or NVD could not be reached), *valid (checked <time>)* (green,
273
+ HTTP 200) or *NVD API key rejected* (red, HTTP 403, or HTTP 404 with NVD's invalid-apiKey
274
+ message), each followed by *from Settings (DB)* or *from NVD_API_KEY env var* whenever a key is
275
+ configured. For *unknown* the reason (HTTP status or network error, or *not checked yet*) is in
276
+ the badge tooltip and the Settings notice; HTTP 429 / 5xx is retried once (after Retry-After)
277
+ before a check ends as unknown. At start the app checks the key again in the background if its
278
+ last check is unknown or older than 24 h. A rejected key never
279
+ marks a CVE as unknown to NVD and is never cached. If the saved key cannot be decrypted the badge
280
+ is red and asks to enter it again in Settings (no key is sent until then).
281
+
282
+ ### Secrets at rest
283
+
284
+ Server passwords and the Settings NVD API key are encrypted with Fernet (`cryptography`). The
285
+ key file is `~/.config/ec2patcher/secret.key` (or `$EC2PATCHER_CONFIG_DIR/secret.key`), created
286
+ with mode `0600` on first use and never stored in the database or the repository. If it is
287
+ missing or does not match, nothing fails silently: the Servers / Settings pages, Test SSH,
288
+ analysis and patching show a clear error asking you to enter the secret again (it is then
289
+ encrypted with the current key). Back up the key file together with the database if you move
290
+ them to another machine.
291
+
292
+ ## Requirements (workstation)
293
+
294
+ - Python 3.10+
295
+ - OpenSSH client (`ssh`, `scp`) on `PATH`
296
+ - APT (`apt-get`, `apt-cache`) and `/usr/share/keyrings/ubuntu-archive-keyring.gpg`
297
+ - `sshpass` only for servers with password login (`sudo apt install sshpass`)
298
+ - Internet access to the Ubuntu archive (`archive.ubuntu.com` / `security.ubuntu.com`, or
299
+ `ports.ubuntu.com` for arm64), `ubuntu.com`, `cdn.amazonlinux.com` (Amazon Linux servers)
300
+ and `services.nvd.nist.gov` (optional: without it severities are Unknown).
301
+ `HTTPS_PROXY` / `NO_PROXY` are honoured.
302
+
303
+ ## Install
304
+
305
+ With [pipx](https://pipx.pypa.io/) (recommended; installs the `ec2patcher` command in its own
306
+ virtual environment):
307
+
308
+ ```bash
309
+ pipx install ec2patcher # from PyPI
310
+ pipx install git+https://github.com/Amiri83/EC2Patcher.git # latest main from GitHub
311
+ pipx upgrade ec2patcher # later updates
312
+ ```
313
+
314
+ From a checkout, for development:
315
+
316
+ ```bash
317
+ python3 -m venv .venv
318
+ .venv/bin/pip install -e ".[dev]" # or without [dev] if you don't run tests
319
+ ```
320
+
321
+ ## Run
322
+
323
+ ```bash
324
+ ec2patcher # from a checkout: .venv/bin/ec2patcher or .venv/bin/python -m ec2patcher
325
+ ec2patcher --version
326
+ ```
327
+
328
+ Open <http://127.0.0.1:8080/> (a browser opens automatically when a desktop session is
329
+ available). Stop with **Shutdown App** in the sidebar or Ctrl+C.
330
+
331
+ | Option | Default | Description |
332
+ |---|---|---|
333
+ | `--host` | `127.0.0.1` | Bind address (no authentication: keep it local) |
334
+ | `--port` | `8080` | Port |
335
+ | `--data-dir` | per-user data dir | Database and log location (also `$EC2PATCHER_DATA_DIR`) |
336
+ | `--apt-state-dir` | `<data dir>/apt` | Private APT state (also `$EC2PATCHER_APT_STATE_DIR`) |
337
+ | `--apt-max-age-hours` | `6` | Refresh the private APT lists when older; `0` = every run (also `$EC2PATCHER_APT_MAX_AGE_HOURS`) |
338
+ | `--no-browser` | off | Do not open a browser |
339
+ | `--version` | | Print the version and exit |
340
+
341
+ Other environment variables: `NVD_API_KEY` (a key saved in Settings overrides it),
342
+ `EC2PATCHER_CONFIG_DIR` (location of the secret key file), `EC2PATCHER_LOG_DIR` (default log directory),
343
+ `EC2PATCHER_CANONICAL_TIMEOUT_SECONDS` (20),
344
+ `EC2PATCHER_CANONICAL_CACHE_TTL_HOURS` (24), `EC2PATCHER_CANONICAL_BREAKER_THRESHOLD` (3).
345
+
346
+ ## Data location (Linux defaults)
347
+
348
+ | What | Path |
349
+ |---|---|
350
+ | SQLite database (incl. Canonical and NVD caches) | `~/.local/share/ec2patcher/ec2patcher.db` |
351
+ | Secret key file (encrypts stored passwords / NVD key; mode 0600) | `~/.config/ec2patcher/secret.key` |
352
+ | Log file (rotating, 5 × 5 MB; directory configurable in Settings) | `~/.local/state/ec2patcher/log/ec2patcher.log` |
353
+ | Private APT state | `~/.local/share/ec2patcher/apt/<codename>-<arch>/` |
354
+
355
+ The schema is created and migrated automatically on startup (`PRAGMA user_version`); existing
356
+ data is kept. Run one EC2Patcher process per database.
357
+
358
+ ## Test and lint
359
+
360
+ ```bash
361
+ .venv/bin/python -m pytest -q
362
+ .venv/bin/ruff check .
363
+ .venv/bin/ruff format --check .
364
+ ```
365
+
366
+ The tests mock `ssh`, `scp`, downloads, Canonical, NVD and the local APT backend; they never
367
+ need a real server, PEM key, internet access or package installs.
368
+
369
+ ### Integration tests (local containers, no AWS)
370
+
371
+ `scripts/test-targets/` holds two SSH targets with key-only auth on localhost: Ubuntu 24.04
372
+ (user `ubuntu`, port 2201) and Amazon Linux 2023 (user `ec2-user`, port 2202). The key pair is
373
+ generated at runtime into `scripts/test-targets/.keys/` (git-ignored). Docker runs via `sudo`
374
+ (set `DOCKER=docker` to change that).
375
+
376
+ ```bash
377
+ sh scripts/test-targets/up.sh # build + start, generate the key on first use
378
+ .venv/bin/python -m pytest -q -m integration # deselected by default
379
+ sh scripts/test-targets/down.sh
380
+ ```
381
+
382
+ Overrides: `EC2P_IT_HOST`, `EC2P_IT_UBUNTU_PORT`, `EC2P_IT_AMAZON_PORT`, `EC2P_IT_KEY`.
383
+
384
+ GitHub Actions (`.github/workflows/tests.yml`) runs ruff, the test suite (without integration
385
+ tests) and a package build check on every pull request.
386
+
387
+ ## Release
388
+
389
+ The version lives only in `pyproject.toml` (`ec2patcher --version` reads the installed
390
+ metadata). To release:
391
+
392
+ 1. Bump `version` in `pyproject.toml` and merge it to `main`.
393
+ 2. Check locally: `.venv/bin/python -m build && .venv/bin/twine check dist/*`.
394
+ 3. Push a tag `v<version>` (e.g. `v1.0.0`). `.github/workflows/publish.yml` checks that the
395
+ tag matches the version, runs the tests, builds the sdist + wheel and publishes them to
396
+ PyPI with **Trusted Publishing** (OIDC, environment `pypi`): no API token is stored in the
397
+ repository or in GitHub secrets. The trusted publisher must be configured once on PyPI for
398
+ this repository and workflow.
399
+
400
+ ## Security notes
401
+
402
+ - Binds to `127.0.0.1` by default; requests with a non-local `Host` header and cross-site POSTs
403
+ are rejected (CSRF / DNS rebinding).
404
+ - Subprocesses never use `shell=True`; the remote analysis command is fixed; package names,
405
+ versions and paths are validated against strict patterns.
406
+ - Analysis uses no sudo and changes nothing. Patching uses `sudo -n` only for the sudo check,
407
+ the `apt-get` simulation/install of the explicit `.deb` files and the optional reboot.
408
+ - PEM contents are never read; the NVD API key and SSH passwords are stored only encrypted
409
+ (key file outside the database, mode 0600) and never appear in HTML, logs, exports, error
410
+ messages or command lines (passwords reach sshpass via `SSHPASS` only). Unexpected errors
411
+ show a generic message in the GUI; details go to the log.
412
+ - The Fernet key file lives in the config directory, never in the package, the database or
413
+ this repository; the built wheel and sdist contain no secrets, keys or server data.
414
+
415
+ ## License
416
+
417
+ MIT, see [LICENSE](https://github.com/Amiri83/EC2Patcher/blob/main/LICENSE).