audit

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 17, 2026 License: GPL-3.0 Imports: 7 Imported by: 0

README

Audit Logging

The internal/audit package provides structured audit logging for security-sensitive operations in the cd-operator.

Overview

Audit logging captures important events like PR merges, promotions, external test executions, and configuration changes. Events are logged as structured JSON to stdout for ingestion by log aggregation systems.

Features

  • Non-blocking: Events are processed asynchronously through a buffered channel
  • Context-aware: Extracts actor and correlation ID from context
  • Structured JSON: All events logged as JSON for easy parsing
  • Graceful shutdown: Flushes pending events during shutdown
  • Error resilience: Logging failures don't fail business operations

Usage

Creating an Audit Logger
import (
    "github.com/grhili/cd-operator/internal/audit"
    "github.com/grhili/cd-operator/pkg/logger"
)

// Create underlying logger
log, err := logger.New(logger.Config{
    Level:  "info",
    Format: "json",
})

// Create audit logger
auditLog, err := audit.NewJSONLogger(log, audit.Config{
    BufferSize: 1000,  // Optional, defaults to 1000
})
defer auditLog.Close(context.Background())
Logging Events
// Add actor and correlation ID to context
ctx := context.Background()
ctx = audit.WithActor(ctx, "user@example.com")
ctx = audit.WithCorrelationID(ctx, "req-123")

// Log a successful PR merge
err := auditLog.LogEvent(ctx, audit.AuditEvent{
    EventType: audit.EventTypePRMerged,
    Resource:  "PR #123",
    Action:    "merge",
    Status:    audit.EventStatusSuccess,
    Metadata: map[string]string{
        "repo":   "cd-operator",
        "branch": "main",
    },
    Message: "Pull request merged successfully",
})
Event Types

Available event types:

  • PR Operations: pr.merged, pr.qualified, pr.disqualified
  • Promotions: promotion.triggered, promotion.completed, promotion.failed
  • External Tests: external_test.started, external_test.completed, external_test.failed
  • ArgoCD: argocd.sync, argocd.sync_completed, argocd.sync_failed
  • Configuration: config.changed, config.deleted
  • Authentication: auth.failure, auth.success
  • Webhooks: webhook.received, webhook.rejected
Event Statuses
  • success: Operation completed successfully
  • failed: Operation failed
  • pending: Operation in progress
  • skipped: Operation was skipped

Example Output

{
  "timestamp": "2026-02-16T22:00:00Z",
  "event_type": "pr.merged",
  "actor": "user@example.com",
  "resource": "PR #123",
  "action": "merge",
  "status": "success",
  "metadata": {
    "repo": "cd-operator",
    "branch": "main"
  },
  "correlation_id": "req-123",
  "message": "Pull request merged successfully"
}

Integration with Controllers

import "github.com/grhili/cd-operator/internal/audit"

type Controller struct {
    auditLog audit.Logger
}

func (c *Controller) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
    // Add service account as actor
    ctx = audit.WithActor(ctx, "system:serviceaccount:cd-system:cd-operator")
    ctx = audit.WithCorrelationID(ctx, req.NamespacedName.String())

    // ... business logic ...

    // Log success
    c.auditLog.LogEvent(ctx, audit.AuditEvent{
        EventType: audit.EventTypePRMerged,
        Resource:  fmt.Sprintf("PR #%d", prNumber),
        Action:    "merge",
        Status:    audit.EventStatusSuccess,
    })

    return reconcile.Result{}, nil
}

Best Practices

  1. Always set actor: Use audit.WithActor() to track who performed the action
  2. Use correlation IDs: Link related events with audit.WithCorrelationID()
  3. Add metadata: Include relevant context in the Metadata field
  4. Handle errors gracefully: Don't fail operations if audit logging fails
  5. Close on shutdown: Call auditLog.Close() during graceful shutdown

Testing

Use audit.NewNoOpLogger() in tests to disable audit logging:

func TestMyFunction(t *testing.T) {
    auditLog := audit.NewNoOpLogger()
    // ... test code ...
}

Performance

  • Buffered channel: Default 1000 events, configurable
  • Async processing: Non-blocking - events queued immediately
  • Dropped events: If buffer is full, events are dropped with error log
  • Graceful degradation: Audit failures don't block operations

Security Considerations

  • Audit logs contain sensitive information - secure log storage
  • Use centralized log aggregation for compliance
  • Set up alerts on authentication failures and unauthorized access
  • Regularly review audit logs for anomalies
  • Consider log retention policies for compliance (SOC2, HIPAA, etc.)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetActor

func GetActor(ctx context.Context) string

GetActor extracts the actor from context. Returns "system" if no actor is set.

func GetCorrelationID

func GetCorrelationID(ctx context.Context) string

GetCorrelationID extracts the correlation ID from context. Returns empty string if no correlation ID is set.

func WithActor

func WithActor(ctx context.Context, actor string) context.Context

WithActor returns a new context with the actor set. The actor should be a user identifier or service account name.

func WithCorrelationID

func WithCorrelationID(ctx context.Context, correlationID string) context.Context

WithCorrelationID returns a new context with a correlation ID. This ID can be used to link related audit events together.

Types

type AuditEvent

