Metadata-Version: 2.4
Name: netqa
Version: 0.1.0
Summary: NetQA — read-only AWS network discovery, knowledge graph, and Q&A
Author-email: Zhenyu Wen <wenzhenyu36@gmail.com>
License-Expression: MIT
Project-URL: Repository, https://github.com/RayVenn/network-steward-oss
Project-URL: Issues, https://github.com/RayVenn/network-steward-oss/issues
Keywords: aws,networking,vpc,knowledge-graph,llm,claude
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Networking
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: boto3>=1.34
Requires-Dist: networkx>=3.2
Requires-Dist: click>=8.1
Requires-Dist: rich>=13.0
Requires-Dist: anthropic>=0.111.0
Dynamic: license-file

# NetQA

**Ask questions about your AWS network in plain English.**

NetQA is an open-source, **read-only** CLI that discovers your AWS
network (VPCs, subnets, route tables, security groups, NACLs, Transit
Gateways), builds a local knowledge graph from it, and lets you interrogate it
— with plain commands or with an LLM (Claude):

```bash
netqa ask "can the app subnet reach the database subnet?"
netqa ask "what breaks if I delete subnet-0a1b2c3d?"
netqa ask "what is attached to my transit gateway?"
netqa plan "isolate the staging VPC from the rest of the network"
```

Everything runs locally: your AWS credentials stay in your boto3 chain, state
is a single JSON file on disk, and the LLM layer is optional — every check
also works deterministically without an API key.

## Why

AWS networking questions — "why can't these two subnets talk?", "who still
references this security group?", "what's the blast radius of deleting this
route?" — usually mean clicking through five consoles or writing one-off
boto3 scripts. NetQA answers them from a typed graph of your real
network, and the LLM answers are **grounded in that graph**: Claude can only
call deterministic graph-query tools and narrate their results, so it can't
hallucinate resources or paths that don't exist.

## Features

- **Read-only discovery** — pulls network state with `ec2:Describe*` only;
  never modifies anything in your account.
- **Knowledge graph** — VPCs, subnets, route tables, SGs, NACLs, and Transit
  Gateways as a typed graph, persisted as one JSON file.
- **Deterministic checks** — reachability (same-VPC, and cross-VPC via
  Transit Gateway or VPC peering), dependency lookups, delete blast-radius
  reports. No LLM required.
- **Natural-language Q&A** — `ask` (one-shot) and `chat` (multi-turn) answered
  by Claude calling the graph primitives as tools; `--trace` shows every query
  it ran.
- **Change-impact simulation** — "what happens if I detach this TGW / remove
  this route / delete this subnet?" applied to an in-memory copy and diffed.
  The stored graph is never touched, and nothing is ever executed in AWS.
- **Change planning** — turn an intent into an ordered, approval-ready plan
  where every step's impact report comes from the simulator, not the model.

## Install

Requires Python 3.11+.

```bash
uv tool install netqa      # or: pipx install netqa
# from a source checkout:
uv tool install /path/to/netqa
```

## Quick start

NetQA runs against your AWS account, **read-only**: discovery needs nothing
beyond `ec2:Describe*` and uses your standard boto3 credential chain — env
vars, `~/.aws/credentials`, SSO, assumed roles. It never creates, modifies,
or deletes anything.

```bash
# 1. Discover your network and build the graph (read-only)
netqa discover --region us-east-1 --region us-west-2 \
    --profile my-readonly-profile

# 2. Explore deterministically
netqa summary                             # what was discovered
netqa show vpc-0abc...                    # a resource and its relationships
netqa tgw tgw-0abc...                     # what's attached to this TGW?
netqa reach subnet-0123... subnet-0456... # cross-VPC reachability
netqa impact subnet-0123...               # delete blast-radius
netqa consumers sg-0123...                # who references this SG?

# 3. Ask in plain English (works offline via a keyword router; see below for LLM mode)
netqa ask "what is attached to my transit gateway?"
netqa ask "can the app subnet reach the db subnet?"
```

