Files
BE_IBL/docs/pdp-encryption-runbook.md
2026-05-31 17:50:14 +07:00

11 KiB

Runbook: Implementasi Enkripsi PII Pasien (UU PDP) — Production

Dibuat: 2026-05-31 | Task: FHM31052601IBL Teknologi: AES-256-GCM, Trigram Blind Index, PHP CodeIgniter 3, MariaDB


Arsitektur

.env (passphrase)
    │
    ▼
Ibl_encryptor library
├── encrypt/decrypt  → kolom _enc  (AES-256-GCM)
└── search_bidx      → kolom _bidx (HMAC-SHA256 trigram, untuk search)

m_patient (plaintext kolom lama = MASKED)
├── M_PatientName     → "FAJRI H*******"   (masked)
├── M_PatientName_enc → "base64ciphertext"  (encrypted, real value)
├── M_PatientName_bidx→ ["tok1","tok2",...] (search index)
└── ...field lainnya

Search patient: nama + HP + DOB + NIK via JSON_CONTAINS(_bidx)
Read patient  : decrypt _enc di PHP sebelum return ke client

Pre-flight Checklist

# 1. Pastikan disk minimal 10GB free
df -h /

# 2. Catat ukuran DB sebelum (untuk verifikasi)
mysql -e "SELECT table_name, ROUND((data_length+index_length)/1024/1024,1) MB
  FROM information_schema.tables WHERE table_schema='one_lab'
  ORDER BY (data_length+index_length) DESC LIMIT 15;"

# 3. Pastikan tidak ada proses berat berjalan
pgrep -a php

⚠️ Disk Space: Enkripsi menambah ~1-2GB ke database (kolom _enc + _bidx). Jika disk penuh, lihat bagian Troubleshooting di bawah.


Urutan Eksekusi di Production

Step 1 — Buat file .env

cat > /path/to/one-api-lab/.env << 'EOF'
IBL_ENCRYPT_KEY=<passphrase-kamu>
IBL_ENCRYPT_SEARCH_KEY=<passphrase-search-kamu>
EOF
chmod 600 /path/to/one-api-lab/.env

⚠️ WAJIB simpan kedua passphrase di password manager sebelum lanjut. Key hilang = data di _enc tidak bisa didekripsi selamanya.


Step 2 — Backup Database

bash scripts/backup_pdp_tables.sh
# Backup tersimpan di: ~/backup_pdp_YYYY_MM_DD_HHMMSS/
# Verifikasi backup ada dan tidak kosong
ls -lh ~/backup_pdp_*/

Step 3 — Jalankan SQL Migration (tambah kolom + update trigger)

# Tambah kolom _enc dan _bidx
mysql one_lab < sql/manual_changes/2026-05-31-pdp-encrypt-columns.sql

# Update trigger m_patient & m_patientaddress (pakai _enc di log JSON)
mysql one_lab < sql/manual_changes/2026-05-31-pdp-update-triggers-enc.sql

# Verifikasi kolom terbentuk
mysql -e "SHOW COLUMNS FROM one_lab.m_patient LIKE '%_enc';"
mysql -e "SHOW COLUMNS FROM one_lab.m_patient LIKE '%_bidx';"
mysql -e "SHOW COLUMNS FROM one_lab.m_patientaddress LIKE '%_enc';"

Step 4 — Encrypt Data Pasien (m_patient)

# Estimasi: 30-60 menit untuk 178K rows
php scripts/migrate_encrypt_patient.php

# Verifikasi
mysql -e "SELECT COUNT(*) total, COUNT(M_PatientName_enc) done
  FROM one_lab.m_patient;"
# Expected: total == done

Step 5 — Populate NIK Bidx

# Isi search index untuk NIK (dari _enc yang sudah ada)
# Estimasi: 5-10 menit
php scripts/migrate_nik_bidx.php

# Verifikasi
mysql -e "SELECT COUNT(*) total, COUNT(M_PatientNIK_bidx) done
  FROM one_lab.m_patient WHERE M_PatientNIK_enc IS NOT NULL;"

Step 6 — Encrypt Alamat Pasien (m_patientaddress)

# Enkripsi alamat TANPA bidx (hemat disk)
# Estimasi: 15-30 menit untuk 133K rows
php scripts/migrate_address_enc.php

# Verifikasi
mysql -e "SELECT COUNT(*) total, COUNT(M_PatientAddressDescription_enc) done
  FROM one_lab.m_patientaddress;"
