plexavo 0.1.2__tar.gz → 0.2.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.
Files changed (48) hide show
  1. {plexavo-0.1.2 → plexavo-0.2.0}/PKG-INFO +76 -50
  2. {plexavo-0.1.2 → plexavo-0.2.0}/README.md +75 -49
  3. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/__init__.py +1 -1
  4. plexavo-0.2.0/plexavo/aws_profile_setup.py +66 -0
  5. plexavo-0.2.0/plexavo/cli.py +303 -0
  6. plexavo-0.2.0/plexavo/interactive.py +300 -0
  7. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/ai_narration.py +25 -15
  8. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo.egg-info/PKG-INFO +76 -50
  9. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo.egg-info/SOURCES.txt +2 -0
  10. {plexavo-0.1.2 → plexavo-0.2.0}/pyproject.toml +1 -1
  11. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_ai_narration_offline.py +22 -6
  12. plexavo-0.1.2/plexavo/cli.py +0 -220
  13. {plexavo-0.1.2 → plexavo-0.2.0}/LICENSE +0 -0
  14. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/auth.py +0 -0
  15. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/checks/__init__.py +0 -0
  16. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/checks/encryption.py +0 -0
  17. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/checks/iam.py +0 -0
  18. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/checks/iam_hygiene.py +0 -0
  19. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/checks/logging.py +0 -0
  20. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/checks/network.py +0 -0
  21. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/checks/storage.py +0 -0
  22. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/checks/usage.py +0 -0
  23. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/findings.py +0 -0
  24. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/principals.py +0 -0
  25. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/__init__.py +0 -0
  26. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/fonts/DejaVuSans-Bold.ttf +0 -0
  27. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/fonts/DejaVuSans-BoldOblique.ttf +0 -0
  28. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/fonts/DejaVuSans-Oblique.ttf +0 -0
  29. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/fonts/DejaVuSans.ttf +0 -0
  30. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/fonts/GEIST-FONT-LICENSE.txt +0 -0
  31. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/html_report.py +0 -0
  32. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/pdf.py +0 -0
  33. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/report/templates/report.html.j2 +0 -0
  34. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo/scoring.py +0 -0
  35. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo.egg-info/dependency_links.txt +0 -0
  36. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo.egg-info/entry_points.txt +0 -0
  37. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo.egg-info/requires.txt +0 -0
  38. {plexavo-0.1.2 → plexavo-0.2.0}/plexavo.egg-info/top_level.txt +0 -0
  39. {plexavo-0.1.2 → plexavo-0.2.0}/setup.cfg +0 -0
  40. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_encryption_offline.py +0 -0
  41. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_iam_hygiene_offline.py +0 -0
  42. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_iam_offline.py +0 -0
  43. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_logging_offline.py +0 -0
  44. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_network_offline.py +0 -0
  45. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_report_offline.py +0 -0
  46. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_scoring.py +0 -0
  47. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_storage_offline.py +0 -0
  48. {plexavo-0.1.2 → plexavo-0.2.0}/tests/test_usage_offline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexavo
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Open-source AWS misconfiguration scanner — runs with your own local AWS credentials, nothing sent to anyone else.
5
5
  Author: Kavee
6
6
  License: AGPL-3.0-or-later
@@ -75,7 +75,9 @@ Plus `scoring.py` (the 0-100 score) and `plexavo/report/ai_narration.py`
75
75
 
76
76
  Illustrative example (a public S3 bucket, an unencrypted volume, a role that's
77
77
  never been assumed) — this is what a scan actually surfaces, including a
78
- finding walked through `--explain`:
78
+ finding's free template remediation (shown by default, no `--explain`
79
+ needed; `--explain` would replace this panel with a full AI narrative
80
+ instead):
79
81
 
