Audit failed SSH certificate authentication

What does this MR do and why?

Addresses "Gap 2" in https://gitlab.com/gitlab-org/gitlab/-/work_items/611349#gap-2-failed-authentications-are-not-audited

GitLab now records an audit event when SSH certificate authentication fails. This gives administrators a trail of rejected certificate logins.

  • Add the ssh_certificate_authentication_failed audit event. It is recorded once, after both the group-level and instance-level CA lookups in /api/v4/internal/authorized_certs fail. A group denial followed by a successful instance fallback is a successful login, so it is not audited.
  • Recording is gated on the ssh_certificate_authentication_rate_limit flag, so audit writes only happen alongside the per-IP limit from !263054 (merged). The endpoint is reachable without a GitLab account. A throttled request is rejected before resolution and is not audited.
  • The event is instance-scoped, authored by an unauthenticated author named after the attempted identity. It records the signing CA fingerprint, the attempted identity, the source IP, and the reason the endpoint already uses for its error: certificate_not_found, user_not_found, feature_not_available, or not_enterprise_user. The fingerprint and identity are limited to 255 characters. No other request parameters are recorded.
  • GitLab Shell sends the SSH client IP in a new optional check_ip parameter (companion MR). Rails records it if it is a single valid IP address. An IPv6 zone ID (for example fe80::1%eth0) is stripped first, because the PostgreSQL inet type rejects zone IDs.
  • If check_ip is missing (older GitLab Shell versions) or invalid, the audit framework's normal fallback applies. The event then records the request IP, which is GitLab Shell's address. This matches how Git audit events from git_audit_event already behave.
  • Authn::UnauthenticatedSecurityEventAuditor accepts optional additional_details: and ip_address: arguments; existing callers are unchanged. Like its other events, this one includes failed_login: 'SSH_CERTIFICATE'.
  • Auditing follows normal audit event licensing, not SSH certificate licensing. Without an audit license, nothing is recorded.
  • HTTP responses, and which error wins when both lookups fail, are unchanged. No event is written when the database is read-only (Geo secondary or maintenance mode).
  • Auditing is synchronous, like existing failed-login events. Successful authentication returns before the audit code runs.
  • Docs: new row in the audit event types page, and check_ip added to the internal API docs. No database migration.

When check_ip is missing or invalid, the event records GitLab Shell's IP.

Why
  • This is how Git audit events from git_audit_event already behave, so the two stay consistent.
  • Rails and GitLab Shell versions are pinned together within a release (GITLAB_SHELL_VERSION). A missing check_ip only happens during version skew.
  • It uses the shared audit framework as it is, with no special handling for this one caller.

References

Screenshots or screen recordings

No UI changes.

How to set up and validate locally

Prerequisites

  • GDK running GitLab EE with an Ultimate license, using gitlab-sshd without PROXY protocol.
  • GitLab Shell at the version in GITLAB_SHELL_VERSION (v14.60.0 or later includes the companion MR). No separate Shell branch or feature flag is needed.
  • A second loopback address for the SSH client, such as 127.0.0.2, so the client IP can be told apart from GitLab Shell's IP. On macOS, add it with sudo ifconfig lo0 alias 127.0.0.2 up.
  • A group with an SSH certificate authority (CA), an enterprise user of that group, a group member who is not an enterprise user, and a user outside the group.

Validation

  • With ssh_certificate_authentication_rate_limit disabled, a certificate from an unregistered CA is rejected and no audit event is recorded.
  • With the flag enabled, a certificate from an unregistered CA is rejected and records an ssh_certificate_authentication_failed event with reason certificate_not_found and the SSH client's IP (127.0.0.2).
  • A group CA certificate for an enterprise user of the group authenticates and records no audit event.
  • A group CA certificate for a user outside the group, or for a user that does not exist, is rejected and records reason user_not_found.
  • A group CA certificate for a group member who is not an enterprise user is rejected and records reason not_enterprise_user.
  • When the group rejects a user but the same CA is also registered as an instance CA, the user authenticates and no audit event is recorded.
  • Each recorded event has an unauthenticated author named after the certificate identity, instance scope, the CA fingerprint, the certificate identity, and failed_login: SSH_CERTIFICATE.
  • When check_ip is missing, invalid, a CIDR range, or an array, the event records GitLab Shell's IP (127.0.0.1).
  • IPv6 values for check_ip are recorded correctly: a zone ID is stripped (fe80::1%eth0 becomes fe80::1), an IPv4-mapped address is unwrapped (::ffff:10.1.2.3 becomes 10.1.2.3), and a plain address is kept as is.
  • An identity and fingerprint longer than 255 characters are cut to 255 in the event.
  • HTTP responses from /api/v4/internal/authorized_certs are unchanged for every case above.

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Edited by Fred Reinink

Merge request reports

Loading
Loading