มีอยู่ช่วงนึงที่ pipeline เราช้าลงจาก 4 นาทีเป็น 18 นาที แล้วทุกคนก็เริ่มบ่น จนเกือบมีมติให้ปิด security scan บางตัวทิ้งเพื่อความเร็ว นั่นแหละคือจุดที่รู้ตัวว่าเราวาง scanning layer ผิดตั้งแต่ต้น ไม่ใช่เพราะ tool ห่วย แต่เพราะยัดทุกอย่างไว้จุดเดียวแล้วรันตามลำดับแบบโง่ๆ
เรื่องนี้เลยอยากเขียนสรุปไว้ว่าจริงๆ แล้ว pipeline ที่มี security gate ครบ ไม่จำเป็นต้องช้า ไม่จำเป็นต้องทำให้ dev ด่า ถ้าวางเลเยอร์ให้ถูกจุด
ทำไมต้องมีหลายเลเยอร์ ไม่ใช่ tool เดียวจบ
คำถามที่เจอบ่อยคือ "ทำไมไม่ใช้ตัวเดียวสแกนให้หมดเลย" คำตอบสั้นๆ คือไม่มี tool ตัวไหนสแกนทุกชั้นได้ดีเท่ากันหมด แต่ละตัวถูกออกแบบมาสำหรับ attack surface คนละแบบ:
- Secret ที่หลุดเข้า commit — ต้องใช้ tool ที่สแกนเนื้อหา diff/history โดยเฉพาะ
- Logic bug และ security anti-pattern ในโค้ด — ต้องใช้ static analysis ที่เข้าใจ syntax ของภาษานั้นจริงๆ
- Vulnerability ใน dependency ที่ดึงมาใช้ — ต้องเทียบกับ CVE database
- ช่องโหว่ใน base image ตอน build container — ต้องสแกนที่ layer ของ image ไม่ใช่ source code
รวมเป็น scan ตัวเดียวไม่ได้ เพราะแต่ละอันมองคนละมุม ทีมเราเลยจบที่ 4 ชั้น: Gitleaks, Semgrep, OWASP Dependency-Check (กับ OSV เสริม), แล้วก็ Trivy
เลเยอร์ที่ 1: Gitleaks — ดักตั้งแต่ก่อน commit หลุด
Gitleaks เป็นด่านแรกที่ต้องไวที่สุด เพราะ secret ที่หลุดเข้า git history แล้วลบยากกว่าที่คิดเยอะ ต่อให้ force-push ทับ commit ปัจจุบัน key เก่าก็ยังอยู่ใน history เดิม (เว้นแต่จะ rewrite history ทั้ง repo ซึ่งเจ็บกว่าเดิมอีก)
ทีมเราวาง Gitleaks ไว้ 2 จุด:
- pre-commit hook — เบาสุด รันแค่ diff ที่กำลังจะ commit ไม่กิน CI time เลย
- CI stage แรกสุด — เผื่อกรณีมีคน bypass pre-commit hook มา (เกิดขึ้นจริง บ่อยกว่าที่คิด)
จุดที่คนมักพลาด: ตั้ง Gitleaks แล้วไม่เคย custom allowlist สำหรับ false positive พวก test fixture ที่มี string หน้าตาเหมือน API key แต่จริงๆ เป็น dummy data — พอ noise เยอะ dev ก็เริ่ม ignore alert ทั้งหมด ซึ่งอันตรายกว่าไม่มี scan อีก
เลเยอร์ที่ 2: Semgrep — จับ pattern ที่ linter ธรรมดาจับไม่ได้
Semgrep ต่างจาก linter ทั่วไปตรงที่เขียน rule แบบ semantic ได้ ไม่ใช่แค่ regex บนข้อความ เช่นจับ pattern "query string ต่อกับ user input ตรงๆ โดยไม่ผ่าน parameterized query" ซึ่งเป็นต้นตอ SQL injection
ข้อดีคือ community rule set ของ Semgrep ครอบคลุมเยอะมากอยู่แล้ว ไม่ต้องเขียนเองทั้งหมด แต่ rule set default บางอันก็ noisy เกินไปสำหรับ codebase เฉพาะทาง ทีมเราเลยแยก rule เป็น 2 ระดับ:
- Blocking rule — เจอแล้ว fail pipeline ทันที (injection pattern, hardcoded credential pattern, insecure deserialization)
- Warning rule — ขึ้นเป็น comment ใน PR แต่ไม่ block ให้ reviewer ตัดสินใจเอง
แบ่งแบบนี้แล้ว noise ลดไปเยอะ คนไม่เบื่อ alert
เลเยอร์ที่ 3: OWASP Dependency-Check + OSV — เช็ค dependency ที่ดึงมาใช้
อันนี้คือจุดที่ pipeline เราเคยช้าที่สุด เพราะ OWASP Dependency-Check ต้อง sync NVD database ซึ่งบางทีช้าหรือ rate-limit เอา ทางแก้คือ:
- Cache NVD database ไว้ใน artifact storage แทนที่จะ download ใหม่ทุกรอบ
- รัน OSV-Scanner คู่กันไปด้วย เพราะ query ผ่าน API ตรงๆ ไม่ต้อง sync database เอง เร็วกว่ามาก แต่ coverage อาจไม่ครบเท่า NVD ในบาง ecosystem
พอทำสองข้อนี้ เวลารวมของเลเยอร์นี้ลดจาก ~6 นาที เหลือประมาณ 40 วินาที นี่แหละคือจุดที่กู้เวลากลับมาได้เยอะสุดในทั้ง pipeline
เลเยอร์ที่ 4: Trivy — สแกน container image ก่อน push ขึ้น registry
เลเยอร์สุดท้ายอยู่หลัง build image เสร็จ สแกนทั้ง OS package และ language-specific dependency ที่ฝังอยู่ใน image เกณฑ์ที่ทีมเราใช้คือ:
- CRITICAL/HIGH ที่มี fix แล้ว → block ทันที ห้าม push
- CRITICAL/HIGH ที่ยังไม่มี fix → ให้ผ่านได้แต่ต้องมี ticket track ไว้ ห้ามเงียบหาย
- MEDIUM ลงไป → ไม่ block แต่รวมอยู่ใน weekly report
จุดสำคัญคือต้อง set exit-code ให้แยกตาม severity ไม่ใช่ fail ทุกอย่างเป็นก้อนเดียว ไม่งั้น dev จะเจอ pipeline แดงจาก MEDIUM vulnerability ที่ยังไม่มี fix ด้วยซ้ำ แล้วก็เริ่มเบื่อ ignore ทั้งกระดาน
ตัวอย่าง Jenkinsfile แบบย่อ
pipeline {
agent any
stages {
stage('Secret scan') {
steps { sh 'gitleaks detect --source . --exit-code 1' }
}
stage('SAST') {
steps { sh 'semgrep --config=p/security-audit --error' }
}
stage('SCA') {
parallel {
stage('OWASP DC') {
steps { sh 'dependency-check.sh --project myapp --scan . --nvdApiKey $NVD_KEY' }
}
stage('OSV') {
steps { sh 'osv-scanner -r .' }
}
}
}
stage('Build image') {
steps { sh 'docker build -t myapp:$BUILD_NUMBER .' }
}
stage('Image scan') {
steps { sh 'trivy image --severity CRITICAL,HIGH --exit-code 1 myapp:$BUILD_NUMBER' }
}
}
}
จุดที่ทำให้เร็วขึ้นเยอะคือเอา SCA (OWASP DC กับ OSV) มารันแบบ parallel กัน แทนที่จะรันเรียงต่อกัน เพราะสองตัวนี้ไม่มี dependency ต่อกันเลย
บทเรียนที่แพงที่สุดจากเรื่องนี้
ตอนที่ pipeline ช้า 18 นาทีนั่น ปัญหาจริงๆ ไม่ได้อยู่ที่ tool scan ช้า แต่อยู่ที่เรารันทุกอย่างแบบ sequential ทั้งที่ Gitleaks กับ Semgrep ไม่เกี่ยวกันเลย รันคู่กันได้สบายๆ พอจัดใหม่เป็น parallel ตาม dependency จริง เวลารวมลดลงมากกว่าครึ่งโดยไม่ต้องปิด scan ตัวไหนทิ้งสักตัว
Checklist สรุปเลเยอร์ทั้งหมด
ลำดับที่ทีมเราใช้จริงตอนนี้ (บาง stage รันขนานกันได้ตามที่บอกไปข้างบน):
ถ้าใครกำลังเริ่มวาง pipeline ใหม่ ลองเริ่มจาก Gitleaks กับ Trivy ก่อนสองตัวนี้ effort ต่ำ ผลตอบแทนไว แล้วค่อยๆ เพิ่ม Semgrep กับ SCA ทีหลังได้ ไม่ต้องรีบทำครบ 4 ชั้นตั้งแต่วันแรก

Top comments (0)