type AuditEvent struct {
	// Timestamp is when the event occurred
	Timestamp time.Time `json:"timestamp"`

	// EventType categorizes the type of operation
	EventType EventType `json:"event_type"`

	// Actor is the user or service account that initiated the action
	Actor string `json:"actor"`

	// Resource identifies the target resource (e.g., "PR #123", "promotion-policy/prod")
	Resource string `json:"resource"`

	// Action describes what operation was performed (e.g., "merge", "trigger", "promote")
	Action string `json:"action"`

	// Status indicates whether the operation succeeded or failed
	Status EventStatus `json:"status"`

	// Metadata contains additional context-specific information
	Metadata map[string]string `json:"metadata,omitempty"`

	// CorrelationID links related events together for tracing
	CorrelationID string `json:"correlation_id,omitempty"`

	// Message provides human-readable description of the event
	Message string `json:"message,omitempty"`

	// Error contains error details if Status is Failed
	Error string `json:"error,omitempty"`
}

AuditEvent represents a single auditable event in the system. Events are logged as structured JSON for analysis and compliance.

type Config

type Config struct {
	// BufferSize is the size of the event buffer channel.
	// Default: 1000
	BufferSize int

	// FlushTimeout is the maximum time to wait when flushing events.
	// Default: 5 seconds
	FlushTimeout time.Duration
}

Config holds configuration for the audit logger.

type EventStatus

type EventStatus string

EventStatus represents the outcome of an event

const (
	// EventStatusSuccess indicates the operation succeeded
	EventStatusSuccess EventStatus = "success"

	// EventStatusFailed indicates the operation failed
	EventStatusFailed EventStatus = "failed"

	// EventStatusPending indicates the operation is in progress
	EventStatusPending EventStatus = "pending"

	// EventStatusSkipped indicates the operation was skipped
	EventStatusSkipped EventStatus = "skipped"
)

type EventType

type EventType string

EventType represents the type of audit event

const (
	// EventTypePRMerged indicates a pull request was merged
	EventTypePRMerged EventType = "pr.merged"

	// EventTypePRQualified indicates a pull request was qualified
	EventTypePRQualified EventType = "pr.qualified"

	// EventTypePRDisqualified indicates a pull request was disqualified
	EventTypePRDisqualified EventType = "pr.disqualified"

	// EventTypePromotionTriggered indicates a promotion was triggered
	EventTypePromotionTriggered EventType = "promotion.triggered"

	// EventTypePromotionCompleted indicates a promotion completed
	EventTypePromotionCompleted EventType = "promotion.completed"

	// EventTypePromotionFailed indicates a promotion failed
	EventTypePromotionFailed EventType = "promotion.failed"

	// EventTypeExternalTestStarted indicates an external test execution started
	EventTypeExternalTestStarted EventType = "external_test.started"

	// EventTypeExternalTestCompleted indicates an external test execution completed
	EventTypeExternalTestCompleted EventType = "external_test.completed"

	// EventTypeExternalTestFailed indicates an external test execution failed
	EventTypeExternalTestFailed EventType = "external_test.failed"

	// EventTypeArgoCDSync indicates an ArgoCD sync operation was triggered
	EventTypeArgoCDSync EventType = "argocd.sync"

	// EventTypeArgoCDSyncCompleted indicates an ArgoCD sync completed
	EventTypeArgoCDSyncCompleted EventType = "argocd.sync_completed"

	// EventTypeArgoCDSyncFailed indicates an ArgoCD sync failed
	EventTypeArgoCDSyncFailed EventType = "argocd.sync_failed"

	// EventTypeConfigChanged indicates operator configuration was changed
	EventTypeConfigChanged EventType = "config.changed"

	// EventTypeConfigDeleted indicates operator configuration was deleted
	EventTypeConfigDeleted EventType = "config.deleted"

	// EventTypeAuthFailure indicates an authentication failure
	EventTypeAuthFailure EventType = "auth.failure"

	// EventTypeAuthSuccess indicates successful authentication
	EventTypeAuthSuccess EventType = "auth.success"

	// EventTypeWebhookReceived indicates a webhook was received
	EventTypeWebhookReceived EventType = "webhook.received"

	// EventTypeWebhookRejected indicates a webhook was rejected
	EventTypeWebhookRejected EventType = "webhook.rejected"
)

type Logger

type Logger interface {
	// LogEvent records an audit event.
	// Returns an error only if the event could not be queued for logging.
	// The actual logging happens asynchronously.
	LogEvent(ctx context.Context, event AuditEvent) error

	// Close flushes any pending events and shuts down the logger.
	// This should be called during graceful shutdown.
	Close(ctx context.Context) error
}

Logger defines the interface for audit logging. Implementations must be non-blocking and handle errors gracefully.

func NewJSONLogger

func NewJSONLogger(log *logger.Logger, cfg Config) (Logger, error)

NewJSONLogger creates a new JSON audit logger that writes to stdout. Events are processed asynchronously to avoid blocking business logic. The logger must be closed with Close() during graceful shutdown.

func NewNoOpLogger

func NewNoOpLogger() Logger

NewNoOpLogger creates a logger that discards all events.

type NoOpLogger

type NoOpLogger struct{}

NoOpLogger is a no-op implementation of Logger for testing or disabling audit logging.

func (*NoOpLogger) Close

func (n *NoOpLogger) Close(ctx context.Context) error

Close does nothing.

func (*NoOpLogger) LogEvent

func (n *NoOpLogger) LogEvent(ctx context.Context, event AuditEvent) error

LogEvent discards the event.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL