diff --git a/docs/administration/audit-logging.md b/docs/administration/audit-logging.md new file mode 100644 index 00000000..9c97e346 --- /dev/null +++ b/docs/administration/audit-logging.md @@ -0,0 +1,142 @@ +--- +date: "2026-09-12T00:00:00+00:00" +slug: "audit-logging" +sidebar_position: 41 +aliases: + - /en-us/audit-logging +--- + +# Audit Logging + +Gitea can record security-relevant events as structured audit records: each +event has an `action`, `actor`, `scope`, `origin`, `message` and a JSON +`metadata` blob. Recording is **off by default** and does not change request +behavior when it is disabled. + +This is separate from the application, router and access logs described in +[Logging Configuration](logging-config.md). Those logs are for operations and +debugging. The audit log is a durable trail of who changed what. + +## Enable recording + +Set `[audit].RECORD_OUTPUT` to `database` in `app.ini` and restart Gitea: + +```ini +[audit] +RECORD_OUTPUT = database +RETENTION_DAYS = 30 +``` + +Invalid values of `RECORD_OUTPUT` fall back to `disabled`. See the +[configuration cheat sheet](config-cheat-sheet.md#audit-audit) for every option. + +With Docker, the same settings can be applied as +`GITEA__audit__RECORD_OUTPUT=database` and `GITEA__audit__RETENTION_DAYS=30` +before Gitea writes them into `app.ini`. + +## View events + +The Audit Log page is always available. When recording is disabled, it shows a +warning and still lists any events that were stored earlier. + +| Audience | Location | Events shown | +| --- | --- | --- | +| Site administrators | Site administration → Monitoring → Audit Log (`/-/admin/monitor/audit_logs`) | Every event | +| Signed-in users | User settings → Audit Log (`/user/settings/audit_logs`) | Events scoped to that user | +| Organization owners | Organization settings → Audit Log (`//settings/audit_logs`) | Events scoped to that organization | +| Repository administrators | Repository settings → Audit Log (`///settings/audit_logs`) | Events scoped to that repository | + +Each listing can be filtered by actor (username), action and origin, and sorted +by timestamp. Action filters accept an exact action or a family prefix such as +`user:impersonation` or `repository:webhook`. + +Site administrators can download the current filtered result as JSONL +(newline-delimited JSON) from **Export JSONL**. The file name is +`gitea-audit-log-.jsonl`. + +## Event shape + +A recorded event contains: + +- **Action**: a stable, colon-separated identifier such as `repository:archive` + or `user:accesstoken:add`. +- **Actor**: the user who performed the action. Actions taken while impersonating + a user record both the impersonator and the impersonated user. +- **Actor credential**: when the request authenticated with a named credential, + the event stores its kind and id, never the secret. Typical values: + `access-token:`, `oauth2-grant:`, `gitea-actions:`, + `deploy-key:`. +- **Scope**: the affected unit — `user`, `organization`, `repository` or + `system` (instance-wide). +- **Origin**: how the action was initiated — `ui`, `api`, `cli` or `system`. +- **Message**: a human-readable sentence rendered from a per-action template. +- **Metadata**: extra fields that fill the message template (token name, new + visibility, and so on). Secrets and token values are not stored. +- **IP address**: the client address of the request, when one exists. +- **Timestamp**: when the event was recorded. + +An exported JSONL line looks like: + +```json +{"action":"repository:archive","actor":{"type":"user","id":1,"name":"alice"},"scope":{"type":"repository","id":42,"name":"org/repo"},"message":"Archived repository org/repo.","time":"2026-09-12T11:00:00Z","ip_address":"203.0.113.10","origin":"ui"} +``` + +The `system:startup` message is kept stable across Gitea versions so operators +can parse it: `System started [Gitea {version}]`. + +## Actors and origins + +Most web and API requests pick up the signed-in user automatically. + +Some entry points have no signed-in user. Those events use a dedicated actor: + +- `CLI` for `gitea` command-line operations +- `AuthenticationSource` for authentication-source syncs and related background + work +- `Unknown` if the actor cannot be resolved; the event is still recorded + +Cron tasks and other background work use origin `system`. Git hooks and similar +non-interactive paths are attributed the same way. + +## What is recorded + +Events cover security-relevant changes, grouped by action family: + +- **User**: create and delete, name, password, visibility, emails, 2FA, + WebAuthn, OpenID, external logins, access tokens, OAuth2 applications and + grants, SSH/GPG/principal keys, secrets, webhooks, impersonation, failed + two-factor authentication +- **Organization**: create and delete, name, visibility, members, teams, + OAuth2 applications, secrets, webhooks +- **Repository**: create, fork, archive, delete, rename, visibility, transfer, + mirrors, signing verification, collaborators and teams, default branch, + branch and tag protection, deploy keys, webhooks, secrets +- **Issues, pull requests, projects and wiki**: create, update, delete, comments + and merges +- **Actions**: enable, disable and dispatch a workflow +- **System**: startup and shutdown, instance-wide webhooks, authentication + sources, instance-wide OAuth2 applications + +Recording never fails the request that triggered it. A write error is logged +and the original operation continues. + +## What is not recorded + +- Read-only access such as browsing, clone, fetch or API GET requests +- Successful password or SSH logins (use the [access log](logging-config.md) + and [Fail2ban Setup](fail2ban-setup.md) for authentication traffic) +- Token or secret values +- Which SSH key a real user pushed with (deploy keys are named; ordinary SSH + user pushes are not) + +## Retention + +`RETENTION_DAYS` (default **30**) is the age after which recorded events are +eligible for deletion. Set it to `0` to keep events forever. + +Cleanup is performed by the `cron.delete_old_audit_events` task (`SCHEDULE` +defaults to `@every 24h`, `OLDER_THAN` defaults to `[audit].RETENTION_DAYS`). +The task is registered only while `RECORD_OUTPUT` records events **and** +`RETENTION_DAYS` is greater than `0`. Turning recording off therefore also +stops pruning; existing rows stay until you delete them or turn recording back +on. diff --git a/docs/administration/config-cheat-sheet.md b/docs/administration/config-cheat-sheet.md index 0b21d18a..da89c2d4 100644 --- a/docs/administration/config-cheat-sheet.md +++ b/docs/administration/config-cheat-sheet.md @@ -656,6 +656,11 @@ And the following unique queues: - CIDR list: `1.2.3.0/8` for IPv4 and `2001:db8::/32` for IPv6 - Wildcard hosts: `*.mydomain.com`, `192.168.100.*` +## Audit (`audit`) + +- `RECORD_OUTPUT`: **disabled**: Where security-relevant events are recorded. `disabled` records nothing. `database` writes events to the `audit_event` table and shows them in the admin, organization, repository and user settings. Invalid values fall back to `disabled`. See [Audit Logging](audit-logging.md). +- `RETENTION_DAYS`: **30**: Days to keep recorded events. `0` keeps them forever. Pruning is done by `cron.delete_old_audit_events`. That task is only registered while `RECORD_OUTPUT` records events and `RETENTION_DAYS` is greater than `0`. + ## Camo (`camo`) - `ENABLED`: **false**: Enable media proxy, we support images only at the moment. @@ -1198,6 +1203,16 @@ Synchronize external user data (only LDAP user synchronization is supported) - `SCHEDULE`: **@every 168h**: Cron syntax to set how often to check. - `OLDER_THAN`: **8760h**: any action older than this expression will be deleted from database, suggest using `8760h` (1 year) because that's the max length of heatmap. +#### Cron - Delete old audit events (`cron.delete_old_audit_events`) + +Deletes audit events older than the retention period. The task is only registered when `[audit].RECORD_OUTPUT` records events and `[audit].RETENTION_DAYS` is greater than `0`. See [Audit Logging](audit-logging.md). + +- `ENABLED`: **true**: Enable service. +- `RUN_AT_START`: **false**: Run tasks at start up time (if ENABLED). +- `NOTICE_ON_SUCCESS`: **false**: Set to true to switch on success notices. +- `SCHEDULE`: **@every 24h**: Cron syntax to set how often to check. +- `OLDER_THAN`: **720h**: any audit event older than this expression will be deleted from the database. Defaults to `[audit].RETENTION_DAYS`. + #### Cron - Check for new Gitea versions (`cron.update_checker`) - `ENABLED`: **true**: Enable service. diff --git a/docs/administration/logging-config.md b/docs/administration/logging-config.md index 4f9991cb..5696792e 100644 --- a/docs/administration/logging-config.md +++ b/docs/administration/logging-config.md @@ -16,6 +16,8 @@ The logging configuration of Gitea mainly consists of 3 types of components: There is a fully functional log output by default, so it is not necessary to define one. +Security-relevant events (repository transfers, token changes, impersonation, and similar) are recorded separately. See [Audit Logging](audit-logging.md). + ## Collecting Logs for Help To collect logs for help and issue report, see [Support Options](help/support.md). diff --git a/docs/index.md b/docs/index.md index 15879749..37c33364 100644 --- a/docs/index.md +++ b/docs/index.md @@ -43,7 +43,7 @@ You can try it out using [the online demo](https://demo.gitea.com). - **Security** - Gitea places a strong emphasis on security, offering features such as user permission management, access control lists, and more to ensure the security of code and data. + Gitea places a strong emphasis on security, offering features such as user permission management, access control lists, [audit logging](administration/audit-logging.md), and more to ensure the security of code and data. - **Code Review** diff --git a/docs/installation/comparison.md b/docs/installation/comparison.md index 8ec1fb3d..3dc87205 100644 --- a/docs/installation/comparison.md +++ b/docs/installation/comparison.md @@ -34,6 +34,7 @@ _Symbols used in table:_ | Telemetry | **✘** | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | | Third-party render tool support | ✓ | ✘ | ✘ | ✘ | ✓ | ✘ | ✘ | | WebAuthn (2FA) | ✓ | ✓ | ✓ | ✓ | ✓ | ✘ | ✓ | +| Audit log | ✓ | ✓ | ⁄ | ✓ | ✓ | ? | ? | | Extensive API | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Built-in Package/Container Registry | ✓ | ✓ | ✓ | ✓ | ✘ | ✘ | ✘ | | Sync commits to an external repo (push mirror) | ✓ | ✘ | ✓ | ✓ | ✘ | ✓ | ✓ |