Skip to content

Add Audit Logging for Security Events #304

Open
iqbalcodes6602 wants to merge 2 commits intocontainer-registry:mainfrom
iqbalcodes6602:audit-logger
Open

Add Audit Logging for Security Events #304
iqbalcodes6602 wants to merge 2 commits intocontainer-registry:mainfrom
iqbalcodes6602:audit-logger

Conversation

@iqbalcodes6602
Copy link
Contributor

@iqbalcodes6602 iqbalcodes6602 commented Feb 5, 2026

Description

This PR introduces audit logging support and improves server-side request handling across the Ground Control and shared components. The changes focus on better traceability, observability, and consistency in how authentication, user actions, and satellite operations are processed.

Key Changes

  1. Added a new audit logger implementation for Ground Control.
  2. Integrated audit logging hooks to capture relevant actions and requests.
  3. Shared logger updates to ensure consistency across components.
  4. Extended authentication handlers to support audit-aware request flows.
  5. Updated middleware to improve request context handling and logging.
  6. Enhanced satellite and user handlers to emit structured audit events.

Summary by cubic

Adds structured audit logging for security events across Ground Control to improve traceability of auth flows, user actions, and satellite operations. Events include IDs, timestamps, actor, source IP, and details, and are written to stdout or a file.

  • New Features

    • Added audit logger (zerolog) with lazy init; configurable via AUDIT_LOG_PATH or AppConfig.audit_log_path.
    • Logs user auth failures with reasons and request metadata (login and middleware).
    • Emits events for user create, delete, and password changes.
    • Logs satellite registration, deregistration, and token auth failures.
    • Includes client IP helper and shared audit logger utilities (InitAuditLogger, LogAuditEvent, file writer).
  • Migration

    • Optional: set AUDIT_LOG_PATH or AppConfig.audit_log_path to write audit logs to a file; defaults to stdout.
    • Handle log rotation externally (e.g., logrotate).
    • No database changes.

Written for commit 5632549. Summary will update on new commits.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added comprehensive audit logging for authentication events, user management operations, and satellite lifecycle changes.
    • Audit logs capture event type, actor, IP address, and timestamp for security and compliance tracking.
    • Optional configuration to specify a custom audit log file path; defaults to standard output if not set.

@github-actions github-actions bot added the golang label Feb 5, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

This PR implements a structured audit logging facility for security events across the ground-control and satellite applications. It introduces new audit logger packages that emit JSON-formatted events with unique IDs and timestamps, adds configuration for audit log file paths, and instruments authentication, user management, and satellite lifecycle handlers with audit logging calls.

Changes

Cohort / File(s) Summary
Audit Logger Infrastructure
ground-control/internal/logger/audit.go, internal/logger/audit.go
Implements audit logging with AuditEvent type, lazy initialization or explicit setup, event emission via LogEvent/LogAuditEvent, and client IP extraction. Ground-control version uses zerolog with configurable output path; satellite version uses sync.Once-based initialization with optional file writer.
Ground Control Authentication & Middleware
ground-control/internal/server/auth_handlers.go, ground-control/internal/server/middleware.go
Adds audit logging for authentication failures (missing credentials, locked accounts, unknown users, invalid passwords, token failures) with event type user.auth.failure, capturing actor, source IP, and failure reason.
Ground Control Handlers
ground-control/internal/server/satellite_handlers.go, ground-control/internal/server/user_handlers.go
Instruments satellite lifecycle events (registration, deregistration, auth failures) and user management operations (create, delete, password change) with audit logging using event types satellite.registration, satellite.deregistration, user.create, user.delete, and user.password.change.
Configuration
pkg/config/config.go
Adds optional AuditLogPath field to AppConfig struct for specifying audit log file destination.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add Audit Logging for Security Events' clearly summarizes the main focus of the PR, which is the addition of audit logging functionality across multiple components.
Linked Issues check ✅ Passed The PR implements all primary requirements from issue #238: creates audit logger packages with structured JSON format, adds configurable output paths, instruments handlers for auth failures, user actions, and satellite operations, and includes unique event IDs and timestamps.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with issue #238 requirements. No out-of-scope modifications detected; changes focus on audit logging implementation across specified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description includes the issue reference, comprehensive description of changes, and additional context as required by the template.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link

codacy-production bot commented Feb 5, 2026

Codacy's Analysis Summary

0 new issue (≤ 0 issue)
0 new security issue
24 complexity
6 duplications

Review Pull Request in Codacy →

