Chuyển đến nội dung chính

Bài 21: Zero Trust Architecture cho Hệ thống Y tế

Triển khai Zero Trust Architecture cho healthcare: NIST SP 800-207 framework, never trust always verify principles, micro-segmentation, identity-centric security, continuous verification, device trust assessment, network access control, ZTNA implementation với Istio & Keycloak, và policy engine architecture cho healthcare workflows.

🏗️ Kiến trúc — Bài 21 Bài 21: Zero Trust Architecture cho Hệ thống Y tế

Xây dựng Hệ thống Y tế Microservices — Quarkus, PostgreSQL, Keycloak chuẩn HIPAA

Phần 6: Production & Vận hành

xdev.asia

1. Tổng quan Zero Trust Architecture

Zero Trust Architecture cho hệ thống y tế — Micro-segmentation, OPA, Keycloak

1.1. Tại sao Healthcare cần Zero Trust?

Mô hình bảo mật truyền thống dựa trên perimeter security — "tin tưởng mọi thứ bên trong firewall" — đã không còn phù hợp với hệ thống y tế hiện đại. Với sự gia tăng của telemedicine, IoT medical devices, cloud adoption, và remote access cho bác sĩ, perimeter không còn tồn tại rõ ràng.

Traditional Perimeter Security vs Zero Trust — so sánh mô hình bảo mật

Problems với Perimeter Security:

  • Ransomware lây lan lateral trong trusted zone
  • Insider threats không bị kiểm soát
  • IoT devices yếu bảo mật → entry point
  • Remote doctors bypass perimeter
  • Cloud services nằm ngoài perimeter

Thống kê đáng lo ngại:

  • 89% tổ chức healthcare từng bị data breach (Ponemon 2024)
  • Chi phí trung bình của healthcare data breach: $10.93 triệu (cao nhất mọi ngành)
  • 60% ransomware attacks vào healthcare bắt nguồn từ lateral movement trong internal network

1.2. NIST SP 800-207 Framework

NIST Special Publication 800-207 định nghĩa Zero Trust Architecture (ZTA) là mô hình bảo mật dựa trên nguyên tắc: không có implicit trust cho bất kỳ asset, user, hay network segment nào.

NIST SP 800-207 — Zero Trust Architecture:

Core Components:

  • Policy Engine (PE) — Quyết định access dựa trên data sources
  • Policy Administrator (PA) — Thực thi decisions từ PE
  • Policy Enforcement Point (PEP) — Điểm kiểm soát access

Data Sources: CDM, Threat Intel, Activity Logs, PKI

Enterprise Resources: EHR, Lab APIs, Databases, FHIR, PACS

7 Tenets:

  1. All data sources and computing services are resources
  2. All communication is secured regardless of location
  3. Access to individual resources is granted per-session
  4. Access is determined by dynamic policy
  5. Enterprise monitors and measures security posture
  6. Authentication and authorization are dynamic
  7. Enterprise collects info about current state of assets

1.3. Zero Trust Principles cho Healthcare

PrincipleMô tảHealthcare Application
Never Trust, Always VerifyMọi request phải được xác thực và ủy quyềnBác sĩ phải authenticate mỗi lần truy cập bệnh án
Least PrivilegeChỉ cấp quyền tối thiểu cần thiếtNurse chỉ xem được bệnh nhân của mình
Assume BreachThiết kế hệ thống giả định đã bị compromiseEncrypt ePHI at-rest và in-transit, ngay cả internal
Verify ExplicitlySử dụng tất cả available data points để verifyDevice + Location + Time + Role + Context
Micro-segmentationChia nhỏ network thành isolated segmentsMỗi department là 1 segment riêng
Continuous MonitoringLiên tục đánh giá và giám sátReal-time anomaly detection cho ePHI access

2. Zero Trust Reference Architecture cho Hospital

2.1. Healthcare ZTA Overview

Zero Trust Architecture — Hospital System với PEP, Policy Engine, Micro-segmented Services

Layers:

  • Users: Doctor (Mobile), Nurse (Workstation), IoT Medical Device
  • PEP: Istio Ingress Gateway + Envoy Proxy (mTLS, JWT Validation, Rate Limiting + WAF)
  • Policy Engine: Keycloak (AuthN) + OPA (AuthZ) + Risk Engine (Scoring)
  • Data Sources: Device posture (MDM), User behavior analytics, Threat intelligence, GeoIP + Time-of-day
  • Micro-segmented Services: Patient/Lab/Appointment Service (mTLS) → Encrypted Database Layer (RLS + TDE)

2.2. So sánh Perimeter Security vs Zero Trust

AspectPerimeter SecurityZero Trust
Trust modelTrust inside firewallTrust nothing
Network accessFlat internal networkMicro-segmented
AuthenticationOnce at perimeterContinuous per-request
AuthorizationNetwork-based (IP)Identity + Context-based
EncryptionOnly at perimeter (TLS termination)End-to-end (mTLS everywhere)
Lateral movementEasy after breachBlocked by micro-segmentation
MonitoringPerimeter logs onlyFull traffic visibility
IoT devicesTrusted once on networkIsolated, continuously verified
Healthcare fitPoor (too many access points)Excellent (granular control)

3. Identity-Centric Security với Keycloak

Trong Zero Trust, Identity là perimeter mới. Mọi quyết định access dựa trên identity verification, không phải network location.

3.1. Continuous Token Validation

package vn.hospital.zerotrust.identity;

import io.quarkus.oidc.TokenIntrospection;
import io.quarkus.security.identity.SecurityIdentity;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.jboss.logging.Logger;

import java.time.Instant;
import java.util.Set;

