Module 3: Technical Implementation

Access Control for PII

15 min
+50 XP

Access Control for PII

Effective access control is critical for ISO 27018 compliance. This lesson covers implementing least privilege, role-based access control (RBAC), and monitoring PII access in cloud environments.

Access Control Principles

Core Requirements

ISO 27018 Position: "Access to PII shall be restricted to authorized personnel with a legitimate need to know for specified purposes."

Key Principles:

  1. Least Privilege - Minimum access necessary
  2. Need-to-Know - Access based on job function
  3. Separation of Duties - No single person has complete control
  4. Regular Review - Periodic access audits
  5. Revocation - Immediate removal when no longer needed

Role-Based Access Control (RBAC)

RBAC Architecture

Users → Roles → Permissions → Resources (PII)

Example:
- Customer Support Rep → Support Role → Read Customer Contact → Email, Phone
- Developer → Dev Role → Read Anonymous Data → Analytics DB
- Admin → Admin Role → Full Access → All Systems (heavily audited)

Implementing RBAC

Role Definition:

enum Role {
  CUSTOMER_SUPPORT = 'customer_support',
  DEVELOPER = 'developer',
  DATA_ANALYST = 'data_analyst',
  MARKETING = 'marketing',
  ADMIN = 'admin',
  COMPLIANCE_OFFICER = 'compliance_officer'
}

interface Permission {
  resource: string;
  actions: ('read' | 'write' | 'delete')[];
  conditions?: AccessCondition[];
}

interface RoleDefinition {
  role: Role;
  permissions: Permission[];
  piiAccess: PIIAccessLevel;
  requiresMFA: boolean;
  sessionTimeout: number; // minutes
}

const roleDefinitions: RoleDefinition[] = [
  {
    role: Role.CUSTOMER_SUPPORT,
    permissions: [
      {
        resource: 'customer_profile',
        actions: ['read'],
        conditions: [{ field: 'support_ticket_exists', value: true }]
      },
      {
        resource: 'support_tickets',
        actions: ['read', 'write']
      }
    ],
    piiAccess: 'limited', // Only contact info, not payment
    requiresMFA: true,
    sessionTimeout: 30
  },
  {
    role: Role.DEVELOPER,
    permissions: [
      {
        resource: 'analytics_data',
        actions: ['read']
      },
      {
        resource: 'system_logs',
        actions: ['read']
      }
    ],
    piiAccess: 'none', // No PII access for developers
    requiresMFA: true,
    sessionTimeout: 60
  },
  {
    role: Role.DATA_ANALYST,
    permissions: [
      {
        resource: 'aggregated_data',
        actions: ['read']
      },
      {
        resource: 'anonymized_datasets',
        actions: ['read', 'write']
      }
    ],
    piiAccess: 'pseudonymized', // Only pseudonymized data
    requiresMFA: true,
    sessionTimeout: 120
  }
];

Access Control Implementation

Middleware/Gateway:

class PIIAccessControl {
  async checkAccess(
    userId: string,
    resource: string,
    action: string,
    context: AccessContext
  ): Promise<AccessDecision> {
    // 1. Get user's roles
    const roles = await this.getUserRoles(userId);

    // 2. Get combined permissions from all roles
    const permissions = await this.getRolePermissions(roles);

    // 3. Check if action on resource is permitted
    const permitted = this.isActionPermitted(permissions, resource, action);

    if (!permitted) {
      await this.auditLog.record('ACCESS_DENIED', {
        userId,
        resource,
        action,
        reason: 'insufficient_permissions'
      });
      return { allowed: false, reason: 'Insufficient permissions' };
    }

    // 4. Evaluate conditions
    const conditionsMet = await this.evaluateConditions(
      permissions,
      resource,
      context
    );

    if (!conditionsMet) {
      await this.auditLog.record('ACCESS_DENIED', {
        userId,
        resource,
        action,
        reason: 'conditions_not_met'
      });
      return { allowed: false, reason: 'Access conditions not met' };
    }

    // 5. Check MFA if required
    const role = roles[0]; // Primary role
    if (this.requiresMFA(role) && !context.mfaVerified) {
      return { allowed: false, reason: 'MFA required', requiresMFA: true };
    }

    // 6. Log successful access
    await this.auditLog.record('ACCESS_GRANTED', {
      userId,
      resource,
      action,
      timestamp: new Date()
    });

    return { allowed: true };
  }