80
82
  ```
81
83
  Your AWS Security Score: 74/100 (Good)
@@ -125,37 +127,27 @@ scoped by a Condition block, or a CloudTrail lookup that hit its page cap on
125
127
 
126
128
  ## Quick start
127
129
 
128
- Everything below uses a **virtual environment (venv)** — a self-contained
129
- folder holding Plexavo and its dependencies, completely separate from
130
- your system Python. This is the one install path tested end to end with
131
- zero friction, so it's the one documented here for now (other methods
132
- are planned, but aren't ready to recommend yet).
130
+ **Recommended: [uv](https://docs.astral.sh/uv/).** It installs Plexavo into
131
+ its own isolated environment automatically — no venv to create, activate,
132
+ or remember to reactivate in every new terminal.
133
133
 
134
- ### 1. Create the environment (once)
134
+ ### 1. Install uv (if you don't already have it)
135
135
 
136
136
  ```bash
137
- python -m venv plexavo-env
137
+ curl -LsSf https://astral.sh/uv/install.sh | sh # Mac/Linux
138
+ powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
138
139
  ```
139
140
 
140
- You only run this once, ever — not every time you want to use Plexavo.
141
- It creates a `plexavo-env` folder in whatever directory you're in.
141
+ (Full options: [uv installation docs](https://docs.astral.sh/uv/getting-started/installation/).)
142
142
 
143
- ### 2. Activate it
143
+ ### 2. Install Plexavo
144
144
 
145
145
  ```bash
146
- plexavo-env\Scripts\activate # Windows
147
- source plexavo-env/bin/activate # Mac/Linux
146
+ uv tool install plexavo
148
147
  ```
149
148
 
150
- Your prompt should now show `(plexavo-env)` at the start of the line —
151
- that's your confirmation it's active. You'll do this step **every time
152
- you open a new terminal**, but you never repeat step 1.
153
-
154
- ### 3. Install
155
-
156
- ```bash
157
- pip install plexavo
158
- ```
149
+ That's it — `plexavo` is now on your PATH in every terminal, until you
150
+ uninstall it. No activate step, ever.
159
151
 
160
152
  **Have an Anthropic API key and want AI-narrated explanations** (see
161
153
  [Cost](#cost) — it's optional and costs a few cents per scan, not
@@ -163,67 +155,97 @@ free)? Install with the `[ai]` extra instead — same package, just with
163
155
  the `anthropic` library included:
164
156
 
165
157
  ```bash
166
- pip install "plexavo[ai]"
158
+ uv tool install "plexavo[ai]"
167
159
  ```
168
160
 
169
161
  No key yet, or not sure? Skip it for now — the plain install is
170
- genuinely complete on its own. You can run `pip install "plexavo[ai]"`
171
- later in this same environment whenever you decide you want it; nothing
172
- needs reinstalling or redone.
162
+ genuinely complete on its own. Add it later with:
163
+
164
+ ```bash
165
+ uv tool install --reinstall "plexavo[ai]"
166
+ ```
167
+
168
+ Just want to try it once without installing anything?
173
169
 
174
- ### 4. Run a scan
170
+ ```bash
171
+ uvx plexavo scan --profile my-aws-profile
172
+ ```
173
+
174
+ Updating or removing later:
175
+
176
+ ```bash
177
+ uv tool upgrade plexavo
178
+ uv tool uninstall plexavo
179
+ ```
180
+
181
+ ### 3. Run a scan
175
182
 
176
183
  ```bash
177
184
  plexavo scan --profile my-aws-profile --report-html report.html
178
185
  ```
179
186
 
180
187
  No `--profile`? It uses your default profile / environment variables,
181
- same resolution order as the AWS CLI. No AI, no API key, no cost — this
182
- alone is a complete, genuinely useful scan.
188
+ same resolution order as the AWS CLI. No AI, no API key, no cost — and
189
+ findings still come with free Next Step / Full Fix Detail guidance
190
+ wherever a template exists (10 common check types); this alone is a
191
+ complete, genuinely useful scan.
183
192
 
184
- Want plain-English explanations for each finding too (needs the
185
- `[ai]` install above):
193
+ Want a full AI-written explanation for *every* finding instead (needs
194
+ the `[ai]` install above)?
186
195
 
187
196
  ```bash
188
197
  export ANTHROPIC_API_KEY="sk-ant-..." # your own key, your own account
189
198
  plexavo scan --profile my-aws-profile --explain --report-html report.html --report-pdf report.pdf
