Compliance isn’t just a checkbox exercise. Done right, compliance requirements drive you toward better security architecture. Done wrong, they become expensive security theater that doesn’t actually protect your data.

I’ve spent the last year helping our organization achieve and maintain compliance with PCI-DSS, HIPAA, and SOC 2. Here’s what I’ve learned about building systems that are both compliant and genuinely secure.

Understanding the Landscape

Different regulations protect different types of data:

PCI-DSS: Payment Card Industry Data Security Standard. Protects credit card data.

HIPAA: Health Insurance Portability and Accountability Act. Protects health information.

SOC 2: Service Organization Control 2. Validates internal controls for security, availability, and confidentiality.

GDPR: (Coming 2018) General Data Protection Regulation. Protects personal data of EU citizens.

Each has specific requirements, but common themes emerge:

  • Encryption of sensitive data
  • Access controls and authentication
  • Audit logging
  • Network segmentation
  • Regular security assessments
  • Incident response procedures

Encryption Requirements

Almost every regulation requires encryption of sensitive data.

Data at Rest

PCI-DSS Requirement 3.4: β€œRender PAN unreadable anywhere it is stored.”

HIPAA Β§ 164.312(a)(2)(iv): β€œImplement a mechanism to encrypt electronic protected health information.”

Implementation:

type ComplianceEncryption struct {
    kms KeyManagementService
}

func (ce *ComplianceEncryption) EncryptPII(data []byte, dataType string) (*EncryptedData, error) {
    // Use different keys for different data types
    keyID := ce.getKeyForDataType(dataType)

    // Encrypt with AES-256 GCM
    ciphertext, nonce, err := ce.kms.Encrypt(keyID, data)
    if err != nil {
        return nil, err
    }

    // Store metadata for compliance tracking
    return &EncryptedData{
        Ciphertext:    ciphertext,
        Nonce:         nonce,
        KeyID:         keyID,
        Algorithm:     "AES-256-GCM",
        EncryptedAt:   time.Now(),
        DataType:      dataType,
        ComplianceTag: getComplianceTag(dataType), // "PCI", "HIPAA", etc.
    }, nil
}

func (ce *ComplianceEncryption) getKeyForDataType(dataType string) string {
    switch dataType {
    case "credit_card":
        return "pci-dss-key-2015"
    case "health_info":
        return "hipaa-key-2015"
    default:
        return "general-encryption-key"
    }
}

Key points:

  1. Use strong encryption: AES-256 is the standard
  2. Separate keys for different data types: Limit blast radius if a key is compromised
  3. Track what was encrypted when: Essential for audit trails
  4. Use authenticated encryption: GCM mode provides both confidentiality and integrity

Data in Transit

All regulations require encryption of data in transit. TLS 1.2+ is the standard:

func createSecureServer() *http.Server {
    tlsConfig := &tls.Config{
        MinVersion: tls.VersionTLS12,
        CipherSuites: []uint16{
            // PCI-DSS approved cipher suites
            tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
            tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
        },
        PreferServerCipherSuites: true,
    }

    return &http.Server{
        Addr:      ":443",
        TLSConfig: tlsConfig,
        // PCI-DSS recommends against SSLv2, SSLv3, TLS 1.0, TLS 1.1
    }
}

Access Control

Least Privilege

SOC 2 CC6.3: β€œLogical and physical access is restricted to authorized users.”

Every user and system should have the minimum permissions necessary:

type AccessControl struct {
    rbac *RBACEngine
}

func (ac *AccessControl) CheckAccess(user *User, resource string, action string) error {
    // Check if user has role that grants access
    roles := ac.rbac.GetUserRoles(user.ID)

    for _, role := range roles {
        permissions := ac.rbac.GetRolePermissions(role)

        for _, perm := range permissions {
            if perm.Resource == resource && perm.Action == action {
                // Log access for compliance audit
                ac.logAccess(user, resource, action, true)
                return nil
            }
        }
    }

    // Access denied - log for compliance
    ac.logAccess(user, resource, action, false)
    return errors.New("access denied")
}

func (ac *AccessControl) logAccess(user *User, resource string, action string, granted bool) {
    auditLog.Record(AuditEvent{
        Type:      "access_control",
        UserID:    user.ID,
        Resource:  resource,
        Action:    action,
        Granted:   granted,
        Timestamp: time.Now(),
    })
}

Multi-Factor Authentication

PCI-DSS 8.3: β€œSecure all individual non-console administrative access using multi-factor authentication.”