# Expected: total == done

Step 7 — Encrypt Tujuan Pengiriman Hasil (t_orderdelivery)

# HANYA t_orderdelivery — berisi email/HP pasien (PII nyata, bisa dimasking)
# Tabel hasil lab (t_orderdetail, so_resultentry*, dll) TIDAK dienkripsi —
# lihat bagian "Keputusan Arsitektur" di bawah
php scripts/migrate_encrypt_orderdelivery.php

# Verifikasi
mysql -e "SELECT COUNT(*) total, COUNT(T_OrderDeliveryDestination_enc) done
  FROM one_lab.t_orderdelivery;"

Step 8 — Masking Kolom Plaintext

# Masking semua kolom PII lama di m_patient & m_patientaddress
# Format nama: "FAJRI H*******" (kata pertama penuh + inisial)
php scripts/mask_patient_plaintext.php

# Re-mask nama dengan format terbaru (jika sudah pernah dimasking sebelumnya)
php scripts/remask_patient_name.php

# Verifikasi: cek beberapa baris
mysql -e "SELECT M_PatientID, M_PatientName, M_PatientHP, M_PatientEmail
  FROM one_lab.m_patient ORDER BY RAND() LIMIT 5;"
# Expected: tampil "BUDI S******", "0812*****890", "bu***@gmail.com"

Step 9 — Truncate Log Lama (Opsional tapi Direkomendasikan)

# log_patient berisi JSON plaintext PII dari sebelum enkripsi
# Truncate meningkatkan compliance (hapus data PII lama yang tidak terenkripsi)
mysql one_lab_log -e 'TRUNCATE TABLE log_patient;'

# Verifikasi
mysql -e "SELECT COUNT(*) FROM one_lab_log.log_patient;"
# Expected: 0

Step 10 — Verifikasi End-to-End

# 1. Cek search patient berjalan
curl -s -X POST https://[SERVER]/mockup/fo/ibl_registration/patient/search \
  -H "Content-Type: application/json" \
  -d '{"token":"[VALID_TOKEN]","search":"BUDI","noreg":"","current_page":1}' \
  | python3 -m json.tool | head -20

# 2. Cek data terdekripsi dengan benar (nama muncul lengkap, bukan masked)
# Expected di response: "M_PatientName": "BUDI SANTOSO" (bukan "BUDI S******")

# 3. Cek disk usage akhir
df -h /

# 4. Cek MySQL masih sehat
mysql -e "SHOW STATUS LIKE 'Threads_connected';"

Field yang Dienkripsi

one_lab.m_patient

Field _enc _bidx (search)
M_PatientName
M_PatientHP
M_PatientDOB
M_PatientNIK
M_PatientEmail
M_PatientPhone
M_PatientPOB
M_PatientIDNumber
M_PatientNIP

one_lab.m_patientaddress

Field _enc _bidx
M_PatientAddressDescription — (dihapus, hemat disk)
M_PatientAddressEmail
M_PatientAddressPhone

Tujuan Pengiriman Hasil (PII nyata)

Tabel Field
t_orderdelivery T_OrderDeliveryDestination (email/HP)

Log

Tabel Field
one_lab_log.log_patient Log_PatientJsonBefore/After (di-truncate di production)
one_lab_log.log_fo Log_FoJson
one_lab_log.log_resultentry Log_ResultEntryJSONBefore/After

TIDAK Dienkripsi (keputusan disengaja)

Tabel Alasan
t_orderdetail, t_orderheader Nilai hasil lab bukan PII tanpa identitas pasien. Trigger butuh plaintext untuk flag H/L/N.
so_resultentry_*, member_eligible Nilai klinis, bukan PII langsung. Plaintext dibutuhkan proses operasional.
mcu_resume_results JSON nilai lab tanpa PII. Enkripsi memberatkan global MCU report.

Perlindungan hasil lab tetap via: identitas pasien terenkripsi di m_patient + access control + audit log.


Format Masking Kolom Plaintext

Field Format Contoh
Nama Kata pertama penuh + inisial+bintang per kata FAJRI H******* M****
HP/Phone 4 digit pertama + bintang + 3 digit akhir 0812*****890
Email 2 huruf pertama + *** + @domain fa***@gmail.com
NIK/IDNumber 4 digit pertama + *** + 2 digit akhir 3201***01
POB 2 huruf pertama + *** JA***
Alamat 5 karakter pertama + *** Jl. S***

Search Pasien

