Logs, traces and audit records
This part lists each channel that records what the software and its users do: the audit events, the logs, the traces and the job records. For each channel it gives the personal data, the purpose of the data, the store, the persons who can read it, the retention, the control that keeps credentials out of it, and the test that shows the control.
The page describes what the code does. It does not say what the law requires or permits. Where a legal question applies, the page says "Open". The hosting provider decides it, see the hosting provider guide.
The stores themselves (location, owner, encryption, backup, erasure) are in the state register. The roles and the directory groups are in the access model.
The tables that have the label "generated" come from the code at each build of the documentation (core/docs/md/technical-design/observability-records.nix).
Words on this page
- "None" means that the code was examined and has no such control, test or limit.
- "Open" means that no source in the tree gives the fact, or that the question is a legal one.
- "A person with
rooton the node" is a person who can open a shell asrooton that node. The access model says who that is.
How a record goes from a node to the stores
flowchart TD units["Units of a node"] journal["Journal of the node"] nodeLogs["Node log shipper (otel-node-logs)"] auditShipper["Audit shipper (fediversity-audit-shipper)"] collector["otel-collector"] lokiDefault["Loki, default tenant"] lokiAudit["Loki, audit tenant"] tempo["Tempo"] grafana["Grafana"] apiTable["audit_event table of api/"] api["api/"] units --> journal api --> apiTable api --> journal journal --> nodeLogs journal --> auditShipper nodeLogs --> lokiDefault auditShipper --> lokiAudit collector --> lokiDefault collector --> tempo lokiDefault --> grafana lokiAudit --> grafana tempo --> grafana
The nginx of the telemetry node sets the tenant of each request (X-Scope-OrgID), thus a client cannot select a tenant (core/components/hosting-provider/telemetry.nix).
Retention
The hosting provider sets the retention periods. Each period below is the default of a setting, and the hosting provider can change it in its configuration. The reason for a period, and the law that applies to it, are not in the code: Open.
Generated:
| Records | Setting | Default | Source |
|---|---|---|---|
The audit_event table of api/ |
services.fediversity-api.auditRetentionDays |
365 days. 0 keeps all events. |
api/nix/configuration.nix |
The tenant audit of Loki |
services.fediversity-telemetry.auditRetentionDays |
365 days. 0 keeps all lines. |
core/components/hosting-provider/telemetry.nix |
The tenant fake of Loki, Tempo and Mimir |
services.fediversity-telemetry.retentionDays |
90 days | core/components/hosting-provider/telemetry.nix |
| The journal of each node | MaxRetentionSec and SystemMaxUse of services.journald.settings.Journal |
30day, and at most 512M |
core/setups/nixos/common.nix |
| The journal lines that a node of the hosting provider sends to Loki | services.fediversity-node-logs.priority |
The lines at level info and higher |
core/components/hosting-provider/node-logs.nix |
| The completed jobs of Windmill | services.fediversity-windmill.jobRetentionDays |
30 days | core/components/hosting-provider/windmill/windmill.nix |
The build logs and the operation_run table of api/ |
services.fediversity-api.logRetentionDays |
30 days. 0 keeps all logs and runs. |
api/nix/configuration.nix |
The operation_run table of api/ |
The constant OPERATION_RUNS_KEPT |
The last 100 runs of each deployment | api/src/fediversity_api/state.py |
| The nginx access log of the forge node | services.logrotate.settings.nginx |
182 daily files | core/components/dev/forgejo/forgejo.nix |
Limits that are not a setting of this software:
- The nginx access log of a node other than the forge node: the
logrotaterules of nixpkgs keep 26 weeks of files (the comment incore/setups/nixos/nginx.nix). - The rows of the
buildtable ofapi/: None. No job removes old rows.logRetentionDaysremoves only the log of a row. - The disk queue of the audit shipper: None. The module sets no limit of size or of age.
- The logs of the CI jobs and the change log of NetBox: Open.
Channels
Audit events of the api
api/ records each action on a deployment as one audit event (api/src/fediversity_api/audit.py).
- Store: the
audit_eventtable of the database ofapi/, and one JSON line on the loggerfediversity_api.audit. The line goes to the journal, and then to Loki, see Audit lines in Loki. - Readers: an operator reads its own events through the panel (
recent_actions,GET /audit-events). A superuser of the panel reads the events of each account (reads_all_history). A person with access to the database reads the table. - Retention:
services.fediversity-api.auditRetentionDays.api/removes the older events when it starts and each time that it records an event (prune_audit_eventsinapi/src/fediversity_api/state.py). The events of a deleted deployment stay until they are older than the retention. - Control that keeps credentials out: an event holds only the fields of the table below. For a request that is not valid,
reasongives the location of the error and not the value. For an import, the event holds only the result.api/cuts a longreasonof the authorization callback, and cutsrequest_idto 128 characters. - Test:
api/src/fediversity_api/tests/test_audit.py:test_an_invalid_request_records_where_without_the_value,test_an_invalid_configuration_records_where_without_the_value,test_an_import_records_only_its_result,test_an_unknown_action_stores_nothing_of_the_caller,test_a_long_reason_of_the_callback_is_cut. The retention has the teststest_a_new_event_removes_the_events_older_than_the_retention,test_the_retention_is_a_settingandtest_zero_keeps_all_events. - Protection from changes: each event has a hash of the event and of the event before it.
fediversity-api-audit-verifyexamines the chain (verify_audit_chain). The tests aretest_a_changed_event_is_found,test_a_removed_event_is_foundandtest_a_prune_keeps_the_chain_correct.
The fields of an event, with the purpose of each. Generated:
| Field | From | Personal data | Purpose | Serves to trace abuse |
|---|---|---|---|---|
actor |
_record |
Yes. The OpenID Connect sub of the account, or the primary key of an account without single sign-on. |
To tell who did an action on a deployment. | Yes |
actor_issuer |
_record |
Yes, together with actor. The OpenID Connect issuer of the account. |
To tell which identity provider knows the actor. | Yes |
internal |
_record |
No. | To tell an action of the system from an action of a person. | No |
action |
_record |
Yes, together with actor. |
To tell what occurred. | Yes |
deployment_id |
_record |
Indirectly. It links the event to an operator. For domain_contact_put and domain_contact_delete, it is the ID of a contact of the operator. |
To find the deployment that an action changed. | Yes |
build_id |
_record |
Indirectly. It links the event to a build of a deployment. | To find the build that an action started. | No |
request_id |
_record |
No. nginx makes a random value for each request. | To tell which audit events come from one request. | Yes. It connects the events of one request. |
result |
_record |
Yes, together with actor. |
To tell if api/ permitted the action. |
Yes |
reason |
_record |
Possibly. It is a text from the code or from the authorization callback. | To tell why api/ refused an action. |
Yes |
domain |
_record |
Possibly. A domain name can contain the name of a person. | To tell who claimed a domain. | Yes |
domain_terms |
_record |
No. It is a version of the terms. | To show which terms the actor accepted. | No |
id |
The store | No. | To identify the event. | No |
created_at |
The store | Yes, together with actor. |
To tell when the action occurred. The retention uses it. | Yes |
seq |
The store | No. | To detect a removed event. | No |
hash |
The store | No. | To detect a changed event. | No |
An audit event does not hold the client address. To connect an event to a client address, a person must find the journal line or the access log line of the same request, and the access log holds only the network of the client.
api/ records the first refused call without a login in each interval of 300 seconds, and then one event with the number of the other calls. Thus an anonymous caller cannot fill the table.
Audit lines in Loki
The audit shipper of a node (core/components/hosting-provider/audit-shipper.nix) reads the journal of the unit of a component, keeps the lines that match the pattern of the component, and sends them to the audit tenant of Loki. Generated:
| Source | Unit that writes the lines | Pattern of an audit line | Tenant of Loki | Other tenants |
|---|---|---|---|---|
api |
fediversity-api.service |
[A-Z]+:fediversity_api\.audit: |
audit |
None. The node log shipper drops these lines, thus they do not go to the tenant fake. |
- Personal data and purpose: the same fields as the audit event.
- Store: the audit tenant of Loki. Before the shipper sends a line, the line is in its disk queue in
/var/lib/fediversity-audit-shipper. The shipper does not stop its retries, thus a line stays in the queue while thetelemetrynode does not accept it. - Readers: each account that can log in to the Grafana of the hosting provider, thus the members of
fediversity-adminsandfediversity-staff. Grafana has one organization and the datasourceLoki audithas no permission of its own, thus aViewercan query the tenant. A person withrooton the node ofapi/reads the queue. - Retention:
services.fediversity-telemetry.auditRetentionDays. The tenant refuses the deletion API of Loki, thus only the retention removes a line. The setting ofapi/does not apply to these lines. - Control that keeps credentials out: the control of the audit event. The shipper adds none.
- Test: None for the content of a shipped line.
The deploy credentials can change or delete the tenant, see the hosting provider guide.
Journal of each node
- Personal data: what each unit writes.
sshd, Authelia and the nginx error log write full client IP addresses and user names.maddywrites email addresses. The applications write what their log level gives. - Purpose: to find the cause of a failure on a node (the comment in
core/setups/nixos/common.nix). A purpose for each field: Open. - Store: the journal of the node. A node of the hosting provider sends the lines at the level of
services.fediversity-node-logs.priorityand higher to the default tenant of Loki (core/components/hosting-provider/node-logs.nix). A node of an operator deployment sends the lines at levelwarningand higher, and thetelemetrynode sets the labeldeployment_id(core/components/hosting-provider/deployment-ingest.nix). - Readers: a person with
rooton the node reads the journal. Each account that can log in to the Grafana of the hosting provider reads the copy in Loki, for all deployments. - Retention: see Retention. The lines of a deleted deployment stay in Loki until the retention removes them.
- Control that keeps credentials out: each unit can write a credential, because a request line or an error message of an application can hold one. Thus the control is in the pipeline and not in each unit. The node log shipper replaces the text that has the shape of a credential with
[masked]before a line goes to Loki (core/components/hosting-provider/node-logs-masks.nix). The shapes are: the query string of a request line or of a URL, the user and the password of a URL, the value of anAuthorizationheader, a bearer token, the value of a key whose name containspassword,passwd,secret,token,api_keyorcredential, a JSON Web Token, and a token of OpenBao. A credential that has none of these shapes goes to Loki. The shipper does not read a message that is not UTF-8. The journal of the node keeps the full line: None for the journal. - Test: the check
node-logs-masks(core/components/hosting-provider/node-logs-masks-test.nix) sends an example of each shape through the masks. The checktest-monitoring-storage(core/components/hosting-provider/monitoring-test.py, "the node log shipper masks a credential") writes values to the journal of a node and reads Loki.core/setups/hypervisor-config-test.nixexamines the retention settings of the journal.
Audit device of OpenBao
OpenBao writes each request and each response as one JSON line to its standard output (core/components/hosting-provider/openbao/openbao.nix).
- Personal data: the client IP address, the path and the operation of the request, and the display name and the entity of the token, for example the name that an OIDC login gives to an administrator.
- Purpose: Open. No source in the tree gives the purpose of a field.
- Store: the journal of
openbao.service, and then the default tenant of Loki. These lines do not go to the audit tenant. - Readers: the readers of the journal and of the default tenant, see Journal of each node.
- Retention: that of the journal and of the default tenant. The lines do not have the retention of the audit tenant.
- Control that keeps credentials out: OpenBao writes secret values and tokens as HMAC-SHA256 hashes, and
elide_list_responsesremoves the keys of a list response. - Test: the check
integrations-openbao-pki(core/checks/integrations/openbao-pki/nixosTest.nix, "The audit device writes each request to the journal with hashed values").
nginx access log
Each node that runs nginx writes one line for each request to /var/log/nginx/access.log (core/setups/nixos/nginx.nix).
- Personal data: the network of the client (the address with its last part removed:
/24for IPv4,/48for IPv6), the method, the path and the protocol of the request, the referrer without its query string, the user agent, and the user name of HTTP basic authentication. On the vhosts withauth_basic(telemetry ingest, metrics query, audit path) this user name identifies the credential of one deployment, not a person. On other vhosts it is the text that the client sends. - Purpose: Open. No source in the tree gives the purpose of a field.
- Store: files on the node. The forge node does not record the calls of the CI runners (
core/components/dev/forgejo/forgejo.nix). - Readers: a person with
rooton the node. The node log shipper reads the journal and not this file, thus the lines do not go to Loki. - Retention: see Retention.
- Control that keeps credentials out: the log format holds no query string, of the request or of the referrer, and no password of HTTP basic authentication. A credential in a path or in the user agent goes to the file.
- Test: the check
nginx-access-log(core/setups/nixos/nginx-access-log-test.nix) sends requests with a query string, a referrer and a user name with a password, and examines the lines. It examines the mask of an IPv4 address. For the mask of an IPv6 address: None.
Request logs of the api and the panel
api/ and the panel run on uvicorn with its access log on (api/src/fediversity_api/http_main.py, panel/nix/configuration.nix). Each request gives one line with the address of the peer, the method, the path with its query string, and the status.
- Personal data: the query string can hold an identity, for example the actor in
GET /audit-eventsand the account in the history page of the panel. - Purpose: Open.
- Store: the journal of the unit, and then the default tenant of Loki, because the lines are at level
info. - Readers and retention: those of the journal, see Journal of each node.
- Control that keeps credentials out: in Loki, the masks of the node log shipper, which replace the query string of a request line, see Journal of each node. In the journal of the node: None.
- Test: the check
node-logs-maskshas a request line ofuvicornas an example.
Jobs of Windmill
Windmill keeps the arguments, the result and the log of each completed job.
- Personal data: a deploy job holds the configuration of the deployment, which can hold email addresses and user names, and the deploy log.
- Purpose: the job runs the deploy, and
api/reads the log of the job to show it to the operator. A purpose for a completed job: Open. - Store: the database of Windmill, see the state register.
- Readers: the administrators of Windmill. An operator reads the log of the builds of its own deployments through
api/(build_logsinapi/src/fediversity_api/procedures.py). - Retention:
services.fediversity-windmill.jobRetentionDays. - Control that keeps credentials out, deploy job:
api/removes the secret keys from thedeploy_envthat it sends to the job. Nothing removes a value from the text of the deploy log: None. - Control that keeps credentials out, node-effect job (
f/fediversity/node_effect): the arguments and the result of this job can hold bearer credentials, and Windmill keeps them in clear.api/deletes the job when it is completed, with a success or a failure, before the result goes back to the caller. Whenapi/cannot delete a job, it writes the ID of the job to its log, and a person must delete the job (_run_effect_jobinapi/src/fediversity_api/node_effect.py). Until then, the credentials stay in the job. - Test:
api/src/fediversity_api/tests/test_deploy_env.py(SecretKeysStayOffTheWindmillPayload) for the deploy job.api/src/fediversity_api/tests/test_migration.pyfor the node-effect job:test_windmill_route_runs_the_job_and_deletes_it,test_a_failed_job_reports_its_error_and_is_deleted,test_a_timed_out_job_fails_and_is_deleted,test_a_job_that_cannot_be_deleted_is_logged_by_id.
Build logs and operation output in the api
- Personal data: the
actorof each run of an operation (operation_runtable). The text of a build log and of an operation output can hold what the deploy tools and the operation write. - Purpose:
api/shows the result of a build or of an operation to the operator. - Store: the
buildandoperation_runtables of the database ofapi/. With Windmill,api/reads the build log from the job. Without Windmill,api/keeps the last 64 KiB of the output of a failed build in thebuildtable. - Readers: the operator of the deployment, through
api/. A person with access to the database. - Retention:
api/keeps the last runs of each deployment, see Retention, and deletes the runs after the deployment is destroyed. Withservices.fediversity-api.logRetentionDays,api/also removes the log of a build and each run of an operation that finished before that number of days (prune_logsinapi/src/fediversity_api/state.py). It does this when it starts, and each time that it stores a build log or a run. The default is 30 days.0keeps all logs and runs. The rows of thebuildtable have no limit, because the usage records come from them: None. - Control that keeps credentials out:
api/does not keep the output of an operation that hassensitiveset. For the text of a build log, see the deploy job in Jobs of Windmill: the same runner writes the two logs. - Test:
api/src/fediversity_api/tests/test_operations.py:test_the_output_of_a_sensitive_operation_is_not_kept,test_the_deployed_revision_decides_if_the_output_stays.api/src/fediversity_api/tests/test_log_retention.pyfor the retention. For the build log:api/src/fediversity_api/tests/test_log_credentials.py(FailedBuildLogHoldsNoSecret).
Traces
- Sources: Grafana sends its own spans when
tracingEndpointis set. NetBox sends the spans of its requests, its HTTP calls and its database queries whenfediversity.otel.endpoints.netboxis set (core/components/hosting-provider/netbox/otel.nix). Windmill has the settings but its open-source build sends no spans (core/components/hosting-provider/windmill-otel.nix). - Personal data: a span can hold a request path. The attributes that the instrumentation libraries set: Open.
- Purpose: Open.
- Store: Tempo.
- Readers: each account that can log in to the Grafana of the hosting provider.
- Retention:
services.fediversity-telemetry.retentionDays. - Control that keeps credentials out: None. The collector has only the
batchprocessor (core/components/hosting-provider/otel-collector.nix). - Test: None.
Debug exporter of the collector
When services.fediversity-otel-collector.backends is not set, the collector has only the debug exporter with verbosity = "detailed". It then writes each record that it receives (logs, metrics and spans) to its own journal. The readers, the retention and the missing controls are those of the journal.
Records of Grafana and NetBox
- Grafana keeps its accounts and its sessions in its own database, see the state register.
- NetBox keeps a change log with the user of each change. No file in the tree sets its retention or its readers: Open.
Logs of the CI jobs
The forge keeps the log of each CI job. The workflows in .forgejo/workflows/ give secrets to the CD jobs as environment variables.
- Personal data: Open.
- Store, readers and retention: Open. No file in the tree sets them.
- Control that keeps credentials out: Open. No file in the tree sets a mask.
- Test: None.
Fields that serve to trace abuse
To trace abuse, a person must know who did a thing, from where and when. These fields give that:
- The audit events of
api/: the fields with "Yes" in the last column of the table of the fields. They give the account, the action, the deployment and the time. They do not give a client address. - The journal: the full client IP address and the user name in the lines of
sshdand Authelia, with the time. - The audit device of OpenBao: the client IP address and the display name of the token, with the path and the time.
- The nginx access log: only the network of the client. It cannot identify one address.
If these fields are sufficient for a duty of the hosting provider to trace abuse, and how long the hosting provider must or can keep them: Open.
Open items
| Channel | Item | State |
|---|---|---|
| All | The legal basis and the reason of each retention period | Open |
| Audit lines in Loki | A test of the content of a shipped line | None |
| Audit lines in Loki | A limit on the disk queue of the shipper | None |
| Audit lines in Loki | A limit on which Grafana account reads the tenant | None |
| Journal | A control for a credential that has no known shape, and for the journal of the node | None |
| Journal | A purpose for each field | Open |
| Audit device of OpenBao | The retention of the audit tenant | None |
| Audit device of OpenBao | A purpose for each field | Open |
| Jobs of Windmill | A purpose for a completed job | Open |
| nginx access log | A purpose for each field | Open |
| nginx access log | A test of the mask of an IPv6 client address | None |
Request logs of api/, panel |
A purpose | Open |
Request logs of api/, panel |
A control that keeps credentials out of the journal of the node | None |
| Jobs of Windmill | A control on an encoded or a derived credential in the text of a deploy log or a build log | None |
| Jobs of Windmill | An alert when the sweep cannot delete a node-effect job | None |
Build logs in api/ |
A retention of the rows of the build table |
None |
| Traces | The attributes of a span, and the purpose | Open |
| Traces | A control that keeps credentials out, and a test | None |
debug exporter |
A control that keeps credentials out | None |
| Change log of NetBox | The retention and the readers | Open |
| Logs of the CI jobs | The personal data, store, readers, retention and mask | Open |