A minimal IAM policy for a dedicated read-only role:

```json
{
  "Version": "2012-10-17",
  "Statement": [{ "Effect": "Allow", "Action": "ec2:Describe*", "Resource": "*" }]
}
```

## LLM-powered Q&A with Claude

Set an Anthropic API key — in the environment or in a `.env` file (see
[Configuration](#configuration)) — and `ask` / `chat` are answered by Claude
using the graph-query primitives as tools:

```bash
export ANTHROPIC_API_KEY=sk-ant-...   # or put it in ~/.netqa/.env
netqa ask "why might the app tier not reach the data tier?"
netqa ask --trace "can the app subnet reach the data subnet?"
netqa chat            # interactive: context carries across turns
```

- `ask` is one-shot; `chat` is a conversation — when the model needs to
  disambiguate ("there are four app subnets — which environment?"), just
  answer and the follow-up resolves against the full dialogue.
- `--trace` prints the exact graph queries the model ran to reach its answer,
  so every response is auditable.
- Human names like "the app subnet" are resolved to resource IDs via a
  `search_resources` tool.
- Without a key, `ask` falls back to a deterministic keyword router; force a
  mode with `--llm` / `--no-llm`.
- Default model is `claude-haiku-4-5` (cheap and fast). Use
  `--model claude-opus-4-8` for the highest-quality answers and change plans,
  or set `model` in the config file.

> Reasoning lives in code (`qa/engine.py`), not in a prompt. Claude only
> chooses which primitive to call and narrates the structured result — keeping
> answers reproducible, auditable, and grounded in your actual network.

## Change-impact analysis ("what happens if I…")

Predict the consequences of a change **before** making it. The engine applies
the proposed change to an in-memory copy of the graph, re-runs reachability
and dependency analysis, and diffs — reporting broken/new paths and dangling
dependencies:

```bash
netqa simulate tgw-1   --op detach_tgw --vpc vpc-bbb
netqa simulate rtb-aaa --op remove_route --destination 10.1.0.0/16
netqa simulate subnet-a1 --op delete_resource
```

```
Change Impact: detach tgw-1 from vpc-bbb
Not recommended   risk=high
Not recommended — 4 reachability path(s) would break.
  ✗ subnet-a1 → subnet-b1
  ✗ subnet-b1 → subnet-a1
  ...
```

Supported ops: `delete_resource`, `remove_route`, `add_route`, `detach_tgw`.
The LLM agent exposes this as a tool too, so
`ask "what happens if I detach tgw-1 from vpc-bbb?"` answers in natural
language. (Reachability is route-level; SG/NACL enforcement is on the
roadmap.)

## Change planning from natural language

Give NetQA an intent and it drafts an **approval-ready plan**: an
ordered list of structured change ops, each with a deterministic impact
report. Claude must run the simulator on every candidate step — submissions
with unsimulated steps are rejected in code, and accepted plans are
re-simulated cumulatively (step N on top of steps 1..N-1). Nothing is
executed; the plan is a draft for human review.

```bash
export ANTHROPIC_API_KEY=sk-ant-...
netqa plan "isolate vpc-b from the rest of the network"
netqa plan --trace "stop the app subnets reaching the data VPC"
```

```
Change Plan: isolate vpc-b from the rest of the network
Needs review   risk=high   steps=2   status=draft

1. remove route 10.1.0.0/16 from rtb-a  medium
   Drop vpc-a's route toward vpc-b before detaching.
   ...
2. detach tgw-core from vpc-b  high
   ✗ subnet-b1 → subnet-a1
   ...
```

## Use as a Python library

Everything the CLI does is available programmatically — `pip install netqa`
and import it. The same pipeline: discover → build graph → query.

```python
from netqa import AwsDiscoverer, MockDiscoverer, build_graph, QueryEngine

# Discover from AWS (read-only) — or MockDiscoverer("fixture.json") for tests
inventories = AwsDiscoverer(regions=["us-east-1"]).discover()
graph = build_graph(inventories)
graph.save("graph.json")            # reload later with NetworkGraph.load()

# Deterministic queries — no LLM involved
engine = QueryEngine(graph)
r = engine.can_subnet_reach("subnet-0123", "subnet-0456")
print(r.reachable, r.path, r.reasons)

rep = engine.delete_impact("subnet-0123")   # blast radius
print(rep.recommended, rep.findings)
```

Simulate a change before making it:

```python
from netqa import ProposedChange, analyze_change

impact = analyze_change(graph, ProposedChange(
    op="detach_tgw", target="tgw-1", vpc_id="vpc-bbb"))
print(impact.risk, impact.summary, impact.broken_reachability)
```

And the LLM layer (needs `ANTHROPIC_API_KEY` in the environment):

```python
from netqa import NetworkAgent, ChangePlanner

agent = NetworkAgent(engine)                 # optional: model="claude-opus-4-8"
result = agent.ask("what breaks if I delete subnet-0123?")
print(result.text)      # grounded answer
print(result.trace)     # every graph query the model ran

plan = ChangePlanner(engine).plan("isolate vpc-b from the network")
```

The supported surface is exactly what `netqa.__all__` exports (discoverers,
graph, `QueryEngine`, impact analysis, agent, planner) — anything deeper is
internal and may change without notice. Pre-1.0, breaking changes to these
names bump the minor version.

## Configuration

Everything is configurable via files, so day-to-day use needs zero flags.
Settings live in a TOML config; secrets live in a `.env` file.

**Settings** — `~/.netqa/config.toml` (CLI flags always win;
override the location with `NETQA_CONFIG`):

```toml
model   = "claude-haiku-4-5"          # Claude model for ask/chat/plan
graph   = "~/.netqa/graph.json"
regions = ["us-east-1", "us-west-2"]  # AWS regions to discover
profile = "my-readonly-profile"       # AWS profile (else boto3 default chain)
```

Every key is optional — anything missing falls back to the built-in default.

**Secrets** — a `.env` file, deliberately separate from config.toml so your
settings file is safe to share or commit. Loaded from `./.env` or
`~/.netqa/.env` (override the location with `NETQA_ENV`):

```bash
# ~/.netqa/.env  — then: chmod 600 ~/.netqa/.env
ANTHROPIC_API_KEY=sk-ant-...
```

Standard dotenv format: one `KEY=VALUE` per line; blank lines and `#`
comments are ignored; values may be quoted; an `export ` prefix is allowed
(so the same file also works with `source .env`). Any variable can go here —
e.g. `AWS_PROFILE` or `AWS_DEFAULT_REGION` — but a variable already set in
your real environment is never overridden.

**Precedence** (highest first):

```
CLI flag  >  environment variable  >  ./.env  >  ~/.netqa/.env  >  config.toml  >  built-in default
```

The tool warns if the `.env` file is readable by other users. Check your
effective setup any time with `netqa config` — it shows every value
and where it came from (it never prints the key itself).

## Privacy & security

- **Read-only by design.** The tool only calls `ec2:Describe*`. It never
  creates, modifies, or deletes anything in your account — including in
  `simulate` and `plan`, which operate on an in-memory copy of the local
  graph.
- **Your credentials never leave your machine.** AWS access goes through your
  local boto3 chain; nothing is proxied through any service.
- **What the LLM sees.** Only when you use `ask`/`chat`/`plan` with an API
  key, your question and the results of the graph queries Claude requests
  (resource IDs, CIDRs, route/SG relationships) are sent to the Anthropic
  API. No AWS credentials are ever sent. Skip the API key and everything
  deterministic still works entirely offline.
- **Local state.** The graph is a single JSON file (default
  `~/.netqa/graph.json`). Delete it and the tool forgets everything.

## How it works

```
 Your AWS account (ec2:Describe* only)                       ┌─►  QueryEngine (deterministic)
         │                                                   │    reach · deps · impact · simulate
         └──►  RawInventory ─► NetworkGraph ─────────────────┤
               (boto3 describe_*)   (NetworkX)               └─►  NetworkAgent (Claude tool-use)
                                                                  ask · chat · plan
```

| Layer | Module | Responsibility |
|-------|--------|----------------|
| Discovery | `discovery/` | Pull raw network state from AWS via read-only `describe_*` calls. (A JSON-fixture discoverer with the identical `RawInventory` shape exists for tests.) |
| Graph | `graph/` | Turn raw `describe_*` responses into a typed NetworkX graph, serialized to JSON. |
| Q&A engine | `qa/engine.py` | Deterministic graph-traversal primitives — reachability, dependencies, impact. The auditable source of truth. |
| LLM layer | `qa/agent.py`, `qa/planner.py` | Claude calls the engine primitives as tools and narrates the results; the planner drafts simulated change plans. Optional — offline keyword fallback without a key. |

Nodes are AWS resources (keyed by resource ID); edges are typed relationships:

```
VPC        --contains-->        Subnet / RouteTable / SecurityGroup / NACL
RouteTable --associated_with--> Subnet
RouteTable --routes_to-->       igw / nat / tgw / pcx / local-vpc / ...
SecurityGroup --allows-->       SecurityGroup
NACL       --protects-->        Subnet
TransitGateway --attached_to--> VPC
VpcPeering     --attached_to--> VPC (requester and accepter)
```

See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full design.

## Supported resources & roadmap

Today: VPC · Subnet · Route Table · Security Group · NACL · Transit Gateway
(+ VPC attachments) · VPC Peering Connection. Other route targets (IGW/NAT/…)
appear as lightweight nodes.

| Capability | Status |
|-----------|--------|
| Read-only discovery (7 core resource types) | ✅ |
| Typed knowledge graph + JSON persistence | ✅ |
| Dependency / "who uses this" queries | ✅ |
| Route-level reachability (same-VPC & cross-VPC via TGW or VPC peering) | ✅ |
| Delete-impact (blast radius) report | ✅ |
| LLM-backed natural-language Q&A + multi-turn `chat` | ✅ |
| Change-impact simulation ("what happens if I change X") | ✅ |
| NL change planning — intent → simulated, approval-ready plan | ✅ |
| SG + NACL enforcement layered onto reachability | 🔜 |
| ENI / Load Balancer / VPN / multi-account (Organizations) discovery | 🔜 |
| Graph diffing between discoveries ("what changed") | 🔜 |

## Development

This project uses [uv](https://docs.astral.sh/uv/). One command creates the
virtualenv and installs everything from `uv.lock`:

```bash
uv sync
uv run pytest -q
```

Tests run the real builder and Q&A engine against
`examples/sample_network.json`, which is shaped exactly like live boto3
`describe_*` output — the offline path exercises the same code that runs
against AWS. The same fixture powers a no-AWS demo of the whole CLI
(testing/development only):

```bash
uv run netqa discover --fixture examples/sample_network.json
uv run netqa reach subnet-a1 subnet-b1     # cross-VPC via the sample TGW
uv run netqa ask "what is attached to tgw-1?"
```

To test at scale, `scripts/generate_network.py` emits a parameterized fixture
in the same boto3 shape — N VPCs on a central TGW, with the last `--isolated`
VPCs left unattached for guaranteed unreachable cases:

```bash
uv run python scripts/generate_network.py --vpcs 12 --subnets-per-vpc 4 \
    --isolated 2 --out /tmp/network.json
uv run netqa discover --fixture /tmp/network.json
```

`scripts/test_workflow.sh` runs the whole tool end-to-end — generate →
discover → every CLI query → offline Q&A → change-impact simulation —
asserting on each step's output. It needs no AWS account or Anthropic key.

## Contributing

Issues and pull requests are welcome. Good first areas: new resource types in
discovery, SG/NACL-aware reachability, and graph diffing between discoveries
(see the roadmap above). Please make sure `uv run pytest -q` and
`scripts/test_workflow.sh` pass before submitting.

## License

[MIT](LICENSE)