For privileged access, require MFA:

func (auth *AuthService) AuthenticateAdmin(username, password, mfaToken string) (*Session, error) {
    // First factor: password
    user, err := auth.validatePassword(username, password)
    if err != nil {
        return nil, err
    }

    // Require MFA for admin users
    if user.IsAdmin {
        if mfaToken == "" {
            return nil, errors.New("MFA required for admin access")
        }

        // Second factor: TOTP token
        valid := auth.validateMFAToken(user.ID, mfaToken)
        if !valid {
            auth.logMFAFailure(user.ID)
            return nil, errors.New("invalid MFA token")
        }
    }

    // Log successful authentication for compliance
    auth.logAuthentication(user.ID, true, user.IsAdmin)

    return auth.createSession(user)
}

Audit Logging

Every regulation requires comprehensive audit logs.

What to Log

Log all security-relevant events:

  • Authentication attempts (success and failure)
  • Access to sensitive data
  • Privileged operations
  • Configuration changes
  • System access (who logged into systems)
type AuditLogger struct {
    storage *ImmutableStorage
}

func (al *AuditLogger) LogDataAccess(user *User, dataID string, dataType string) {
    event := AuditEvent{
        Type:       "data_access",
        UserID:     user.ID,
        DataID:     dataID,
        DataType:   dataType,
        Timestamp:  time.Now(),
        SourceIP:   user.IP,
        Compliance: getComplianceFramework(dataType),
    }

    // Write to immutable append-only log
    al.storage.Append(event)
}

func getComplianceFramework(dataType string) []string {
    switch dataType {
    case "credit_card":
        return []string{"PCI-DSS"}
    case "health_info":
        return []string{"HIPAA"}
    default:
        return []string{"SOC2"}
    }
}

Immutable Audit Logs

Audit logs must be tamper-proof. Once written, they can’t be modified or deleted:

type ImmutableStorage struct {
    writer *s3.Writer
}

func (is *ImmutableStorage) Append(event AuditEvent) error {
    // Write to append-only S3 bucket with object lock
    data, _ := json.Marshal(event)

    // File name includes timestamp for ordering
    filename := fmt.Sprintf("audit/%s/%s.json",
        event.Timestamp.Format("2006/01/02"),
        event.ID,
    )

    return is.writer.WriteWithLock(filename, data, 7*365*24*time.Hour) // 7 year retention
}

Use cloud provider features like S3 Object Lock or write-once-read-many (WORM) storage.

Log Retention

Different regulations require different retention periods:

  • PCI-DSS: 1 year, with 3 months immediately available
  • HIPAA: 6 years
  • SOC 2: Depends on policy, typically 1+ years

Plan storage accordingly:

const (
    PCILogRetention   = 365 * 24 * time.Hour  // 1 year
    HIPAALogRetention = 6 * 365 * 24 * time.Hour  // 6 years
    SOC2LogRetention  = 2 * 365 * 24 * time.Hour  // 2 years
)

func (al *AuditLogger) GetRetentionPeriod(dataType string) time.Duration {
    switch dataType {
    case "credit_card":
        return PCILogRetention
    case "health_info":
        return HIPAALogRetention
    default:
        return SOC2LogRetention
    }
}

Network Segmentation

PCI-DSS Requirement 1: β€œInstall and maintain a firewall configuration to protect cardholder data.”

Segment your network so not everything can access sensitive data:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Public Zone                        β”‚
β”‚  - Web servers                      β”‚
β”‚  - Public APIs                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚ Firewall
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Application Zone                   β”‚
β”‚  - Application servers              β”‚
β”‚  - Business logic                   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
              β”‚ Firewall
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Data Zone                          β”‚
β”‚  - Databases with PII/PHI           β”‚
β”‚  - Encryption key services          β”‚
β”‚  - No direct internet access        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Implement with security groups, network ACLs, or firewall rules:

# Example: AWS security group rules
data_zone_sg = SecurityGroup(
    name="data-zone",
    ingress_rules=[
        # Only allow access from application zone
        Rule(
            protocol="tcp",
            port=5432,  # PostgreSQL
            source_security_group="application-zone-sg"
        )
    ],
    egress_rules=[
        # No outbound internet access
        Rule(
            protocol="tcp",
            port=443,
            destination="10.0.0.0/8"  # Internal only
        )
    ]
)

Vulnerability Management

SOC 2 CC7.1: β€œThe entity identifies and manages security vulnerabilities.”

Regular Scanning

