Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ This project is intended to help teams investigate a missing security layer betw
| Pluggable model advisor | OpenAI Responses API, OpenAI-compatible endpoints, or a generic model gateway webhook | Adds an independent structured intent recommendation without making a model the enforcement authority |
| Protected integration API | OWASP CRS WAF, backend network isolation, bearer-token ingestion, and bounded requests | Reduces attack surface for the security signals that influence policy |
| Repeatable deployment | Docker Compose, Terraform, and Ansible | Makes the POC reproducible for labs, demos, and controlled evaluations |
| Fleet administration | Endpoint inventory, security-group discovery, deployment plans, and agent-pull jobs | Demonstrates controlled network distribution without exposing arbitrary remote shell execution |

## Where it can be used

Expand Down Expand Up @@ -214,6 +215,8 @@ intentgate: BLOCK risk=100
| `uig-service` | Start signal-ingestion and metrics endpoints |
| `uig-collector config/integrations.json` | Poll configured security sources |
| `uig-notifier` | Deliver queued manager risk reports |
| `uig-admin discover` | Snapshot enrolled endpoints and security groups over the authenticated API |
| `uig-admin deploy --group engineering --version 0.4.0 --execute` | Queue the fixed install manifest to a discovered security group |

Exit codes are `0` for success, `2` for a non-allow dry run, `125` for review not approved, `126` for a blocked command, and `127` when the executable is missing.

Expand Down Expand Up @@ -300,8 +303,18 @@ Use **Set API token** in the console to enter the configured bearer token. The t
- Versioned, locally persisted review and block thresholds
- Versioned zero-trust step-up controls and a micro-segmentation flow designer
- An inspectable destructive-action rule catalog
- Fleet discovery with endpoint, platform, security-group, online, and installed-version coverage
- Dry-run and queued deployment waves for a fixed Intent Gate installation manifest
- Endpoint security policy coverage for antivirus, malware prevention, DLP, behavior monitoring, and vulnerability/CVE scanning
- A group-oriented inventory explorer showing policy assignments and every endpoint in each security group

The console is intentionally **assessment-only**. Approving a review records human authorization but never launches the command from the browser. A trusted command broker remains the required production execution boundary.
The Command Lab and review workflow are intentionally **assessment-only**. Approving a command review records human authorization but never launches that command from the browser. Fleet Admin can separately queue only the fixed software-install manifest; a trusted endpoint agent remains the required deployment execution boundary.

### Fleet Admin

The **Fleet Admin** section discovers enrolled endpoints and security groups, shows agent coverage, previews the equivalent `uig-admin` network command, and plans or queues deployment waves. Discovery is based on registered inventory and heartbeats rather than blind network scanning. Deployment jobs use a fixed `install-or-upgrade-intentgate` manifest and an agent-pull transport; arbitrary remote commands are not accepted.

For command examples, endpoint-agent API routes, safety properties, and production requirements, see [Fleet Administration](docs/FLEET_ADMIN.md).

HTTP assessments use a standard `console-operator` execution context by default rather than inheriting the container service account's root identity. A trusted broker can submit an explicit `execution_context` containing the originating user and privilege level; production enforcement must authenticate that context rather than accepting it directly from an untrusted client.

Expand Down
66 changes: 66 additions & 0 deletions docs/FLEET_ADMIN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Fleet administration

The Fleet Admin POC adds an authenticated deployment control plane to the Intent Gate service. It demonstrates scoped software distribution without turning the console into a general-purpose remote shell.

## How it works

1. Endpoint agents register hostname, address, operating system, security groups, and installed version with `POST /v1/endpoints/register`.
2. A discovery session snapshots enrolled inventory and summarizes group, online, and managed coverage.
3. An administrator targets a security group or explicit endpoint IDs and creates a dry-run plan.
4. An executed plan creates one fixed `install-or-upgrade-intentgate` manifest per endpoint.
5. Endpoint agents poll `GET /v1/deployment-jobs/next?endpoint_id=...` and report lifecycle state to `POST /v1/deployment-jobs/{job_id}`.

The repository implements the control plane, API contract, demo inventory, network CLI, and UI. A production endpoint agent and artifact repository are intentionally separate trust components.

## Network administration command

Set the bearer token without putting it in shell history:

```powershell
$env:UIG_ADMIN_SERVER = "https://intentgate.example"
$env:UIG_INGEST_TOKEN = "replace-with-a-secret-from-your-vault"
```

Run discovery and inspect inventory:

```powershell
uig-admin discover
uig-admin inventory
```

Plan a deployment, then explicitly queue it:

```powershell
uig-admin deploy --group engineering --version 0.4.0
uig-admin deploy --group engineering --version 0.4.0 --execute
uig-admin deployments
```