/**
 * Zero Trust: Continuous verification filter.
 * Validates token + context on EVERY request, not just at login.
 */
@Provider
@ApplicationScoped
public class ZeroTrustVerificationFilter implements ContainerRequestFilter {

    private static final Logger LOG = Logger.getLogger(ZeroTrustVerificationFilter.class);

    @Inject
    JsonWebToken jwt;

    @Inject
    SecurityIdentity identity;

    @Inject
    DeviceTrustService deviceTrustService;

    @Inject
    RiskScoringService riskScoringService;

    @Override
    public void filter(ContainerRequestContext requestContext) {
        // 1. Verify token is not expired (Quarkus OIDC handles this)
        // 2. Additional Zero Trust checks:

        String userId = jwt.getSubject();
        String sessionId = jwt.getClaim("sid");
        String clientIp = requestContext.getHeaderString("X-Forwarded-For");
        String userAgent = requestContext.getHeaderString("User-Agent");
        String deviceId = requestContext.getHeaderString("X-Device-ID");

        // Check: Token freshness — require recent authentication
        long authTime = jwt.getClaim("auth_time");
        long maxAuthAge = 3600; // 1 hour for standard access
        if (Instant.now().getEpochSecond() - authTime > maxAuthAge) {
            LOG.warnf("ZT-DENY: Token auth_time too old for user=%s", userId);
            requestContext.abortWith(
                Response.status(Response.Status.UNAUTHORIZED)
                    .entity("{\"error\":\"re-authentication_required\","
                            + "\"message\":\"Session expired, please re-authenticate\"}")
                    .build()
            );
            return;
        }

        // Check: Device trust assessment
        if (deviceId != null && !deviceTrustService.isDeviceTrusted(deviceId, userId)) {
            LOG.warnf("ZT-DENY: Untrusted device=%s for user=%s", deviceId, userId);
            requestContext.abortWith(
                Response.status(Response.Status.FORBIDDEN)
                    .entity("{\"error\":\"device_not_trusted\","
                            + "\"message\":\"Device is not registered or compliant\"}")
                    .build()
            );
            return;
        }

        // Check: Risk score — dynamic authorization
        RiskScore risk = riskScoringService.calculateRisk(
            userId, clientIp, userAgent, deviceId, requestContext.getUriInfo().getPath()
        );

        if (risk.score() > 80) {
            LOG.warnf("ZT-DENY: High risk score=%d for user=%s, path=%s",
                risk.score(), userId, requestContext.getUriInfo().getPath());
            requestContext.abortWith(
                Response.status(Response.Status.FORBIDDEN)
                    .entity("{\"error\":\"high_risk_detected\","
                            + "\"message\":\"Access denied due to risk assessment\"}")
                    .build()
            );
            return;
        }

        if (risk.score() > 50) {
            // Medium risk — require step-up authentication
            requestContext.getHeaders().putSingle("X-Require-StepUp", "true");
        }

        LOG.debugf("ZT-ALLOW: user=%s, device=%s, risk=%d, path=%s",
            userId, deviceId, risk.score(), requestContext.getUriInfo().getPath());
    }
}

3.2. Step-Up Authentication cho Sensitive Operations

package vn.hospital.zerotrust.identity;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Response;
import org.eclipse.microprofile.jwt.JsonWebToken;

import java.time.Instant;
import java.util.Set;

/**
 * Step-Up Authentication: Yêu cầu xác thực bổ sung cho operations nhạy cảm.
 * VD: Xem lab results → chỉ cần password
 *     Export patient records → cần password + MFA
 *     Override medication alert → cần password + MFA + supervisor approval
 */
@ApplicationScoped
public class StepUpAuthenticationService {

    private static final Set<String> LEVEL2_OPERATIONS = Set.of(
        "patient.record.view_full",
        "lab.result.view_sensitive",
        "prescription.create"
    );

    private static final Set<String> LEVEL3_OPERATIONS = Set.of(
        "patient.record.export",
        "patient.record.bulk_access",
        "audit.log.modify",
        "system.config.change"
    );

    private static final Set<String> LEVEL4_OPERATIONS = Set.of(
        "patient.record.delete",
        "medication.alert.override",
        "emergency.break_glass"
    );

    @Inject
    JsonWebToken jwt;

    public AuthLevel getRequiredLevel(String operation) {
        if (LEVEL4_OPERATIONS.contains(operation)) return AuthLevel.LEVEL4_SUPERVISOR;
        if (LEVEL3_OPERATIONS.contains(operation)) return AuthLevel.LEVEL3_MFA;
        if (LEVEL2_OPERATIONS.contains(operation)) return AuthLevel.LEVEL2_RECENT;
        return AuthLevel.LEVEL1_STANDARD;
    }

    public StepUpResult verifyStepUp(String operation) {
        AuthLevel required = getRequiredLevel(operation);
        AuthLevel current = getCurrentAuthLevel();

        if (current.ordinal() >= required.ordinal()) {
            return StepUpResult.allowed();
        }

        // Generate step-up challenge
        String authEndpoint = buildStepUpEndpoint(required);
        return StepUpResult.stepUpRequired(required, authEndpoint);
    }

    private AuthLevel getCurrentAuthLevel() {
        // Check Authentication Context Class Reference (ACR)
        String acr = jwt.getClaim("acr");
        long authTime = jwt.getClaim("auth_time");
        long secondsSinceAuth = Instant.now().getEpochSecond() - authTime;

        if ("urn:healthcare:supervisor-approval".equals(acr)) {
            return AuthLevel.LEVEL4_SUPERVISOR;
        }
        if ("urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract".equals(acr)
            && secondsSinceAuth < 300) {
            return AuthLevel.LEVEL3_MFA;
        }
        if (secondsSinceAuth < 900) { // Less than 15 minutes
            return AuthLevel.LEVEL2_RECENT;
        }
        return AuthLevel.LEVEL1_STANDARD;
    }

