LogTide
Compliance
Advanced
· SaaS, Fintech, Healthcare, Enterprise

SOC2 & ISO27001 Audit Trail Logging

Build compliant audit trails for SOC2 and ISO27001 certifications with LogTide. Evidence collection, retention, and access controls.

SOC2 Type II ready ISO27001 Annex A Tamper-evident logs Automated retention

SOC2 and ISO27001 certifications require comprehensive audit trails. This guide shows how to implement compliant logging with LogTide that satisfies auditor requirements and makes evidence collection straightforward.

Why Audit Trail Logging Matters

SOC2 Trust Service Criteria

SOC2 Type II requires demonstrable controls across five categories. Logging is central to three of them:

Trust CriteriaLogging RequirementEvidence Needed
Security (CC6)Access controls, authentication eventsLogin/logout, permission changes, MFA events
Availability (A1)System monitoring, incident detectionUptime logs, error rates, incident timelines
Processing Integrity (PI1)Data processing accuracyTransaction logs, data validation events

ISO27001 Annex A Controls

ISO27001 Annex A specifies controls that require audit logging:

  • A.8.15 - Logging: Event logs recording user activities, exceptions, faults
  • A.8.16 - Monitoring activities: Monitoring for anomalous behavior
  • A.5.33 - Protection of records: Ensuring log integrity and retention
  • A.5.23 - Information security for cloud services: Cloud access logging

What Auditors Actually Ask

From real SOC2 and ISO27001 audits:

  1. “Show me who accessed the production database in the last 90 days”
  2. “How do you detect unauthorized access attempts?”
  3. “What’s your log retention policy and how is it enforced?”
  4. “Can logs be tampered with? How do you ensure integrity?”
  5. “Show me the incident response timeline for your last security event”

If you can’t answer these with data, you fail the audit.

The LogTide Approach

LogTide provides the infrastructure for compliant audit trails:

  1. Structured audit events with consistent schema
  2. Immutable storage with TimescaleDB
  3. Role-based access to log data
  4. Configurable retention with automatic enforcement
  5. Built-in SIEM for anomaly detection (Sigma rules)
  6. Self-hosted for full data sovereignty

Implementation

1. Audit Event Schema

Define a consistent schema for all audit events:

// lib/audit-logger.ts
import { LogTideClient } from '@logtide/sdk-node';

interface AuditEvent {
  // WHO performed the action
  actorId: string;
  actorEmail: string;
  actorRole: string;
  actorIp: string;
  actorUserAgent: string;

  // WHAT happened
  action: string;
  resourceType: string;
  resourceId: string;
  outcome: 'success' | 'failure' | 'denied';

  // DETAILS
  details?: Record<string, unknown>;
  previousState?: Record<string, unknown>;
  newState?: Record<string, unknown>;

  // CONTEXT
  sessionId: string;
  requestId: string;
}

const client = new LogTideClient({
  apiUrl: process.env.LOGTIDE_API_URL!,
  apiKey: process.env.LOGTIDE_API_KEY!,
  // Or use a DSN string instead:
  // dsn: process.env.LOGTIDE_DSN,
  globalMetadata: {
    service: 'audit',
    environment: process.env.NODE_ENV,
    auditVersion: '1.0',
  },
});

export function logAudit(event: AuditEvent) {
  const level = event.outcome === 'denied' ? 'warn' :
                event.outcome === 'failure' ? 'error' : 'info';

  client[level]('audit', `AUDIT: ${event.action}`, {
    audit: true,
    actor_id: event.actorId,
    actor_email: event.actorEmail,
    actor_role: event.actorRole,
    actor_ip: event.actorIp,
    actor_user_agent: event.actorUserAgent,
    action: event.action,
    resource_type: event.resourceType,
    resource_id: event.resourceId,
    outcome: event.outcome,
    session_id: event.sessionId,
    request_id: event.requestId,
    details: event.details,
    previous_state: event.previousState,
    new_state: event.newState,
  });
}

2. Authentication Events (SOC2 CC6.1)

// auth/login.ts
import { logAudit } from '@/lib/audit-logger';

