From 979baa9f4958ff321f54fb2335d84975c30bdab8 Mon Sep 17 00:00:00 2001 From: Ben Fellows Date: Tue, 4 Aug 2026 07:54:21 +1200 Subject: [PATCH] Pick MkDocs + Material for docs.teemops.com, and write the priority-1 pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit D-10 deferred the renderer choice until section count and search actually bit; that trigger fired against the eight-section, 34-page IA. Records the choice as D-13: MkDocs with the Material theme, built with `mkdocs build` and deployed to Cloudflare Pages from `site/`. Chosen over Docsify because it renders real static HTML per page rather than client-side, which matters for the evaluator audience arriving via search or a vendor-review link rather than already inside the app — and it adds no new language to the repo, since python3 already backs the link-checker and SVG-validator scripts sitting next to it in user-docs/README.md. `.github/workflows/tests.yml` gained a `docs` job: `mkdocs build --strict`, the existing relative-link checker, and the SVG validator, so a broken nav entry or a dead cross-reference fails CI instead of shipping to docs.teemops.com. Writes the six pages the IA's inventory marks priority 1 and this session hadn't yet: what TOPS is, install, connect your first AWS account, run your first scan, the security model, and reporting a vulnerability — closing the whole evaluator-plus-first-run path. Sourced from the actual UI copy (NewScanModal.vue, Scans/Index.vue), the roadmap's decisions log (D-1, D-2, D-9), docs/PROGRESS.md, and SECURITY.md — deliberately not from docs/architecture.md, which is stale and still describes the pre-pivot hosted-SaaS design. Nothing here documents PCI, roles/permissions, MFA, report export or scheduled scans, per docs/PROGRESS.md. Connecting the Cloudflare Pages project itself and trimming README.md's setup section are still open — the former needs dashboard access this commit can't have, the latter needs an install page to link to, which now exists. Signed-off-by: Ben Fellows Co-authored-by: Cursor --- .github/workflows/tests.yml | 55 ++++++++++++ .gitignore | 3 + design/docs-site/information-architecture.md | 62 ++++++++----- docs/roadmap.md | 13 ++- mkdocs.yml | 64 +++++++++++++ requirements.txt | 1 + user-docs/README.md | 23 +++-- user-docs/assets/extra.css | 8 ++ user-docs/index.md | 36 ++++++++ .../security/reporting-a-vulnerability.md | 49 ++++++++++ user-docs/security/the-security-model.md | 86 ++++++++++++++++++ .../connect-your-first-aws-account.md | 90 +++++++++++++++++++ user-docs/start-here/install.md | 52 +++++++++++ user-docs/start-here/run-your-first-scan.md | 70 +++++++++++++++ user-docs/start-here/what-tops-is.md | 47 ++++++++++ 15 files changed, 628 insertions(+), 31 deletions(-) create mode 100644 mkdocs.yml create mode 100644 requirements.txt create mode 100644 user-docs/assets/extra.css create mode 100644 user-docs/index.md create mode 100644 user-docs/security/reporting-a-vulnerability.md create mode 100644 user-docs/security/the-security-model.md create mode 100644 user-docs/start-here/connect-your-first-aws-account.md create mode 100644 user-docs/start-here/install.md create mode 100644 user-docs/start-here/run-your-first-scan.md create mode 100644 user-docs/start-here/what-tops-is.md diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 425f60dd..e05048c5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -167,3 +167,58 @@ jobs: # is what keeps them there. - name: Audit dependencies run: npm audit --audit-level=critical + + docs: + name: Docs site build + runs-on: ubuntu-latest + timeout-minutes: 5 + # Repo root, not app/ — mkdocs.yml and user-docs/ both live there. + defaults: + run: + working-directory: . + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: pip + + - name: Install MkDocs Material + run: pip install -r requirements.txt + + # --strict turns a broken nav entry, a dead cross-reference mkdocs itself + # can see, or a missing page into a failed build instead of a silently + # broken link on docs.teemops.com. + - name: Build docs.teemops.com + run: mkdocs build --strict + + # user-docs/README.md's own link-checker, for the relative links mkdocs + # doesn't validate because they point at pages not yet in the nav. + - name: Check user-docs cross-references + run: | + python3 - <<'EOF' + import pathlib, re, sys + bad = [] + for md in pathlib.Path("user-docs").rglob("*.md"): + text = re.sub(r"```.*?```", "", md.read_text(), flags=re.S) + for m in re.finditer(r"\]\((?!https?:|#)([^)#]+)(#[^)]*)?\)", text): + if not (md.parent / m.group(1)).resolve().exists(): + bad.append(f"{md}: -> {m.group(1)}") + if bad: + print("\n".join(bad)) + sys.exit(1) + print("all relative links resolve") + EOF + + # SVGs fail as a silent broken image, not a build error — see + # user-docs/README.md's Diagrams section. + - name: Validate diagram SVGs + run: | + python3 -c " + import xml.etree.ElementTree as ET, glob + [ET.parse(f) for f in glob.glob('user-docs/assets/diagrams/*.svg')] + print('all diagrams parse as valid XML') + " diff --git a/.gitignore b/.gitignore index 57c9ecd8..b00d68c6 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ dist/ .nuxt/ .output/ +# docs.teemops.com build output (mkdocs build) — generated, not source +/site/ + # MySQL Data Files mysql-data/ generated/ diff --git a/design/docs-site/information-architecture.md b/design/docs-site/information-architecture.md index ef105cdd..2461de77 100644 --- a/design/docs-site/information-architecture.md +++ b/design/docs-site/information-architecture.md @@ -117,12 +117,12 @@ somewhere and needs porting and a change of voice, not that a `user-docs/` page | Page | For | Status | Source | Priority | | --- | --- | --- | --- | :---: | | **Start here** | -| What TOPS is — and what it is not | All | ✍️ Write | `README.md` intro, roadmap Direction | 1 | +| What TOPS is — and what it is not | All | ✅ **Written** | `README.md` intro, `docs/PROGRESS.md` | **1** | | How TOPS connects to AWS | Evaluator | ✅ **Built this session** | `design/architecture/aws-integration.html` | 1 | | What it costs to run | Evaluator, Operator | ⚠️ Partial | `install.sh` `aws_intro()`, `docker-compose.README.md` | 2 | -| Install | Operator | ✅ Exists | `README.md` § Install — link, don't copy | 1 | -| Connect your first AWS account | Operator | ✅ Exists | `README.md` § Scanning a real AWS account | 1 | -| Run your first scan | Operator, User | ⚠️ Thin | `README.md`, `docs/features/scan-*.md` | 1 | +| Install | Operator | ✅ **Written** | `README.md` § Install — links, doesn't copy | **1** | +| Connect your first AWS account | Operator | ✅ **Written** | `README.md` § Scanning a real AWS account, the architecture page | **1** | +| Run your first scan | Operator, User | ✅ **Written** | `Pages/Scans/Index.vue`, `Pages/Scans/NewScanModal.vue` | **1** | | **Using TOPS** | | The dashboard | User | ✍️ Write | `Pages/Dashboard.vue` | 3 | | Running a scan | User | ✍️ Write | `Pages/Scans/*`, `NewScanModal.vue` | 2 | @@ -148,9 +148,9 @@ somewhere and needs porting and a change of voice, not that a `user-docs/` page | Troubleshooting | Operator | ✅ Exists | `DEBUG.md`, `README.md` § If something goes wrong | 2 | | Uninstalling | Operator, Evaluator | ⚠️ Partial | `docker-compose.README.md`; the two CFN stacks | 3 | | **Security** | -| The security model | Evaluator | ✅ **Built this session** | The architecture page's boundary table | **1** | +| The security model | Evaluator | ✅ **Written** | Roadmap D-1/D-2/D-9, `docs/PROGRESS.md`, `SECURITY.md` | **1** | | Where your data lives | Evaluator | ✍️ Write | Nothing leaves the install — needs stating plainly | 2 | -| Reporting a vulnerability | Evaluator | ✅ **Done** | `SECURITY.md` — link to it rather than restating it | **1** | +| Reporting a vulnerability | Evaluator | ✅ **Written** | `SECURITY.md` — links to it rather than restating it | **1** | | Licence and trademark | Evaluator | ✅ Exists | `LICENSE`, `TRADEMARK.md`, roadmap D-7 | 3 | | **Reference** | | Glossary | All | ✅ Exists | `docs/GLOSSARY.md` | 3 | @@ -158,9 +158,13 @@ somewhere and needs porting and a change of voice, not that a `user-docs/` page | Command reference | Operator | ✍️ Write | `artisan` commands, `backup.sh`, `install.sh` flags | 4 | | Release notes | All | ✅ Exists | `CHANGELOG.md` | 4 | -**Totals:** 34 pages — 12 exist in substance, 5 partial or scattered, 17 to write from -scratch. Roughly a third of the site is a porting-and-rewriting job rather than an authoring -job, which is the argument for doing the IA before the writing. +**Totals, original:** 34 pages — 12 exist in substance, 5 partial or scattered, 17 to write +from scratch. Roughly a third of the site was a porting-and-rewriting job rather than an +authoring job, which is the argument for doing the IA before the writing. + +**Updated 2026-08-04:** 10 of 34 pages are now written and live in `user-docs/` — the +whole priority-1 set (all six Start here and Security pages, plus the two Using TOPS pages +from the first slice). 24 remain, all priority 2 or lower. ### Two gaps this inventory exposed @@ -352,19 +356,33 @@ selection, and a theme whose image CSS is one rule away from correct. That is th requirement list, and every MkDocs- or Docsify-class renderer meets it. **Nothing found here argues for a framework.** -## Tooling stays deferred, on purpose - -D-10 said plain Markdown, renderer chosen once there is content. **This design does not -change that**, and the wireframes should not be read as a vote for a framework. - -What the wireframes commit to is only what any renderer in the MkDocs/Docsify class gives -you free: a left sidebar of sections, a content column at a readable measure, an on-page -table of contents, and search. Nothing in the IA needs MDX, React components, or a build -step. The one page with real layout demands — the architecture explainer — is already a -self-contained HTML page with inline SVG, which every candidate renderer can embed. - -**Revisit when:** the first slice is written and the section count or search need actually -bites. Not before. +## Tooling: MkDocs + Material — decided 2026-08-03, recorded as D-13 + +D-10 said plain Markdown, renderer chosen once there is content. That trigger fired once +the first slice's four pages existed against an eight-section, 34-page map — section count +and search both actually bite now, not hypothetically. + +**MkDocs with the Material theme**, deployed to `docs.teemops.com` via Cloudflare Pages +(build command `mkdocs build`, output directory `site`). It's the choice this document +already argued for without naming it: only what any renderer in the MkDocs/Docsify class +gives free — a left sidebar of sections, a content column at a readable measure, an +on-page table of contents, and search — nothing here ever needed MDX, React components, or +a hand-rolled build step. Chosen over Docsify specifically because it renders real static +HTML per page rather than client-side; the evaluator audience this document identifies +above arrives via a vendor-review request or a search result, not already inside the app, +and static HTML is what a crawler and a skimmed link both want. It adds no new language to +the repo — `python3` already backs the link-checker and SVG-validator scripts sitting next +to it in `user-docs/README.md`. + +The one page with real layout demands — the architecture explainer — is already a +self-contained Markdown page with linked SVGs, which the theme embeds with one CSS rule +(`p:has(img) { max-width: none }`, the fix this document's sample test below found +necessary). Config lives in `mkdocs.yml` and `requirements.txt` at the repository root, and +`.github/workflows/tests.yml` gained a `docs` job so a broken nav entry or a dead diagram +fails CI instead of shipping to `docs.teemops.com`. + +**Revisit when:** content needs something outside MkDocs' plugin ecosystem, or the Python +build step becomes a maintenance burden of its own. ## First slice diff --git a/docs/roadmap.md b/docs/roadmap.md index 734e4c41..5f9542b0 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -100,6 +100,7 @@ Decisions already made, so we don't relitigate them. Each has a trigger for revi | **D-10** | **User documentation lives in a top-level `user-docs/` directory, not under `docs/`, and deploys to `docs.teemops.com`.** One site for technical and non-technical readers — no separate tracks. | 2026-07-31 | See below. | The content outgrows plain Markdown, or a contributor proposes a better home. | | **D-12** | **No security score.** Removed rather than recalibrated. Severity counts and the breakdown are what we show; a score can come back later if a design partner asks for one and we know what it should mean. | 2026-08-02 | See below. Closes [#91](https://github.com/teemops/tops/issues/91). | A design partner asks for a single headline number — and then design it so it can *move*. | | **D-11** | **A finding is a durable record keyed by AWS account + resource + rule, and findings are current state. No per-scan history, no observations table.** The most recent scan that examined a resource is authoritative for its content; status belongs to the user. | 2026-08-01 | See below. Shipped as [#81](https://github.com/teemops/tops/issues/81). | A design partner asks for trends over time — and then treat it as a new data model, not an addition to this one. | +| **D-13** | **Docs renderer: MkDocs + Material theme**, deployed to `docs.teemops.com` via Cloudflare Pages (`mkdocs build`, output `site/`). Fills in the choice D-10 deliberately deferred. | 2026-08-03 | Clears the "MkDocs- or Docsify-class" bar D-10 set, now that section count and search actually bite (four pages against an eight-section, 34-page IA). Material gives sidebar nav, on-page TOC and client-side search for free — nothing here needed building by hand. Chosen over Docsify because it renders real static HTML per page rather than client-side, which matters for the evaluator audience D-10's own design work identified as arriving via search or a vendor-review link rather than already inside the app. Adds no new language to the repo: `python3` already backs the link-checker and SVG-validator scripts next to it in `user-docs/README.md`. | Content needs something outside MkDocs' plugin ecosystem, or the Python build step becomes a maintenance burden of its own. | > **Naming collision, flagged 2026-08-01.** [`durable-findings.md`](./features/durable-findings.md) > and the wireframes label the durable-findings decision **D-1**, which in *this* document is @@ -585,9 +586,15 @@ Unlike the scanner-coverage backlog this doesn't have a natural per-item unit, s as a first slice plus continuous growth rather than a checklist. **First slice** -- [ ] `user-docs/` exists with a handful of pages covering what N-3 doesn't: reading and - resolving a finding, switching scan profiles, managing organization members -- [ ] Cloudflare Pages is connected and `docs.teemops.com` resolves to it +- [x] `user-docs/` exists — 10 of the IA's 34 pages written, closing the entire + priority-1 set: what TOPS is, how TOPS connects to AWS, install, connect your first + AWS account, run your first scan, what the IAM role can do, reading a finding, + resolving a finding, the security model, reporting a vulnerability. Scan profiles + and member management are priority-2, still ahead, per the IA's writing order +- [ ] Cloudflare Pages is connected and `docs.teemops.com` resolves to it — **D-13** picked + the renderer (MkDocs + Material) and the repo now builds; the Pages project itself + still needs connecting in the Cloudflare dashboard, which isn't something a repo + commit can do - [ ] `README.md`'s setup section trims to a summary linking into `user-docs/`, so installation instructions have exactly one canonical copy diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..4b51906d --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,64 @@ +site_name: TOPS Documentation +site_url: https://docs.teemops.com +site_description: >- + Documentation for running TOPS — connecting an AWS account, reading a finding, + and everyday use. +repo_url: https://github.com/teemops/tops +repo_name: teemops/tops +edit_uri: edit/develop/user-docs/ + +docs_dir: user-docs +site_dir: site + +# README.md in user-docs/ is contributor-facing (conventions, archetypes, status) +# — see D-10. It documents how to write the site, it isn't a page of it. +exclude_docs: | + README.md + +theme: + name: material + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.sections + - navigation.top + - navigation.indexes + - toc.follow + - search.suggest + - content.action.edit + +markdown_extensions: + - tables + - toc: + permalink: true + +extra_css: + - assets/extra.css + +# Only pages that exist. Per user-docs/README.md's own rule ("no links to pages +# that do not exist yet"), the nav grows one entry per page as it's written — +# see design/docs-site/information-architecture.md for the full 34-page map. +nav: + - Start here: + - What TOPS is: start-here/what-tops-is.md + - How TOPS connects to AWS: start-here/how-tops-connects-to-aws.md + - Install: start-here/install.md + - Connect your first AWS account: start-here/connect-your-first-aws-account.md + - Run your first scan: start-here/run-your-first-scan.md + - Using TOPS: + - Reading a finding: using-tops/reading-a-finding.md + - Resolving a finding: using-tops/resolving-a-finding.md + - AWS accounts: + - What the IAM role can do: aws-accounts/what-the-iam-role-can-do.md + - Security: + - The security model: security/the-security-model.md + - Reporting a vulnerability: security/reporting-a-vulnerability.md diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..d3504c7a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +mkdocs-material==9.7.7 diff --git a/user-docs/README.md b/user-docs/README.md index d477119c..13f2adcd 100644 --- a/user-docs/README.md +++ b/user-docs/README.md @@ -5,7 +5,8 @@ to `docs/`, which is the contributor and maintainer knowledge base. See [D-10](../docs/roadmap.md#d-10-in-full-where-user-documentation-lives) for why the two are kept apart. -**Status: first slice.** Four pages so far. The structure it will grow into is designed in +**Status: priority-1 set complete.** 10 of the IA's 34 pages so far — every page the +inventory marks priority 1. The structure it will grow into is designed in [`design/docs-site/information-architecture.md`](../design/docs-site/information-architecture.md) — 34 pages across 8 sections, with a content map showing what already exists elsewhere in the repo and needs porting rather than writing. @@ -14,17 +15,27 @@ the repo and needs porting rather than writing. | Page | For | | --- | --- | +| [What TOPS is — and what it isn't](start-here/what-tops-is.md) | Anyone arriving at the site for the first time | | [How TOPS connects to AWS](start-here/how-tops-connects-to-aws.md) | Security reviewers approving TOPS for an AWS organisation | -| [What the IAM role can do](aws-accounts/what-the-iam-role-can-do.md) | The same reviewers, once they ask what the permissions actually grant | +| [Install](start-here/install.md) | Whoever is about to run it | +| [Connect your first AWS account](start-here/connect-your-first-aws-account.md) | The same person, once TOPS is up | +| [Run your first scan](start-here/run-your-first-scan.md) | The same person, once an account is connected | | [Reading a finding](using-tops/reading-a-finding.md) | Whoever has just run their first scan and is looking at the results | | [Resolving a finding](using-tops/resolving-a-finding.md) | The same person, once they have fixed something and want it off the list | +| [What the IAM role can do](aws-accounts/what-the-iam-role-can-do.md) | Reviewers, once they ask what the permissions actually grant | +| [The security model](security/the-security-model.md) | Reviewers asking about the product itself, not just the AWS boundary | +| [Reporting a vulnerability](security/reporting-a-vulnerability.md) | Anyone who found one | ## Conventions -**Plain Markdown, no framework.** The renderer is deliberately undecided until there is -enough content to need navigation and search. Nothing here may depend on renderer-specific -syntax — no MDX, no components, no shortcodes. If a page cannot be written in CommonMark plus -tables, that is a signal about the page, not about the tooling. +**Plain Markdown, minimal renderer.** Deployed via [MkDocs](https://www.mkdocs.org/) with +the [Material](https://squidfunk.github.io/mkdocs-material/) theme — see **D-13** in the +roadmap and [Tooling](../design/docs-site/information-architecture.md#tooling-mkdocs--material--decided-2026-08-03-recorded-as-d-13) +in the IA doc for why. Config is `mkdocs.yml` and `requirements.txt` at the repository +root; build locally with `pip install -r requirements.txt && mkdocs serve`. Nothing here +may depend on renderer-specific syntax regardless — no MDX, no components, no shortcodes. +If a page cannot be written in CommonMark plus tables, that is a signal about the page, not +about the tooling. **Install instructions live in `README.md`, not here.** Linking beats copying; two canonical copies drift. Pages reference it rather than restating commands. diff --git a/user-docs/assets/extra.css b/user-docs/assets/extra.css new file mode 100644 index 00000000..b98140a0 --- /dev/null +++ b/user-docs/assets/extra.css @@ -0,0 +1,8 @@ +/* The one rule the sample test (design/docs-site/information-architecture.md) + found necessary: Markdown wraps an image in a