    private String buildStepUpEndpoint(AuthLevel level) {
        String baseUrl = "/realms/healthcare/protocol/openid-connect/auth";
        return switch (level) {
            case LEVEL2_RECENT -> baseUrl + "?prompt=login&max_age=0";
            case LEVEL3_MFA -> baseUrl + "?acr_values=urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract";
            case LEVEL4_SUPERVISOR -> baseUrl + "?acr_values=urn:healthcare:supervisor-approval";
            default -> baseUrl;
        };
    }

    public enum AuthLevel {
        LEVEL1_STANDARD,     // Basic JWT token valid
        LEVEL2_RECENT,       // Re-authenticated within 15 min
        LEVEL3_MFA,          // MFA completed recently
        LEVEL4_SUPERVISOR    // MFA + Supervisor co-sign
    }

    public record StepUpResult(boolean allowed, AuthLevel requiredLevel, String authEndpoint) {
        static StepUpResult allowed() { return new StepUpResult(true, null, null); }
        static StepUpResult stepUpRequired(AuthLevel level, String endpoint) {
            return new StepUpResult(false, level, endpoint);
        }
    }
}

3.3. Keycloak Realm Configuration cho Zero Trust

{
  "realm": "healthcare",
  "enabled": true,
  "sslRequired": "all",
  "bruteForceProtected": true,
  "maxFailureWaitSeconds": 900,
  "failureFactor": 5,
  "passwordPolicy": "length(12) and digits(1) and upperCase(1) and specialChars(1) and notUsername and passwordHistory(5)",
  "otpPolicyType": "totp",
  "otpPolicyAlgorithm": "HmacSHA256",
  "otpPolicyDigits": 6,
  "otpPolicyPeriod": 30,
  "accessTokenLifespan": 300,
  "ssoSessionMaxLifespan": 28800,
  "offlineSessionMaxLifespan": 0,
  "accessCodeLifespan": 60,
  "attributes": {
    "zero-trust-enabled": "true",
    "continuous-auth-required": "true",
    "device-trust-enforcement": "strict"
  },
  "requiredActions": [
    "CONFIGURE_TOTP",
    "VERIFY_EMAIL",
    "UPDATE_PASSWORD"
  ],
  "authenticationFlows": [
    {
      "alias": "zero-trust-browser",
      "description": "Zero Trust browser authentication with device check",
      "providerId": "basic-flow",
      "topLevel": true,
      "builtIn": false,
      "authenticationExecutions": [
        {
          "authenticator": "auth-cookie",
          "requirement": "ALTERNATIVE",
          "priority": 10
        },
        {
          "authenticator": "auth-username-password-form",
          "requirement": "REQUIRED",
          "priority": 20
        },
        {
          "authenticator": "auth-otp-form",
          "requirement": "REQUIRED",
          "priority": 30
        },
        {
          "authenticator": "auth-device-trust-check",
          "requirement": "REQUIRED",
          "priority": 40
        }
      ]
    }
  ]
}

4. Micro-segmentation với Kubernetes NetworkPolicies

4.1. Network Architecture cho Healthcare

Micro-segmented Healthcare Kubernetes Cluster:

  • Namespace: healthcare-frontend
    • Patient Portal / API Gateway → ONLY port 443
  • ── Network Policy ──
  • Namespace: healthcare-services
    • Patient Svc (port:8080), Lab Svc (port:8080), Appointment Svc (port:8080)
  • ── Network Policy ──
  • Namespace: healthcare-data
    • PostgreSQL (port:5432), Redis Cache (port:6379)
  • Namespace: healthcare-monitoring (READ ONLY)
    • Prometheus (scrape only), Falco (eBPF hooks)

4.2. Kubernetes NetworkPolicies

# networkpolicy-default-deny.yaml
# Zero Trust: deny all traffic by default in healthcare namespaces
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: healthcare-services
spec:
  podSelector: {}     # Applies to ALL pods in namespace
  policyTypes:
    - Ingress
    - Egress
  # No ingress/egress rules = deny all

---
# networkpolicy-patient-service.yaml
# Allow specific traffic to Patient Service only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-patient-service
  namespace: healthcare-services
  labels:
    app.kubernetes.io/part-of: healthcare-platform
    security.hospital.vn/policy: zero-trust
spec:
  podSelector:
    matchLabels:
      app: patient-service
  policyTypes:
    - Ingress
    - Egress
  ingress:
    # Allow from API gateway only
    - from:
        - namespaceSelector:
            matchLabels:
              name: healthcare-frontend
          podSelector:
            matchLabels:
              app: api-gateway
      ports:
        - protocol: TCP
          port: 8080
    # Allow from Istio sidecar (envoy-to-envoy mTLS)
    - from:
        - namespaceSelector:
            matchLabels:
              name: healthcare-services
          podSelector:
            matchLabels:
              app: appointment-service
      ports:
        - protocol: TCP
          port: 8080
    # Allow Prometheus scraping
    - from:
        - namespaceSelector:
            matchLabels:
              name: healthcare-monitoring
          podSelector:
            matchLabels:
              app: prometheus
      ports:
        - protocol: TCP
          port: 9090  # metrics endpoint
  egress:
    # Allow to PostgreSQL only
    - to:
        - namespaceSelector:
            matchLabels:
              name: healthcare-data
          podSelector:
            matchLabels:
              app: postgresql
      ports:
        - protocol: TCP
          port: 5432
    # Allow to Keycloak for token validation
    - to:
        - namespaceSelector:
            matchLabels:
              name: healthcare-auth
          podSelector:
            matchLabels:
              app: keycloak
      ports:
        - protocol: TCP
          port: 8443
    # Allow DNS resolution
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