  private isActionPermitted(
    permissions: Permission[],
    resource: string,
    action: string
  ): boolean {
    return permissions.some(
      p => p.resource === resource && p.actions.includes(action as any)
    );
  }
}

Attribute-Based Access Control (ABAC)

Advanced Access Control

When to Use ABAC:

  • Complex, dynamic access requirements
  • Fine-grained control needed
  • Context-dependent access (time, location, device)

ABAC Policy Example:

interface ABACPolicy {
  id: string;
  description: string;
  subject: SubjectAttributes;
  resource: ResourceAttributes;
  action: string;
  environment: EnvironmentAttributes;
  effect: 'allow' | 'deny';
}

interface SubjectAttributes {
  roles?: string[];
  department?: string;
  clearanceLevel?: number;
  mfaVerified?: boolean;
}

interface ResourceAttributes {
  type: string;
  sensitivity: 'low' | 'medium' | 'high' | 'critical';
  owner?: string;
  tags?: string[];
}

interface EnvironmentAttributes {
  time?: TimeConstraint;
  location?: LocationConstraint;
  ipAddress?: string;
  deviceTrusted?: boolean;
}

const piiAccessPolicies: ABACPolicy[] = [
  {
    id: 'policy_001',
    description: 'Support can view customer contact during business hours',
    subject: {
      roles: ['customer_support'],
      mfaVerified: true
    },
    resource: {
      type: 'customer_contact',
      sensitivity: 'medium'
    },
    action: 'read',
    environment: {
      time: {
        startHour: 8,
        endHour: 18,
        timezone: 'UTC'
      },
      deviceTrusted: true
    },
    effect: 'allow'
  },
  {
    id: 'policy_002',
    description: 'Deny all access to payment data from untrusted devices',
    subject: {},
    resource: {
      type: 'payment_info',
      sensitivity: 'critical'
    },
    action: '*',
    environment: {
      deviceTrusted: false
    },
    effect: 'deny'
  }
];

class ABACEngine {
  async evaluatePolicy(
    user: User,
    resource: Resource,
    action: string,
    environment: Environment
  ): Promise<boolean> {
    const applicablePolicies = this.findApplicablePolicies(
      user,
      resource,
      action,
      environment
    );

    // Deny takes precedence
    if (applicablePolicies.some(p => p.effect === 'deny')) {
      return false;
    }

    // At least one allow policy must match
    return applicablePolicies.some(p => p.effect === 'allow');
  }
}

Field-Level Access Control

Granular PII Access

Control at Field Level:

interface FieldAccessPolicy {
  field: string;
  roles: Role[];
  masked: boolean;
  conditions?: string[];
}

const customerFieldAccess: FieldAccessPolicy[] = [
  {
    field: 'email',
    roles: [Role.CUSTOMER_SUPPORT, Role.ADMIN],
    masked: false
  },
  {
    field: 'phone',
    roles: [Role.CUSTOMER_SUPPORT, Role.ADMIN],
    masked: false
  },
  {
    field: 'ssn',
    roles: [Role.ADMIN],
    masked: true, // Always masked, even for admins
    conditions: ['has_valid_business_justification']
  },
  {
    field: 'credit_card',
    roles: [Role.ADMIN],
    masked: true,
    conditions: ['payment_issue_investigation']
  }
];

class FieldLevelAccessControl {
  async filterFields(
    record: any,
    userRole: Role,
    context: AccessContext
  ): Promise<any> {
    const filteredRecord: any = { id: record.id };

    for (const [field, value] of Object.entries(record)) {
      const policy = customerFieldAccess.find(p => p.field === field);

      if (!policy) {
        // Non-PII field, include by default
        filteredRecord[field] = value;
        continue;
      }

      // Check role permission
      if (!policy.roles.includes(userRole)) {
        continue; // Field not accessible to this role
      }

      // Check conditions
      if (policy.conditions) {
        const conditionsMet = await this.checkConditions(
          policy.conditions,
          context
        );
        if (!conditionsMet) {
          continue;
        }
      }

      // Apply masking if required
      if (policy.masked) {
        filteredRecord[field] = this.maskValue(field, value);
      } else {
        filteredRecord[field] = value;
      }
    }

    // Audit field access
    await this.auditFieldAccess(record.id, filteredRecord, userRole);

    return filteredRecord;
  }

  private maskValue(field: string, value: string): string {
    switch (field) {
      case 'ssn':
        return '***-**-' + value.slice(-4);
      case 'credit_card':
        return '****-****-****-' + value.slice(-4);
      case 'email':
        return value[0] + '***@' + value.split('@')[1];
      default:
        return '***';
    }
  }
}