AI Reviewer available: add the codacy-review label to get contextual insights without leaving GitHub.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 7 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ground-control/internal/server/middleware.go (1)

29-69: ⚠️ Potential issue | 🟡 Minor

Capture attempted username on failed Basic auth.

actor is only set after successful authentication, so failed Basic-auth attempts log an empty actor even when a username was supplied. Set actor immediately after parsing Basic auth so the audit log captures the attempted username.

🛠️ Suggested change
 		if !authenticated {
 			if username, password, ok := extractBasicAuth(r); ok {
+				actor = username
 				dbUser, err := s.dbQueries.GetUserByUsername(r.Context(), username)
 				if err == nil {
 					valid, err := auth.VerifyPassword(password, dbUser.PasswordHash)
 					if err == nil && valid {
🤖 Fix all issues with AI agents
In `@ground-control/internal/logger/audit.go`:
- Around line 31-49: The getAuditLogger initialization currently returns early
on os.OpenFile failure which leaves auditLogger nil and auditLoggerOnce
preventing retries; update getAuditLogger so that when OpenFile(path, ...)
returns an error it logs that error (including the error value) and falls back
to using os.Stdout for w instead of returning, then continue to create and
assign auditLogger (so auditLogger is never left nil); reference the
getAuditLogger function, auditLoggerOnce, AUDIT_LOG_PATH env var and the
auditLogger variable when making this change.
- Around line 24-27: The package-level globals auditLogger and auditLoggerOnce
must be removed and replaced with an instance-based logger: create an
AuditLogger type (or add a field to the existing Server struct) that holds a
zerolog.Logger and any initialization state, provide a constructor like
NewAuditLogger(ctx, cfg...) to initialize and return the instance, update all
call sites that referenced the globals to use the instance (e.g.,
server.AuditLogger or pass *AuditLogger into functions), and eliminate sync.Once
usage by ensuring the constructor handles safe initialization; reference the
symbols auditLogger and auditLoggerOnce to locate and replace the globals with
the new instance-based pattern.

In `@internal/logger/audit.go`:
- Around line 23-26: Remove the package-level globals auditLogger and
auditLoggerOnce and refactor to an instance-based logger: create an AuditLogger
struct that holds a *zerolog.Logger, add a constructor NewAuditLogger(logger
*zerolog.Logger) (or accept a factory) and convert any package-level functions
that used auditLogger to methods on *AuditLogger so callers inject or create the
instance. Replace singleton lazy-init logic (auditLoggerOnce) by requiring the
caller to provide a configured *zerolog.Logger (or by making NewAuditLogger
initialize it once per instance), update all call sites to use the AuditLogger
receiver instead of the globals, and ensure thread-safety is preserved by
avoiding shared mutable package state.

In `@pkg/config/config.go`:
- Line 34: Add validation for the new AppConfig.AuditLogPath by updating
pkg/config/validate.go: inside the config validation function that validates
AppConfig (e.g., Validate or ValidateAppConfig), check that AuditLogPath is
non-empty when provided, that the path exists and is a directory or file as
expected, and that the process has required permissions (read/write as
appropriate); return a clear error if checks fail. Also update the example
configuration (config.example.json) to include the AuditLogPath key with a
descriptive sample value and brief comment so users can discover and configure
it.
🧹 Nitpick comments (1)
internal/logger/audit.go (1)

74-85: Return a WriteCloser (or document closing) to avoid file descriptor leaks.

Since this function opens a file, it’s safer to return io.WriteCloser so callers can close it on shutdown.

♻️ Proposed signature tweak
-func NewFileAuditWriter(path string) (io.Writer, error) {
+func NewFileAuditWriter(path string) (io.WriteCloser, error) {
   if path == "" {
     return nil, nil
   }
   f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
   if err != nil {
     return nil, err
   }
   return f, nil
 }

Comment on lines +24 to +27
var (
auditLogger *zerolog.Logger
auditLoggerOnce sync.Once
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Replace package-level audit logger globals to satisfy lint rules.

Package-level state violates the “no globals” lint rule and will fail strict golangci-lint. Please refactor to an instance-based logger (e.g., attach an AuditLogger to Server or pass it explicitly).

As per coding guidelines: no global variables (gochecknoglobals).

🤖 Prompt for AI Agents
In `@ground-control/internal/logger/audit.go` around lines 24 - 27, The
package-level globals auditLogger and auditLoggerOnce must be removed and
replaced with an instance-based logger: create an AuditLogger type (or add a
field to the existing Server struct) that holds a zerolog.Logger and any
initialization state, provide a constructor like NewAuditLogger(ctx, cfg...) to
initialize and return the instance, update all call sites that referenced the
globals to use the instance (e.g., server.AuditLogger or pass *AuditLogger into
functions), and eliminate sync.Once usage by ensuring the constructor handles
safe initialization; reference the symbols auditLogger and auditLoggerOnce to
locate and replace the globals with the new instance-based pattern.

Comment on lines +31 to +49
func getAuditLogger() *zerolog.Logger {
auditLoggerOnce.Do(func() {
path := os.Getenv("AUDIT_LOG_PATH")
var w *os.File
if path == "" {
w = os.Stdout
} else {
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
if err != nil {
return
}
w = f
}

l := zerolog.New(w).With().Timestamp().Logger()
auditLogger = &l
})

return auditLogger
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Silent init failure permanently disables audit logging.

If the file open fails, the logger stays nil and sync.Once prevents any retry or fallback. Consider logging the error and allowing a retry or a fallback to stdout to avoid silent loss of audit events.

🤖 Prompt for AI Agents
In `@ground-control/internal/logger/audit.go` around lines 31 - 49, The
getAuditLogger initialization currently returns early on os.OpenFile failure
which leaves auditLogger nil and auditLoggerOnce preventing retries; update
getAuditLogger so that when OpenFile(path, ...) returns an error it logs that
error (including the error value) and falls back to using os.Stdout for w
instead of returning, then continue to create and assign auditLogger (so
auditLogger is never left nil); reference the getAuditLogger function,
auditLoggerOnce, AUDIT_LOG_PATH env var and the auditLogger variable when making
this change.

Comment on lines +23 to +26
var (
auditLogger *zerolog.Logger
auditLoggerOnce sync.Once
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove package-level audit logger globals (lint violation).

Strict golangci-lint disallows package-level globals. Please refactor to an injected/instance-based logger.

As per coding guidelines: no global variables (gochecknoglobals).

🤖 Prompt for AI Agents
In `@internal/logger/audit.go` around lines 23 - 26, Remove the package-level
globals auditLogger and auditLoggerOnce and refactor to an instance-based
logger: create an AuditLogger struct that holds a *zerolog.Logger, add a
constructor NewAuditLogger(logger *zerolog.Logger) (or accept a factory) and
convert any package-level functions that used auditLogger to methods on
*AuditLogger so callers inject or create the instance. Replace singleton
lazy-init logic (auditLoggerOnce) by requiring the caller to provide a
configured *zerolog.Logger (or by making NewAuditLogger initialize it once per
instance), update all call sites to use the AuditLogger receiver instead of the
globals, and ensure thread-safety is preserved by avoiding shared mutable
package state.

DisableHeartbeat bool `json:"disable_heartbeat,omitempty"`
HeartbeatInterval string `json:"heartbeat_interval,omitempty"`
Metrics MetricsConfig `json:"metrics,omitempty"`
AuditLogPath string `json:"audit_log_path,omitempty"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for AuditLogPath references across the codebase
echo "=== Searching for AuditLogPath references ==="
rg -n "AuditLogPath|audit_log_path" -S

echo -e "\n=== Checking config.example.json ==="
fd -a 'config.example.json' -x cat {}

echo -e "\n=== Checking pkg/config/validate.go ==="
fd -a 'validate.go' pkg -x cat {}

Repository: container-registry/harbor-satellite

Length of output: 5460


Add AuditLogPath to validation and config example.

The AuditLogPath field added to AppConfig lacks validation in pkg/config/validate.go and is not documented in config.example.json. Add validation logic (path existence, permissions if needed) and include it in the example config so users can discover and configure this setting.

🤖 Prompt for AI Agents
In `@pkg/config/config.go` at line 34, Add validation for the new
AppConfig.AuditLogPath by updating pkg/config/validate.go: inside the config
validation function that validates AppConfig (e.g., Validate or
ValidateAppConfig), check that AuditLogPath is non-empty when provided, that the
path exists and is a directory or file as expected, and that the process has
required permissions (read/write as appropriate); return a clear error if checks
fail. Also update the example configuration (config.example.json) to include the
AuditLogPath key with a descriptive sample value and brief comment so users can
discover and configure it.

Signed-off-by: Anas Iqbal <mohd.abd.6602@gmail.com>
Signed-off-by: Anas Iqbal <mohd.abd.6602@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audit Logging for Security Events

1 participant