diff --git a/.env.example b/.env.example index 6ed82d7..c608235 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,8 @@ APP_NAME=FastAPI Template APP_HOST=127.0.0.1 PORT=80 RELOAD=False + +## Docker Compose settings ## # Optional Compose isolation. Give each simultaneous stack a unique project name. # COMPOSE_PROJECT_NAME=fastapi-template-dev # Host-facing URLs and ports used by Docker Compose. diff --git a/.github/workflows/actions-lint.yml b/.github/workflows/actions-lint.yml new file mode 100644 index 0000000..013cd88 --- /dev/null +++ b/.github/workflows/actions-lint.yml @@ -0,0 +1,40 @@ +name: GitHub workflow checks + +on: + push: + paths: + - ".github/workflows/**/*.yml" + pull_request: + paths: + - ".github/workflows/**/*.yml" + +permissions: + contents: read + +jobs: + gitleaks: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + + - name: Scan workflow changes for secrets + uses: gitleaks/gitleaks-action@dcedce43c6f43de0b836d1fe38946645c9c638dc # v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + actionlint: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Lint GitHub Actions workflows + uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..5bba262 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,30 @@ +name: Documentation + +on: + push: + paths: + - "docs/**/*.md" + - ".markdownlint-cli2.yaml" + - ".github/workflows/documentation.yml" + pull_request: + paths: + - "docs/**/*.md" + - ".markdownlint-cli2.yaml" + - ".github/workflows/documentation.yml" + +permissions: + contents: read + +jobs: + markdownlint: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Lint documentation + uses: DavidAnson/markdownlint-cli2-action@8de2aa07cae85fd17c0b35642db70cf5495f1d25 # v24.0.0 + with: + config: .markdownlint-cli2.yaml + globs: "docs/**/*.md" diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index a434d51..3bf5e34 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -17,7 +17,7 @@ on: - "sonar-project.properties" - ".sonarcloud.properties" - ".github/dependabot.yml" - - ".github/workflows/**.yml" + - ".github/workflows/workflow.yml" pull_request: paths: - "**.py" @@ -34,7 +34,7 @@ on: - "sonar-project.properties" - ".sonarcloud.properties" - ".github/dependabot.yml" - - ".github/workflows/**.yml" + - ".github/workflows/workflow.yml" env: REGISTRY: ghcr.io diff --git a/.markdownlint-cli2.yaml b/.markdownlint-cli2.yaml new file mode 100644 index 0000000..7f38084 --- /dev/null +++ b/.markdownlint-cli2.yaml @@ -0,0 +1,6 @@ +config: + default: true + MD013: false + MD033: + allowed_elements: + - br diff --git a/docs/environment-variables.md b/docs/environment-variables.md new file mode 100644 index 0000000..bd8cab4 --- /dev/null +++ b/docs/environment-variables.md @@ -0,0 +1,112 @@ +# Environment Variables + +`.env.example` is the reference for the root `.env` file. Replace any applicable `` value before use. + +**Required by** describes when a value must be supplied. Compose injects several application values itself, so host-run and Compose requirements differ. + +## Application + +| Key | Required by | Meaning | +| ---------- | ----------- | -------------------------------------------------------------- | +| `APP_NAME` | Optional | Name shown in FastAPI metadata. | +| `APP_HOST` | Optional | Host interface for a host-run API. Default: `127.0.0.1`. | +| `PORT` | Optional | API listening port. Default: `80`. | +| `RELOAD` | Optional | Enables Uvicorn reload for host development. Default: `False`. | + +## Compose + +| Key | Required by | Meaning | +| ------------------------- | ----------- | -------------------------------------------------------------------------------------- | +| `COMPOSE_PROJECT_NAME` | Optional | Isolates Compose containers, networks, volumes, and default image names. | +| `APP_PORT` | Optional | Host port mapped to the API. Default: `80`. | +| `APP_PUBLIC_URL` | Optional | Browser-visible API origin used for the Swagger callback. Default: `http://localhost`. | +| `KEYCLOAK_PORT` | Optional | Host port mapped to Keycloak. Default: `8080`. | +| `OIDC_PUBLIC_URL` | Optional | Browser-visible Keycloak origin and token issuer. Default: `http://localhost:8080`. | +| `APP_IMAGE` | Optional | Local API image name. | +| `MIGRATION_IMAGE` | Optional | Local migration image name. | +| `POSTGRES_PASSWORD` | **Compose** | Password for the local PostgreSQL service. | +| `KEYCLOAK_ADMIN_PASSWORD` | **Compose** | Password for the local `admin` account and configuration service. | + +A minimal Compose `.env` needs only: + +```dotenv +POSTGRES_PASSWORD=local-postgres +KEYCLOAK_ADMIN_PASSWORD=local-keycloak +``` + +## Database + +| Key | Required by | Meaning | +| ------------------------ | ------------ | ----------------------------------------------------------------------- | +| `DATABASE_URL` | **Host run** | PostgreSQL connection URL. Compose creates its own internal URL. | +| `DATABASE_POOL_SIZE` | Optional | Number of persistent connections in the host-run pool. Default: `5`. | +| `DATABASE_MAX_OVERFLOW` | Optional | Extra temporary connections allowed above the pool size. Default: `10`. | +| `DATABASE_ECHO` | Optional | Logs generated SQL when enabled. Default: `False`. | +| `DATABASE_POOL_PRE_PING` | Optional | Checks pooled connections before reuse. Default: `True`. | + +## OIDC + +| Key | Required by | Meaning | +| ----------------------------- | ------------------- | ------------------------------------------------------------------------------------------------- | +| `OIDC_ISSUER_URL` | **Host run** | Public realm URL used for discovery and issuer validation. | +| `OIDC_INTERNAL_URL` | Optional | Internal URL used to fetch discovery metadata and signing keys while retaining the public issuer. | +| `OIDC_CLIENT_ID` | **Host run** | Audience expected in API access tokens. | +| `OIDC_DOCS_CLIENT_ID` | **Host run** | Public PKCE client used by Swagger UI. | +| `OIDC_CLIENT_SECRET` | Scenario tests only | Confidential client credential; the main API and Swagger UI do not use it. | +| `OIDC_JWKS_CACHE_TTL_SECONDS` | Optional | Signing-key cache duration in seconds. Default: `300`. | + +Compose supplies the issuer and client IDs for its local realm. + +## OIDC discovery overrides + +These keys are optional as a group. If one is set, all four must be set; partial overrides are rejected at startup. + +| Key | Meaning | +| ----------------------------- | -------------------------------------------------- | +| `OIDC_JWKS_URI` | Signing-key endpoint used by the API. | +| `OIDC_ISSUER` | Exact issuer expected in tokens. | +| `OIDC_AUTHORIZATION_ENDPOINT` | Browser authorization endpoint used by Swagger UI. | +| `OIDC_TOKEN_ENDPOINT` | Token endpoint used by Swagger UI. | + +The overrides bypass network discovery. They are mainly useful for tests, fixed infrastructure, or air-gapped environments. + +## Copy-ready local `.env` + +This example works with the default Docker Compose stack. The values are local-development credentials and must not be reused in production. + +```dotenv +## Application settings ## +APP_NAME=FastAPI Template +APP_HOST=127.0.0.1 +PORT=80 +RELOAD=False + +## Docker Compose settings ## +COMPOSE_PROJECT_NAME=fastapi-template-dev +APP_PORT=80 +APP_PUBLIC_URL=http://localhost +KEYCLOAK_PORT=8080 +OIDC_PUBLIC_URL=http://localhost:8080 +APP_IMAGE=fastapi-template:local +MIGRATION_IMAGE=fastapi-template-migration:local + +POSTGRES_PASSWORD=local-postgres +KEYCLOAK_ADMIN_PASSWORD=local-keycloak-admin + +## Database settings ## +# Used by a host-run API; Compose injects its own internal database URL. +DATABASE_URL=postgresql+psycopg://postgres:local-postgres@localhost:5432/fastapi_db +DATABASE_POOL_SIZE=5 +DATABASE_MAX_OVERFLOW=10 +DATABASE_ECHO=False +DATABASE_POOL_PRE_PING=True + +## OIDC / Authentication settings ## +OIDC_ISSUER_URL=http://localhost:8080/realms/fastapi-realm +OIDC_CLIENT_ID=fastapi-client +OIDC_DOCS_CLIENT_ID=fastapi-docs +OIDC_CLIENT_SECRET=change-me +OIDC_JWKS_CACHE_TTL_SECONDS=300 +``` + +Save the copied block as `.env` in the repository root. The four discovery override keys can remain unset for the local stack. diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 0000000..a40336a --- /dev/null +++ b/docs/local-development.md @@ -0,0 +1,60 @@ +# Local Development + +The project supports a host-run application and a complete Docker Compose stack. Keycloak details are documented separately in [Local Keycloak](local-keycloak.md). Environment keys are listed in [Environment Variables](environment-variables.md). + +## Host-run application + +`make run` applies Alembic migrations and then starts FastAPI using `.env`: + +```text +Alembic migration → FastAPI process +``` + +PostgreSQL and the configured OIDC provider must already be reachable. Reloading is controlled by `RELOAD`; it is disabled in `.env.example`. + +## Compose stack + +The local stack has five services. Solid arrows are startup gates; dotted arrows are runtime connections: + +```mermaid +flowchart LR + db["db
PostgreSQL"] -->|healthy| migrate["migrate
apply schema, then exit"] + migrate -->|completed| app["app
FastAPI API"] + + keycloak["keycloak
local identity"] -->|healthy| config["keycloak-config
set Swagger callback, then exit"] + config -->|completed| app + + db -.->|SQL| app + keycloak -.->|OIDC and keys| app +``` + +- `db` is PostgreSQL and stores data in a named volume. +- `migrate` applies Alembic migrations and exits. +- `keycloak-config` adjusts the local browser callback and exits. +- `app` starts only after the database, migration, and identity services are ready. + +`POSTGRES_PASSWORD` and `KEYCLOAK_ADMIN_PASSWORD` are required Compose values. The application database and OIDC addresses are supplied by Compose, so their container-facing hostnames differ from host-run values. + +The application source is copied into its image rather than mounted. Code changes therefore require an image rebuild. This makes the local container match the image tested by CI. + +## Images and state + +The application and migrations use separate images. The application image contains runtime code; the migration image contains Alembic and its migration-only dependencies. This keeps schema changes explicit instead of running them during application startup. + +PostgreSQL data survives a normal `docker compose down`. Removing volumes resets it: + +```bash +docker compose down -v +``` + +## Isolated stacks + +`COMPOSE_PROJECT_NAME` separates container names, networks, volumes, and default image tags. Parallel stacks must also use distinct host ports and public URLs: + +```text +APP_PORT, APP_PUBLIC_URL, KEYCLOAK_PORT, OIDC_PUBLIC_URL +``` + +`APP_PUBLIC_URL` and `OIDC_PUBLIC_URL` are externally visible addresses. Compose service names such as `db` and `keycloak` are internal addresses and do not change with host ports. + +The Compose stack is development infrastructure. Production deployments use separately managed databases, identity services, networking, secrets, and migration jobs. diff --git a/docs/local-keycloak.md b/docs/local-keycloak.md new file mode 100644 index 0000000..9f80560 --- /dev/null +++ b/docs/local-keycloak.md @@ -0,0 +1,33 @@ +# Local Keycloak + +The Compose stack runs Keycloak 26.7 in development mode. It is local test infrastructure, not a production Keycloak configuration. + +## Realm + +`docker-compose.yml` starts Keycloak with `start-dev --import-realm` and imports `.docker/realm-export.json` as `fastapi-realm`. + +Keycloak has no persistent data volume. Recreating the container resets it to the committed realm export. This keeps fresh local stacks reproducible. + +## Clients + +| Client | Purpose | Configuration | +| ---------------- | ------------------ | ------------------------------------------------------------- | +| `fastapi-docs` | Swagger UI login | Public authorization-code client with PKCE; no browser secret | +| `fastapi-client` | API token audience | Confidential local client; the API validates its audience | + +The `keycloak-config` one-shot service replaces the Swagger redirect URI and web origin with the current `APP_PUBLIC_URL`. Exact URLs are used because wildcard browser callbacks are unsafe and break isolated stacks on alternate ports. + +The browser uses the host-facing issuer, such as `http://localhost:8080`. The API fetches Keycloak metadata and keys through the internal Compose address, `http://keycloak:8080`. Tokens still retain the public issuer. + +## Local identities + +- Keycloak administrator: `admin`; its password comes from `KEYCLOAK_ADMIN_PASSWORD`. +- Local user: `testuser` / `testuser@example.com`. + +`testuser` has no initial password. The local browser flow accepts the known username, skips the absent password check, and immediately requires the user to create a password. Recreating Keycloak removes that password. + +This password-claiming behavior is intentional local convenience. It must not be copied into a production realm. + +## Scenario-test realm + +`docker-compose.e2e.yml` replaces the normal import with `.docker/e2e-realm-export.json`. That realm is `fastapi-e2e-realm` and contains the fixed `e2e-user` identity used by automated scenario tests. Its credentials are test fixtures only. diff --git a/reports/open-github-issues-audit.html b/reports/open-github-issues-audit.html new file mode 100644 index 0000000..833bb8a --- /dev/null +++ b/reports/open-github-issues-audit.html @@ -0,0 +1,48 @@ + + + + + +Open GitHub Issues Audit — FastAPI Template + + +
+
Live issue audit · main @ 9e5b527 · updated 11 July 2026