app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body;

  try {
    const user = await authenticate(email, password);

    if (!user) {
      logAudit({
        actorId: 'unknown',
        actorEmail: email,
        actorRole: 'unknown',
        actorIp: req.ip,
        actorUserAgent: req.headers['user-agent'] || '',
        action: 'auth.login',
        resourceType: 'session',
        resourceId: 'N/A',
        outcome: 'failure',
        sessionId: 'N/A',
        requestId: req.id,
        details: { reason: 'invalid_credentials' },
      });

      return res.status(401).json({ error: 'Invalid credentials' });
    }

    const session = await createSession(user);

    logAudit({
      actorId: user.id,
      actorEmail: user.email,
      actorRole: user.role,
      actorIp: req.ip,
      actorUserAgent: req.headers['user-agent'] || '',
      action: 'auth.login',
      resourceType: 'session',
      resourceId: session.id,
      outcome: 'success',
      sessionId: session.id,
      requestId: req.id,
      details: { method: 'password', mfaUsed: user.mfaEnabled },
    });

    res.json({ token: session.token });
  } catch (error) {
    logAudit({
      actorId: 'unknown',
      actorEmail: email,
      actorRole: 'unknown',
      actorIp: req.ip,
      actorUserAgent: req.headers['user-agent'] || '',
      action: 'auth.login',
      resourceType: 'session',
      resourceId: 'N/A',
      outcome: 'failure',
      sessionId: 'N/A',
      requestId: req.id,
      details: { reason: 'system_error', error: error.message },
    });

    res.status(500).json({ error: 'Login failed' });
  }
});

3. Authorization & Access Control (SOC2 CC6.3)

// middleware/audit-access.ts
import { logAudit } from '@/lib/audit-logger';

// Audit all sensitive data access
export function auditDataAccess(resourceType: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const startTime = Date.now();

    // Capture original json to audit response
    const originalJson = res.json.bind(res);
    res.json = function (body) {
      const duration = Date.now() - startTime;
      const outcome = res.statusCode < 400 ? 'success' :
                      res.statusCode === 403 ? 'denied' : 'failure';

      logAudit({
        actorId: req.user.id,
        actorEmail: req.user.email,
        actorRole: req.user.role,
        actorIp: req.ip,
        actorUserAgent: req.headers['user-agent'] || '',
        action: `${resourceType}.${req.method.toLowerCase()}`,
        resourceType,
        resourceId: req.params.id || 'list',
        outcome,
        sessionId: req.sessionId,
        requestId: req.id,
        details: {
          method: req.method,
          path: req.path,
          statusCode: res.statusCode,
          durationMs: duration,
        },
      });

      return originalJson(body);
    };

    next();
  };
}

// Apply to sensitive endpoints
app.use('/api/users', auditDataAccess('user'));
app.use('/api/billing', auditDataAccess('billing'));
app.use('/api/settings', auditDataAccess('settings'));
app.use('/api/api-keys', auditDataAccess('api_key'));

4. Change Tracking (SOC2 CC8.1)

// middleware/audit-changes.ts
import { logAudit } from '@/lib/audit-logger';