Repeat `--endpoint` to target explicit enrolled endpoint IDs instead of a group. The token is accepted through `--token`, but environment or secret-store injection is preferred.

## API routes

| Route | Purpose |
|---|---|
| `GET /v1/endpoints` | Inventory, security groups, and coverage summary |
| `POST /v1/endpoints/register` | Agent registration and heartbeat |
| `POST /v1/discovery-sessions` | Create an inventory snapshot |
| `GET /v1/deployments` | Recent deployment waves |
| `POST /v1/deployments` | Plan or queue a scoped deployment |
| `GET /v1/deployment-jobs/next` | Fetch the next queued endpoint manifest |
| `POST /v1/deployment-jobs/{id}` | Record endpoint deployment state |

Every route requires the existing bearer token. WAF, rate limiting, enterprise identity, and separate endpoint credentials should be applied before this control plane is exposed beyond a lab.

## Safety properties

- No endpoint accepts arbitrary command text from this API.
- Deployment jobs contain a fixed package/action manifest.
- Planning is the default; queueing requires the explicit `execute` field or `--execute` flag.
- Offline endpoints are deferred rather than treated as successful.
- Every deployment records the selector, requestor, target version, endpoint jobs, timestamps, and state transitions.
- Discovery uses enrolled inventory and heartbeats; it does not perform unauthenticated subnet scanning.

## Production additions

Before real enterprise rollout, add mutual TLS and per-agent identity, signed artifacts and manifests, RBAC with approval separation, maintenance windows, phased rings/canaries, health-based pause and rollback, durable encrypted storage, rate limits, immutable audit export, inventory connectors for Entra ID/Intune/AD/CMDB, and an endpoint service that verifies signatures before installation.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ uig-scan = "intentgate.provenance:main"
uig-service = "intentgate.service:main"
uig-collector = "intentgate.collector:main"
uig-notifier = "intentgate.notifier:main"
uig-admin = "intentgate.admin_cli:main"

[tool.setuptools.packages.find]
where = ["src"]
Expand Down
70 changes: 70 additions & 0 deletions src/intentgate/admin_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from __future__ import annotations

import argparse
import json
import os
import urllib.error
import urllib.parse
import urllib.request


def _request(server: str, token: str, path: str, method: str = "GET", body: object | None = None) -> dict:
payload = json.dumps(body).encode("utf-8") if body is not None else None
request = urllib.request.Request(
server.rstrip("/") + path,
data=payload,
method=method,
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
return json.loads(response.read())
except urllib.error.HTTPError as exc:
try:
detail = json.loads(exc.read()).get("error", exc.reason)
except (json.JSONDecodeError, AttributeError):
detail = exc.reason
raise SystemExit(f"Intent Gate admin request failed: {detail}") from exc


def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="uig-admin", description="Intent Gate network administration client")
parser.add_argument("--server", default=os.environ.get("UIG_ADMIN_SERVER", "http://127.0.0.1:8787"))
parser.add_argument("--token", default=os.environ.get("UIG_INGEST_TOKEN", ""))
sub = parser.add_subparsers(dest="action", required=True)
sub.add_parser("inventory", help="List discovered endpoints and security groups")
sub.add_parser("discover", help="Create an enrolled-endpoint discovery session")
deployments = sub.add_parser("deployments", help="List recent deployment waves")
deployments.add_argument("--limit", type=int, default=20)
deploy = sub.add_parser("deploy", help="Plan or queue the fixed Intent Gate deployment manifest")
deploy.add_argument("--group", help="Target a security group")
deploy.add_argument("--endpoint", action="append", default=[], help="Target an endpoint id; repeat as needed")
deploy.add_argument("--version", default="0.4.0")
deploy.add_argument("--execute", action="store_true", help="Queue jobs; omission creates a dry-run plan")
return parser


def main(argv: list[str] | None = None) -> int:
args = _parser().parse_args(argv)
if not args.token:
raise SystemExit("Set UIG_INGEST_TOKEN or pass --token")
if args.action == "inventory":
result = _request(args.server, args.token, "/v1/endpoints")
elif args.action == "discover":
result = _request(args.server, args.token, "/v1/discovery-sessions", "POST", {"requested_by": "network-cli"})
elif args.action == "deployments":
result = _request(args.server, args.token, f"/v1/deployments?limit={max(1, min(args.limit, 250))}")
else:
result = _request(args.server, args.token, "/v1/deployments", "POST", {
"security_group": args.group,
"endpoint_ids": args.endpoint,
"version": args.version,
"execute": args.execute,
"requested_by": "network-cli",
})
print(json.dumps(result, indent=2))
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading