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
10 changes: 10 additions & 0 deletions docs/diagnostics/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ All `trogon.eventstore.*` instruments are development semantic conventions. Attr

The failure, unimplemented, and deadline-exceeded counters mirror distinct diagnostic events. They are separate instruments because the source events can overlap for one call and therefore must not be summed as mutually exclusive outcomes.

### Password authentication

| Instrument | Kind | Unit | Attributes | Description |
| --- | --- | --- | --- | --- |
| `trogon.eventstore.authentication.password.admitted` | Counter | `{attempt}` | None | Password authentication attempts admitted for processing, regardless of authentication outcome |
| `trogon.eventstore.authentication.password.rejected` | Counter | `{attempt}` | `trogon.eventstore.authentication.password.rejection.reason` | Attempts rejected because the `rate` or `concurrency` limit was exhausted |
| `trogon.eventstore.authentication.password.active` | UpDownCounter | `{attempt}` | None | Admitted password authentication attempts still being processed |

These node-local admission metrics do not identify users, client addresses, or credentials. Rejections describe exhausted capacity, not invalid passwords.

### Queues

| Instrument | Kind | Unit | Attributes | Description |
Expand Down
36 changes: 36 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,42 @@ making the database authentication method explicit, so password and OAuth access
Authentication is applied to all HTTP endpoints by default, except `/-/liveness`, `/-/readiness`, static web
content, and redirects.

### Password authentication admission limits

Built-in password authentication shares a node-local admission budget across UI sign-in, HTTP and gRPC
credentials, TCP authentication, and forwarded credentials. Cached credentials still require password
verification and use the same budget. Attempts are admitted before account reads, including requests
for nonexistent accounts, so changing usernames cannot bypass the node-wide limit.

| Setting | Default | Purpose |
|:--------|--------:|:--------|
| `Auth:Password:MaxConcurrentAttempts` | 4 | Maximum simultaneous account reads and password checks. |
| `Auth:Password:AttemptsPerSecond` | 100 | Attempt tokens replenished each second. |
| `Auth:Password:BurstSize` | 200 | Maximum accumulated attempt tokens. |

All values must be positive, and `BurstSize` must be at least `AttemptsPerSecond` so the bucket can
hold a full second's replenishment. Invalid limits prevent the password provider from starting.
There is no waiting queue. When either budget is exhausted, requests
receive the existing authentication-not-ready response: HTTP returns `503` with `Retry-After`, gRPC
returns `Unavailable`, TCP returns `NotReady`, and browser sign-in reports that the provider is not
ready. Clients should use bounded retries with backoff and jitter.

Limits are shared by all password users on a node, not per account or per IP address. They do not lock
accounts or provide distributed brute-force protection. Use ingress abuse controls and network access
restrictions as well. Size the limits under representative load; cached API password checks also count,
and each node has an independent budget. Changing these settings requires a restart.

Custom authentication plugins own their admission controls. Certificate authentication, OAuth validation,
and validation of established UI sessions do not consume the password budget. Cancelling a caller does not
stop an account read or password hash already in progress. Its concurrency permit remains held until the
operation completes or the account read times
out, so cancellation cannot allow more password work than the configured limit.

The `EventStore.Core` meter exports `trogon.eventstore.authentication.password.admitted`,
`trogon.eventstore.authentication.password.rejected`, and `trogon.eventstore.authentication.password.active`.
Rejections distinguish rate exhaustion from concurrency exhaustion without recording usernames,
credentials, or client addresses.

### Management UI sessions

Browser sign-in uses ASP.NET Core cookie authentication with a protected session identifier. Passwords are
Expand Down
37 changes: 37 additions & 0 deletions otel/semconv/registry/trogon/eventstore/metrics.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ groups:
stability: development
brief: Attributes used by TrogonEventStore metrics.
attributes:
- id: trogon.eventstore.authentication.password.rejection.reason
type:
members:
- id: rate
value: rate
stability: development
brief: The password authentication rate limit was exhausted.
- id: concurrency
value: concurrency
stability: development
brief: All password authentication concurrency permits were in use.
stability: development
brief: The exhausted password authentication admission limit.
- id: trogon.eventstore.activity.name
type: string
stability: development
Expand Down Expand Up @@ -105,6 +118,30 @@ groups:
brief: Load average sampling period.
examples: [1m, 5m, 15m]

- id: metric.trogon.eventstore.authentication.password.admitted
type: metric
stability: development
brief: Number of password authentication attempts admitted for processing.
metric_name: trogon.eventstore.authentication.password.admitted
instrument: counter
unit: "{attempt}"
- id: metric.trogon.eventstore.authentication.password.rejected
type: metric
stability: development
brief: Number of password authentication attempts rejected by admission limits.
metric_name: trogon.eventstore.authentication.password.rejected
instrument: counter
unit: "{attempt}"
attributes:
- ref: trogon.eventstore.authentication.password.rejection.reason
requirement_level: required
- id: metric.trogon.eventstore.authentication.password.active
type: metric
stability: development
brief: Number of admitted password authentication attempts still being processed.
metric_name: trogon.eventstore.authentication.password.active
instrument: updowncounter
unit: "{attempt}"
- id: metric.trogon.eventstore.component.status
type: metric
stability: development
Expand Down
2 changes: 1 addition & 1 deletion src/EventStore.ClusterNode/ClusterVNodeHostedService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ AuthenticationProviderFactory GetAuthenticationProviderFactory()
var authenticationMethodFactories = new Dictionary<string, AuthenticationProviderFactory> {
{
AuthenticationMethodNames.Password, new AuthenticationProviderFactory(components =>
new InternalAuthenticationProviderFactory(components, _options.DefaultUser))
new InternalAuthenticationProviderFactory(components, _options.DefaultUser, _options.Auth.Password))
},
{
AuthenticationMethodNames.OAuth, new AuthenticationProviderFactory(_ =>
Expand Down
Loading
Loading