---
# networkpolicy-database.yaml
# Database: only accessible from service namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-database-access
  namespace: healthcare-data
spec:
  podSelector:
    matchLabels:
      app: postgresql
  policyTypes:
    - Ingress
  ingress:
    # Only from healthcare services
    - from:
        - namespaceSelector:
            matchLabels:
              name: healthcare-services
          podSelector:
            matchExpressions:
              - key: app
                operator: In
                values:
                  - patient-service
                  - lab-service
                  - appointment-service
      ports:
        - protocol: TCP
          port: 5432

4.3. Istio Service Mesh cho mTLS Everywhere

# istio-peer-authentication.yaml
# Enforce STRICT mTLS cho toàn bộ healthcare mesh
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: healthcare-strict-mtls
  namespace: healthcare-services
spec:
  mtls:
    mode: STRICT  # All traffic MUST be mTLS

---
# istio-authorization-policy.yaml
# Fine-grained authorization: chỉ cho phép specific service-to-service calls
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: patient-service-authz
  namespace: healthcare-services
spec:
  selector:
    matchLabels:
      app: patient-service
  action: ALLOW
  rules:
    # Rule 1: API Gateway có thể gọi all patient endpoints
    - from:
        - source:
            principals:
              - "cluster.local/ns/healthcare-frontend/sa/api-gateway"
      to:
        - operation:
            methods: ["GET", "POST", "PUT"]
            paths: ["/api/v1/patients/*"]
      when:
        - key: request.auth.claims[realm_access][roles]
          values: ["doctor", "nurse", "admin"]
    # Rule 2: Appointment service chỉ GET patient demographics
    - from:
        - source:
            principals:
              - "cluster.local/ns/healthcare-services/sa/appointment-service"
      to:
        - operation:
            methods: ["GET"]
            paths: ["/api/v1/patients/*/demographics"]
    # Rule 3: Lab service chỉ GET patient identifiers
    - from:
        - source:
            principals:
              - "cluster.local/ns/healthcare-services/sa/lab-service"
      to:
        - operation:
            methods: ["GET"]
            paths: ["/api/v1/patients/*/identifier"]

---
# istio-request-authentication.yaml
# JWT validation at mesh level
apiVersion: security.istio.io/v1beta1
kind: RequestAuthentication
metadata:
  name: healthcare-jwt-auth
  namespace: healthcare-services
spec:
  jwtRules:
    - issuer: "https://keycloak.hospital.vn/realms/healthcare"
      jwksUri: "https://keycloak.hospital.vn/realms/healthcare/protocol/openid-connect/certs"
      audiences:
        - "healthcare-api"
      forwardOriginalToken: true
      outputClaimToHeaders:
        - header: "x-user-id"
          claim: "sub"
        - header: "x-user-roles"
          claim: "realm_access.roles"
        - header: "x-auth-time"
          claim: "auth_time"

5. Device Trust Assessment

5.1. Device Posture Checking

Trong Zero Trust, device cũng phải được verify, không chỉ user. Một bác sĩ đăng nhập từ personal laptop chưa cập nhật patches sẽ bị hạn chế quyền truy cập.

package vn.hospital.zerotrust.device;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;

/**
 * Device Trust Assessment — Đánh giá mức tin cậy của thiết bị.
 * Tích hợp với MDM (Mobile Device Management) để verify device compliance.
 */
@ApplicationScoped
public class DeviceTrustService {

    private static final Logger LOG = Logger.getLogger(DeviceTrustService.class);

    @Inject
    MdmIntegrationClient mdmClient;

    @Inject
    DeviceRegistryService deviceRegistry;

    public boolean isDeviceTrusted(String deviceId, String userId) {
        DeviceInfo device = deviceRegistry.getDevice(deviceId);
        if (device == null) {
            LOG.warnf("Unknown device: %s for user: %s", deviceId, userId);
            return false;
        }

        // Check device is registered to this user
        if (!device.registeredUserId().equals(userId)) {
            LOG.warnf("Device %s not registered to user %s", deviceId, userId);
            return false;
        }

        // Get MDM compliance status
        DevicePosture posture = mdmClient.getDevicePosture(deviceId);
        return evaluatePosture(posture);
    }

    public DeviceTrustLevel assessTrustLevel(String deviceId) {
        DevicePosture posture = mdmClient.getDevicePosture(deviceId);

        int score = 100;

        // OS up to date?
        if (!posture.osUpToDate()) score -= 20;

        // Disk encryption enabled?
        if (!posture.diskEncrypted()) score -= 30;

        // Screen lock enabled?
        if (!posture.screenLockEnabled()) score -= 15;

        // MDM managed?
        if (!posture.mdmManaged()) score -= 20;

        // Firewall enabled?
        if (!posture.firewallEnabled()) score -= 10;

        // Antivirus running?
        if (!posture.antivirusActive()) score -= 15;

        // Jailbroken/Rooted?
        if (posture.jailbroken()) score = 0;

        // Last compliance check > 24h?
        if (posture.lastComplianceCheck().isBefore(
                Instant.now().minus(24, ChronoUnit.HOURS))) {
            score -= 10;
        }

        if (score >= 80) return DeviceTrustLevel.FULL_ACCESS;
        if (score >= 50) return DeviceTrustLevel.LIMITED_ACCESS;
        if (score >= 20) return DeviceTrustLevel.READ_ONLY;
        return DeviceTrustLevel.BLOCKED;
    }