190
199
  ```
191
200
 
192
- - Drop `--explain` for a fast, free scan with raw technical findings only.
201
+ - Drop `--explain` and findings still get free template remediation where
202
+ available (no API key needed) and raw technical detail otherwise.
203
+ `--explain` replaces that with a live AI narrative for every finding,
204
+ including the templated ones.
193
205
  - Drop `--report-html`/`--report-pdf` to just see the console table.
194
- - `--explain-limit N` (default 25) caps how many findings get AI narration
195
- in one run, as a safety rail against unexpectedly large real scans.
206
+ - `--explain-limit N` (default 25) caps how many findings get a live AI
207
+ call when `--explain` is passed, as a safety rail against unexpectedly
208
+ large real scans — it doesn't limit the free template remediation.
196
209
  - No `ANTHROPIC_API_KEY` set, or a call fails for any reason (invalid key,
197
210
  rate limit, network issue)? The scan and report are completely
198
- unaffected — you get raw finding detail instead of narration for that
199
- finding, not an error. See [Cost](#cost).
211
+ unaffected — that finding falls back to template/raw detail instead of
212
+ an AI narrative, not an error. See [Cost](#cost).
213
+
214
+ ### Alternative: pipx
200
215
 
201
- ### 5. When you're done for now
216
+ Already use [pipx](https://pipx.pypa.io/)? It works exactly the same way —
217
+ its own isolated environment, one global command, no venv:
202
218
 
203
219
  ```bash
204
- deactivate
220
+ pipx install plexavo
221
+ pipx install "plexavo[ai]" # with AI-narrated explanations
205
222
  ```
206
223
 
207
- This just exits the environment — nothing gets deleted or uninstalled.
208
- Your prompt goes back to normal.
209
-
210
- ### 6. Coming back later
224
+ ### Alternative: pip + venv
211
225
 
212
- Skip straight to activating again — no need to repeat steps 1 or 3:
226
+ Prefer to manage the environment yourself, or on a system without uv/pipx?
227
+ Modern Python (PEP 668) blocks a plain `pip install` outside a venv on
228
+ many systems, so create one first:
213
229
 
214
230
  ```bash
231
+ python -m venv plexavo-env
215
232
  plexavo-env\Scripts\activate # Windows
216
233
  source plexavo-env/bin/activate # Mac/Linux
234
+
235
+ pip install plexavo
236
+ pip install "plexavo[ai]" # with AI-narrated explanations
217
237
  ```
218
238
 
219
- `plexavo` is immediately available again, exactly as you left it.
239
+ You'll need to reactivate this venv (`plexavo-env\Scripts\activate` /
240
+ `source plexavo-env/bin/activate`) every time you open a new terminal —
241
+ `deactivate` exits it without uninstalling anything.
220
242
 
221
243
  ### From source (for contributing, or trying an unreleased change)
222
244
 
223
245
  ```bash
224
246
  git clone https://github.com/plexavo/plexavo.git
225
247
  cd plexavo
226
- pip install -e .
248
+ uv pip install -e . # or: pip install -e . (inside a venv)
227
249
  ```
228
250
 
229
251
  ## Project structure
@@ -275,10 +297,14 @@ docs/
275
297
  ## Cost
276
298
 
277
299
  Detection is free (pure Python/boto3), always, regardless of anything
278
- else in this section. AI narration only runs with `--explain`, and even
279
- then: 10 of the most common, narratively-generic finding types are
280
- hand-written templates with zero API cost; only genuinely account-specific
281
- findings call Claude, typically $0.01-0.02 per finding depending on answer
300
+ else in this section. Every scan also gets free Next Step / Full Fix
301
+ Detail remediation wherever one of 10 hand-written templates matches the
302
+ finding type — no flag, no API key, zero cost, on by default.
303
+
304
+ Live AI only runs with `--explain`, and when it does it's used for
305
+ *every* finding, including the 10 templated ones (a deliberate choice —
306
+ "AI narration on" always means fully AI-written content, not a mix of
307
+ template and AI), typically $0.01-0.02 per finding depending on answer
282
308
  length. A full scan with `--explain` on a real account is usually well
283
309
  under a dollar. This is **your own** `ANTHROPIC_API_KEY`, in **your own**
284
310
  Anthropic account — this project never sees your key, never embeds one of
@@ -46,7 +46,9 @@ Plus `scoring.py` (the 0-100 score) and `plexavo/report/ai_narration.py`
46
46
 
47
47
  Illustrative example (a public S3 bucket, an unencrypted volume, a role that's
48
48
  never been assumed) — this is what a scan actually surfaces, including a
49
- finding walked through `--explain`:
49
+ finding's free template remediation (shown by default, no `--explain`
50
+ needed; `--explain` would replace this panel with a full AI narrative
51
+ instead):
50
52
 
51
53
  ```