, and Material caps prose at + a readable measure, which silently caps every diagram at that same width. + Diagrams are authored at a 900px viewBox specifically to read at ~1:1 without + that cap — see user-docs/README.md's Diagrams section. */ +.md-content__inner p:has(img) { + max-width: none; +} diff --git a/user-docs/index.md b/user-docs/index.md new file mode 100644 index 00000000..6ac94fe3 --- /dev/null +++ b/user-docs/index.md @@ -0,0 +1,36 @@ +# TOPS documentation + +TOPS scans your AWS accounts against CIS and TOPS' own baseline, and turns what it finds +into findings you can act on. This site covers evaluating, connecting, and using it. + +Install and upgrade commands live in the [`README.md`](https://github.com/teemops/tops) +at the repository root — this site links to them rather than restating them. + +**This site is growing, not finished.** More sections arrive as the features behind them +ship. What exists so far: + +## Start here + +- [What TOPS is — and what it isn't](start-here/what-tops-is.md) +- [How TOPS connects to AWS](start-here/how-tops-connects-to-aws.md) — for whoever has to + approve TOPS before it's installed. +- [Install](start-here/install.md) +- [Connect your first AWS account](start-here/connect-your-first-aws-account.md) +- [Run your first scan](start-here/run-your-first-scan.md) + +## Using TOPS + +- [Reading a finding](using-tops/reading-a-finding.md) — what each field means, and where + to start on a long list. +- [Resolving a finding](using-tops/resolving-a-finding.md) — fixing, ignoring, and what the + next scan does. + +## AWS accounts + +- [What the IAM role can do](aws-accounts/what-the-iam-role-can-do.md) — the honest, + policy-by-policy answer to "what does this actually grant?" + +## Security + +- [The security model](security/the-security-model.md) +- [Reporting a vulnerability](security/reporting-a-vulnerability.md) diff --git a/user-docs/security/reporting-a-vulnerability.md b/user-docs/security/reporting-a-vulnerability.md new file mode 100644 index 00000000..ec0e5add --- /dev/null +++ b/user-docs/security/reporting-a-vulnerability.md @@ -0,0 +1,49 @@ +# Reporting a vulnerability + +TOPS reads customers' AWS accounts, so a vulnerability here can expose another +organisation's cloud posture. If you've found one, this is what to do and what happens +next. The full policy lives in +[`SECURITY.md`](https://github.com/teemops/tops/blob/develop/SECURITY.md) at the +repository root; this page is the short version. + +## Do this + +**Do not open a public issue.** Use one of these instead: + +1. [Report it privately on GitHub](https://github.com/teemops/tops/security/advisories/new) — + preferred. Keeps the report private while a fix is developed. +2. **security@teemops.com** — if you'd rather not use GitHub. + +You don't need a fix, a CVE, or a polished write-up first. A rough description of +something that looks wrong is worth sending, with whatever you have: what the issue is, +how to reproduce it, and the version you found it on. + +## What happens next + +| | | +| --- | --- | +| Acknowledgement | Within 5 working days | +| Initial assessment | Within 10 working days | +| Fix and release | Depends on severity — you'll be told the plan and kept updated | +| Credit | Named in the advisory and the changelog, unless you'd rather not be | + +## Before you report + +**Never test against infrastructure you don't own.** Reproduce against a local install or +your own AWS account — a report isn't worth another organisation's data. + +A finding the *scanner* reports about your AWS account is the product working as intended, +not a vulnerability in TOPS — that's a normal issue, not a security report. And anything +that needs shell access to the host TOPS runs on is out of scope by design: this is a +self-hosted application, and an operator with a shell can read the database on purpose — +see [The security model](the-security-model.md). + +`SECURITY.md` has the complete scope, the full list of what's already known and being +worked on, and the disclosure timeline in full. + +## Related + +- [The security model](the-security-model.md) + +*Source of truth for this page: `SECURITY.md` in the TOPS repository — read that one if +anything here is out of date, not the other way around.* diff --git a/user-docs/security/the-security-model.md b/user-docs/security/the-security-model.md new file mode 100644 index 00000000..2be73f75 --- /dev/null +++ b/user-docs/security/the-security-model.md @@ -0,0 +1,86 @@ +# The security model + +> **Written for security reviewers.** If you have been asked to approve TOPS, this page is +> the whole answer for the product itself. For the AWS-specific boundary — what crosses +> the wire into an account you scan — see +> [How TOPS connects to AWS](../start-here/how-tops-connects-to-aws.md) instead; this page +> doesn't repeat it. + +TOPS is a single Laravel application and one MySQL database, self-hosted on infrastructure +you control. There is no hosted control plane, no telemetry endpoint, and no path for your +data to leave your install other than the ones you configure yourself (email, and the AWS +calls the other page covers). + +## At a glance + +| Layer | What protects it | Enforced by | +| --- | --- | --- | +| Login | Password auth by default; OAuth available if you opt into Firebase | `Auth/` controllers, session middleware | +| Cross-organisation access | Every query touching accounts, scans or findings is scoped to the signed-in user's organisation | `SetOrganizationContext` middleware, applied to every web and API route | +| Team roles | Four roles — owner, administrator, auditor, viewer — gate who can invite, remove, or change settings | `OrganizationPermission` service | +| Data at rest | Delegated to your host's disk encryption and database permissions, not the application | See *What we do not claim* below | + +## Authentication + +The default, and the only path that runs without any external dependency, is ordinary +email and password — Laravel's own session-based auth. A second path exists for operators +who want Google, GitHub or Microsoft sign-in: it's built, tested, and off by default +(`FIREBASE_USER_AUTH=false` in the Docker configuration), because the default install must +never require a Firebase project. Turning it on is a configuration change, not a code +change. + +## Authorization and data isolation + +Every user belongs to one or more organisations, and every row that matters — AWS +accounts, scans, findings — is scoped to one. That scoping is applied once, in middleware, +to every web and API route, rather than repeated per-controller: the practice this +codebase holds itself to is that every endpoint touching tenant data has a test proving a +different organisation cannot read it. + +Within an organisation, four roles — owner, administrator, auditor, viewer — decide who +can invite members, change roles, or remove someone. **This part has a known rough edge**: +some code comments describe a narrower rule ("only the owner can manage members") than the +code actually enforces, and the internal plan document agrees with the comments, not the +code. It's tracked and open, not silently wrong — see roadmap item X-5 in +[`docs/roadmap.md`](https://github.com/teemops/tops/blob/develop/docs/roadmap.md) if you +want the specifics before relying on the exact boundary. + +## Data at rest + +TOPS stores the IAM role ARN for each connected account, and nothing more sensitive than +that — no AWS access keys are ever requested or stored. That ARN is kept in plaintext, by +a deliberate decision recorded as **D-9** in the roadmap: an ARN alone grants nothing +without the trust policy and `ExternalId` that actually gate `AssumeRole`, and on a +single-host self-hosted install, application-level encryption would put the key on the +same disk as the data it protects — real encryption at rest, on this deployment model, is +your disk encryption and your database permissions, not a layer TOPS adds on top. + +## What we do not claim + +- **The child-account IAM role is not read-only.** It carries write permissions for + operational features that don't exist yet in the product. See + [What the IAM role can do](../aws-accounts/what-the-iam-role-can-do.md) for the + policy-by-policy detail, and how to strip the parts you don't want before you hand out + the link. +- **There is no application-level encryption at rest.** See *Data at rest* above — this + was a deliberate removal, not an oversight, and D-9 explains why. +- **Anyone with shell access to your host can read the database.** This is a self-hosted + application; an operator with a shell is trusted by design. There is no protection + against your own operators, and none is planned. +- **The member-permissions boundary has a known inconsistency**, described above, until + roadmap item X-5 closes it. + +## Currently known issues + +Publicly tracked, with what's already been done about each: see the +[Already known](https://github.com/teemops/tops/security/policy) section of +`SECURITY.md` in the repository, rather than this page keeping its own copy that can drift +out of date. + +## Related + +- [How TOPS connects to AWS](../start-here/how-tops-connects-to-aws.md) +- [Reporting a vulnerability](reporting-a-vulnerability.md) + +*Source of truth for this page: `docs/roadmap.md` decisions D-1, D-2 and D-9, +`docs/PROGRESS.md`, and `SECURITY.md` in the TOPS repository.* diff --git a/user-docs/start-here/connect-your-first-aws-account.md b/user-docs/start-here/connect-your-first-aws-account.md new file mode 100644 index 00000000..b0ee69eb --- /dev/null +++ b/user-docs/start-here/connect-your-first-aws-account.md @@ -0,0 +1,90 @@ +# Connect your first AWS account + +Links one AWS account to TOPS so it can be scanned. Two stages: setting up messaging in a +parent account (once, ever), then linking the account you actually want scanned (once per +account). Together, about ten minutes plus however long you take to find someone who can +create a CloudFormation stack. + +## Before you start + +- TOPS is installed and you can sign in — see [Install](install.md). +- The AWS CLI is configured on the machine you installed TOPS on, with permission to + create CloudFormation stacks, SNS topics, SQS queues and an S3 bucket. This is only + needed once, for the parent-account step below — it is not needed for every account you + connect afterward. +- You can create a CloudFormation stack in the account you want to scan, or you know who + can. It does not need to be the same account or the same AWS credentials as the step + above. + +## Steps + +### 1. Set up messaging, if you haven't already + +If you said yes to the AWS question during install, this is done — skip to step 2. + +Otherwise, from the directory the installer created (`tops/`, unless you set `TOPS_DIR`): + +```bash +./install.sh --aws-only +``` + +It tells you which AWS account it's about to deploy into — read from +`aws sts get-caller-identity` — and lists what it creates before doing anything: two +CloudFormation stacks, an SNS topic, four SQS queues, and an S3 bucket, all in an account +and region you choose. If your AWS CLI isn't configured, it says so and skips the step +rather than failing halfway; you can run it again once it is. + +This is the *only* step that needs your own AWS credentials on the machine running TOPS. +Nothing in step 2 does. + +### 2. Add the account you want scanned + +1. **In TOPS, open AWS accounts and choose Add account.** TOPS creates a pending record + and generates a CloudFormation quick-create link that's specific to this account. + +2. **Send the link to whoever administers the AWS account**, or open it yourself if that's + you. It opens the CloudFormation quick-create page in the AWS console, pre-filled. + + The link carries an `ExternalId` generated for this account alone. Treat it like a + password — anyone with it can complete this link. + +3. **Create the stack.** The defaults are correct. Acknowledge the IAM capability + checkbox — the stack creates a role, so AWS requires it. + + ``` + Stack name: tops-vendor-audit + Status: CREATE_IN_PROGRESS → CREATE_COMPLETE + ``` + +4. **Wait for TOPS to pick it up.** Usually a few seconds after the stack completes. The + account moves from *Pending* to *Active* on its own — nothing to refresh or paste back. + +## When it goes wrong + +**The stack sits at CREATE_IN_PROGRESS for more than a few minutes.** +TOPS never received the notification, so CloudFormation is waiting for a reply that isn't +coming. Confirm step 1 actually completed — check for `TOPS_SQS_ARN` in your `.env` — and +that the worker is running: `docker compose logs worker | grep account`. + +**"Add AWS Account" says messaging is not configured.** +Step 1 hasn't run yet, or it failed partway. Run `./install.sh --aws-only` again; its log +is `generated/install.log`. + +**The stack fails with "Account not found".** +The link was generated for a different TOPS organisation, or the pending record was +deleted. Generate a fresh link from AWS accounts and create the stack again. + +**The account shows Active but a scan returns nothing.** +The role exists but can't be assumed — usually a region your credentials can't reach. +`docker compose logs -f worker` shows what the scan actually tried. + +## Next + +- [Run your first scan](run-your-first-scan.md) +- [What the IAM role can do](../aws-accounts/what-the-iam-role-can-do.md) — if you were + asked to justify the permissions before creating the stack + +*Source of truth for this page: [How TOPS connects to AWS](how-tops-connects-to-aws.md), +which shows the same two stages as diagrams, and the README's +[Scanning a real AWS account](https://github.com/teemops/tops#scanning-a-real-aws-account) +section.* diff --git a/user-docs/start-here/install.md b/user-docs/start-here/install.md new file mode 100644 index 00000000..b15a192e --- /dev/null +++ b/user-docs/start-here/install.md @@ -0,0 +1,52 @@ +# Install TOPS + +Gets TOPS running on your own machine. Five to ten minutes, most of it waiting for Docker +to pull images. + +## Before you start + +- **Docker, with Compose v2.** That's the whole list — no PHP, no Composer, no Node, no + npm, no database to set up separately. +- You do **not** need an AWS account or a Firebase project to try TOPS. The default login + is ordinary email and password, and connecting a real AWS account is a separate, + explicit step you can skip and come back to. + +## Steps + +1. **Run the installer.** The one-line command is in the + [README's Install section](https://github.com/teemops/tops#install) — copy it from + there rather than from here, so there's exactly one canonical copy. It pulls the + published images from Docker Hub, generates a `.env` with its own application key, and + starts the stack. + +2. **Answer the AWS question.** Partway through, the installer asks whether you want to + connect an AWS account now. It's safe to say no — nothing about running TOPS or trying + it out depends on that answer, and you can run it later. See + [Connect your first AWS account](connect-your-first-aws-account.md). + +3. **Open the app and register.** Once the installer says it's up, go to + **http://localhost:8080** and create an account — the first one you create is yours. + Sign-up and password-reset emails are captured locally at **http://localhost:8090** + instead of being sent anywhere, so nothing needs an SMTP server to get started. + +## When it goes wrong + +**"the Docker daemon is not reachable."** +Docker is installed but your user can't talk to it yet. See the README's +[troubleshooting section](https://github.com/teemops/tops#if-something-goes-wrong) — the +fix is a group membership change and a re-login. + +**Port 8080 is already in use, or a container with that name already exists.** +Both are covered, with the exact fix, in the same +[troubleshooting section](https://github.com/teemops/tops#if-something-goes-wrong). + +**Something else.** [`DEBUG.md`](https://github.com/teemops/tops/blob/develop/DEBUG.md) in +the repository has more symptoms and causes than fit on this page. + +## Next + +- [Connect your first AWS account](connect-your-first-aws-account.md) +- [What TOPS is — and what it isn't](what-tops-is.md) — if you haven't read it yet + +*Source of truth for this page: the README's Install section, kept there deliberately so +installation has exactly one canonical copy.* diff --git a/user-docs/start-here/run-your-first-scan.md b/user-docs/start-here/run-your-first-scan.md new file mode 100644 index 00000000..1239e0b8 --- /dev/null +++ b/user-docs/start-here/run-your-first-scan.md @@ -0,0 +1,70 @@ +# Run your first scan + +Starts a scan against a connected AWS account and gets you to a list of findings. A full +scan of one account takes a few minutes; you don't have to wait on the page for it. + +## Before you start + +- At least one AWS account showing **Active** — see + [Connect your first AWS account](connect-your-first-aws-account.md). + +## Steps + +1. **Open Scans, and choose New Scan.** If this is the account's first scan, you'll land + here already — the empty state offers the same button. + +2. **Choose the AWS account.** Only accounts showing **Active** appear in the list; if + none do, the modal tells you to add one first. + +3. **Choose what to scan.** Each row is a scan profile — the general "basic" baseline and + the CIS AWS Foundations Benchmark — shown with its service and rule counts. Ticking a + profile scans everything it covers. Expand one to tick individual services instead; as + you narrow it down, a summary line updates to say exactly what will run, for example + *"Scanning 2 of 11 services against Basic — 12 rules."* + + You don't have to choose one or the other. Both profiles combine, and a service common + to both is only scanned once. + +4. **Choose Start Scan.** The scan begins immediately as **Pending**, then **Running** + once a worker picks it up — the list updates on its own every few seconds, so you don't + need to refresh. + +5. **Choose View once it's Completed.** That's the scan detail page: what ran, how long it + took, and a severity breakdown that links straight into + [Findings](../using-tops/reading-a-finding.md), filtered to what that scan found. + +## When it goes wrong + +**"No active AWS accounts available."** +The account you want to scan isn't showing Active yet. See +[Connect your first AWS account](connect-your-first-aws-account.md) — its "when it goes +wrong" section covers a stack that's stuck. + +**A scan stays "Pending" and never moves to "Running".** +Nothing is picking jobs up. Check the worker is alive: +`docker compose logs -f worker`. If you just started TOPS, give it a few seconds — the +worker and the app start at roughly the same time. + +**A scan stays "Running" much longer than you'd expect.** +Check the same worker log. To clear a scan that's genuinely stuck rather than just slow: + +```bash +docker compose exec app php artisan scans:mark-stale-region-complete --dry-run +``` + +Drop `--dry-run` once you're happy with what it lists. A scan cleared this way is marked +**partial**, not clean — it means "we didn't finish looking," not "nothing was found." + +**A service you use shows no findings at all.** +Either nothing failed, or TOPS doesn't have rules for that service yet. It currently +covers IAM, S3, RDS, EC2, CloudTrail, KMS, Lambda, SQS, SNS, DynamoDB and ELBv2 — an +uncovered service looks exactly like a clean one, which is worth knowing before you treat +a quiet result as good news. + +## Next + +- [Reading a finding](../using-tops/reading-a-finding.md) +- [Resolving a finding](../using-tops/resolving-a-finding.md) + +*Source of truth for this page: `Pages/Scans/Index.vue` and `Pages/Scans/NewScanModal.vue` +in the TOPS repository.* diff --git a/user-docs/start-here/what-tops-is.md b/user-docs/start-here/what-tops-is.md new file mode 100644 index 00000000..65a706ed --- /dev/null +++ b/user-docs/start-here/what-tops-is.md @@ -0,0 +1,47 @@ +# What TOPS is — and what it isn't + +TOPS scans your AWS accounts for security misconfigurations and tells you how to fix them. +It's open source, self-hosted on your own infrastructure, and Apache-2.0 — no paid tier, +no usage limits, no feature gated behind a licence. + +## What it does + +- **74 checks** across 11 AWS services — S3, IAM, EC2, RDS, CloudTrail, KMS, Lambda, + DynamoDB, ELBv2, SNS, SQS. +- **Two scan profiles today**: a general "basic" baseline and the CIS AWS Foundations + Benchmark. You can run either, both, or narrow either one down to specific services. +- **Every finding carries a remediation.** Critical and high-severity findings also carry + step-by-step guidance with links to AWS documentation — see + [Reading a finding](../using-tops/reading-a-finding.md). +- **Multi-tenant**: organisations, team roles, and per-organisation data isolation, so one + install can run scans for more than one team without either seeing the other's accounts. +- **No stored AWS access keys.** TOPS reads your accounts through cross-account IAM roles + your own administrators create and can revoke — see + [How TOPS connects to AWS](how-tops-connects-to-aws.md). + +## What it isn't + +- **Not a runtime protection tool.** TOPS reads configuration through the AWS API; it does + not watch network traffic, block requests, or sit in front of anything. +- **Not multi-cloud yet.** AWS only. Azure and GCP are on the roadmap, not in the code. +- **Not a compliance guarantee.** A clean scan means the checks TOPS runs found nothing — + it isn't a substitute for an audit against a framework's full requirement set. The PCI + profile in particular is not usable yet: it exists but has no rules written, so it's + hidden from the scan picker rather than shown half-finished. +- **Doesn't fix anything for you.** TOPS tells you what's wrong and how to fix it; applying + the fix is still a change you make in your own AWS account. +- **No scheduled scans, and no report export**, as of this writing. Both are on the + roadmap; neither has a controller, a route, or a UI yet — see + [`docs/PROGRESS.md`](https://github.com/teemops/tops/blob/develop/docs/PROGRESS.md) if + you want to check what's actually built before relying on something you read about. + +## Where to go next + +| If you are | Start with | +| --- | --- | +| Deciding whether to allow TOPS into your AWS organisation | [How TOPS connects to AWS](how-tops-connects-to-aws.md) | +| Ready to run it | [Install](install.md) | +| Looking at your first scan results | [Reading a finding](../using-tops/reading-a-finding.md) | + +*Source of truth for this page: `README.md` and `docs/PROGRESS.md` in the TOPS repository — +the second is checked against the code, not against intent.*