    private boolean evaluatePosture(DevicePosture posture) {
        // Minimum requirements for healthcare access
        return posture.diskEncrypted()
            && posture.screenLockEnabled()
            && !posture.jailbroken()
            && posture.osUpToDate();
    }

    public enum DeviceTrustLevel {
        FULL_ACCESS,     // Trusted corp device, full ePHI access
        LIMITED_ACCESS,  // Partially compliant, no bulk export
        READ_ONLY,       // Non-compliant device, view only
        BLOCKED          // Jailbroken/compromised, no access
    }

    public record DeviceInfo(
        String deviceId,
        String registeredUserId,
        String deviceType,
        String os,
        Instant registeredAt
    ) {}

    public record DevicePosture(
        boolean osUpToDate,
        boolean diskEncrypted,
        boolean screenLockEnabled,
        boolean mdmManaged,
        boolean firewallEnabled,
        boolean antivirusActive,
        boolean jailbroken,
        Instant lastComplianceCheck
    ) {}
}

5.2. Device Trust Matrix

Device TypeTrust LevelePHI AccessRequired Controls
Hospital workstation (MDM)FullAll ePHIMDM, encryption, auto-lock
Doctor's corp laptop (MDM)FullAll ePHIMDM, encryption, VPN not required
Doctor's personal phoneLimitedView only, no exportIntune enrolled, biometric lock
Nurse's shared tabletLimitedAssigned patients onlyKiosk mode, auto-logout 5min
IoT medical deviceRestrictedOwn data onlyCertificate-based, network isolated
Unknown/BYODBlockedNo ePHIRegistration required

6. OPA (Open Policy Agent) cho Centralized Policy

6.1. OPA Architecture trong Healthcare ZTA

OPA Policy Architecture — Bundle Server → OPA Server → Healthcare Services

Components:

  • Policy Bundle Server: Git repo → OPA Bundle → Distribution
  • OPA Server (Sidecar/Central):
    • Rego Policies: patient_access, device_trust, data_classification, emergency_access
    • Data: roles_permissions.json, department_assignments.json, data_classification_rules.json
  • Clients (query): Patient Svc, Lab Svc, Appt. Svc, API Gateway

6.2. OPA Rego Policies cho Healthcare

# healthcare/patient_access.rego
# Zero Trust policy: who can access which patient data

package healthcare.patient_access

import future.keywords.if
import future.keywords.in

default allow := false

# Data: loaded from external JSON
roles_permissions := data.healthcare.roles_permissions
department_map := data.healthcare.department_assignments

# Rule 1: Doctor can access patients in their department
allow if {
    input.user.role == "doctor"
    input.action in ["read", "write"]
    patient_department := input.resource.department
    user_departments := department_map[input.user.id]
    patient_department in user_departments
}

# Rule 2: Doctor can access patient they are treating (care team)
allow if {
    input.user.role == "doctor"
    input.action in ["read", "write"]
    input.user.id in input.resource.care_team
}

# Rule 3: Nurse read-only for assigned patients
allow if {
    input.user.role == "nurse"
    input.action == "read"
    input.user.id in input.resource.assigned_nurses
}

# Rule 4: Lab technician can only see lab-related fields
allow if {
    input.user.role == "lab_tech"
    input.action == "read"
    input.resource.type == "lab_result"
    input.resource.fields_requested == allowed_lab_fields
}

# Rule 5: Emergency access (break glass)
allow if {
    input.emergency == true
    input.user.role in ["doctor", "nurse"]
    valid_emergency_justification(input.emergency_reason)
}

# Rule 6: Block access from untrusted devices
deny if {
    input.device.trust_level == "blocked"
}

# Rule 7: Block bulk access without approval
deny if {
    input.action == "bulk_export"
    not input.approval.bulk_export_approved
}

# Final decision
decision := {
    "allowed": allow,
    "denied": deny,
    "reasons": reasons,
    "required_audit": audit_required,
}

# Always audit ePHI access
audit_required := true if {
    input.resource.contains_phi == true
}

reasons[msg] if {
    deny
    input.device.trust_level == "blocked"
    msg := "Device not trusted"
}

reasons[msg] if {
    not allow
    msg := "No matching access policy"
}

allowed_lab_fields := {
    "patient_id",
    "test_name",
    "test_date",
    "result_value",
    "reference_range",
    "ordering_doctor_id"
}

valid_emergency_justification(reason) if {
    reason in [
        "life_threatening",
        "emergency_department",
        "code_blue",
        "disaster_response"
    ]
}

6.3. OPA Integration với Quarkus

package vn.hospital.zerotrust.policy;

import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.logging.Logger;

import java.net.URI;
import java.util.Map;

@ApplicationScoped
public class OpaAuthorizationService {

    private static final Logger LOG = Logger.getLogger(OpaAuthorizationService.class);

    @ConfigProperty(name = "opa.url", defaultValue = "http://localhost:8181")
    String opaUrl;

    private OpaClient getClient() {
        return QuarkusRestClientBuilder.newBuilder()
            .baseUri(URI.create(opaUrl))
            .build(OpaClient.class);
    }

    public PolicyDecision evaluate(AccessRequest request) {
        Map<String, Object> input = Map.of(
            "user", Map.of(
                "id", request.userId(),
                "role", request.userRole(),
                "department", request.userDepartment()
            ),
            "resource", Map.of(
                "type", request.resourceType(),
                "id", request.resourceId(),
                "department", request.resourceDepartment(),
                "contains_phi", request.containsPhi(),
                "care_team", request.careTeam(),
                "assigned_nurses", request.assignedNurses()
            ),
            "action", request.action(),
            "device", Map.of(
                "trust_level", request.deviceTrustLevel()
            ),
            "emergency", request.isEmergency(),
            "emergency_reason", request.emergencyReason() != null
                ? request.emergencyReason() : ""
        );

        OpaRequest opaRequest = new OpaRequest(input);
        OpaResponse response = getClient().query(opaRequest);

        LOG.infof("OPA decision: user=%s, resource=%s, action=%s, allowed=%s",
            request.userId(), request.resourceId(),
            request.action(), response.result().allowed());

        return new PolicyDecision(
            response.result().allowed(),
            response.result().denied(),
            response.result().reasons(),
            response.result().requiredAudit()
        );
    }

    @RegisterRestClient
    @Path("/v1/data/healthcare/patient_access")
    public interface OpaClient {
        @POST
        @Consumes(MediaType.APPLICATION_JSON)
        @Produces(MediaType.APPLICATION_JSON)
        OpaResponse query(OpaRequest request);
    }

    public record OpaRequest(Map<String, Object> input) {}
    public record OpaResponse(OpaResult result) {}
    public record OpaResult(
        boolean allowed, boolean denied,
        java.util.List<String> reasons, boolean requiredAudit
    ) {}
    public record PolicyDecision(
        boolean allowed, boolean denied,
        java.util.List<String> reasons, boolean auditRequired
    ) {}
    public record AccessRequest(
        String userId, String userRole, String userDepartment,
        String resourceType, String resourceId, String resourceDepartment,
        boolean containsPhi, java.util.List<String> careTeam,
        java.util.List<String> assignedNurses, String action,
        String deviceTrustLevel, boolean isEmergency, String emergencyReason
    ) {}
}

7. Risk Scoring Engine

7.1. Adaptive Risk-Based Authentication

package vn.hospital.zerotrust.risk;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.jboss.logging.Logger;

import java.time.LocalTime;
import java.util.Map;

/**
 * Risk Scoring Engine — tính điểm risk cho mỗi access request.
 * Score 0-100: 0 = no risk, 100 = maximum risk.
 *
 * Factors:
 * - Location (known IP vs unknown)
 * - Time (business hours vs off-hours)
 * - Device trust level
 * - User behavior (anomaly detection)
 * - Resource sensitivity
 * - Request pattern (frequency, volume)
 */
@ApplicationScoped
public class RiskScoringService {

    private static final Logger LOG = Logger.getLogger(RiskScoringService.class);

    @Inject
    GeoIpService geoIpService;

    @Inject
    UserBehaviorAnalytics ubaService;

    public RiskScore calculateRisk(
        String userId, String clientIp, String userAgent,
        String deviceId, String requestPath
    ) {
        int score = 0;
        Map<String, Integer> factors = new java.util.HashMap<>();

        // Factor 1: Location risk
        int locationRisk = assessLocationRisk(clientIp, userId);
        factors.put("location", locationRisk);
        score += locationRisk;

        // Factor 2: Time-based risk
        int timeRisk = assessTimeRisk();
        factors.put("time", timeRisk);
        score += timeRisk;

        // Factor 3: Resource sensitivity
        int sensitivityRisk = assessResourceSensitivity(requestPath);
        factors.put("sensitivity", sensitivityRisk);
        score += sensitivityRisk;

        // Factor 4: User behavior anomaly
        int behaviorRisk = assessBehaviorRisk(userId, requestPath);
        factors.put("behavior", behaviorRisk);
        score += behaviorRisk;

        // Factor 5: Request velocity
        int velocityRisk = assessVelocityRisk(userId);
        factors.put("velocity", velocityRisk);
        score += velocityRisk;

        // Normalize 0-100
        score = Math.min(score, 100);

        RiskScore result = new RiskScore(score, factors, determineAction(score));

        LOG.infof("Risk score: user=%s, score=%d, action=%s, factors=%s",
            userId, score, result.action(), factors);

        return result;
    }

    private int assessLocationRisk(String clientIp, String userId) {
        GeoIpService.GeoInfo geo = geoIpService.lookup(clientIp);
        // Hospital network = 0 risk
        if (geo.isHospitalNetwork()) return 0;
        // Known home IP of user = 5 risk
        if (geoIpService.isKnownUserLocation(clientIp, userId)) return 5;
        // Same country = 15 risk
        if ("VN".equals(geo.countryCode())) return 15;
        // Foreign IP = 30 risk
        return 30;
    }

    private int assessTimeRisk() {
        LocalTime now = LocalTime.now();
        // Business hours (7:00 - 19:00) = 0 risk
        if (now.isAfter(LocalTime.of(7, 0)) && now.isBefore(LocalTime.of(19, 0))) return 0;
        // After hours (19:00 - 23:00) = 10 risk
        if (now.isBefore(LocalTime.of(23, 0))) return 10;
        // Late night (23:00 - 7:00) = 20 risk
        return 20;
    }

    private int assessResourceSensitivity(String path) {
        if (path.contains("/export") || path.contains("/bulk")) return 25;
        if (path.contains("/patients") && path.contains("/records")) return 15;
        if (path.contains("/lab-results")) return 10;
        if (path.contains("/appointments")) return 5;
        return 0;
    }

    private int assessBehaviorRisk(String userId, String path) {
        return ubaService.getAnomalyScore(userId, path);
    }