Database Access Control

Row-Level Security

PostgreSQL Example:

-- Enable Row-Level Security
ALTER TABLE customers ENABLE ROW LEVEL SECURITY;

-- Policy: Support can only see assigned customers
CREATE POLICY support_access_policy ON customers
FOR SELECT
TO support_role
USING (
  assigned_support_id = current_user_id()
  AND support_ticket_active = true
);

-- Policy: Analysts see only anonymized data
CREATE POLICY analyst_access_policy ON customers
FOR SELECT
TO analyst_role
USING (
  -- Returns anonymized view
  false -- Force through anonymized_customers view instead
);

CREATE VIEW anonymized_customers AS
SELECT
  md5(customer_id::text) as pseudo_id,
  age_range(date_of_birth) as age_range,
  zip_to_region(zip_code) as region,
  -- No PII fields
FROM customers;

GRANT SELECT ON anonymized_customers TO analyst_role;

Column-Level Security

Restrict Column Access:

-- Revoke default access
REVOKE ALL ON customers FROM PUBLIC;

-- Grant specific column access to support role
GRANT SELECT (customer_id, email, phone, order_history)
ON customers TO support_role;

-- Admin gets full access
GRANT ALL ON customers TO admin_role;

-- Developers get no PII access
GRANT SELECT (customer_id, created_at, last_login)
ON customers TO developer_role;

API Access Control

OAuth 2.0 Scopes for PII

Scope Definition:

const piiScopes = {
  'pii:read:contact': 'Read customer contact information',
  'pii:read:payment': 'Read customer payment methods',
  'pii:write:profile': 'Update customer profile',
  'pii:delete:account': 'Delete customer account',
  'pii:export:all': 'Export all customer PII'
};

// API endpoint protection
@RequireScope('pii:read:contact')
@RequireMFA()
async getCustomerContact(customerId: string): Promise<ContactInfo> {
  // Only called if user has required scope and MFA
  return await this.customerService.getContact(customerId);
}

// Scope validation middleware
function requireScope(scope: string) {
  return async (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    const decoded = await this.verifyToken(token);

    if (!decoded.scopes.includes(scope)) {
      return res.status(403).json({
        error: 'insufficient_scope',
        required: scope
      });
    }

    next();
  };
}

Multi-Factor Authentication (MFA)

MFA Requirements for PII Access

When MFA is Required:

  • Administrative access to PII
  • Bulk PII exports
  • PII deletion operations
  • Access from new devices/locations
  • High-sensitivity PII access

Implementation:

class MFAService {
  async requireMFA(userId: string, operation: string): Promise<boolean> {
    const user = await this.getUser(userId);

    // Check if MFA already verified in current session
    if (user.session.mfaVerified && !this.sessionExpired(user.session)) {
      return true;
    }

    // Require MFA for operation
    const mfaChallenge = await this.generateMFAChallenge(userId, operation);

    // Wait for MFA verification (webhook/polling)
    const verified = await this.waitForMFAVerification(
      mfaChallenge.id,
      timeout: 300000 // 5 minutes
    );

    if (verified) {
      await this.markSessionMFAVerified(user.session.id);
      await this.auditLog.record('MFA_VERIFIED', {
        userId,
        operation,
        timestamp: new Date()
      });
    }

    return verified;
  }

  async generateMFAChallenge(
    userId: string,
    operation: string
  ): Promise<MFAChallenge> {
    const user = await this.getUser(userId);
    const code = this.generateTOTPCode(user.mfaSecret);

    // Send via configured method
    if (user.mfaMethod === 'sms') {
      await this.sendSMS(user.phone, `Your code: ${code}`);
    } else if (user.mfaMethod === 'email') {
      await this.sendEmail(user.email, `Your code: ${code}`);
    } else if (user.mfaMethod === 'totp') {
      // User uses authenticator app
    }

    return {
      id: generateId(),
      userId,
      operation,
      expiresAt: new Date(Date.now() + 300000)
    };
  }
}

Access Monitoring and Auditing

Comprehensive Audit Logging

What to Log:

interface PIIAccessLog {
  id: string;
  timestamp: Date;
  userId: string;
  userName: string;
  userRole: Role;
  action: 'read' | 'write' | 'delete' | 'export';
  resource: string;
  resourceId: string;
  piiFields: string[];
  ipAddress: string;
  deviceId: string;
  sessionId: string;
  mfaVerified: boolean;
  accessDenied: boolean;
  denialReason?: string;
  businessJustification?: string;
}