export function auditChanges(resourceType: string) {
  return async (req: Request, res: Response, next: NextFunction) => {
    // Only audit mutating operations
    if (!['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
      return next();
    }

    // Capture previous state for updates
    let previousState: Record<string, unknown> | undefined;
    if (req.params.id && ['PUT', 'PATCH', 'DELETE'].includes(req.method)) {
      previousState = await getResourceState(resourceType, req.params.id);
    }

    const originalJson = res.json.bind(res);
    res.json = function (body) {
      const action = req.method === 'POST' ? 'create' :
                     req.method === 'DELETE' ? 'delete' : 'update';

      logAudit({
        actorId: req.user.id,
        actorEmail: req.user.email,
        actorRole: req.user.role,
        actorIp: req.ip,
        actorUserAgent: req.headers['user-agent'] || '',
        action: `${resourceType}.${action}`,
        resourceType,
        resourceId: req.params.id || body?.id || 'unknown',
        outcome: res.statusCode < 400 ? 'success' : 'failure',
        sessionId: req.sessionId,
        requestId: req.id,
        previousState,
        newState: req.method !== 'DELETE' ? sanitizeForAudit(req.body) : undefined,
        details: {
          changedFields: previousState
            ? getChangedFields(previousState, req.body)
            : undefined,
        },
      });

      return originalJson(body);
    };

    next();
  };
}

function getChangedFields(
  previous: Record<string, unknown>,
  current: Record<string, unknown>
): string[] {
  return Object.keys(current).filter(
    key => JSON.stringify(previous[key]) !== JSON.stringify(current[key])
  );
}

function sanitizeForAudit(data: Record<string, unknown>): Record<string, unknown> {
  const { password, token, secret, ...safe } = data;
  return safe;
}

5. Retention Policy Configuration

Configure retention to meet compliance requirements:

Log TypeSOC2 RequirementISO27001 RequirementRecommended Retention
Authentication events1 yearPer risk assessment1 year
Access control changes1 yearPer risk assessment2 years
Data modifications1 yearPer risk assessment1 year
System errors90 daysPer risk assessment90 days
Security incidents3 yearsPer risk assessment3 years
Financial transactions7 yearsPer risk assessment7 years

In LogTide, configure retention per-service or per-level through your organization settings.

Evidence Collection for Auditors

Common Auditor Queries

“Show authentication events for the last 90 days”

service:audit AND action:auth.* AND time:>90d

“Show all permission changes”

service:audit AND action:*.update AND resource_type:role

“Show failed access attempts”

service:audit AND outcome:denied

“Show all admin actions”

service:audit AND actor_role:admin

“Show incident timeline for specific date”

service:audit AND time:2025-01-15 AND (outcome:failure OR outcome:denied)

Generating Audit Reports

// scripts/generate-audit-report.ts
import { LogTideClient } from '@logtide/sdk-node';

async function generateAuditReport(startDate: string, endDate: string) {
  const client = new LogTideClient({
    apiUrl: process.env.LOGTIDE_API_URL!,
    apiKey: process.env.LOGTIDE_API_KEY!,
    // Or use a DSN string instead:
    // dsn: process.env.LOGTIDE_DSN,
  });

  // Authentication summary
  const authEvents = await client.search({
    service: 'audit',
    q: 'action:auth.*',
    from: startDate,
    to: endDate,
  });

  // Access denied events
  const deniedEvents = await client.search({
    service: 'audit',
    q: 'outcome:denied',
    from: startDate,
    to: endDate,
  });

  // Configuration changes
  const configChanges = await client.search({
    service: 'audit',
    q: 'action:settings.*',
    from: startDate,
    to: endDate,
  });

  return {
    period: { start: startDate, end: endDate },
    summary: {
      totalAuthEvents: authEvents.length,
      failedLogins: authEvents.filter(e => e.metadata.outcome === 'failure').length,
      accessDenied: deniedEvents.length,
      configChanges: configChanges.length,
    },
    // Detailed data for auditor review
    authEvents,
    deniedEvents,
    configChanges,
  };
}

SOC2 Controls Mapping

SOC2 ControlWhat to LogLogTide Implementation
CC6.1 (Access Control)Login/logout, MFA eventsauth.login, auth.logout, auth.mfa
CC6.2 (User Registration)Account creation, deactivationuser.create, user.deactivate
CC6.3 (Role-Based Access)Permission grants/revokesrole.update, permission.grant
CC6.6 (External Access)API key usage, webhook callsapi_key.create, api_key.use
CC6.8 (Malicious Software)Security alerts, threat detectionSigma rules + alert triggers
CC7.2 (Monitoring)System health, error ratesAlert rules on error thresholds
CC7.3 (Change Detection)Config changes, deploymentssettings.update, deployment.create
CC8.1 (Change Management)Code/config change trackingchange.create, change.approve

ISO27001 Controls Mapping

ISO27001 ControlLogTide Feature
A.5.23 (Cloud Security)Self-hosted, full data sovereignty
A.5.33 (Record Protection)Immutable TimescaleDB storage
A.8.15 (Logging)Structured audit events with consistent schema
A.8.16 (Monitoring)Sigma rules, alert rules, real-time streaming
A.8.24 (Cryptography)TLS for data in transit, encryption at rest via PostgreSQL

Compliance Audit Checklist

Use this before your audit:

  • Audit Logging

    • All authentication events logged (login, logout, MFA)
    • All authorization changes logged (roles, permissions)
    • All data access to sensitive resources logged
    • All configuration changes logged with before/after state
    • Consistent audit event schema across all services
  • Log Integrity

    • Logs stored in append-only storage (TimescaleDB)
    • Access to log storage restricted to authorized personnel
    • Log deletion requires explicit admin action
  • Retention

    • Retention policies documented and configured
    • Automatic enforcement verified
    • Retention periods meet regulatory requirements
    • Long-term archive for financial/legal records
  • Access Control

    • Role-based access to LogTide dashboard
    • API key rotation policy in place
    • Access to logs audited (meta-audit)
  • Incident Detection

    • Sigma rules enabled for common threats
    • Alert rules configured for critical events
    • Incident response playbook documented
    • Escalation paths defined
  • Evidence Readiness

    • Audit report generation tested
    • Common auditor queries documented
    • Export capability verified
    • Sample reports prepared

Common Pitfalls

1. “We log everything, that’s enough”

Auditors want structured, queryable audit trails, not raw application logs.

Solution: Implement dedicated audit events with consistent schema.

2. “Our cloud provider handles compliance”

Using a SOC2-certified cloud provider doesn’t make your application SOC2 compliant.

Solution: Implement application-level audit logging regardless of infrastructure.

3. “We’ll set up logging before the audit”

Auditors for SOC2 Type II need 6-12 months of historical evidence.

Solution: Start logging now. You need at least 6 months of data.

4. “Audit logs and application logs are the same”

Application logs are for debugging. Audit logs are for compliance. Different purposes, different retention, different access controls.

Solution: Use separate service names and retention policies for audit vs application logs.

This guide provides technical implementation guidance for audit trail logging. It does not constitute legal, compliance, or audit advice. Work with qualified auditors and compliance professionals to ensure your specific implementation meets applicable requirements.

Resources


Need help with compliance logging?