Parameter via + separator di field search:

search=NAMA+HP+DOB+NIK
Posisi Field Contoh
e[0] Nama (min 3 karakter) BUD
e[1] HP (min 3 karakter) 081
e[2] DOB format dd-mm-yyyy 25-0
e[3] NIK (min 3 karakter) 320

BIRT Report & FPDF

Strategi Decrypt untuk Report

BIRT Reports (print_transaction):

  • PHP Birt_proxy.php → decrypt PII → INSERT patient_print_cache → call BIRT
  • 6 SP header yang diupdate dengan LEFT JOIN ke cache: sp_rpt_hasil_header, sp_rpt_hasil_header_2, sp_rpt_hasil_header_eng, sp_rpt_fo_001, sp_rpt_card_patient, sp_rpt_t_002
  • SP signature tidak berubah — .rptdesign tidak perlu diupdate
  • Cache TTL: 5 menit, auto-cleanup di request berikutnya

Endpoint Birt_proxy:

  • POST /tools/birt_proxy/stream — return PDF binary langsung
  • POST /tools/birt_proxy/get_url — return URL untuk buka di browser

FPDF Controllers (tools/):

  • Inform_consent.php, Medical_checkup_report.php — decrypt langsung dari _enc (direct SQL)
  • Kartu_kontrol.php, Rpt_t_002.php, Rpt_t_002_eng.php — populate cache → call SP → delete cache

SQL produksi yang perlu dijalankan:

mysql one_lab < sql/manual_changes/2026-05-31-pdp-birt-sp-cache-join.sql

Controller yang Sudah Diupdate (Decrypt + Encrypt)

Controller Fungsi
mockup/fo/ibl_registration/Patient.php Search, add, edit pasien FO
mockup/fo/ibl_registration/Order.php Order management (nama, email, HP)
mockup/fo/ibl_registration/Payment.php Kasir (nama pasien)
mockup/fo/ibl_registration/History.php History delivery (email/HP)
mockup/fo/ibl_registration/Delivery.php Pengiriman hasil (email/HP)
mockup/fo/ibl_registration/Order copy.php Order MCU
mockup/masterdata/Patientv4.php Masterdata pasien — tampil data lengkap

Controller yang Belum Diupdate (Tampil Data Masked)

Semua ~300+ controller lain otomatis tampilkan data termasking karena kolom plaintext sudah dimasking. Tidak perlu update satu-satu untuk compliance dasar.

Sprint berikutnya — update controller prioritas yang butuh data lengkap:

  • Sampling (samplinglab-vvii, samplingelectromedisnew)
  • Result entry (resultentrysoothers-v20, resultentrysoxray-v8, resultentrysoelectromedis-v8)
  • Result verification
  • MCU resume (resumeindividufacelift)

Troubleshooting

Disk Penuh Saat Migration

# Cek pemakai disk terbesar
du -sh /home/one/* /tmp/* 2>/dev/null | sort -rh | head -20

# Bersihkan file lama yang aman dihapus:
# - /home/one/project/one/dump_*.sql (backup lama)
# - /home/one/project/one/*.tar.gz (archive lama)
# - /tmp/intelephense (cache IDE)

# Bersihkan journal (butuh sudo)
sudo journalctl --vacuum-size=300M
sudo truncate -s 0 /var/log/btmp

MySQL Crash (Disk Penuh)

# Restart MySQL setelah disk dibebaskan
sudo systemctl start mariadb
# atau
sudo service mysql start

Migration Lambat (Trigger Overhead)

Trigger m_patientaddress_bu nulis ke log_patient setiap UPDATE. Jika log_patient sangat besar (>1GB) dan disk hampir penuh:

# Truncate log lama (aman — data PII lama di log justru harus dihapus untuk compliance)
mysql one_lab_log -e 'TRUNCATE TABLE log_patient;'

Restore jika Ada Masalah

mysql one_lab < ~/backup_pdp_YYYY_MM_DD_HHMMSS/one_lab_tables.sql
mysql one_lab_log < ~/backup_pdp_YYYY_MM_DD_HHMMSS/one_lab_log_tables.sql

Catatan Key Management

  • Key disimpan di .envJANGAN commit ke git (sudah ada di .gitignore)
  • Backup passphrase di: password manager + file enkripsi di lokasi terpisah dari server
  • Key rotation di masa depan: perlu re-encrypt semua data (decrypt lama → encrypt baru)
  • Tidak ada recovery jika key hilang