GitHub issues: implementation status

Evidence-based review of the 26 issues originally audited in Subhransu-De/FastAPI-Template. Seven completed issues have since been closed, leaving 19 open. “Partially done” means meaningful criteria are present; “Still to work on” means no substantive issue-level implementation was found.

+
19issues still open
7closed as done
3partially done
16still to work on
+
+

Done

+
CI workflow for quality and build checks
Done
.github/workflows/workflow.yml runs on pushes/PRs, installs with uv, runs Ruff, Ty, tests, and Docker builds. README.md links the live workflow badge.
+
Move migrations out of FastAPI startup
Done
app/main.py no longer runs Alembic. Explicit migration paths exist in Makefile, Dockerfile.migration, and the Compose migrate service; README explains pre-start/release usage.
+
Remove Swagger confidential client secret
Done
app/main.py configures client ID, scopes, and PKCE without a browser secret. tests/unit/test_main.py verifies this; README documents the public docs client.
+
Remove import-time OIDC network I/O
Done · closed
AuthNSettings construction is network-free. Async discovery lives in resolve_oidc_metadata() and is awaited from the FastAPI lifespan in app/main.py.
+
Harden Docker image
Done · closed
The multi-stage uv image runs as a non-root user and builds in CI. The issue was revised to delegate production health policy to ALB/ECS or Kubernetes configuration rather than requiring a Dockerfile HEALTHCHECK; endpoint work remains tracked in #18 and #77.
+
Supply-chain security gates
Done · closed
.github/workflows/workflow.yml now runs Python dependency audits, Trivy container-image scans, Hadolint, actionlint, dependency review, and Gitleaks. The Snyk repository integration remains connected and its live badge reports that the project is monitored. The latest pull-request and main workflow runs passed.
+
Reproducible local Keycloak
Done · closed
Committed realm exports are mounted by Compose, exact Swagger callbacks are configured automatically, and an isolated fresh-stack test verified the API, OIDC discovery, imported user, clients, migrations, and one-shot configuration. docs/local-keycloak.md documents identities, clients, reset behavior, internal/public URLs, and local-only quirks; docs/local-development.md explains the rest of the local stack.
+
+