52
54
  Your AWS Security Score: 74/100 (Good)
@@ -96,37 +98,27 @@ scoped by a Condition block, or a CloudTrail lookup that hit its page cap on
96
98
 
97
99
  ## Quick start
98
100
 
99
- Everything below uses a **virtual environment (venv)** — a self-contained
100
- folder holding Plexavo and its dependencies, completely separate from
101
- your system Python. This is the one install path tested end to end with
102
- zero friction, so it's the one documented here for now (other methods
103
- are planned, but aren't ready to recommend yet).
101
+ **Recommended: [uv](https://docs.astral.sh/uv/).** It installs Plexavo into
102
+ its own isolated environment automatically — no venv to create, activate,
103
+ or remember to reactivate in every new terminal.
104
104
 
105
- ### 1. Create the environment (once)
105
+ ### 1. Install uv (if you don't already have it)
106
106
 
107
107
  ```bash
108
- python -m venv plexavo-env
108
+ curl -LsSf https://astral.sh/uv/install.sh | sh # Mac/Linux
109
+ powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
109
110
  ```
110
111
 
111
- You only run this once, ever — not every time you want to use Plexavo.
112
- It creates a `plexavo-env` folder in whatever directory you're in.
112
+ (Full options: [uv installation docs](https://docs.astral.sh/uv/getting-started/installation/).)
113
113
 
114
- ### 2. Activate it
114
+ ### 2. Install Plexavo
115
115
 
116
116
  ```bash
117
- plexavo-env\Scripts\activate # Windows
118
- source plexavo-env/bin/activate # Mac/Linux
117
+ uv tool install plexavo
119
118
  ```
120
119
 
121
- Your prompt should now show `(plexavo-env)` at the start of the line —
122
- that's your confirmation it's active. You'll do this step **every time
123
- you open a new terminal**, but you never repeat step 1.
124
-
125
- ### 3. Install
126
-
127
- ```bash
128
- pip install plexavo
129
- ```
120
+ That's it — `plexavo` is now on your PATH in every terminal, until you
121
+ uninstall it. No activate step, ever.
130
122
 
131
123
  **Have an Anthropic API key and want AI-narrated explanations** (see
132
124
  [Cost](#cost) — it's optional and costs a few cents per scan, not
@@ -134,67 +126,97 @@ free)? Install with the `[ai]` extra instead — same package, just with
134
126
  the `anthropic` library included:
135
127
 
136
128
  ```bash
137
- pip install "plexavo[ai]"
129
+ uv tool install "plexavo[ai]"
138
130
  ```
139
131
 
140
132
  No key yet, or not sure? Skip it for now — the plain install is
141
- genuinely complete on its own. You can run `pip install "plexavo[ai]"`
142
- later in this same environment whenever you decide you want it; nothing
143
- needs reinstalling or redone.
133
+ genuinely complete on its own. Add it later with:
134
+
135
+ ```bash
136
+ uv tool install --reinstall "plexavo[ai]"
137
+ ```
138
+
139
+ Just want to try it once without installing anything?
144
140
 
145
- ### 4. Run a scan
141
+ ```bash
142
+ uvx plexavo scan --profile my-aws-profile
143
+ ```
144
+
145
+ Updating or removing later:
146
+
147
+ ```bash
148
+ uv tool upgrade plexavo
149
+ uv tool uninstall plexavo
150
+ ```
151
+
152
+ ### 3. Run a scan
146
153
 
147
154
  ```bash
148
155
  plexavo scan --profile my-aws-profile --report-html report.html
149
156
  ```
150
157
 
151
158
  No `--profile`? It uses your default profile / environment variables,
152
- same resolution order as the AWS CLI. No AI, no API key, no cost — this
153
- alone is a complete, genuinely useful scan.
159
+ same resolution order as the AWS CLI. No AI, no API key, no cost — and
160
+ findings still come with free Next Step / Full Fix Detail guidance
161
+ wherever a template exists (10 common check types); this alone is a
162
+ complete, genuinely useful scan.
154
163
 
155
- Want plain-English explanations for each finding too (needs the
156
- `[ai]` install above):
164
+ Want a full AI-written explanation for *every* finding instead (needs
165
+ the `[ai]` install above)?
157
166
 
158
167
  ```bash
159
168
  export ANTHROPIC_API_KEY="sk-ant-..." # your own key, your own account
160
169
  plexavo scan --profile my-aws-profile --explain --report-html report.html --report-pdf report.pdf
161
170
  ```
162
171
 
163
- - Drop `--explain` for a fast, free scan with raw technical findings only.
172
+ - Drop `--explain` and findings still get free template remediation where
173
+ available (no API key needed) and raw technical detail otherwise.
174
+ `--explain` replaces that with a live AI narrative for every finding,
175
+ including the templated ones.
164
176
  - Drop `--report-html`/`--report-pdf` to just see the console table.
165
- - `--explain-limit N` (default 25) caps how many findings get AI narration
166
- in one run, as a safety rail against unexpectedly large real scans.
177
+ - `--explain-limit N` (default 25) caps how many findings get a live AI
178
+ call when `--explain` is passed, as a safety rail against unexpectedly
179
+ large real scans — it doesn't limit the free template remediation.
167
180
  - No `ANTHROPIC_API_KEY` set, or a call fails for any reason (invalid key,
168
181
  rate limit, network issue)? The scan and report are completely
169
- unaffected — you get raw finding detail instead of narration for that
170
- finding, not an error. See [Cost](#cost).
182
+ unaffected — that finding falls back to template/raw detail instead of
183
+ an AI narrative, not an error. See [Cost](#cost).
184
+
185
+ ### Alternative: pipx
171
186
 
172
- ### 5. When you're done for now
187
+ Already use [pipx](https://pipx.pypa.io/)? It works exactly the same way —
188
+ its own isolated environment, one global command, no venv:
173
189
 
174
190
  ```bash
175
- deactivate
191
+ pipx install plexavo
192
+ pipx install "plexavo[ai]" # with AI-narrated explanations
176
193
  ```
177
194
 
178
- This just exits the environment — nothing gets deleted or uninstalled.
179
- Your prompt goes back to normal.
180
-
181
- ### 6. Coming back later
195
+ ### Alternative: pip + venv
182
196
 
183
- Skip straight to activating again — no need to repeat steps 1 or 3:
197
+ Prefer to manage the environment yourself, or on a system without uv/pipx?
198
+ Modern Python (PEP 668) blocks a plain `pip install` outside a venv on
199
+ many systems, so create one first:
184
200
 
185
201
  ```bash
202
+ python -m venv plexavo-env
186
203
  plexavo-env\Scripts\activate # Windows
187
204
  source plexavo-env/bin/activate # Mac/Linux
205
+
206
+ pip install plexavo
207
+ pip install "plexavo[ai]" # with AI-narrated explanations
188
208
  ```
189
209
 
190
- `plexavo` is immediately available again, exactly as you left it.
210
+ You'll need to reactivate this venv (`plexavo-env\Scripts\activate` /
211
+ `source plexavo-env/bin/activate`) every time you open a new terminal —
212
+ `deactivate` exits it without uninstalling anything.
191
213
 
192
214
  ### From source (for contributing, or trying an unreleased change)
193
215
 
194
216
  ```bash
195
217
  git clone https://github.com/plexavo/plexavo.git
196
218
  cd plexavo
197
- pip install -e .
219
+ uv pip install -e . # or: pip install -e . (inside a venv)
198
220
  ```
199
221
 
200
222
  ## Project structure
@@ -246,10 +268,14 @@ docs/
246
268
  ## Cost
247
269
 
248
270
  Detection is free (pure Python/boto3), always, regardless of anything
249
- else in this section. AI narration only runs with `--explain`, and even
250
- then: 10 of the most common, narratively-generic finding types are
251
- hand-written templates with zero API cost; only genuinely account-specific
252
- findings call Claude, typically $0.01-0.02 per finding depending on answer
271
+ else in this section. Every scan also gets free Next Step / Full Fix
272
+ Detail remediation wherever one of 10 hand-written templates matches the
273
+ finding type — no flag, no API key, zero cost, on by default.
274
+
275
+ Live AI only runs with `--explain`, and when it does it's used for
276
+ *every* finding, including the 10 templated ones (a deliberate choice —
277
+ "AI narration on" always means fully AI-written content, not a mix of
278
+ template and AI), typically $0.01-0.02 per finding depending on answer
253
279
  length. A full scan with `--explain` on a real account is usually well
254
280
  under a dollar. This is **your own** `ANTHROPIC_API_KEY`, in **your own**
255
281
  Anthropic account — this project never sees your key, never embeds one of
@@ -4,4 +4,4 @@ Runs entirely with your own local AWS credentials. Nothing is sent to
4
4
  anyone else. See README.md for usage, or `plexavo scan --help`.
5
5
  """
6
6
 
7
- __version__ = "0.1.2"
7
+ __version__ = "0.2.0"
@@ -0,0 +1,66 @@
1
+ """plexavo/aws_profile_setup.py — writes a new named AWS profile to disk.
2
+
3
+ Used by the interactive "+ Configure new profile" flow. Writes in the
4
+ same layout `aws configure --profile NAME` itself produces:
5
+ ~/.aws/credentials -> [NAME] aws_access_key_id / aws_secret_access_key
6
+ ~/.aws/config -> [profile NAME] region
7
+ (bare [default] for the "default" profile)
8
+
9
+ Only the target profile's section is added/updated — every other
10
+ section in either file is read back untouched via configparser's
11
+ read-modify-write round trip. One known limitation: configparser does
12
+ not preserve comment lines on rewrite, so hand-written comments in
13
+ these files (uncommon, since they're normally CLI/tool-managed) would
14
+ be dropped. Actual key/value data for every other profile is preserved
15
+ exactly.
16
+
17
+ Respects AWS_SHARED_CREDENTIALS_FILE / AWS_CONFIG_FILE if set, same as
18
+ the AWS CLI and boto3 itself.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import configparser
24
+ import os
25
+ from pathlib import Path
26
+
27
+
28
+ def credentials_path() -> Path:
29
+ return Path(os.environ.get("AWS_SHARED_CREDENTIALS_FILE", "~/.aws/credentials")).expanduser()
30
+
31
+
32
+ def config_path() -> Path:
33
+ return Path(os.environ.get("AWS_CONFIG_FILE", "~/.aws/config")).expanduser()
34
+
35
+
36
+ def _write_ini(path: Path, section: str, values: dict[str, str]) -> None:
37
+ path.parent.mkdir(parents=True, exist_ok=True)
38
+ parser = configparser.ConfigParser()
39
+ if path.exists():
40
+ parser.read(path)
41
+ if not parser.has_section(section):
42
+ parser.add_section(section)
43
+ for key, value in values.items():
44
+ parser.set(section, key, value)
45
+ with open(path, "w", encoding="utf-8") as f:
46
+ parser.write(f)
47
+ try:
48
+ os.chmod(path, 0o600)
49
+ except OSError:
50
+ # Windows doesn't support POSIX file-mode bits the same way —
51
+ # best-effort only, same as the AWS CLI itself does here.
52
+ pass
53
+
54
+
55
+ def write_profile(name: str, access_key_id: str, secret_access_key: str, region: str) -> None:
56
+ """Write/overwrite one named profile's credentials and region.
57
+
58
+ Never logs or returns the secret — callers must not print it either.
59
+ """
60
+ _write_ini(credentials_path(), name, {
61
+ "aws_access_key_id": access_key_id,
62
+ "aws_secret_access_key": secret_access_key,
63
+ })
64
+
65
+ config_section = "default" if name == "default" else f"profile {name}"
66
+ _write_ini(config_path(), config_section, {"region": region})