    private int assessVelocityRisk(String userId) {
        long requestsLastMinute = ubaService.getRequestCount(userId, 60);
        if (requestsLastMinute > 100) return 25;
        if (requestsLastMinute > 50) return 15;
        if (requestsLastMinute > 20) return 5;
        return 0;
    }

    private String determineAction(int score) {
        if (score >= 80) return "BLOCK";
        if (score >= 50) return "STEP_UP_AUTH";
        if (score >= 30) return "ENHANCED_LOGGING";
        return "ALLOW";
    }

    public record RiskScore(int score, Map<String, Integer> factors, String action) {}
}

8. Zero Trust Network Access (ZTNA) Implementation

8.1. ZTNA vs Traditional VPN

FeatureTraditional VPNZTNA
Access scopeFull network accessPer-application access
Trust modelTrust after connectContinuous verification
User experienceVPN client, slowTransparent, fast
Lateral movementPossibleImpossible
ScalabilityVPN concentrator bottleneckCloud-native, scalable
IoT supportDifficultNative support
VisibilityLimitedFull traffic analysis

8.2. ZTNA Configuration

# ztna-gateway-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ztna-gateway-config
  namespace: healthcare-frontend
data:
  ztna-policy.yaml: |
    ztna:
      applications:
        - name: "patient-portal"
          upstream: "https://patient-service.healthcare-services.svc:8443"
          authentication:
            provider: "keycloak"
            realm: "healthcare"
            required_roles: ["patient", "doctor", "nurse"]
          device_trust:
            minimum_level: "limited"
          access_policy:
            allowed_hours: "00:00-23:59"
            allowed_geolocations: ["VN"]
            max_session_duration: "8h"
            idle_timeout: "30m"

        - name: "ehr-admin"
          upstream: "https://ehr-admin.healthcare-services.svc:8443"
          authentication:
            provider: "keycloak"
            realm: "healthcare"
            required_roles: ["admin"]
            require_mfa: true
          device_trust:
            minimum_level: "full"
            require_mdm: true
          access_policy:
            allowed_hours: "07:00-19:00"
            allowed_geolocations: ["VN"]
            allowed_ips:
              - "10.0.0.0/8"        # Hospital network
              - "172.16.0.0/12"     # VPN range
            max_session_duration: "4h"

        - name: "lab-system"
          upstream: "https://lab-service.healthcare-services.svc:8443"
          authentication:
            provider: "keycloak"
            realm: "healthcare"
            required_roles: ["lab_tech", "doctor"]
          device_trust:
            minimum_level: "limited"
          data_loss_prevention:
            block_download: true
            block_copy_paste: true
            watermark: true

9. Implementation Roadmap cho Legacy Hospital Systems

9.1. Phased Zero Trust Migration

Zero Trust Migration Roadmap — Healthcare:

Phase 1: Foundation (Month 1-3)

  • Deploy Keycloak (centralized identity)
  • Enable MFA for all staff
  • Inventory all assets and data flows
  • Classify data (PHI vs non-PHI)
  • Implement basic audit logging

Phase 2: Identity-Centric Security (Month 4-6)

  • SSO for all applications (Keycloak OIDC)
  • RBAC enforcement (roles → permissions)
  • Device registration program
  • Certificate-based auth for services
  • Decommission shared accounts

Phase 3: Micro-segmentation (Month 7-9)

  • Network segmentation (VLANs per department)
  • Kubernetes NetworkPolicies
  • Istio service mesh (mTLS everywhere)
  • Database: RLS per user context
  • Block lateral movement paths

Phase 4: Continuous Verification (Month 10-12)

  • OPA policy engine deployment
  • Risk scoring engine
  • Device posture checking (MDM integration)
  • Step-up authentication for sensitive ops
  • Behavioral analytics (UBA)

Phase 5: Advanced & Optimization (Month 13-18)

  • Replace VPN with ZTNA
  • Full DLP integration
  • IoT medical device isolation
  • Automated incident response
  • Continuous compliance monitoring

Ongoing: Red team exercises quarterly, policy review, new threat assessment, staff training

9.2. Migration Checklist

PhaseTaskPriorityComplexityHIPAA Mapping
1Deploy centralized IdP (Keycloak)CriticalHigh§164.312(d)
1Enable MFA for all usersCriticalMedium§164.312(d)
1Asset inventoryCriticalMedium§164.308(a)(1)
1Data classificationCriticalHigh§164.312(a)
2SSO integrationHighHigh§164.312(a)(2)(i)
2RBAC enforcementCriticalHigh§164.312(a)(1)
2Device registrationHighMedium§164.310(d)
3Network segmentationHighHigh§164.312(e)(1)
3mTLS for all servicesHighMedium§164.312(e)(2)(ii)
3Database RLSCriticalHigh§164.312(a)(1)
4Policy engine (OPA)MediumHigh§164.312(a)(1)
4Risk scoringMediumHigh§164.308(a)(1)
4Device posture checkingMediumMedium§164.310(d)
5ZTNA deploymentMediumHigh§164.312(e)(1)
5DLP integrationMediumMedium§164.312(c)(1)

10. Zero Trust Data Access

10.1. Data-Centric Zero Trust

Zero Trust Data Protection Layers — Classify, Encrypt, Control, Monitor

Layer 1: Classify Everything

  • PHI: patient name, SSN, diagnosis, labs
  • PII: email, phone, address
  • Sensitive: billing, insurance
  • Internal: schedules, inventory
  • Public: hospital info, general health tips

Layer 2: Encrypt Everything

At RestIn TransitIn Use
PHIAES-256TLS 1.3Enclaves
PIIAES-256TLS 1.3Masking
SensitiveAES-256TLS 1.3—
InternalTDETLS 1.2+—