Partially done

+
Observability expectations
Partial
app/telemetry.py instruments FastAPI and SQLAlchemy, excludes health traffic, and JSON logging exists.
+
Expand README into production documentation
Partial
README covers local/Docker setup, tests, migrations, OIDC basics, and the real CI badge.
+
Full ECS Fargate Terraform deployment
Partial
A generic API image, migration image, health endpoint, and brief ECS migration note are reusable prerequisites.
+
+

Still to be worked on

+
Production server and reverse-proxy guidance
Still to work on
Only basic host/port/reload Uvicorn settings exist. Worker/replica strategy, trusted forwarded headers, HTTPS termination, graceful shutdown, and timeout guidance are absent.
+
Authorization foundation
Still to work on
Authentication exposes raw claims, but there is no normalized identity, reusable role/scope dependency, example permissioned route, distinct 403 behavior, or authorization test suite.
+
Configurable security middleware
Still to work on
No configurable CORS, TrustedHost, correlation ID, or security-header middleware and no representative tests were found.
+
Split liveness and readiness checks
Still to work on
Only GET /health exists. There are no /health/live or DB-backed /health/ready routes, aliases, healthcheck updates, or split tests.
+
Rate limiting and abuse protection
Still to work on
No app rate limiter or infrastructure enforcement guidance, example limits, tests, or OWASP resource-consumption discussion was found.
+
Request size, timeout, and resource limits
Still to work on
No max body/upload policy, server/proxy timeout contract, worker memory/concurrency guidance, or matching deployment examples exist.
+
Production configuration validation
Still to work on
There is no explicit production mode or validator rejecting placeholders, local-only URLs, or missing security settings; tests and docs are also absent.
+
API versioning convention
Still to work on
Business routes remain directly under /entities; no /api/v1 convention, updated tests/OpenAPI grouping, or future-version documentation exists.
+
Governance files
Still to work on
No LICENSE, SECURITY.md, or CONTRIBUTING.md exists, and README cannot link to them.
+
Resource extension workflow and shared test helpers
Still to work on
No vertical-slice extension guide; API assertions remain local to test_entities_api.py; service fixtures remain duplicated; Makefile bootstrap is POSIX-specific with no PowerShell alternative.
+
ECS Express Mode Terraform
Still to work on
No infra/ directory, Terraform files, ECS Express resources, outputs, or deployment/cost documentation exists.
+
Lambda Web Adapter Terraform
Still to work on
No Lambda Terraform, Web Adapter setup, secrets integration, outputs, or Lambda caveat documentation exists.
+
Lambda with Mangum and API Gateway
Still to work on
No Mangum dependency/handler and no Lambda, API Gateway, IAM, logging, outputs, or deployment documentation exists.
+
Lightsail Container Service Terraform
Still to work on
No Lightsail Terraform, configurable service setup, outputs, secret limitations, image publishing guide, or ECS comparison exists.
+
Elastic Beanstalk Docker Terraform
Still to work on
No EB Terraform, source bundle/Dockerrun setup, configurable environment, outputs, or deployment/verification guide exists.
+
Actuator-style health and build information
Still to work on
Only lowercase {"status":"up"} at /health exists. All four Actuator routes, readiness dependency checks, package metadata, OCI build provenance, policies, tests, and docs remain absent.
+
+
Method: fetched issues with GitHub CLI, checked each acceptance criterion against the current default branch, and cited repository evidence. Closed as completed: #11, #12, #13, #16, and #57. Classification reflects repository state—not unmerged branches or external deployments.
+
\ No newline at end of file