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
8 changes: 4 additions & 4 deletions .github/SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This project provides a reusable ASP.NET Core application template. Support is f

## Maintenance Posture

The `2.x` line reached feature completion at release `2.10.0`.
The `2.x` line reached feature completion at release `2.10.0`, which is the latest published release. From `2.10.0` onward, the `2.x` line accepts only the changes listed as in scope below.

**In scope for future releases**

Expand Down Expand Up @@ -61,13 +61,13 @@ Users should not expect:
- Backports to every historical release line.
- Support for heavily modified downstream applications unless the issue reproduces from the template baseline.
- Support for unsupported .NET SDK versions or package versions outside the documented release line.
- New features, options, or configuration surfaces in the `2.x` line.
-
- New features, options, or configuration surfaces in the `2.x` line after `2.10.0`.

## Version Support Lifecycle

| Version line | Support expectation |
|:---|:---|
| `2.x` | Current stable line, feature-complete as of `2.10.0`. Supported for security fixes, dependency servicing, reproducible defects, and documentation fixes. |
| `2.x` | Current stable line; latest release `2.10.0`. Feature-complete from `2.10.0` onward. Supported for security fixes, dependency servicing, reproducible defects, and documentation fixes. |
| `1.0.x` | Legacy stable line under the previous NuGet package identity. Best effort unless a release note states otherwise. |
| Pre-1.0 releases | Best effort only. Consumers should upgrade to the current stable release when practical. |
| Older stable releases after a newer minor or major release | Best effort unless a release note states otherwise. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"UseGlobalLimiter": true,
"UseSharedUnknownClientPartition": false,
"UnknownClientPartitionKey": "unknown-client",
"IPv6PartitionPrefixLength": 64,
"GlobalFixedWindow": {
"PermitLimit": 60,
"WindowSeconds": 60,
Expand All @@ -100,7 +101,8 @@
},
"ConcurrencyPolicy": {
"PermitLimit": 10,
"QueueLimit": 0
"QueueLimit": 0,
"PartitionByClient": true
}
},
"RequestLogging": {
Expand Down Expand Up @@ -229,6 +231,9 @@
"StorageMode": "Local"
}
},
"HealthChecks": {
"DatabaseReadinessCheckEnabled": true
},
"ApiVersioning": {
"DefaultMajorVersion": 1,
"DefaultMinorVersion": 0,
Expand Down
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ This project follows Semantic Versioning using the format `MAJOR.MINOR.PATCH`.
* Added a fail-closed release evidence manifest and automated asset validation.
* Added a dated decision record for deferred NuGet package signing with an
assigned owner, mandatory review date, and re-evaluation criteria.
* Added an `application-database` readiness check, tagged `ready` and `database`, that reports whether the application database accepts connections. It is registered when EF Core data access is enabled. `ProjectTemplate:HealthChecks:DatabaseReadinessCheckEnabled` (default `true`) is read each time the check runs; when it is `false`, the check reports `Healthy` without contacting the database. Previously `/health/ready` ran no checks and always reported `Healthy`.
* Added the anonymous `/health/audit-integrity` endpoint, which runs checks tagged `audit`.
* Added `ApplicationAuditReconciliationOptions.HealthStaleRunThreshold`. It defaults to three times `Interval` and must be greater than `Interval`.
* Added `ProjectTemplate:RateLimiting:IPv6PartitionPrefixLength` (default `64`, valid range 1–128) and `ProjectTemplate:RateLimiting:ConcurrencyPolicy:PartitionByClient` (default `true`).

### Changed

Expand Down Expand Up @@ -112,6 +116,15 @@ This project follows Semantic Versioning using the format `MAJOR.MINOR.PATCH`.
* Relocated community, governance, maintainer, support, release, and asset-notice documents to `.github/` and consolidated overlapping community and maintainer files. Content is unchanged; GitHub resolves community health files from `.github/` identically to the repository root.
* Reordered the README so installation commands and the default security posture precede project goals, and consolidated the AsiBackbone boundary and documentation-ownership sections into a single related-projects block.

* **Behavior change for adopters using audit reconciliation:** `application-audit-integrity` is now tagged `audit` and `integrity` instead of `ready`, `audit`, and `integrity`, and is served by `/health/audit-integrity`. A critical audit finding no longer removes every replica from load balancing through `/health/ready`. Move audit alerting to `/health/audit-integrity`. `/health` still runs every registered check.
* **Behavior change for adopters using audit reconciliation:** the audit integrity check now reports `Degraded` when reconciliation has not completed a run in the current process, or when its last successful run is older than `HealthStaleRunThreshold`. Previously a stopped or failing worker left the check reading zero findings and `Healthy` indefinitely. Freshness is evaluated only when `RunWorker` is `true`.
* **Behavior change for adopters:** `/health/ready` now fails when EF Core data access is enabled and the database is unreachable. For SQLite, a database file that has not been created yet counts as unreachable, so apply migrations before a local instance reports ready. Set `ProjectTemplate:HealthChecks:DatabaseReadinessCheckEnabled` to `false` when another component owns database readiness.
* **Behavior change for adopters:** IPv6 clients are now rate limited by /64 prefix rather than by full address, so rotating addresses within one prefix no longer bypasses per-client limits. IPv4 and IPv4-mapped IPv6 clients are still limited per IPv4 address. Set `IPv6PartitionPrefixLength` to `128` to restore per-address partitioning.
* **Behavior change for adopters using the `concurrency` policy:** permits are now partitioned by endpoint and client instead of by endpoint only, so one client can no longer exhaust an endpoint's concurrency for every other client. Set `ConcurrencyPolicy:PartitionByClient` to `false` to restore one shared pool per endpoint.
* **Behavior change for adopters:** a configured `ExcludedPathPrefixes` list for security headers or request logging now replaces the code defaults instead of being appended to them, so configuration can remove a default exclusion. Blank entries are ignored, which lets a later configuration source remove an inherited entry by setting its index to an empty string; a blank entry therefore no longer fails options validation. Previously the shipped `appsettings.json` values were appended to identical code defaults, producing duplicates, and no default could be removed through configuration.
* **Behavior change for adopters:** the authentication cookie is now named `__Host-ProjectTemplate.Web.Authentication` with `Path=/` whenever it is always `Secure`, binding the session to the exact issuing host. Existing sessions end once after upgrading because the cookie name changes. The Development-only `AllowInsecureHttp` override keeps the unprefixed `.ProjectTemplate.Web.Authentication` name.
* Updated `.github/SUPPORT.md` to reflect published release `2.10.0` as the current stable and feature-complete `2.x` baseline.

### Fixed

* Backfilled release `2.9.0` from its retained original container evidence and exact public NuGet package, with regenerated evidence explicitly labeled.
Expand Down
10 changes: 6 additions & 4 deletions docs/articles/audit-reconciliation.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ Finding keys are deterministic for a reason, mutation batch, and destination. Re

## Health checks

Registration adds `application-audit-integrity` with the tags `ready`, `audit`, and `integrity`. The existing `/health/ready` endpoint includes the check.
Registration adds `application-audit-integrity` with the tags `audit` and `integrity`. The `/health/audit-integrity` endpoint runs the check, and `/health` includes it with every other registered check. It is deliberately not tagged `ready`: an integrity finding needs operator review, and a readiness failure would take every replica out of load balancing at the same time. Alert on `/health/audit-integrity` rather than routing traffic on it.

The check returns:

- **Healthy** when no open finding crosses configured thresholds.
- **Degraded** when warning-level findings, stale delivery, or dead letters require attention.
- **Unhealthy** when a critical finding or manifest verification failure exists, or the open-finding threshold is reached.
- **Healthy** when no open finding crosses configured thresholds and reconciliation has run recently.
- **Degraded** when warning-level findings, stale delivery, or dead letters require attention, or when reconciliation has not completed a run in this process or its last successful run is older than `HealthStaleRunThreshold`.
- **Unhealthy** when a critical finding or manifest verification failure exists, or the open-finding threshold is reached. This takes precedence over staleness.

`HealthStaleRunThreshold` defaults to three times `Interval` (15 minutes with the default 5-minute interval) and must be greater than `Interval`. The last successful run time is tracked in the process that runs the scheduled loop, so freshness is evaluated only when `RunWorker` is `true`. With `RunWorker` set to `false`, the check reports `reconciliationFreshnessTracked: false` and relies on the external scheduler's own monitoring. Because the worker runs immediately at startup, a freshly started instance reports Degraded only until its first successful run.

Health data is minimized to counts, ages, and timestamps. It never exposes audited values or unrestricted exception text.

Expand Down
1 change: 1 addition & 0 deletions docs/articles/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ By default:
- `ProjectTemplate:Authentication:Enabled` is `true`.
- The default authenticate, challenge, and sign-in schemes use `Cookies`.
- Cookie authentication is enabled to store an authenticated session after a configured sign-in flow succeeds.
- The session cookie is named `__Host-ProjectTemplate.Web.Authentication`, with `HttpOnly`, `Secure`, `SameSite=Lax`, `Path=/`, and no `Domain`. Browsers accept a `__Host-` cookie only under those conditions, which binds the session to the exact host that issued it. When the Development-only `ProjectTemplate:Authentication:Cookie:AllowInsecureHttp` override is active, the cookie can be sent over HTTP, so it is named `.ProjectTemplate.Web.Authentication` without the prefix.
- External providers such as OpenID Connect, SAML2, Microsoft, Google, and GitHub are disabled.

The default scaffold does not include ASP.NET Core Identity, local user accounts, a credential form, a seeded user, or an enabled external provider. Cookie authentication does not authenticate credentials by itself. Consequently, the default `/Account/Login` page has no sign-in action and protected routes remain unavailable to anonymous users until the consuming application enables an external provider or supplies its own identity flow. The login page states this condition explicitly instead of presenting the cookie session handler as a local login provider.
Expand Down
31 changes: 22 additions & 9 deletions docs/articles/health-checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,27 @@ app.MapApplicationHealthChecks();
| `/health` | General application health endpoint. |
| `/health/ready` | Readiness endpoint intended for dependency-aware checks such as database, cache, or external service availability. |
| `/health/live` | Liveness endpoint intended to verify that the application process can respond. |
| `/health/audit-integrity` | Audit integrity endpoint. Runs only checks tagged `audit`, such as the optional audit reconciliation check. |

The baseline application provides the readiness endpoint shape. It does not, by itself, prove database, cache, queue, or external service availability.
When EF Core data access is enabled, readiness includes the `application-database` check, which reports whether the application database accepts connections. The check is tagged `ready` and `database` and is registered only when the data access provider is not `None`. For file-backed SQLite, the check verifies that the configured database file already exists before opening the connection; it never creates a missing database as a side effect of readiness. Apply migrations before expecting a local instance to report ready.

The generated template does not add database, cache, queue, or external-service readiness checks automatically. Consuming applications must register the tagged dependency checks that define production readiness for their service.
The database readiness check can be turned off when another component already owns database readiness. The setting is read each time the check runs; when it is `false`, the registered check reports `Healthy` without contacting the database:

```json
"ProjectTemplate": {
"HealthChecks": {
"DatabaseReadinessCheckEnabled": false
}
}
```

The template does not add cache, queue, or external-service readiness checks. Consuming applications must register any additional tagged dependency checks that define production readiness for their service.

Audit integrity is intentionally kept out of readiness. An integrity finding needs operator review, but it does not stop an instance from serving traffic, and a failing readiness check would remove every replica from load balancing at the same moment. Alert on `/health/audit-integrity` instead. That endpoint returns `200` for `Healthy` and `Degraded` and `503` for `Unhealthy`. Because `/health` runs every registered check, it also reflects audit integrity; do not use `/health` as a load-balancer readiness probe.

## Access and Deployment Boundary

All three health endpoints are mapped with `.AllowAnonymous()` intentionally. This keeps container, reverse-proxy, load-balancer, and orchestration probes independent of browser login state and prevents the authenticated fallback policy from turning a failed probe into an authentication redirect.
All four health endpoints are mapped with `.AllowAnonymous()` intentionally. This keeps container, reverse-proxy, load-balancer, and orchestration probes independent of browser login state and prevents the authenticated fallback policy from turning a failed probe into an authentication redirect.

Anonymous application access does not imply unrestricted Internet exposure. Production deployments should restrict health endpoint reachability through the deployment boundary appropriate to the environment, such as:

Expand All @@ -49,7 +62,7 @@ Anonymous application access does not imply unrestricted Internet exposure. Prod

Avoid returning secrets, configuration values, dependency connection details, exception messages, or other sensitive diagnostics from health responses. Applications that require authenticated health diagnostics should add a separate protected diagnostics endpoint rather than changing the lightweight liveness contract accidentally.

When the application starts in the `Production` environment, it emits one structured warning identifying `/health`, `/health/ready`, and `/health/live` as anonymously mapped routes. The warning does not mean anonymous health probes are inherently unsafe; it is an operational signal reminding the deployment operator to confirm that reverse-proxy, ingress, firewall, or service-mesh routing exposes those endpoints only as intended.
When the application starts in the `Production` environment, it emits one structured warning identifying `/health`, `/health/ready`, `/health/live`, and `/health/audit-integrity` as anonymously mapped routes. The warning does not mean anonymous health probes are inherently unsafe; it is an operational signal reminding the deployment operator to confirm that reverse-proxy, ingress, firewall, or service-mesh routing exposes those endpoints only as intended.

Development does not emit this health-route warning. The diagnostic is startup-only and does not add request-path log noise.

Expand All @@ -75,14 +88,14 @@ Should this application instance receive normal traffic?

Only checks tagged `ready` are included in the readiness endpoint. This keeps dependency-aware readiness separate from process liveness.

Example future readiness check:
Example additional readiness check:

```csharp
builder.Services
.AddHealthChecks()
.AddCheck<DatabaseHealthCheck>(
"database",
tags: new[] { "ready" });
.AddCheck<CacheHealthCheck>(
"cache",
tags: [ApplicationHealthCheckTags.Ready]);
```

Use readiness for dependencies that should remove an instance from rotation when unavailable, such as required database connectivity or a required local cache. Avoid adding optional integrations unless the application cannot serve useful traffic without them.
Expand Down Expand Up @@ -122,7 +135,7 @@ The default security header configuration excludes `/health`:
]
```

Because the exclusion is prefix-based, `/health`, `/health/ready`, and `/health/live` are all excluded from the security header middleware, except `X-Content-Type-Options: nosniff`. `Strict-Transport-Security` is registered separately and still applies to HTTPS health responses outside Development. This keeps health probe responses small and infrastructure-friendly.
Because the exclusion is prefix-based, `/health`, `/health/ready`, `/health/live`, and `/health/audit-integrity` are all excluded from the security header middleware, except `X-Content-Type-Options: nosniff`. `Strict-Transport-Security` is registered separately and still applies to HTTPS health responses outside Development. This keeps health probe responses small and infrastructure-friendly.

## Contract References

Expand Down
Loading
Loading