Scan for vulnerabilities regularly:

#!/bin/bash
# vulnerability-scan.sh

# Scan infrastructure
nessus-cli --scan infrastructure --profile compliance

# Scan containers
for image in $(docker images --format "{{.Repository}}:{{.Tag}}"); do
    trivy image --severity HIGH,CRITICAL $image
done

# Scan dependencies
for service in order-service auth-service encryption-service; do
    cd /services/$service
    snyk test
done

# Generate compliance report
generate-compliance-report --framework PCI-DSS --date $(date +%Y-%m-%d)

Automate this in CI/CD:

# .gitlab-ci.yml
security_scan:
  stage: test
  script:
    - trivy image --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - snyk test
  only:
    - branches
  allow_failure: false  # Block deployment if vulnerabilities found

Patch Management

Track and remediate vulnerabilities within defined timeframes:

  • Critical: 24-48 hours
  • High: 7 days
  • Medium: 30 days
type VulnerabilityTracker struct {
    db *Database
}

func (vt *VulnerabilityTracker) Track(vuln *Vulnerability) error {
    deadline := vt.calculateDeadline(vuln.Severity)

    return vt.db.Insert(VulnerabilityRecord{
        ID:          vuln.ID,
        Severity:    vuln.Severity,
        Component:   vuln.Component,
        Description: vuln.Description,
        DetectedAt:  time.Now(),
        Deadline:    deadline,
        Status:      "OPEN",
    })
}

func (vt *VulnerabilityTracker) calculateDeadline(severity string) time.Time {
    switch severity {
    case "CRITICAL":
        return time.Now().Add(48 * time.Hour)
    case "HIGH":
        return time.Now().Add(7 * 24 * time.Hour)
    case "MEDIUM":
        return time.Now().Add(30 * 24 * time.Hour)
    default:
        return time.Now().Add(90 * 24 * time.Hour)
    }
}

Data Retention and Disposal

GDPR Article 17: β€œRight to erasure” (right to be forgotten)

Data Lifecycle

Define lifecycle for different data types:

type DataLifecyclePolicy struct {
    DataType        string
    RetentionPeriod time.Duration
    DisposalMethod  string
}

var policies = []DataLifecyclePolicy{
    {
        DataType:        "credit_card",
        RetentionPeriod: 90 * 24 * time.Hour,  // 90 days
        DisposalMethod:  "crypto-shredding",
    },
    {
        DataType:        "health_info",
        RetentionPeriod: 6 * 365 * 24 * time.Hour,  // 6 years
        DisposalMethod:  "secure-deletion",
    },
}

func (dlm *DataLifecycleManager) ScheduleDisposal(dataID string, dataType string) {
    policy := dlm.getPolicyForDataType(dataType)

    disposalDate := time.Now().Add(policy.RetentionPeriod)

    dlm.scheduler.Schedule(disposalDate, func() {
        dlm.disposeData(dataID, policy.DisposalMethod)
    })
}

Cryptographic Shredding

When disposing of encrypted data, delete the encryption key instead of overwriting data:

func (dlm *DataLifecycleManager) cryptoShred(dataID string) error {
    // Get the encryption key used for this data
    keyID, err := dlm.getKeyForData(dataID)
    if err != nil {
        return err
    }

    // Delete the encryption key
    // This makes the data unrecoverable
    err = dlm.kms.DeleteKey(keyID)
    if err != nil {
        return err
    }

    // Log for compliance
    dlm.auditLog.Record(AuditEvent{
        Type:      "data_disposal",
        DataID:    dataID,
        Method:    "crypto-shredding",
        KeyID:     keyID,
        Timestamp: time.Now(),
    })

    return nil
}

This is faster and more reliable than overwriting data.

Incident Response

SOC 2 CC7.3: β€œThe entity responds to identified security incidents.”

Detection

Monitor for security incidents:

func (ids *IntrusionDetection) MonitorForIncidents() {
    // Monitor failed authentication attempts
    go ids.monitorFailedAuth()

    // Monitor unusual data access patterns
    go ids.monitorDataAccess()

    // Monitor system integrity
    go ids.monitorSystemIntegrity()
}

func (ids *IntrusionDetection) monitorFailedAuth() {
    ticker := time.NewTicker(1 * time.Minute)
    defer ticker.Stop()

    for range ticker.C {
        count := ids.getFailedAuthCount(5 * time.Minute)

        if count > ids.threshold {
            ids.raiseIncident(Incident{
                Type:        "excessive_failed_auth",
                Severity:    "HIGH",
                Description: fmt.Sprintf("%d failed auth attempts in 5 minutes", count),
                DetectedAt:  time.Now(),
            })
        }
    }
}