class PIIAuditLogger {
  async logAccess(log: PIIAccessLog): Promise<void> {
    // Immutable append-only log
    await this.auditDatabase.insert('pii_access_log', {
      ...log,
      logHash: this.calculateHash(log) // Tamper detection
    });

    // Real-time alerting for suspicious access
    if (this.isSuspicious(log)) {
      await this.alertSecurityTeam(log);
    }

    // Compliance reporting
    if (log.action === 'export' || log.action === 'delete') {
      await this.notifyComplianceTeam(log);
    }
  }

  private isSuspicious(log: PIIAccessLog): boolean {
    return (
      // Unusual access time
      this.isOutsideBusinessHours(log.timestamp) ||
      // High volume access
      this.isHighVolumeAccess(log.userId) ||
      // New device/location
      this.isNewDevice(log.deviceId, log.userId) ||
      // Access denied attempts
      log.accessDenied
    );
  }
}

Anomaly Detection

Automated Monitoring:

class AccessAnomalyDetection {
  async detectAnomalies(): Promise<Anomaly[]> {
    const anomalies: Anomaly[] = [];

    // 1. Unusual access patterns
    const unusualAccess = await this.detectUnusualAccessPattern();
    anomalies.push(...unusualAccess);

    // 2. Privilege escalation attempts
    const escalation = await this.detectPrivilegeEscalation();
    anomalies.push(...escalation);

    // 3. Data exfiltration indicators
    const exfiltration = await this.detectPossibleExfiltration();
    anomalies.push(...exfiltration);

    // 4. Suspicious query patterns
    const suspiciousQueries = await this.detectSuspiciousQueries();
    anomalies.push(...suspiciousQueries);

    return anomalies;
  }

  private async detectUnusualAccessPattern(): Promise<Anomaly[]> {
    // ML-based anomaly detection
    const baseline = await this.getBaselineAccessPattern();
    const current = await this.getCurrentAccessPattern();

    if (this.deviationScore(baseline, current) > threshold) {
      return [{
        type: 'unusual_access_pattern',
        severity: 'high',
        description: 'Access pattern significantly differs from baseline',
        recommendation: 'Review recent PII access logs'
      }];
    }

    return [];
  }
}

Access Review Process

Regular Access Audits

Quarterly Review Process:

interface AccessReview {
  id: string;
  reviewDate: Date;
  reviewer: string;
  scope: 'all' | 'department' | 'role';
  findings: AccessReviewFinding[];
  remediationActions: RemediationAction[];
}

interface AccessReviewFinding {
  userId: string;
  currentAccess: Permission[];
  justification: string;
  approved: boolean;
  revokeAccess: boolean;
  modifyAccess: boolean;
  notes: string;
}

class AccessReviewService {
  async conductReview(scope: string): Promise<AccessReview> {
    // 1. Get all users in scope
    const users = await this.getUsersInScope(scope);

    // 2. Review each user's access
    const findings: AccessReviewFinding[] = [];

    for (const user of users) {
      const access = await this.getUserAccess(user.id);
      const lastUsed = await this.getLastAccessDate(user.id);
      const stillNeeded = await this.verifyAccessNeed(user.id, access);

      findings.push({
        userId: user.id,
        currentAccess: access,
        justification: user.accessJustification,
        approved: stillNeeded,
        revokeAccess: !stillNeeded || daysSince(lastUsed) > 90,
        modifyAccess: false,
        notes: ''
      });
    }

    // 3. Generate review report
    return {
      id: generateId(),
      reviewDate: new Date(),
      reviewer: getCurrentUser(),
      scope,
      findings,
      remediationActions: this.generateRemediationActions(findings)
    };
  }
}

Compliance Checklist

Access Control Implementation:

  • RBAC implemented for all PII access
  • Least privilege principle enforced
  • MFA required for sensitive PII access
  • Field-level access control implemented
  • Database row/column-level security configured
  • API access controlled via scopes/tokens
  • Comprehensive audit logging in place
  • Real-time monitoring and alerting
  • Quarterly access reviews scheduled
  • Immediate access revocation process
  • Separation of duties enforced

Next Lesson: Secure data deletion - implementing cryptographic erasure and data destruction.

Complete this lesson

Earn +50 XP and progress to the next lesson