Layer 3: Control Everything — RLS, Column-level encryption, Dynamic masking, DLP, Watermarking

Layer 4: Monitor Everything — Full audit trail, Real-time anomaly detection, Data lineage tracking, Compliance dashboards

10.2. Application Properties cho Zero Trust

# application.properties — Zero Trust configuration

# === Keycloak OIDC (Short-lived tokens) ===
quarkus.oidc.auth-server-url=https://keycloak.hospital.vn/realms/healthcare
quarkus.oidc.client-id=patient-service
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}
quarkus.oidc.token.lifespan-grace=10
quarkus.oidc.token.age=300
quarkus.oidc.authentication.verify-access-token-with-user-info.enabled=true

# === mTLS for service-to-service ===
quarkus.http.ssl.certificate.files=/etc/certs/tls.crt
quarkus.http.ssl.certificate.key-files=/etc/certs/tls.key
quarkus.http.ssl.certificate.trust-store-file=/etc/certs/ca-bundle.crt
quarkus.http.ssl.client-auth=required

# === OPA Policy Engine ===
opa.url=http://opa.healthcare-policy.svc:8181
opa.policy.path=/v1/data/healthcare/patient_access
opa.decision.cache.ttl=30s

# === Risk Scoring ===
zerotrust.risk.max-score-allow=30
zerotrust.risk.max-score-stepup=50
zerotrust.risk.block-threshold=80
zerotrust.risk.velocity.window=60s
zerotrust.risk.velocity.max-requests=100

# === Device Trust ===
zerotrust.device.enforcement=strict
zerotrust.device.mdm.url=https://mdm.hospital.vn/api/v1
zerotrust.device.minimum-trust-level=limited

# === Database Zero Trust ===
quarkus.datasource.jdbc.url=jdbc:postgresql://pg.healthcare-data.svc:5432/healthcare?ssl=true&sslmode=verify-full
quarkus.datasource.jdbc.additional-jdbc-properties.sslcert=/etc/certs/db-client.crt
quarkus.datasource.jdbc.additional-jdbc-properties.sslkey=/etc/certs/db-client.key
quarkus.datasource.jdbc.additional-jdbc-properties.sslrootcert=/etc/certs/db-ca.crt

Tổng kết

Trong bài học này, chúng ta đã triển khai Zero Trust Architecture hoàn chỉnh cho hệ thống y tế:

  1. NIST SP 800-207 Framework: Hiểu kiến trúc Zero Trust với Policy Engine, Policy Administrator, và Policy Enforcement Point — nền tảng lý thuyết cho healthcare ZTA
  2. Zero Trust Principles: Never trust/always verify, least privilege, assume breach, micro-segmentation — áp dụng cụ thể cho bệnh viện
  3. Identity-Centric Security: Keycloak với continuous token validation, step-up authentication 4 levels (standard → MFA → supervisor), short-lived tokens (5 phút)
  4. Micro-segmentation: Kubernetes NetworkPolicies deny-all default, fine-grained per-service rules, namespace isolation per function
  5. Istio Service Mesh: STRICT mTLS cho tất cả service-to-service, AuthorizationPolicy per-endpoint, RequestAuthentication JWT validation
  6. Device Trust: MDM integration, device posture checking (encryption, OS updates, jailbreak), trust level matrix (full → limited → read-only → blocked)
  7. OPA Policy Engine: Centralized Rego policies cho patient access control, emergency break-glass, data classification enforcement
  8. Risk Scoring Engine: Multi-factor risk assessment (location, time, behavior, velocity, sensitivity), adaptive authentication based on risk score
  9. ZTNA: Thay thế VPN bằng per-application access, continuous verify, native IoT support
  10. Implementation Roadmap: 5-phase migration plan (18 months) cho legacy hospital systems, từ foundation tới advanced Zero Trust

Bài tập

  1. NetworkPolicy Lab: Triển khai Kubernetes cluster (minikube/kind) với 3 namespace (frontend, services, data). Tạo default-deny NetworkPolicy cho mỗi namespace. Deploy nginx pods đại diện cho patient-service, lab-service, postgresql. Tạo NetworkPolicies cho phép: frontend → services (port 8080), services → data (port 5432), chặn services → services (trừ appointment → patient). Verify bằng kubectl exec curl giữa các pods.

  2. OPA Policy Engine: Cài OPA server (Docker). Viết Rego policy cho healthcare scenario: doctor in cardiology chỉ xem bệnh nhân cardiology, nurse chỉ xem assigned patients, emergency break-glass cho phép any doctor xem any patient khi emergency=true. Test tất cả scenarios bằng opa eval. Viết unit tests cho policies bằng opa test.

  3. Risk Scoring Prototype: Implement Java class RiskScoringService với 5 factors (location, time, device, behavior, velocity). Viết unit tests cover: business hours + hospital IP = score < 10, midnight + foreign IP + unknown device = score > 70, bulk export request luôn cộng thêm 25 points. Integrate với Quarkus endpoint: GET /api/risk-score trả về current risk cho authenticated user.

  4. Istio mTLS Setup: Deploy Istio trên Kubernetes cluster (istioctl install). Enable sidecar injection cho healthcare namespace. Deploy 2 services (patient-service, lab-service). Apply PeerAuthentication STRICT mode. Verify mTLS bằng istioctl proxy-config và Kiali dashboard. Apply AuthorizationPolicy chỉ cho phép lab-service GET /api/patients/{id}/identifier.



◀ Bài trướcBài tiếp theo ▶
Bài 20: Backup, Disaster Recovery & Business Continuity cho Dữ liệu Y TếBài 22: Container & Kubernetes Security cho Healthcare Workloads