Response Procedures

Document and automate response procedures:

func (ir *IncidentResponse) HandleIncident(incident *Incident) error {
    // 1. Contain
    containErr := ir.contain(incident)

    // 2. Notify
    ir.notify(incident)

    // 3. Investigate
    investigation := ir.investigate(incident)

    // 4. Remediate
    ir.remediate(incident, investigation)

    // 5. Document
    return ir.document(incident, investigation, containErr)
}

func (ir *IncidentResponse) contain(incident *Incident) error {
    switch incident.Type {
    case "excessive_failed_auth":
        // Block the source IP
        return ir.firewall.BlockIP(incident.SourceIP, 1*time.Hour)

    case "data_breach":
        // Rotate encryption keys
        return ir.kms.RotateAllKeys()

    case "malware_detected":
        // Isolate the affected system
        return ir.isolateSystem(incident.SystemID)

    default:
        return nil
    }
}

Compliance Automation

Manual compliance doesn’t scale. Automate everything possible:

Configuration as Code

Define compliant configurations in code:

# security_policy.py
class PCIDSSPolicy:
    @staticmethod
    def check_encryption_at_rest(resource):
        """PCI-DSS 3.4: Encrypt stored cardholder data"""
        if resource.type == "database" and resource.contains_pci_data:
            assert resource.encryption_enabled, "PCI data must be encrypted at rest"

    @staticmethod
    def check_network_segmentation(resource):
        """PCI-DSS 1.2: Segment cardholder data from DMZ"""
        if resource.type == "database" and resource.contains_pci_data:
            assert resource.subnet_type == "private", "PCI data must be in private subnet"
            assert not resource.internet_accessible, "PCI data must not be internet-accessible"

Continuous Compliance Checking

Run compliance checks continuously:

func (cc *ComplianceChecker) RunContinuousChecks() {
    ticker := time.NewTicker(1 * time.Hour)
    defer ticker.Stop()

    for range ticker.C {
        violations := []ComplianceViolation{}

        // Check encryption
        violations = append(violations, cc.checkEncryption()...)

        // Check access controls
        violations = append(violations, cc.checkAccessControls()...)

        // Check audit logging
        violations = append(violations, cc.checkAuditLogging()...)

        if len(violations) > 0 {
            cc.reportViolations(violations)
        }
    }
}

Auditor Perspective

What auditors look for:

  1. Evidence: Audit logs, configuration files, scan results
  2. Consistency: Are controls actually in use, or just documented?
  3. Automation: Manual processes are error-prone
  4. Testing: Do you test your controls?

Make the auditor’s job easy:

// Generate compliance report for auditors
func (cr *ComplianceReporter) GenerateReport(framework string, period TimePeriod) (*Report, error) {
    report := &Report{
        Framework: framework,
        Period:    period,
    }

    // Collect evidence
    report.EncryptionEvidence = cr.collectEncryptionEvidence(period)
    report.AccessControlEvidence = cr.collectAccessControlEvidence(period)
    report.AuditLogEvidence = cr.collectAuditLogEvidence(period)
    report.VulnerabilityScanResults = cr.collectVulnerabilityScanResults(period)
    report.IncidentResponseEvidence = cr.collectIncidentResponseEvidence(period)

    return report, nil
}

Provide clear, organized evidence. Auditors appreciate it.

Lessons Learned

Compliance as Security Driver: Use compliance requirements to justify security improvements.

Automate Everything: Manual compliance doesn’t scale and is error-prone.

Build for Compliance: Retrofit is expensive. Design compliant systems from the start.

Document Everything: If it’s not documented, it doesn’t exist (from compliance perspective).

Test Your Controls: Auditors will test them. You should too.

Conclusion

Compliance is complex and sometimes frustrating. But it forces you to think about security systematically.

The key is to see compliance not as a burden but as a framework for building secure systems. The best compliance programs improve actual security, not just check boxes.

Start with understanding what you need to protect and which regulations apply. Build systems with compliance in mind. Automate checks and evidence collection. Document everything.

Your auditorsβ€”and your customersβ€”will thank you.

In future posts, I’ll dive into specific frameworks (PCI-DSS deep dive, HIPAA implementation, SOC 2 preparation) and compliance automation tools.

Stay compliant, stay secure.