Metadata-Version: 2.5
Name: restad
Version: 0.2.1
Summary: Read-only REST interface over the msad Active Directory library
Project-URL: Homepage, https://github.com/matteoredaelli/restAD
Project-URL: Issues, https://github.com/matteoredaelli/restAD/issues
Author-email: Matteo Redaelli <matteo.redaelli@gmail.com>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Classifier: Framework :: FastAPI
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: System :: Systems Administration :: Authentication/Directory :: LDAP
Requires-Python: >=3.11
Requires-Dist: fastapi>=0.115
Requires-Dist: msad>=0.12.2
Requires-Dist: pydantic-settings>=2.5
Requires-Dist: pydantic>=2.9
Requires-Dist: uvicorn[standard]>=0.32
Provides-Extra: kerberos
Requires-Dist: msad[kerberos]>=0.12.2; extra == 'kerberos'
Description-Content-Type: text/markdown

# restAD

A **read-only** REST interface over the [`msad`](https://github.com/matteoredaelli/msad)
Active Directory library, built with **FastAPI** + **Pydantic**, managed with
**uv** on **Python 3.14**.

Only LDAP *search* operations are exposed. No write operations (create / modify
/ delete / password reset / group membership changes) are reachable through the
API.

## How configuration is resolved

The effective connection config (an `msad.DomainConfig`) is built in two layers,
with the environment always winning:

1. **msad TOML file** (optional) — `~/.msad.toml` by default, or the path in
   `MSAD_CONFIG_FILE`. The `MSAD_DOMAIN` variable selects which
   `[domains.<name>]` section to use (otherwise the `[defaults] domain`).
2. **`MSAD_*` environment overrides** — applied on top of the TOML values.

This lets you keep non-secret settings in the TOML file (or omit it entirely)
and inject **credentials and any connection parameter at runtime** via the
environment, so secrets never need to live on disk.

### Environment variables

| Variable             | Overrides / effect                                             |
| -------------------- | -------------------------------------------------------------- |
| `MSAD_CONFIG_FILE`   | Path to the msad TOML file (default `~/.msad.toml`).           |
| `MSAD_DOMAIN`        | Which `[domains.<name>]` section to use.                       |
| `MSAD_HOST`          | Directory host (e.g. `dc.example.com`).                        |
| `MSAD_BASE`          | Search base DN (e.g. `dc=example,dc=com`).                     |
| `MSAD_PORT`          | Port (int, e.g. `636`).                                        |
| `MSAD_USE_SSL`       | LDAPS on/off (`true`/`false`/`1`/`0`/`yes`/`no`).              |
| `MSAD_USER`          | Bind user. Setting user **and** password enables simple bind. |
| `MSAD_PASSWORD`      | Bind password.                                                 |
| `MSAD_QUERY_TIMEOUT` | Per-operation receive timeout in seconds (int).               |

`host` and `base` are required (from either the TOML file or the environment).

Authentication:
- Omit `MSAD_USER`/`MSAD_PASSWORD` **and** leave them unset in the TOML →
  Kerberos/SASL bind (run `kinit` first).
- Set both `MSAD_USER` and `MSAD_PASSWORD` → simple user/password bind.

### Server variables

| Variable             | Effect                                                     |
| -------------------- | ---------------------------------------------------------- |
| `RESTAD_HOST`        | HTTP bind host for the `restad` command (default `127.0.0.1`). |
| `RESTAD_PORT`        | HTTP bind port (default `8000`).                           |
| `RESTAD_LAZY_CONNECT`| If true, start even if the directory is unreachable at boot. |
| `RESTAD_DOCS`        | Expose `/docs`, `/redoc` and `/openapi.json` (default `true`). Set to `false` to disable them. |

## Install

```bash
uv sync
```

The `msad` dependency is pulled from PyPI (`msad>=0.9`). Kerberos (SASL/GSSAPI)
authentication is optional: `msad`'s `gssapi` dependency is a native extension
that needs the system Kerberos dev headers to build (e.g. `libkrb5-dev` +
`krb5-config` on Debian/Ubuntu, `krb5-devel` on RHEL/Fedora, `krb5` via
Homebrew on macOS). If you only authenticate with user/password, skip it
entirely; to enable Kerberos, sync the `kerberos` extra instead:

```bash
uv sync --extra kerberos
# or: pip install 'restad[kerberos]'
```

Common developer tasks run through the `Makefile`:

```bash
make sync     # install/refresh the dev environment
make lint     # ruff check + format check
make test     # run the test suite
make build    # build wheel + sdist into dist/
make publish  # upload to PyPI (needs credentials)
```

## Run

Credentials are passed via the environment:

```bash
export MSAD_HOST=dc.example.com
export MSAD_BASE='dc=example,dc=com'
export MSAD_PORT=636
export MSAD_USE_SSL=true
export MSAD_USER='svc_reader'
export MSAD_PASSWORD='s3cr3t'

uv run restad
# or:
uv run uvicorn restad.app:app --host 0.0.0.0 --port 8000
```

Then open the interactive docs at <http://127.0.0.1:8000/docs>.

### Run with uvx

`uvx` runs restAD in a throwaway, isolated environment — no clone or manual
install needed. The connection is still configured entirely through the
environment.

```bash
# From PyPI (once restAD is published):
MSAD_HOST=dc.example.com MSAD_BASE='dc=example,dc=com' \
MSAD_USER=svc_reader MSAD_PASSWORD='s3cr3t' \
  uvx restad

# From a local checkout:
uvx --from . restad

# Straight from the Git repository (no clone):
uvx --from git+https://github.com/matteoredaelli/restAD restad
```

All `MSAD_*` and `RESTAD_*` variables work exactly as above. Because `uvx`
builds `msad` (and its `gssapi` C extension) in a fresh environment, the krb5
development headers must be available on the host (see Install).

## Endpoints (all read-only)

- `GET /health` — connectivity probe (connect/bind test).
- **Users** — `GET /users` (by field), `/users/inactive`, `/users/never-expiring`,
  `/users/expired`, `/users/risks/password-not-required`,
  `/users/risks/kerberos-preauth-not-required`, `/users/risks/not-delegated`,
  `/users/{id}`, `/users/{id}/groups`, `/users/{id}/status`,
  `/users/{id}/disabled`, `/users/{id}/locked`.
- **Computers** — `GET /computers` (by field, optionally
  `exclude_domain_controllers=true`), `/computers/stale`,
  `/computers/domain-controllers`, `/computers/os-summary`, `/computers/{id}`.
- **Groups** — `GET /groups` (search), `/groups/empty`,
  `/groups/security` (optionally `scope=global|domain_local|universal`),
  `/groups/{id}`, `/groups/{id}/members`, `/groups/{id}/members/{user}`
  (membership check).
- **Contacts** — `GET /contacts` (search by cn/mail/displayName).
- **OUs** — `GET /ous` (find), `/ous/contents?ou_dn=...`, `/ous/{id}`.
- **Audit** — `GET /audit/domain`, `/audit/password-policy`,
  `/audit/password-policy/violations`, `/audit/privileged-groups`,
  `/audit/fsmo-roles`, `/audit/trusts`, `/audit/sites`, `/audit/gpos`,
  `/audit/gpos/{id}`, `/audit/fine-grained-password-policies`.

## Notes

- LDAP entries are dynamic (attributes depend on what the directory returns),
  so entries are serialized as open JSON objects inside typed envelopes
  (`count` + `entries`, or `entry`).
- A single LDAP connection is opened during the app lifespan and shared across
  requests; it is unbound at shutdown.
