Sign in

Part of 10306343 การจัดการโครงสร้างพื้นฐานและการส่งมอบซอฟต์แวร์อย่างต่อเนื่อง (Development Operations: DevOps)

2026-10-20 · updated 2026-10-29 · 26 min read

บทที่ 10 DevSecOps พื้นฐาน: สแกนช่องโหว่ใน pipeline, SBOM และ GitHub Secrets

ShareFacebookXLinkedInLINE

แผนบริหารการสอนประจำบทที่ 10

หัวข้อเนื้อหา

  • 10.1 Shift Left และกรอบ SSDF
  • 10.2 ห่วงโซ่อุปทานซอฟต์แวร์และ SBOM
  • 10.3 ช่องโหว่ CVE ระดับความรุนแรง และการตัดสินใจแก้
  • 10.4 ความลับใน GitHub Actions
  • 10.5 อ่านรายงานช่องโหว่ก่อนเลือกวิธีแก้
  • 10.6 ไม่พบช่องโหว่ ไม่พบตามตัวกรอง และตรวจไม่สำเร็จ
  • 10.7 SBOM ตอบว่าใช้อะไร แต่ยังต้องตอบว่าได้รับผลหรือไม่
  • 10.8 ความลับใน workflow และขอบเขตความเชื่อถือ
  • ปฏิบัติการที่ 10 เพิ่มประตูตรวจความปลอดภัยใน pipeline (Lab A–E)

วัตถุประสงค์เชิงพฤติกรรม

เมื่อศึกษาบทนี้และทำปฏิบัติการครบถ้วนแล้ว ผู้เรียนสามารถปฏิบัติได้ตามผลลัพธ์การเรียนรู้ต่อไปนี้ โดยแต่ละข้อมีงานที่ใช้วัดผลกำกับไว้

รหัสผลลัพธ์การเรียนรู้วัดจากCLO ของรายวิชา
10.1เพิ่มขั้นสแกน Trivy ใน workflow ให้ pipeline ล้มเมื่อพบช่องโหว่ระดับที่กำหนดงานข้อ 1CLO2
10.2อ่านรายงานสแกน เลือกวิธีแก้อย่างน้อย 1 ช่องโหว่ และบันทึกแหล่งข้อมูลที่เชื่อถือได้งานข้อ 2CLO4
—รายงานความก้าวหน้างานต่อยอดด้วยตนเองงานข้อ 3CLO5 (จุดตรวจที่ 2)

CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):

  • CLO2 สามารถกำหนดค่าและใช้งาน DevOps workflow ที่กำหนดมา เพื่อให้กระบวนการพัฒนา–ทดสอบ–ส่งมอบซอฟต์แวร์เป็นไปอย่างถูกต้องและมีประสิทธิภาพ
  • CLO4 ผู้เรียนแสดงออกถึงความรับผิดชอบและความใฝ่รู้ในการเรียนรู้เทคโนโลยีและเครื่องมือใหม่ที่เกี่ยวข้องกับ DevOps โดยศึกษาค้นคว้าด้วยตนเองจากแหล่งข้อมูลที่เชื่อถือได้ และปฏิบัติตามแนวปฏิบัติด้านความปลอดภัยและคุณภาพของระบบ
  • CLO5 แสวงหาความรู้เพิ่มเติมและสร้างสรรค์ผลงานจากการเรียนรู้ด้วยตัวเอง

งานข้อ 2 เป็นจุดตรวจความปลอดภัยครั้งที่ 3 จาก 4 ครั้งของ CLO4 (บทที่ 1, 9, 10 และ 12) ต่างจากการตรวจ repository ในบทที่ 9 และ 12 ตรงที่ประเมินการตัดสินใจแก้ช่องโหว่ งานข้อ 3 เป็นจุดตรวจที่ 2 ของงานต่อยอดด้วยตนเอง ต่อจากหัวข้อที่เสนอในบทที่ 6 และก่อนการสาธิตในบทที่ 13 จุดตรวจนี้ให้ข้อเสนอแนะ ไม่มีคะแนน คะแนนของงานต่อยอดให้ที่บทที่ 13

วิธีสอนและกิจกรรมการเรียนการสอน

  1. บรรยายแนวคิดตามหัวข้อ 10.1–10.4 โดยเริ่มจากคำถามว่า "image ของทีมมีโค้ดที่ทีมไม่ได้เขียนเองอยู่กี่ส่วน" และใช้คำถามตรวจความเข้าใจท้ายหัวข้อเป็นกิจกรรมอภิปรายเป็นคู่
  2. สาธิตการสร้าง SBOM ของ image 0.4.0 ให้เห็นว่าแพ็กเกจส่วนใหญ่มาจาก base image และสาธิตว่า Trivy จบด้วย exit code 0 แม้พบปัญหา จนกว่าจะกำหนด --exit-code
  3. ปฏิบัติการ Lab A–E บน repository ทีม แต่ละ Lab ประกอบด้วยส่วนฝึกตามตัวอย่างและส่วนที่ผู้เรียนต้องตัดสินใจเอง
  4. ติดตามความก้าวหน้างานต่อยอดรายบุคคล (งานข้อ 3) ระหว่างปฏิบัติการ
  5. มอบหมายแบบฝึกหัดท้ายบท Learning Log ครั้งที่ 10 และการศึกษาค้นคว้าเพิ่มเติม

สื่อการเรียนการสอน

  • เอกสารประกอบการสอนบทที่ 10 (บทความนี้) และหน้ารวมรายวิชา 10306343
  • repository ทีม devops-app ที่ผ่าน M8 จากบทที่ 9 workflow ci.yml และ cd.yml และเครื่องปลายทางของทีมที่รันระบบชุด devops-prod
  • โปรแกรม Trivy และเอกสารทางการของ Trivy, Trivy Action และ GitHub Docs หัวข้อ Using secrets in GitHub Actions
  • เอกสาร NIST SP 800-218 (SSDF), OWASP Top 10:2025 และรายงาน NTIA เรื่ององค์ประกอบขั้นต่ำของ SBOM

การวัดผลและการประเมินผล

กำหนดส่ง: ก่อนเริ่มเรียนบทที่ 12 เพื่อเว้นระยะหนึ่งบท

ข้องานหลักฐานผลลัพธ์CLO
1M9 ประตูสแกนช่องโหว่ใน pipelineลิงก์ pull request ของตนเองที่ merge แล้ว (Lab B, D หรือ E) · ไฟล์ ci.yml และ cd.yml บน main ที่มีขั้นสแกน · ลิงก์ run ที่ล้มเพราะ base image เก่า (Lab C) พร้อมบรรทัดที่บอกว่าขั้นสแกนล้ม10.1CLO2
2จุดตรวจความปลอดภัยครั้งที่ 3: บันทึกการแก้ช่องโหว่ลิงก์ run ที่ผ่านหลังแก้ (Lab D) · บันทึกรายบุคคลไม่เกิน 1 หน้า ระบุรหัส CVE ที่เลือก แพ็กเกจและรุ่นที่ติดตั้ง รุ่นที่แก้แล้ว วิธีแก้ที่เลือกพร้อมเหตุผล และลิงก์แหล่งข้อมูลทางการอย่างน้อย 1 แหล่ง10.2CLO4
3ความก้าวหน้างานต่อยอด (CLO5 จุดตรวจที่ 2)ลิงก์ repository หรือโฟลเดอร์ของงานต่อยอด · ภาพหรือผลรันของส่วนที่ทำงานได้แล้ว · รายการแหล่งข้อมูลที่ใช้จริง · สิ่งที่จะทำต่อก่อนบทที่ 13—CLO5
4Learning Log ครั้งที่ 10ตอบ 4 หัวข้อ: สิ่งที่เรียนในบทนี้ · คำสั่งหรือข้อความ error ที่พบและแก้อย่างไร · แหล่งที่ใช้ค้นหาคำตอบ (ระบุลิงก์เอกสารทางการอย่างน้อย 1 แหล่ง) · ช่องโหว่หรือแนวคิดด้านความปลอดภัยใดที่ยังไม่เข้าใจ—บันทึกสะท้อนการเรียนรู้ ไม่มีหมวดคะแนน portfolio แยก

ประเมินงานแต่ละข้อจากหลักฐานของข้อนั้นโดยตรง ไม่ตัดสิทธิ์รับคะแนนของอีกข้อเพราะ milestone หรือจุดตรวจอื่นยังไม่ผ่าน งานที่ยังทำไม่ได้ให้บันทึกปัญหาและหลักฐานที่มีตามจริง

เกณฑ์ให้คะแนน

ข้อผ่านยังไม่ผ่าน
1ci.yml มี job สแกน image ที่กำหนด exit-code: '1' และระดับความรุนแรง · cd.yml สแกนก่อน push · มี run ที่ล้มที่ขั้นสแกน (ไม่ใช่ล้มที่ขั้นอื่น) · ไม่มีการปิดการสแกนเพื่อให้ผ่านไม่มี pull request ของตนเอง หรือขั้นสแกนไม่เคยล้มเลย หรือ exit-code เป็น '0'
4ตอบครบ 4 หัวข้อ และมีลิงก์เอกสารทางการขาดหัวข้อ หรือไม่มีแหล่งอ้างอิง

ข้อ 2 ใช้ rubric CLO4 3 ระดับ

ระดับพฤติกรรมที่สังเกตได้
3 ดีพบและรายงานปัญหาด้านความปลอดภัยเพิ่มเติมก่อนถูกตรวจ (เช่น สังเกตว่าแพ็กเกจที่มีช่องโหว่ไม่จำเป็นต้องอยู่ใน image หรือเสนอวิธีตรึงรุ่นของ action) และเปรียบเทียบวิธีแก้อย่างน้อย 2 ทาง โดยอ้างแหล่งข้อมูลทางการประกอบ
2 ผ่านทำตามขั้นตอนครบ run หลังแก้ผ่านขั้นสแกน บันทึกระบุ CVE รุ่นที่แก้แล้ว และวิธีแก้ถูกต้อง พร้อมแหล่งข้อมูลทางการอย่างน้อย 1 แหล่ง
1 ยังไม่ผ่านทำเฉพาะเมื่อถูกสั่ง หรือทำให้ run ผ่านด้วยการลดระดับความรุนแรง ปิดการสแกน หรือเพิ่มรายการยกเว้นโดยไม่มีเหตุผล

ข้อ 3 ใช้ rubric CLO5 (จุดตรวจที่ 2 ประเมินความก้าวหน้า)

ระดับพฤติกรรมที่สังเกตได้
3 ดีมีส่วนที่ทำงานได้จริงซึ่งดัดแปลงให้เข้ากับแอปของทีม อธิบายได้ว่าเรียนแต่ละส่วนจากแหล่งใด และช่วยตอบคำถามเพื่อนในหัวข้อเดียวกันได้
2 ผ่านมีส่วนที่ทำงานได้บางส่วนกับแอปของทีม และอธิบายแหล่งข้อมูลที่ใช้ได้
1 ยังไม่ผ่านไม่มีความก้าวหน้า หรือคัดลอกตัวอย่างมาโดยไม่ปรับและอธิบายไม่ได้

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

การคิดคะแนนฉบับปัจจุบัน

มติผู้สอน 28 ต.ค. 2569: เข้าเรียน 10% · ใบงานบทที่ 1–13 รวม 40% · FP 20% · FW 20% · โครงงาน 10% ส่วนบทที่ 14–15 ฝึกเพื่อรับข้อเสนอแนะ ไม่นับคะแนน

ใบงาน C10-WS01 มีคะแนนดิบเต็ม 20 คะแนน แยกตามผลลัพธ์ด้านล่าง ระบบนำไปรวมในหมวดใบงาน 40% โดยไม่เพิ่มคะแนน milestone, portfolio หรือจุดตรวจเป็นหมวดแยก

ผลลัพธ์หลักฐานตามงานส่งคะแนนดิบเต็ม
LLO10.1งานข้อ 110
LLO10.2งานข้อ 210

ประเมินงานแต่ละข้อจากหลักฐานของข้อนั้นโดยตรง ไม่ตัดสิทธิ์รับคะแนนของอีกข้อเพราะ milestone หรือจุดตรวจอื่นยังไม่ผ่าน งานที่ยังทำไม่ได้ให้บันทึกปัญหาและหลักฐานที่มีตามจริง

ใช้เกณฑ์ความถูกต้อง เหตุผล และหลักฐานที่ระบุในงานแต่ละข้อประกอบการประเมินราย LLO งานเตรียมพื้นและ Learning Log ที่ไม่มีรหัส LLO ใช้ให้ข้อเสนอแนะ ไม่เพิ่มคะแนนดิบให้ใบงาน

บทนำ

หลังบทที่ 9 pipeline ของทีมทดสอบโค้ด build image ส่งขึ้น registry และ deploy ได้อัตโนมัติ แต่ยังไม่มีขั้นใดตรวจว่าสิ่งที่ส่งออกไปปลอดภัยหรือไม่ image ของทีมประกอบด้วยโค้ดที่ทีมเขียนเพียงส่วนน้อย ส่วนใหญ่เป็น base image และแพ็กเกจที่ผู้อื่นเขียน ซึ่งอาจมีช่องโหว่ที่ถูกประกาศหลังวันที่ทีมเลือกใช้ บทนี้เพิ่มการสแกนช่องโหว่ด้วย Trivy เข้าไปใน pipeline ให้ run ล้มเมื่อพบช่องโหว่ร้ายแรงที่แก้ได้ ฝึกอ่านรายงานและตัดสินใจแก้ และย้ายรหัสผ่านฐานข้อมูลของเครื่องปลายทางไปเก็บใน GitHub Secrets

ความสำคัญ: การตรวจความปลอดภัยตั้งแต่ช่วงต้นของการพัฒนา (Shift Left) ถูกกว่าการพบปัญหาหลังระบบเปิดใช้งานแล้ว และความล้มเหลวของห่วงโซ่อุปทานซอฟต์แวร์ (Software Supply Chain Failures) ถูกจัดเป็นความเสี่ยงอันดับ A03 ใน OWASP Top 10:2025 (OWASP Top 10:2025) ทักษะอ่านรายงานสแกนและแยกว่าช่องโหว่ใดต้องแก้ทันทีเป็นงานประจำของทีม DevOps

ความเชื่อมโยงกับบทอื่น: บทนี้ใช้ ci.yml จากบทที่ 5 Dockerfile แบบ multi-stage จากบทที่ 6 cd.yml และ scripts/deploy.sh จากบทที่ 8 และ scripts/check-secrets.sh จากบทที่ 9 ผลผลิตของบทนี้ถูกใช้ต่อตามตารางต่อไปนี้

สิ่งที่ได้จากบทนี้ใช้ต่อในบท
job image-scan ใน ci.yml และขั้นสแกนก่อน push ใน cd.yml12 (pipeline ครบสาย commit → test → scan → build → deploy)
GitHub Secret PROD_POSTGRES_PASSWORD11 (deploy gate) · 12 (จุดตรวจความปลอดภัยครั้งที่ 4)
บันทึกการแก้ช่องโหว่12 (สอบปากเปล่า) · 13 (portfolio)
ความก้าวหน้างานต่อยอด13 (สาธิตงานต่อยอด)

ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์

ความรู้พื้นฐาน: โครงสร้าง workflow และ job ที่พึ่งกันด้วย needs จากบทที่ 5 และ 8 ชั้นของ image และ base image จากบทที่ 6 และการตรวจความลับในประวัติ commit จากบทที่ 9

เครื่องมือ: เครื่องที่มี Docker Engine และ Docker Compose plugin โปรแกรม Trivy ที่ติดตั้งตามเอกสารทางการ (Trivy (GitHub)) ผู้เขียนติดตั้งจากไฟล์ trivy_0.70.0_Linux-64bit.tar.gz ในหน้า Releases ของ repository ดังกล่าว ด้วยการแตกไฟล์แล้ววางไฟล์ trivy ไว้ใน /usr/local/bin repository ~/devops-app ที่ตรงกับ main ล่าสุด และสิทธิ์ Admin ของ repository ทีม (สำหรับหน้า Secrets)

รุ่นซอฟต์แวร์ที่ใช้ทดสอบ:

ซอฟต์แวร์รุ่นที่ทดสอบ
Trivy0.70.0 ทดสอบเมื่อ 19 ต.ค. 2569 เฉพาะการสร้าง SBOM, การสแกนความลับ และการตรวจ Dockerfile
Trivy Actionaquasecurity/[email protected] ตาม R10.5 ตรวจไวยากรณ์ workflow ด้วย actionlint 1.7.12 เท่านั้น
Docker Engine และ Docker Compose29.8.2 และ v5.5.1
imageimage จำลอง node:22-slim, postgres:16 และ nginx:1.28 เดียวกับบทที่ 6–9
แอปdevops-app 0.4.0
⚠️ข้อจำกัดของการทดสอบในบทนี้

สภาพแวดล้อมที่ใช้เขียนบทความดาวน์โหลดฐานข้อมูลช่องโหว่ของ Trivy ไม่ได้ (การเชื่อมต่อไปยัง registry ที่เก็บฐานข้อมูลถูกปฏิเสธ) ผู้เขียนจึงยังไม่ได้รันการสแกนช่องโหว่ (vulnerability scan) จริง ตัวอย่างผลลัพธ์ของการสแกนช่องโหว่ในบทนี้บอกเพียงรูปแบบรายงาน ไม่ใช่ผลจริง ส่วนที่รันจริงแล้วคือการสร้าง SBOM การสแกนความลับ การตรวจ Dockerfile และพฤติกรรมของ exit code นอกจากนี้ image จำลอง node:22-slim สร้างด้วย docker import จึงไม่มีข้อมูลแพ็กเกจของระบบปฏิบัติการ SBOM ของ image จริงจะมีรายการแพ็กเกจของ Debian เพิ่มจากที่แสดงในบทนี้ ขั้นที่ทำงานบน GitHub Actions และ GitHub Secrets ยังไม่ได้ทดสอบบน GitHub จริง

ค่าใช้จ่ายและข้อมูลลับ: Trivy และ Trivy Action เป็นซอฟต์แวร์โอเพนซอร์สที่ใช้ได้โดยไม่มีค่าใช้จ่าย GitHub Secrets ใช้ได้ทั้ง repository แบบ public และ private ข้อมูลลับของบทนี้คือรหัสผ่านฐานข้อมูลของเครื่องปลายทาง ซึ่ง Lab E ย้ายจากไฟล์บนเครื่องปลายทางไปไว้ใน GitHub Secrets ห้ามพิมพ์รหัสผ่านลงในคำสั่งที่บันทึกใน history ของ shell ห้ามพิมพ์ลง log ของ workflow และห้ามแนบในภาพหน้าจอที่ส่งเป็นหลักฐาน

10.1 Shift Left และกรอบ SSDF

Shift Left คือแนวคิดย้ายการตรวจคุณภาพและความปลอดภัยไปไว้ช่วงต้นของวงจรพัฒนา (ทางซ้ายของเส้นเวลา commit → test → build → deploy) แทนการตรวจครั้งเดียวก่อนเปิดใช้งาน ช่องโหว่ที่พบตอนเปิด pull request แก้ได้โดยผู้เขียนโค้ดคนเดิมในวันเดียวกัน ส่วนช่องโหว่ที่พบหลัง deploy ต้องหยุดระบบ ตามหาที่มา และออกเวอร์ชันแก้ไข

NIST จัดแนวปฏิบัติการพัฒนาซอฟต์แวร์อย่างปลอดภัยไว้ในเอกสาร Secure Software Development Framework (SSDF) รุ่น 1.1 ซึ่งแบ่งแนวปฏิบัติเป็น 4 กลุ่ม (NIST SP 800-218)

กลุ่มชื่อตัวอย่างในรายวิชา
POPrepare the Organizationกติกา branch และ pull request ในบทที่ 3
PSProtect the Software.gitignore กันความลับ (บทที่ 2, 9) และ GitHub Secrets (Lab E)
PWProduce Well-Secured Softwaretest ใน CI (บทที่ 5) และสแกน image ก่อน push (Lab B)
RVRespond to Vulnerabilitiesอ่านรายงานสแกนและแก้ช่องโหว่ (Lab D)

ระดับที่ 1: Shift Left ไม่ได้แปลว่าตรวจความปลอดภัยครั้งเดียวตอนต้น แต่แปลว่ามีการตรวจกระจายอยู่ทุกช่วง ตารางข้างต้นแสดงว่าทีมทำแนวปฏิบัติบางส่วนของ SSDF มาตั้งแต่บทแรก ๆ โดยไม่ได้เรียกชื่อ

ระดับที่ 2: ประตูตรวจ (gate) ในบทนี้คือขั้นของ pipeline ที่ทำให้ run ล้มเมื่อพบปัญหา การตรวจที่เพียงรายงานโดยไม่ทำให้ run ล้ม มักถูกละเลยเมื่อทีมเร่งงาน Trivy โดยปริยายจบด้วย exit code 0 แม้พบปัญหา (Trivy docs: Others) การเพิ่มขั้นสแกนจึงต้องกำหนดให้ล้มอย่างชัดเจน

ระดับที่ 3: ประตูที่เข้มเกินไปก็ก่อปัญหา ถ้าทุก run ล้มจากช่องโหว่ที่ยังไม่มีใครออกรุ่นแก้ ทีมจะเริ่มปิดการสแกนหรือเพิ่มรายการยกเว้นจนไม่มีความหมาย การออกแบบประตูจึงต้องเลือกเกณฑ์ที่ทีมแก้ได้จริง บทนี้เลือกให้ล้มเฉพาะช่องโหว่ระดับ CRITICAL ที่มีรุ่นแก้แล้ว และให้ช่องโหว่ระดับอื่นปรากฏในรายงานเพื่อวางแผนแก้ต่อ

คำถามตรวจความเข้าใจ 10.1: จัดกิจกรรมต่อไปนี้ลงในกลุ่มของ SSDF พร้อมเหตุผลสั้น ๆ (1) ตั้งกติกาให้ทุก pull request ต้องมีผู้ตรวจทาน (2) สแกน image ก่อน push (3) เปลี่ยนรหัสผ่านทันทีเมื่อพบว่ารั่ว

10.2 ห่วงโซ่อุปทานซอฟต์แวร์และ SBOM

ห่วงโซ่อุปทานซอฟต์แวร์ (software supply chain) คือทุกสิ่งที่ประกอบกันเป็นซอฟต์แวร์ที่ส่งมอบ ตั้งแต่โค้ดของทีม แพ็กเกจจาก npm, base image จาก registry, action ที่ workflow เรียกใช้ ไปจนถึงเครื่องที่ build ความเสี่ยงของห่วงโซ่นี้คือส่วนที่ทีมไม่ได้เขียนเองมีช่องโหว่หรือถูกแก้ไขโดยผู้ไม่หวังดี

SBOM (Software Bill of Materials) คือรายการส่วนประกอบของซอฟต์แวร์ เปรียบได้กับฉลากส่วนผสมของอาหาร รายงานของ NTIA กำหนดฟิลด์ขั้นต่ำของแต่ละรายการ ได้แก่ Supplier Name, Component Name, Version of the Component, Other Unique Identifiers, Dependency Relationship, Author of SBOM Data และ Timestamp และระบุรูปแบบไฟล์ที่ใช้ได้ คือ SPDX, CycloneDX และ SWID tags (NTIA, 2021)

ผลการทดสอบสร้าง SBOM ของ image 0.4.0 ด้วย Trivy

BASH
trivy image --format cyclonedx --output sbom.cdx.json ghcr.io/devops-team-01/devops-app:0.4.0
grep -c '"type": "library"' sbom.cdx.json
grep -o '"purl": "pkg:npm/express@[^"]*"' sbom.cdx.json
TEXT
INFO	"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the "cyclonedx" report.
277
"purl": "pkg:npm/[email protected]"
ตำแหน่งของแพ็กเกจใน imageจำนวนมาจาก
/usr/local/lib/node_modules/...211npm ที่ติดมากับ base image node:22-slim
/app/node_modules/... และ /app/package.json66dependency ของแอป (Express และแพ็กเกจที่ Express ใช้)

ระดับที่ 1: ไฟล์ SBOM แบบ CycloneDX เป็น JSON ที่แต่ละรายการมีชื่อ รุ่น และ purl (package URL) ซึ่งเป็นตัวระบุที่ไม่ซ้ำ เช่น pkg:npm/[email protected] ตรงกับฟิลด์ Component Name, Version และ Other Unique Identifiers ของ NTIA

ระดับที่ 2: ตัวเลขในตารางแสดงว่าแพ็กเกจกว่าสามในสี่ของ image มาจาก base image ไม่ใช่จาก package.json ของทีม การอัปเดต dependency ในโค้ดเพียงอย่างเดียวจึงไม่พอ การเลือกและอัปเดต base image มีผลต่อความปลอดภัยมากกว่า (image จริงจะมีแพ็กเกจของ Debian เพิ่มอีก ตามข้อจำกัดที่ระบุในบทนำ)

ระดับที่ 3: เมื่อมีการประกาศช่องโหว่ใหม่ ทีมที่เก็บ SBOM ของทุกเวอร์ชันไว้ตอบได้ทันทีว่าเวอร์ชันใดใช้แพ็กเกจรุ่นที่ได้รับผลกระทบ โดยไม่ต้อง build ใหม่ Trivy สแกน SBOM ที่เก็บไว้ได้ด้วยคำสั่ง trivy sbom (Trivy (GitHub)) การผูก SBOM กับ tag เวอร์ชันจากบทที่ 8 เป็นงานต่อยอดที่เลือกทำได้

คำถามตรวจความเข้าใจ 10.2: จากตาราง ถ้าแพ็กเกจ tar ที่ npm ใช้มีช่องโหว่ร้ายแรง แต่แอปของทีมไม่ได้เรียกใช้ tar เลย ทีมควรสนใจช่องโหว่นี้หรือไม่ ให้เหตุผลทั้งด้านที่ควรและไม่จำเป็นต้องสนใจ

10.3 ช่องโหว่ CVE ระดับความรุนแรง และการตัดสินใจแก้

CVE (Common Vulnerabilities and Exposures) คือรหัสที่ใช้อ้างถึงช่องโหว่ที่ถูกประกาศแล้ว เช่น CVE-ปี-ลำดับ เครื่องมือสแกนเทียบรายการแพ็กเกจและรุ่นใน image กับฐานข้อมูลช่องโหว่ แล้วรายงานรายการที่ตรงกัน Trivy สแกนได้ทั้ง container image, filesystem, Git repository, VM image และ Kubernetes และตรวจได้ทั้งช่องโหว่ ค่าตั้งที่ผิด (misconfiguration) ความลับ และสัญญาอนุญาต (Trivy (GitHub))

รายงานของ Trivy จัดระดับความรุนแรงเป็น UNKNOWN, LOW, MEDIUM, HIGH และ CRITICAL (เห็นในบรรทัด Total: ของทุกรายงาน) ตารางช่องโหว่แต่ละแถวบอกชื่อแพ็กเกจ รหัสช่องโหว่ ระดับ สถานะ รุ่นที่ติดตั้ง และรุ่นที่แก้แล้ว (ถ้ามี) สถานะที่ Trivy ใช้ เช่น fixed, affected, will_not_fix และ end_of_life ปรากฏในคำอธิบายตัวเลือก --ignore-status ของ trivy image --help

ตัวเลือกผล
--severity CRITICAL หรือ CRITICAL,HIGHแสดงและนับเฉพาะระดับที่กำหนด
--exit-code 1จบด้วย exit code 1 เมื่อพบปัญหาตามเงื่อนไข (ไม่กำหนด = 0 เสมอ)
--ignore-unfixedแสดงเฉพาะช่องโหว่ที่มีรุ่นแก้แล้ว
--format cyclonedxสร้าง SBOM แทนรายงานช่องโหว่

ผลการทดสอบพฤติกรรมของ exit code กับการสแกนความลับ (ใช้ token ปลอมที่สร้างขึ้นให้มีรูปแบบเหมือน GitHub Personal Access Token ในโฟลเดอร์ทดลอง)

BASH
trivy fs --scanners secret . ; echo "exit=$?"
trivy fs --scanners secret --exit-code 1 . ; echo "exit=$?"
TEXT
...
leak.env (secrets)
==================
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1)
 
CRITICAL: GitHub (github-pat)
...
exit=0
...
exit=1

ระดับที่ 1: ทั้งสองคำสั่งพบปัญหาเดียวกันระดับ CRITICAL แต่คำสั่งแรกจบด้วย exit code 0 workflow ที่ใช้คำสั่งแรกจะผ่านทั้งที่รายงานพบปัญหา คำสั่งที่สองจบด้วย 1 จึงทำให้ขั้นนั้นล้ม (Trivy docs: Others)

ระดับที่ 2: การตัดสินใจแก้ช่องโหว่หนึ่งรายการเริ่มจากสามคำถาม (1) มีรุ่นแก้แล้วหรือไม่ (ดูคอลัมน์รุ่นที่แก้แล้วและสถานะ) (2) แพ็กเกจนั้นมาจากไหน (base image, dependency ของแอป หรือเครื่องมือที่ไม่จำเป็นตอนรัน) (3) ทางแก้ใดเปลี่ยนน้อยที่สุดและทีมควบคุมได้ ทางแก้ที่พบบ่อยคือเปลี่ยน base image เป็นรุ่นที่มีแพ็กเกจรุ่นแก้แล้ว อัปเดต dependency ของแอป หรือเอาแพ็กเกจที่ไม่จำเป็นออกจาก image

ระดับที่ 3: เมื่อช่องโหว่ยังไม่มีรุ่นแก้ หรือทีมวิเคราะห์แล้วว่าไม่กระทบแอป Trivy รองรับไฟล์ .trivyignore สำหรับยกเว้นรายการ (trivy image --help ระบุว่าตัวเลือก --ignorefile มีค่าเริ่มต้นเป็น .trivyignore) การยกเว้นต้องมีเหตุผลที่ตรวจสอบได้และวันทบทวน มิฉะนั้นรายการยกเว้นจะกลายเป็นที่ซ่อนปัญหา นอกจากช่องโหว่ Trivy ยังตรวจค่าตั้งของ Dockerfile ได้ด้วย trivy config ผลการทดสอบกับ Dockerfile ของทีมพบเพียง DS-0026 (LOW): Add HEALTHCHECK instruction in your Dockerfile และเมื่อลองลบบรรทัด USER node ออก พบ DS-0002 (HIGH): Specify at least 1 USER command in Dockerfile with non-root user as argument ซึ่งยืนยันเหตุผลของ USER node ที่ใส่ไว้ในบทที่ 6

คำถามตรวจความเข้าใจ 10.3: รายงานสแกนแสดงสามรายการ (ก) CRITICAL สถานะ fixed รุ่นที่แก้ 3.0.15 (ข) CRITICAL สถานะ affected ไม่มีรุ่นที่แก้ (ค) HIGH สถานะ fixed ถ้าทีมตั้ง severity: CRITICAL ร่วม ignore-unfixed: true และ exit-code: '1' run จะล้มเพราะรายการใด และทีมควรทำอย่างไรกับรายการที่ไม่ทำให้ run ล้ม

10.4 ความลับใน GitHub Actions

ตั้งแต่บทที่ 8 workflow ใช้ secrets.GITHUB_TOKEN ที่ GitHub สร้างให้อัตโนมัติ บทนี้สร้างความลับเอง GitHub ให้เก็บความลับระดับ repository แล้วอ้างใน workflow ด้วยนิพจน์ ${{ secrets.ชื่อ }} เอกสารทางการกำหนดข้อปฏิบัติไว้ร่วมกับบทที่ 8 คือห้าม commit รหัสผ่าน ห้ามพิมพ์ความลับลง log และห้ามส่งความลับทาง command line (GitHub Docs: Using secrets in GitHub Actions) วิธีที่ใช้ในบทนี้คือส่งความลับเป็นตัวแปรสภาพแวดล้อมของขั้นที่ต้องใช้เท่านั้น

YAML
      - name: Deploy with Compose
        env:
          TAG: ${{ needs.build-push.outputs.version }}
          POSTGRES_PASSWORD: ${{ secrets.PROD_POSTGRES_PASSWORD }}
        run: bash scripts/deploy.sh "$TAG"

ระดับที่ 1: ก่อนบทนี้รหัสผ่านฐานข้อมูลของเครื่องปลายทางอยู่ในไฟล์ ~/devops-deploy/.env บนเครื่องนั้น ซึ่งไม่อยู่ใน Git แต่ทุกคนที่เข้าเครื่องปลายทางได้อ่านไฟล์นี้ได้ และถ้าเครื่องปลายทางถูกสร้างใหม่ต้องจำค่าให้ได้ การเก็บใน GitHub Secrets ทำให้ค่าอยู่ที่เดียว กำหนดสิทธิ์ผู้แก้ไขได้ และอ่านค่ากลับจากหน้าเว็บไม่ได้

ระดับที่ 2: Compose ให้ค่าจาก shell มาก่อนค่าใน --env-file ผลการทดสอบกับไฟล์ค่าตั้งที่ไม่มี POSTGRES_PASSWORD แสดงสามกรณี

TEXT
$ docker compose -p devops-prod --env-file prod.env config        # ไม่มีค่าใน shell
error while interpolating services.db.environment.POSTGRES_PASSWORD: required variable POSTGRES_PASSWORD is missing a value: POSTGRES_PASSWORD is not set
$ POSTGRES_PASSWORD=from-shell docker compose -p devops-prod --env-file prod.env config | grep POSTGRES_PASSWORD
      POSTGRES_PASSWORD: from-shell
$ POSTGRES_PASSWORD= docker compose -p devops-prod --env-file prod.env config      # ค่าว่าง
error while interpolating services.db.environment.POSTGRES_PASSWORD: required variable POSTGRES_PASSWORD is missing a value: POSTGRES_PASSWORD is not set

กรณีที่สามสำคัญ เพราะถ้าพิมพ์ชื่อ secret ใน workflow ผิด นิพจน์ ${{ secrets.ชื่อที่ผิด }} จะได้ค่าว่าง รูปแบบ ${POSTGRES_PASSWORD:?...} ที่ใส่ไว้ในบทที่ 7 ทำให้ deploy หยุดพร้อมข้อความชัดเจน แทนการสร้างฐานข้อมูลด้วยรหัสผ่านว่าง

ระดับที่ 3: GitHub Secrets แก้ปัญหาการเก็บและส่งความลับ แต่เมื่อความลับถึง container แล้ว ผู้ที่สั่ง docker inspect บนเครื่องปลายทางยังอ่านค่าได้ ผลการทดสอบ docker inspect devops-prod-db-1 แสดงบรรทัด POSTGRES_PASSWORD=... เป็นค่าจริง นี่คือเหตุผลที่เอกสารของ Docker แนะนำ Compose secrets สำหรับรหัสผ่าน (หัวข้อ 9.4) ข้อควรระวังอีกข้อคือ image ทางการของ PostgreSQL ใช้ POSTGRES_PASSWORD เฉพาะตอนสร้างฐานข้อมูลครั้งแรกใน volume ที่ว่าง การเปลี่ยนค่าใน Secrets ภายหลังจึงไม่เปลี่ยนรหัสผ่านของฐานข้อมูลที่มีอยู่แล้ว (image จำลองของผู้เขียนทำงานแบบเดียวกัน ส่วนพฤติกรรมของ image ทางการยังไม่ได้ตรวจกับเอกสาร)

คำถามตรวจความเข้าใจ 10.4: สมาชิกคนหนึ่งเพิ่มขั้น run: echo "password is $POSTGRES_PASSWORD" เพื่อตรวจว่าได้ค่าถูกต้อง ระบุว่าผิดข้อปฏิบัติใดของ GitHub และเสนอวิธีตรวจว่าค่าถูกส่งมาโดยไม่เปิดเผยค่า

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

10.5 อ่านรายงานช่องโหว่ก่อนเลือกวิธีแก้

รายงานสแกนเป็นชุดข้อค้นพบตามฐานข้อมูลและวิธีตรวจ ไม่ใช่คำตัดสินความเสี่ยงทั้งหมด ต้องอ่านอย่างน้อยชื่อแพ็กเกจ ตำแหน่งที่มา เวอร์ชันติดตั้ง รหัสช่องโหว่ ระดับความรุนแรง แหล่ง advisory และเวอร์ชันที่แก้แล้ว จากนั้นจึงประเมินว่า image ที่สแกนตรงกับสิ่งที่จะ deploy หรือไม่ และแพ็กเกจนั้นเข้ามาจาก base image หรือ dependency ของแอป

ตัวอย่างสมมติคือรายงานพบ package-a ระดับ CRITICAL ในแพ็กเกจระบบของ base image แต่ทีมแก้ package.json ของ Node.js แล้วสแกนใหม่ยังพบเดิม การแก้ผิดชั้นเพราะชื่อแพ็กเกจในรายงานไม่ได้หมายความว่าจัดการด้วย npm ทุกตัว ควรตรวจชนิดและตำแหน่งของแพ็กเกจ แล้วปรับฐาน image หรือขั้นติดตั้งที่นำแพ็กเกจนั้นเข้ามา

อีกกรณีหนึ่งคือรุ่น upstream ดูเก่าแต่ผู้จำหน่ายระบบปฏิบัติการได้ backport การแก้แล้ว การเทียบเพียงเลขเวอร์ชัน upstream อาจสรุปผิด Trivy อธิบายว่าการใช้ advisory ของผู้จำหน่ายช่วยลด false positive ในกรณีนี้ และยกข้อจำกัดเรื่องความล่าช้าของ advisory ไว้ด้วย (Trivy: Vulnerability scanning) ดังนั้นรายงานควรเก็บแหล่งที่มาของคำตัดสิน ไม่คัดเฉพาะรหัส CVE กับระดับความรุนแรง

ตัวอย่างที่ไม่ถูกต้องคือแก้ทุกช่องโหว่ด้วยการอัปเดตทุก dependency เป็นรุ่นล่าสุดพร้อมกัน แม้บางรายการไม่เกี่ยวกับช่องโหว่ที่พบ วิธีนี้เพิ่มขอบเขตการเปลี่ยนและทำให้ระบุเหตุของการทดสอบล้มยากขึ้น แนวทางที่ตรวจสอบง่ายกว่าคือระบุแพ็กเกจและรุ่นแก้ที่เกี่ยวข้อง อ่านประกาศและข้อจำกัด ทดสอบการเปลี่ยนที่จำเป็น แล้วสแกน artifact ใหม่พร้อมเก็บรายงานก่อนและหลัง

10.6 ไม่พบช่องโหว่ ไม่พบตามตัวกรอง และตรวจไม่สำเร็จ

ผลสามอย่างนี้ต้องแยกกัน: เครื่องมือสแกนสำเร็จและไม่พบรายการ เครื่องมือสแกนสำเร็จแต่รายงานถูกกรองจนไม่แสดงรายการ และเครื่องมือทำงานไม่สำเร็จ เช่น ดาวน์โหลดฐานข้อมูลไม่ได้ หาก pipeline รวมทั้งหมดเป็นผล “ปลอดภัย” จะสร้างความมั่นใจเกินหลักฐาน

Trivy อนุญาตกำหนด exit code เมื่อพบข้อค้นพบและกรองระดับความรุนแรงได้ การพิมพ์รายงานโดยไม่ตั้งผลตัดสินตามนโยบายอาจทำให้ขั้นสแกนจบสำเร็จแม้พบรายการ เอกสารส่วน configuration อธิบายการกำหนด --exit-code เพื่อใช้เป็น gate (Trivy: Exit code) จึงต้องทดสอบทั้ง image ที่เข้าเกณฑ์หยุดและ image ที่ไม่เข้าเกณฑ์ด้วย

ตัวอย่างที่ผิดคือใช้ --ignore-unfixed แล้วเขียนในรายงานว่า “ไม่มีช่องโหว่” ตัวเลือกนี้ซ่อนรายการที่ยังไม่มีรุ่นแก้ตามข้อมูลที่ใช้ ไม่ได้ทำให้ความเสี่ยงหายไป ถ้านโยบายรายวิชาใช้ gate เฉพาะ CRITICAL ที่มีรุ่นแก้แล้ว ต้องอธิบายว่ารายการอื่นยังอยู่ในงานติดตาม และเก็บรายงานที่เห็นขอบเขตครบเพื่อใช้วางแผน

อีกตัวอย่างคือใส่ข้อยกเว้น CVE โดยไม่มีเจ้าของ เหตุผล หรือวันทบทวน ข้อยกเว้นจะคงอยู่แม้เงื่อนไขเปลี่ยน วิธีที่เหมาะสมควรบันทึกว่าเกี่ยวกับ image digest ใด เหตุใดจึงยังไม่แก้ มาตรการชั่วคราวคืออะไร ใครรับผิดชอบ และเมื่อใดต้องตรวจใหม่ ข้อยกเว้นเป็นการตัดสินใจรับความเสี่ยงภายใต้เงื่อนไข ไม่ใช่การแก้ช่องโหว่

ในห้องปฏิบัติการไม่ควรรับประกันว่า tag เก่าตัวใดจะล้มตามเกณฑ์ตลอดไป เพราะฐานข้อมูลและแพ็กเกจเปลี่ยนได้ ผู้สอนต้องสแกนล่วงหน้าและบันทึกวัน รุ่นเครื่องมือ ฐานข้อมูล และ digest หากต้องใช้ผลสาธิตที่คงที่ควรระบุว่าเป็นรายงานตัวอย่างที่บันทึกไว้ ไม่แสดงเป็นผลสด

10.7 SBOM ตอบว่าใช้อะไร แต่ยังต้องตอบว่าได้รับผลหรือไม่

บัญชีส่วนประกอบซอฟต์แวร์หรือ SBOM ช่วยระบุส่วนประกอบและรุ่นของผลผลิต เมื่อมีประกาศช่องโหว่ใหม่จะช่วยค้นว่ารุ่นใดเกี่ยวข้อง แต่การมี SBOM ไม่ได้หมายความว่าทุกส่วนประกอบปราศจากช่องโหว่ และไม่ได้ยืนยันว่าการสแกนใช้ข้อมูลล่าสุด ต้องเชื่อม SBOM กับ artifact ที่แน่นอนพร้อมเวลาและเครื่องมือที่สร้าง

ตัวอย่างที่ไม่ถูกต้องคือสร้าง SBOM จาก working tree แล้วแนบให้ image ที่ build คนละ commit รายการ dependency อาจไม่ตรงกับสิ่งที่ส่งมอบ วิธีที่ตรวจสอบได้คือสร้างหรือยืนยัน SBOM จากผลผลิตที่ใช้จริง แล้วผูกกับ digest เดียวกับรายงานสแกนและ release record หาก image เปลี่ยนต้องพิจารณา SBOM กับรายงานใหม่ด้วย

ตัวอย่างสมมติคือทีมมี image D1 และ D2 โดย D2 อัปเดตแพ็กเกจหนึ่งตัว รายงานที่เก็บเฉพาะชื่อ devops-app:latest ทำให้แยกไม่ได้ว่ารหัส CVE ถูกตรวจจาก D1 หรือ D2 การบันทึก digest ในทุกผลตรวจช่วยให้ตรวจย้อนกลับได้ แม้ชื่อ tag ถูกชี้ใหม่ภายหลัง

การจัดการ supply chain ยังรวม workflow และเครื่องมือที่ใช้ build หากขั้น build รันโค้ดจากแหล่งที่ไม่ตรวจสอบและมีสิทธิ์เขียน registry การสแกน image ตอนท้ายไม่ครอบคลุมทุกความเสี่ยง การจำกัดสิทธิ์ แยกบริบทงานที่ไม่น่าเชื่อถือ และตรวจแหล่งของ action จึงต้องทำร่วมกับการสแกน dependency

10.8 ความลับใน workflow และขอบเขตความเชื่อถือ

GitHub Secrets ช่วยเก็บค่าให้ workflow ใช้โดยไม่เขียนลง repository แต่ไม่ได้ทำให้โค้ดทุกขั้นที่เข้าถึงค่านั้นน่าเชื่อถือ หาก job รันสคริปต์ที่อ่านความลับได้ สคริปต์นั้นก็อาจส่งค่าออกไปได้ จึงต้องพิจารณาว่าใครเปลี่ยน workflow ได้ event ใดเรียกงาน และข้อมูลใดมาจากภายนอก

ตัวอย่างที่ผิดคือให้ workflow ซึ่งมีสิทธิ์สูง checkout และรันโค้ดจาก pull request ที่ไม่น่าเชื่อถือโดยไม่มีการแยกบริบท ความเสี่ยงไม่ใช่เพียงข้อความใน PR แต่รวมสคริปต์และ dependency ที่ถูกเปลี่ยนด้วย แนวทาง GitHub เตือนเรื่อง pull_request_target และการ checkout โค้ดที่ไม่น่าเชื่อถือในบริบทมีสิทธิ์ (GitHub Docs: Security hardening)

อีกความเข้าใจผิดคือค่าที่อยู่ใน Secrets จะถูกปิดบังใน log ได้ทุกกรณี เอกสาร GitHub ระบุว่าการ redaction ไม่รับประกัน โดยเฉพาะเมื่อแปลงค่าหรือเก็บเป็นข้อมูลมีโครงสร้าง จึงไม่ควรพิมพ์ความลับเพื่อทดสอบการปิดบัง หากสงสัยว่ารั่วต้องเพิกถอนหรือหมุนเวียนและจัดการ log ตามขั้นตอน ไม่ถือว่ามีเครื่องหมายดอกจันบางจุดแล้วไม่มีค่ารั่วที่อื่น

ตัวอย่างที่ถูกต้องสำหรับการตรวจค่าตั้งคือรายงานเพียงว่า key จำเป็นมีหรือไม่มี โดยไม่แสดงค่า แล้วใช้ความลับสิทธิ์ต่ำสุดใน job ที่ต้องใช้เท่านั้น การตรวจความปลอดภัยจึงจบด้วยหลักฐานของขอบเขตสิทธิ์ แหล่ง artifact รายงานสแกน และการจัดการข้อยกเว้น ไม่ใช่เพียงภาพหน้าจอที่มีคำว่า scan complete

กรณีอธิบายต่อเนื่อง: ตัวสแกนผ่านเพราะซ่อนรายการทั้งหมด

ข้อมูลสมมติเริ่มจาก image D1 มีข้อค้นพบ CRITICAL ที่มีรุ่นแก้หนึ่งรายการ และ HIGH ที่ยังไม่มีรุ่นแก้อีกสองรายการ นโยบายรายวิชากำหนดให้หยุดเฉพาะ CRITICAL ที่มีรุ่นแก้ การรายงานควรแสดงว่า gate ไม่ผ่านเพราะรายการใด และรายการที่ไม่เข้าเกณฑ์ยังต้องติดตาม ไม่เรียกรวมว่าระบบปลอดภัยหรือไม่ปลอดภัยโดยไม่มีบริบท

วิธีที่ผิดคือเพิ่มทุก CVE ลง ignore file จน gate ผ่าน แล้วรายงานว่าช่องโหว่ถูกแก้ จำนวนรายการที่แสดงลดลงเพราะตัวกรอง ไม่ได้พิสูจน์ว่าแพ็กเกจเปลี่ยน หาก digest ยังคง D1 และไม่มีการแก้ inputs ของ build ต้องตรวจว่ามีเพียงนโยบายการมองเห็นที่เปลี่ยนหรือไม่

แนวทางที่ถูกต้องคือแก้แพ็กเกจหรือ base image ที่เกี่ยวข้อง สร้าง D2 และสแกนใหม่ด้วยเครื่องมือ ฐานข้อมูล และเงื่อนไขที่ระบุ จากนั้นแยกรายการที่หายเพราะแก้ รายการที่ยังอยู่ และรายการใหม่ การอัปเดตครั้งหนึ่งอาจแก้รายการเดิมและเพิ่มรายการอื่นจากข้อมูลใหม่ได้ จึงไม่ควรรับรองจากจำนวนรวมอย่างเดียว

หากยอมรับข้อยกเว้นชั่วคราว ต้องบันทึกเหตุผล เจ้าของ วันทบทวน มาตรการลดผลกระทบ และขอบเขต digest ที่ยอมรับ การมีข้อยกเว้นไม่ได้ผิดเสมอไป แต่ข้อยกเว้นที่ไร้ขอบเขตทำให้ gate สูญเสียความหมายและตรวจย้อนถึงการตัดสินใจไม่ได้

ปฏิบัติการที่ 10 เพิ่มประตูตรวจความปลอดภัยใน pipeline

ปฏิบัติการนี้ประกอบด้วย Lab A–E ทุกคนสแกนบนเครื่องของตนเองใน Lab A ส่วนการเปลี่ยนแปลงที่เข้า repository แบ่งผู้รับผิดชอบดังนี้ เพื่อให้ทุกคนมี pull request ของตนเองใน milestone M9

pull requestbranchLabผู้รับผิดชอบผู้ตรวจทาน
เพิ่ม job image-scan ใน ci.ymlsec/trivy-ciBสมาชิกคนที่ 1สมาชิกคนที่ 2
สาธิต base image เก่า แล้วแก้ช่องโหว่และปรับเวอร์ชันเป็น 0.4.1sec/old-base-demoC และ Dสมาชิกคนที่ 2สมาชิกคนที่ 3
สแกนก่อน push ใน cd.yml และส่งรหัสผ่านจาก GitHub Secretssec/deploy-secretEสมาชิกคนที่ 3สมาชิกคนที่ 1

pull request ของ Lab B ต้อง merge ก่อน เพราะ Lab C ใช้ job image-scan เป็นตัวพิสูจน์ เมื่อ merge ครบแล้วหัวหน้าทีมออกเวอร์ชัน v0.4.1 ตามบทที่ 8 แต่ละ Lab แบ่งเป็นส่วน ฝึกตามตัวอย่าง และส่วน ตัดสินใจเอง (A.4, B.4, C.3, D.3 และ E.5)

ℹ️ผลลัพธ์อาจต่างจากตัวอย่าง

ผลการสแกนช่องโหว่ขึ้นกับวันที่สแกน เพราะฐานข้อมูลช่องโหว่ถูกปรับปรุงทุกวัน image เดียวกันที่ผ่านวันนี้อาจล้มในสัปดาห์หน้า ผลลัพธ์ของการสร้าง SBOM การตรวจ Dockerfile การส่งรหัสผ่านผ่านตัวแปรสภาพแวดล้อม และสคริปต์ตรวจความลับในปฏิบัติการนี้ได้จากการรันจริง ส่วนผลของการสแกนช่องโหว่และการรันบน GitHub Actions ผู้เขียนยังไม่ได้ทดสอบ (ดูข้อจำกัดในบทนำ)

Lab A: สแกนบนเครื่องของตนเอง

สถานการณ์: ก่อนให้ pipeline ตัดสินแทน ทีมต้องรู้ว่า image ปัจจุบันมีอะไรอยู่และ Trivy รายงานอะไร เพื่อเลือกเกณฑ์ที่ใช้ตัดสินได้อย่างมีเหตุผล

เป้าหมาย: ติดตั้ง Trivy สร้าง SBOM สแกนช่องโหว่ของ image 0.4.0 และตรวจ Dockerfile บนเครื่องของตนเอง (พื้นฐานของ LLO 10.1 และ 10.2)

ฝึกตามตัวอย่าง

A.1 ตรวจการติดตั้ง (สมาชิกทุกคน)

BASH
trivy --version
cd ~/devops-app
git switch main
git pull
docker image ls ghcr.io/devops-team-01/devops-app
TEXT
Version: 0.70.0
...

ถ้าเครื่องยังไม่มี image 0.4.0 ให้ดึงจาก registry ด้วย docker pull ghcr.io/<owner>/devops-app:0.4.0 ตามบทที่ 8 หรือ build ในเครื่องด้วย docker build -t ghcr.io/devops-team-01/devops-app:0.4.0 .

A.2 สร้าง SBOM และตรวจ Dockerfile

BASH
trivy image --format cyclonedx --output sbom.cdx.json ghcr.io/devops-team-01/devops-app:0.4.0
grep -c '"type": "library"' sbom.cdx.json
trivy config .
TEXT
277
...
Dockerfile (dockerfile)
=======================
Tests: 27 (SUCCESSES: 26, FAILURES: 1)
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
 
DS-0026 (LOW): Add HEALTHCHECK instruction in your Dockerfile

จำนวนแพ็กเกจของ image จริงจะมากกว่า 277 เพราะรวมแพ็กเกจของ Debian ใน base image คำเตือน DS-0026 เกิดเพราะทีมกำหนด healthcheck ไว้ใน compose.yaml (บทที่ 7) ไม่ใช่ใน Dockerfile ไฟล์ sbom.cdx.json ไม่ต้อง commit

A.3 สแกนช่องโหว่

BASH
trivy image --severity CRITICAL,HIGH ghcr.io/devops-team-01/devops-app:0.4.0
trivy image --severity CRITICAL,HIGH --ignore-unfixed ghcr.io/devops-team-01/devops-app:0.4.0

การรันครั้งแรกดาวน์โหลดฐานข้อมูลช่องโหว่ก่อน รายงานแบ่งตามเป้าหมาย (target) เช่น แพ็กเกจของระบบปฏิบัติการ และแพ็กเกจ Node.js แต่ละเป้าหมายมีบรรทัด Total: N (HIGH: x, CRITICAL: y) และตารางรายการช่องโหว่ ผลจริงขึ้นกับวันที่สแกน ผู้เขียนยังไม่ได้รันขั้นนี้

ตัดสินใจเอง A.4: เปรียบเทียบผลของสองคำสั่งใน A.3 บันทึกลงตารางใน Learning Log ว่าแต่ละระดับ (CRITICAL, HIGH) มีกี่รายการก่อนและหลังใช้ --ignore-unfixed แล้วเสนอเกณฑ์ที่ทีมควรใช้ตัดสินใน pipeline พร้อมเหตุผล 2 ข้อ โดยคำนึงว่าเกณฑ์ต้องทำให้ image ปัจจุบันของทีมผ่าน (ไม่เช่นนั้นทุก pull request จะล้ม) และต้องจับช่องโหว่ร้ายแรงที่แก้ได้

ผลลัพธ์ที่คาดหวัง A.4: ตารางจำนวนรายการ และเกณฑ์ที่เสนอ เช่น CRITICAL ร่วม --ignore-unfixed พร้อมเหตุผลที่อ้างตัวเลขในตาราง

จุดตรวจ A: สมาชิกทุกคนรัน A.2 และ A.3 ได้ และทีมตกลงเกณฑ์ร่วมกันก่อนเริ่ม Lab B

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
failed to download vulnerability DBเครือข่ายเข้าถึง registry ของฐานข้อมูลไม่ได้ หรือดาวน์โหลดพร้อมกันหลายเครื่องลองใหม่ภายหลัง หรือแจ้งผู้สอน (ผู้สอนเตรียมฐานข้อมูลล่วงหน้าตามบันทึกสำหรับผู้สอน)
unable to find the specified imageไม่มี image ในเครื่องและดึงจาก registry ไม่ได้ตรวจชื่อและ tag ด้วย docker image ls หรือ build ในเครื่องก่อน
trivy: command not foundไม่ได้วางไฟล์ไว้ใน pathตรวจด้วย ls /usr/local/bin/trivy แล้วติดตั้งใหม่ตามเอกสารทางการ

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab B: เพิ่มขั้นสแกนใน workflow

สถานการณ์: การสแกนบนเครื่องขึ้นกับว่าสมาชิกจำได้หรือไม่ ทีมต้องการให้ทุก pull request ถูกสแกนอัตโนมัติ และไม่ให้ merge เมื่อพบช่องโหว่ตามเกณฑ์

เป้าหมาย: เพิ่ม job image-scan ใน ci.yml ที่ build image โดยไม่ push แล้วสแกนด้วย Trivy Action ให้ล้มตามเกณฑ์ที่ตกลงใน Lab A (LLO 10.1)

ฝึกตามตัวอย่าง

B.1 สร้าง branch (สมาชิกคนที่ 1)

BASH
cd ~/devops-app
git switch main
git pull
git switch -c sec/trivy-ci
nano .github/workflows/ci.yml

B.2 เพิ่ม job image-scan ต่อท้ายไฟล์ โดยย่อหน้าระดับเดียวกับ job test

YAML
  image-scan:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
 
      - name: Build image (no push)
        uses: docker/build-push-action@v6
        with:
          context: .
          load: true
          tags: devops-app:ci
 
      - name: Scan image with Trivy
        uses: aquasecurity/[email protected]
        with:
          image-ref: devops-app:ci
          severity: CRITICAL
          ignore-unfixed: true
          exit-code: '1'
บรรทัดความหมาย
needs: testสแกนเมื่อ test ผ่านแล้วเท่านั้น
load: trueเก็บ image ไว้ใน Docker ของเครื่อง runner แทนการ push เพื่อให้ขั้นถัดไปสแกนได้
tags: devops-app:ciชื่อชั่วคราวในเครื่อง runner ไม่ได้ส่งขึ้น registry
image-refimage ที่จะสแกน ต้องตรงกับ tags ของขั้นก่อนหน้า
severity, ignore-unfixedเกณฑ์ที่ทีมตกลงใน Lab A
exit-code: '1'ทำให้ขั้นนี้ล้มเมื่อพบรายการตามเกณฑ์ ใส่เป็นข้อความในเครื่องหมายคำพูดตามตัวอย่างใน R10.5

ชื่อ input image-ref, exit-code และ severity ตรงกับเอกสารของ Trivy Action (Trivy Action) ส่วน ignore-unfixed ใช้ชื่อเดียวกับตัวเลือก --ignore-unfixed ของ Trivy

B.3 ตรวจและเปิด pull request

BASH
actionlint .github/workflows/ci.yml
git add .github/workflows/ci.yml
git commit -m "ci: scan image with Trivy"
git push -u origin sec/trivy-ci

ผลการทดสอบ actionlint ไม่แสดงข้อความใด (ผ่าน) เปิด pull request เข้า main แล้วดูหน้า Checks ว่ามี job image-scan ต่อจาก test และผ่าน

ตัดสินใจเอง B.4: Lab A.2 แสดงว่า trivy config . ตรวจ Dockerfile ได้ และจะพบ DS-0002 (HIGH) ถ้าใครลบ USER node ออก ให้ตัดสินว่าจะเพิ่มขั้นตรวจ Dockerfile ใน job image-scan หรือไม่ ถ้าเพิ่ม ต้องเลือกระดับที่ทำให้ล้มโดย DS-0026 (LOW) ที่มีอยู่ไม่ทำให้ทุก run ล้ม และหาวิธีกำหนดให้ Trivy Action ตรวจค่าตั้งจากเอกสารของ action เอง ถ้าไม่เพิ่ม ให้ให้เหตุผล บันทึกการตัดสินใจและลิงก์เอกสารในคำอธิบาย pull request

ผลลัพธ์ที่คาดหวัง B.4: การตัดสินใจพร้อมเหตุผล ถ้าเพิ่ม ต้องมีหลักฐานว่าทดลองบนเครื่องด้วย trivy config --exit-code 1 --severity HIGH,CRITICAL . แล้วได้ exit code 0 กับ Dockerfile ปัจจุบัน

จุดตรวจ B: pull request ผ่าน job test และ image-scan และผู้ตรวจทานยืนยันว่า exit-code เป็น '1' ก่อน merge หลัง merge หัวหน้าทีมเพิ่ม image-scan เป็น required status check ของ main ตามบทที่ 5 Lab D มิฉะนั้น pull request ที่สแกนล้มยังกด merge ได้

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
ขั้นสแกนรายงานว่าหา image ไม่พบimage-ref ไม่ตรงกับ tags หรือไม่ได้ใส่ load: trueทำให้สองค่าตรงกัน และใส่ load: true
image-scan ผ่านทั้งที่ log แสดงช่องโหว่ระดับ CRITICALไม่ได้ใส่ exit-code หรือรายการนั้นยังไม่มีรุ่นแก้ตรวจ exit-code: '1' และคอลัมน์รุ่นที่แก้แล้วใน log
job image-scan ล้มตั้งแต่ pull request แรกimage ปัจจุบันมีช่องโหว่ตามเกณฑ์อยู่แล้วทำ Lab D กับ main ก่อน ห้ามลดเกณฑ์เพื่อให้ผ่าน

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab C: base image เก่าทำให้ pipeline ล้ม

สถานการณ์: ทีมต้องพิสูจน์ว่าประตูสแกนทำงานจริง ไม่ใช่ผ่านทุกครั้งเพราะตั้งค่าผิด วิธีพิสูจน์คือส่ง image ที่รู้อยู่แล้วว่ามีช่องโหว่ร้ายแรงเข้า pipeline แล้วดูว่าล้มที่ขั้นสแกน

เป้าหมาย: สร้าง run ที่ล้มที่ขั้นสแกนเพราะ base image เก่า เป็นหลักฐานของงานข้อ 1 (LLO 10.1)

ฝึกตามตัวอย่าง

C.1 เปลี่ยน base image (สมาชิกคนที่ 2 หลัง Lab B merge แล้ว)

BASH
cd ~/devops-app
git switch main
git pull
git switch -c sec/old-base-demo
sed -i 's/^FROM node:22-slim/FROM node:<OLD_TAG>/' Dockerfile
grep -n '^FROM' Dockerfile

แทน <OLD_TAG> ด้วย tag ที่ผู้สอนกำหนด ผลของ grep ต้องแสดง FROM สองบรรทัด (stage build และ stage สุดท้าย) ที่เปลี่ยนเป็น tag เก่าแล้ว

C.2 สแกนบนเครื่องก่อน แล้ว push

BASH
docker build -t devops-app:old .
trivy image --severity CRITICAL --ignore-unfixed --exit-code 1 devops-app:old; echo "exit=$?"
git commit -am "demo: old base image (do not merge)"
git push -u origin sec/old-base-demo

เปิด pull request แบบ draft เข้า main ชื่อ "demo: old base image (do not merge)" แล้วรอผลของ CI

ผลลัพธ์ที่คาดหวัง: คำสั่ง trivy บนเครื่องจบด้วย exit=1 บน GitHub job test ผ่าน แต่ job image-scan ล้มที่ขั้น Scan image with Trivy และ log ของขั้นนั้นมีตารางรายการระดับ CRITICAL ที่มีรุ่นแก้แล้ว ปุ่ม merge ถูกปิดเพราะ required status check image-scan ล้ม เก็บลิงก์ run นี้เป็นหลักฐานของงานข้อ 1

ตัดสินใจเอง C.3: จาก log ของ run ที่ล้ม เลือกช่องโหว่ระดับ CRITICAL 3 รายการ แล้วระบุในคำอธิบาย pull request ว่าแต่ละรายการมาจากแพ็กเกจของระบบปฏิบัติการใน base image, จาก npm ที่ติดมากับ base image หรือจาก dependency ของแอป โดยใช้ชื่อเป้าหมาย (target) ในรายงานและเส้นทางของแพ็กเกจเป็นหลักฐาน แล้วอธิบายว่าเหตุใด test ของแอปจึงผ่านทั้งที่ image มีช่องโหว่

ผลลัพธ์ที่คาดหวัง C.3: ตาราง 3 รายการพร้อมที่มา และคำอธิบายว่า test ตรวจพฤติกรรมของแอป ไม่ได้ตรวจส่วนประกอบของ image

จุดตรวจ C: มีลิงก์ run ที่ล้มที่ขั้นสแกน ไม่ใช่ล้มที่ขั้น build หรือ test

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
build ล้มก่อนถึงขั้นสแกนbase image เก่าใช้กับ dependency ของแอปไม่ได้ เช่น Node.js รุ่นเก่าเกินไปสำหรับ Express 5แจ้งผู้สอนเพื่อเลือก tag อื่น งานนี้ต้องให้ build ผ่านแต่สแกนล้ม
image-scan ผ่านtag ที่ใช้ไม่มีช่องโหว่ตามเกณฑ์แล้ว หรือ sed ไม่ได้แก้ทั้งสองบรรทัดตรวจ grep -n '^FROM' Dockerfile และผลสแกนบนเครื่องใน C.2
เผลอ merge pull request สาธิตไม่ได้ตั้งเป็น draftrevert ทันทีด้วย pull request ใหม่

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab D: แก้ช่องโหว่และบันทึกแหล่งข้อมูล

สถานการณ์: run ใน Lab C ล้ม ทีมต้องเลือกวิธีแก้ที่มีเหตุผลรองรับ ไม่ใช่ลดเกณฑ์หรือปิดการสแกนเพื่อให้ผ่าน

เป้าหมาย: ศึกษาช่องโหว่อย่างน้อย 1 รายการจากแหล่งทางการ แก้ด้วยการเปลี่ยน base image และได้ run ที่ผ่าน (LLO 10.2)

ฝึกตามตัวอย่าง

D.1 ศึกษาช่องโหว่ 1 รายการ (สมาชิกทุกคนเลือกคนละรายการจาก C.3)

เปิดลิงก์ที่ Trivy แนบท้ายรายการ หรือค้นรหัส CVE ในแหล่งทางการ แล้วบันทึกตามแบบฟอร์มต่อไปนี้ (เป็นส่วนหนึ่งของงานข้อ 2)

หัวข้อสิ่งที่บันทึก
รหัส CVE และระดับตามรายงานของ Trivy
แพ็กเกจ รุ่นที่ติดตั้ง รุ่นที่แก้แล้วตามรายงานของ Trivy
ที่มาของแพ็กเกจbase image, npm ใน base image หรือ dependency ของแอป
ผลกระทบโดยสรุปถอดความจากแหล่งทางการด้วยคำของตนเอง
แหล่งข้อมูลลิงก์แหล่งทางการอย่างน้อย 1 แหล่ง เช่น หน้าคำแนะนำของผู้ดูแลแพ็กเกจ หรือระบบติดตามความปลอดภัยของ distribution
วิธีแก้ที่เลือกและเหตุผลดู D.2 และ D.3

D.2 เปลี่ยน base image กลับเป็นรุ่นที่ยังได้รับการดูแล และปรับเวอร์ชัน (สมาชิกคนที่ 2)

BASH
sed -i 's/^FROM node:<OLD_TAG>/FROM node:22-slim/' Dockerfile
npm version patch --no-git-tag-version
docker build -t devops-app:fixed .
trivy image --severity CRITICAL --ignore-unfixed --exit-code 1 devops-app:fixed; echo "exit=$?"
git commit -am "fix: use maintained base image, bump 0.4.1"
git push

(ทดสอบแล้วเฉพาะ npm version) คำสั่ง npm version patch --no-git-tag-version เปลี่ยนเวอร์ชันใน package.json และ package-lock.json เป็น 0.4.1 โดยไม่สร้าง tag ของ Git และ npm test ยังผ่านทั้ง 5 รายการ ผลสแกนที่คาดหวังคือ exit=0 ถ้ายังได้ 1 แสดงว่า node:22-slim ณ วันนั้นมีช่องโหว่ตามเกณฑ์ ให้ดูรุ่นที่แก้แล้วในรายงานและเลือก tag ที่ใหม่กว่า

ผลลัพธ์ที่คาดหวัง: run ใหม่ของ pull request เดิมผ่านทั้ง test และ image-scan เก็บลิงก์ run นี้เป็นหลักฐานของงานข้อ 2 แล้วเปลี่ยนชื่อ pull request เป็น "fix: maintained base image, bump 0.4.1" นำออกจากสถานะ draft และขอให้สมาชิกคนที่ 3 ตรวจทาน

ตัดสินใจเอง D.3: FROM node:22-slim เป็น tag ที่เลื่อนไปตามรุ่นย่อยล่าสุด ทีมเลือกได้สามแบบ (ก) คง tag ที่เลื่อนได้ (ข) ระบุรุ่นย่อยแน่นอน เช่น node:22.<x>.<y>-slim (ค) ระบุ digest ของ image เปรียบเทียบสามแบบในด้านการได้รับแพตช์ความปลอดภัย ความสามารถ build ซ้ำได้ผลเดิม และภาระการอัปเดต โดยอ้างเอกสารทางการอย่างน้อย 1 แหล่ง แล้วเลือกแบบที่ใช้ใน pull request นี้

ผลลัพธ์ที่คาดหวัง D.3: ตารางเปรียบเทียบสามแบบในคำอธิบาย pull request การเลือกที่สอดคล้องกับ Dockerfile ที่ merge และลิงก์เอกสาร

จุดตรวจ D: pull request ที่ merge แล้วมี run ล่าสุดที่ผ่าน image-scan สมาชิกทุกคนมีบันทึกตามแบบฟอร์ม D.1 คนละ 1 รายการ

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
แก้แล้วยังล้มด้วยรายการเดิมGitHub ใช้ image ชั้นเก่าจาก cache หรือ tag ใหม่ยังมีแพ็กเกจรุ่นเดิมดูรุ่นที่ติดตั้งในรายงานใหม่ ถ้ายังเป็นรุ่นเดิมให้เลือก tag ที่ใหม่กว่า
อยากเพิ่ม CVE ลงใน .trivyignore ให้ผ่านเร็ว ๆเข้าใจว่าการยกเว้นคือการแก้การยกเว้นใช้เฉพาะรายการที่ยังไม่มีรุ่นแก้หรือวิเคราะห์แล้วว่าไม่กระทบ ต้องมีเหตุผลและวันทบทวน และต้องให้ผู้สอนเห็นชอบ
ลืม --no-git-tag-versionnpm สร้าง commit และ tag ให้เองลบ tag ในเครื่องด้วย git tag -d v0.4.1 ก่อน push หัวหน้าทีมเป็นผู้สร้าง tag เท่านั้น

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab E: ย้ายรหัสผ่านไป GitHub Secrets

สถานการณ์: รหัสผ่านฐานข้อมูลของเครื่องปลายทางอยู่ในไฟล์ ~/devops-deploy/.env ทีมต้องการเก็บไว้ที่ GitHub Secrets ที่เดียว ให้ workflow ส่งค่าให้ตอน deploy และให้ cd.yml สแกน image ก่อน push ด้วยเกณฑ์เดียวกับ CI

เป้าหมาย: ตรวจ repository ก่อนย้ายความลับ สร้าง secret ส่งค่าเข้า deploy job และเพิ่มขั้นสแกนก่อน push ใน cd.yml (LLO 10.1 และหลักฐานประกอบ CLO4)

ฝึกตามตัวอย่าง

E.1 ตรวจก่อนย้าย (สมาชิกคนที่ 3)

BASH
cd ~/devops-app
git switch main
git pull
bash scripts/check-secrets.sh; echo "exit=$?"
TEXT
== ไฟล์ .env ที่เคยถูก commit ==
== บรรทัดที่เคยถูกเพิ่มและเข้าข่ายความลับ ==
ผล: ไม่พบร่องรอยความลับตามรูปแบบที่ค้น
exit=0

ถ้าสคริปต์รายงานบรรทัด +PATTERN='password|passwd|secret|token' แสดงว่าทีมใช้สคริปต์ฉบับก่อนแก้ไขซึ่งรายงานตัวเอง ให้ปรับบรรทัดค้นประวัติเป็น git log -p --all --format='' -- . ':(exclude)scripts/check-secrets.sh' ตามฉบับปัจจุบันในบทที่ 9 Lab D

E.2 สร้าง secret ที่หน้า repository เลือก Settings → Secrets and variables → Actions → New repository secret ตั้งชื่อ PROD_POSTGRES_PASSWORD และวางค่าเดียวกับในไฟล์ ~/devops-deploy/.env ของเครื่องปลายทาง หรือใช้ GitHub CLI ซึ่งจะถามค่าโดยไม่แสดงบนจอ

BASH
gh secret set PROD_POSTGRES_PASSWORD
gh secret list

ห้ามใช้ตัวเลือกที่ใส่ค่าลงในคำสั่งโดยตรง เพราะค่าจะไปอยู่ใน history ของ shell ซึ่งผิดข้อห้ามส่งความลับทาง command line [R10.6] ต้องใช้ค่าเดิม เพราะฐานข้อมูลใน volume ถูกสร้างด้วยรหัสผ่านนั้นแล้ว (หัวข้อ 10.4 ระดับที่ 3)

E.3 แก้ cd.yml

BASH
git switch -c sec/deploy-secret
nano .github/workflows/cd.yml

ใน job build-push แทนขั้น Build and push image เดิมด้วยสามขั้นต่อไปนี้

YAML
      - name: Build image for scanning
        uses: docker/build-push-action@v6
        with:
          context: .
          load: true
          tags: devops-app:scan
      - name: Scan image with Trivy
        uses: aquasecurity/[email protected]
        with:
          image-ref: devops-app:scan
          severity: CRITICAL
          ignore-unfixed: true
          exit-code: '1'
      - name: Push image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

และใน job deploy เพิ่มบรรทัด POSTGRES_PASSWORD ใต้ env ของขั้น Deploy with Compose ตามตัวอย่างในหัวข้อ 10.4 แล้วตรวจ

BASH
actionlint .github/workflows/cd.yml
bash scripts/check-secrets.sh; echo "exit=$?"
git add .github/workflows/cd.yml
git commit -m "cd: scan before push, db password from secrets"
git push -u origin sec/deploy-secret

(ทดสอบแล้ว) actionlint ผ่าน และ check-secrets.sh ยังคืน exit=0 เพราะบรรทัด ${{ secrets.PROD_POSTGRES_PASSWORD }} ตรงกับรูปแบบที่ยกเว้น (secrets.) ขั้น Push image build ซ้ำจาก context เดิม จึงได้ image ตรงกับที่สแกน (ข้อสันนิษฐานว่า build ครั้งที่สองใช้ cache ของ builder เดียวกันยังไม่ได้ทดสอบบน GitHub)

E.4 ลบรหัสผ่านออกจากเครื่องปลายทาง (หัวหน้าทีม หลัง merge ทั้งสาม pull request)

BASH
cp ~/devops-deploy/.env ~/devops-deploy/.env.bak-$(date +%Y%m%d)
sed -i '/^POSTGRES_PASSWORD=/d' ~/devops-deploy/.env
cat ~/devops-deploy/.env
TEXT
PROXY_PORT=80
APP_TITLE=DevOps App (team 01)

จากนั้นออกเวอร์ชัน v0.4.1 ตามบทที่ 8 run ของ CD ต้องผ่านทั้งขั้นสแกนและขั้น deploy และหน้าแรกบนเครื่องปลายทางต้องแสดง v0.4.1 ผลการทดสอบบนเครื่องจำลองด้วยไฟล์ค่าตั้งที่ไม่มีรหัสผ่านและส่ง POSTGRES_PASSWORD จาก shell แสดงว่าทั้งสามบริการ Healthy และฐานข้อมูลตอบ psql ได้ตามปกติ

ถ้าต้อง deploy ด้วยมือ (ทางสำรองจากบทที่ 8) ให้ป้อนรหัสผ่านโดยไม่แสดงบนจอ

BASH
read -rs POSTGRES_PASSWORD && export POSTGRES_PASSWORD
bash scripts/deploy.sh 0.4.1
unset POSTGRES_PASSWORD

ตัดสินใจเอง E.5: หลังย้ายแล้ว รหัสผ่านเดิมยังอาจเหลืออยู่ที่อื่น ให้สำรวจอย่างน้อย 3 ที่ที่อาจมีสำเนา (เช่น ไฟล์สำรองใน E.4, history ของ shell บนเครื่องปลายทาง, ข้อความในกลุ่มแชตของทีม) ตัดสินว่าแต่ละที่ต้องลบ เก็บไว้อย่างปลอดภัย หรือเปลี่ยนรหัสผ่านใหม่ทั้งหมด แล้วบันทึกการตัดสินใจลงในคำอธิบาย pull request โดยไม่ระบุค่ารหัสผ่าน ถ้าเลือกเปลี่ยนรหัสผ่าน ต้องหาจากเอกสารทางการว่าต้องเปลี่ยนในฐานข้อมูลด้วยคำสั่งใด เพราะการเปลี่ยนค่าใน Secrets อย่างเดียวไม่เปลี่ยนรหัสผ่านของฐานข้อมูลที่มีอยู่

ผลลัพธ์ที่คาดหวัง E.5: รายการที่อย่างน้อย 3 ที่ การตัดสินใจและเหตุผลของแต่ละที่ และแหล่งอ้างอิงถ้าเลือกเปลี่ยนรหัสผ่าน

จุดตรวจ E: run ของ v0.4.1 ผ่านทุก job ~/devops-deploy/.env ไม่มีบรรทัด POSTGRES_PASSWORD และไม่มีขั้นใดใน workflow ที่พิมพ์ค่ารหัสผ่าน

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
deploy ล้มด้วย required variable POSTGRES_PASSWORD is missing a valueชื่อ secret ใน workflow ไม่ตรงกับที่สร้าง หรือยังไม่ได้สร้าง secretตรวจด้วย gh secret list และชื่อใน cd.yml (หัวข้อ 10.4 ระดับที่ 2)
deploy ผ่านแต่ฐานข้อมูลยังใช้รหัสผ่านเดิมค่าใน Secrets ต่างจากค่าเดิม แต่ volume มีฐานข้อมูลอยู่แล้วใช้ค่าเดิมใน Secrets หรือเปลี่ยนรหัสผ่านในฐานข้อมูลตามเอกสารทางการ (E.5)
สร้าง secret ไม่ได้ ไม่เห็นเมนู Settingsไม่มีสิทธิ์ Admin ของ repositoryให้เจ้าของ repository ทำ หรือเพิ่มสิทธิ์

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

เก็บกวาดเมื่อจบปฏิบัติการ

บนเครื่องของตนเองลบไฟล์ sbom.cdx.json และ image ชั่วคราวด้วย docker image rm devops-app:old devops-app:fixed (ถ้ามี) ลบ branch sec/* ที่ merge แล้วตามบทที่ 3 ตรวจว่าไม่มีไฟล์ SBOM หรือรายงานสแกนถูก commit ด้วย git status บนเครื่องปลายทาง ลบไฟล์ ~/devops-deploy/.env.bak-* ตามที่ทีมตัดสินใน E.5 หลังยืนยันว่า v0.4.1 ทำงานแล้ว

สรุป

Shift Left คือการกระจายการตรวจความปลอดภัยไปทุกช่วงของวงจรพัฒนา โดยเฉพาะช่วงต้นที่แก้ได้ถูกที่สุด กรอบ SSDF ของ NIST แบ่งแนวปฏิบัติเป็น 4 กลุ่ม (PO, PS, PW, RV) และทีมทำแนวปฏิบัติบางส่วนมาแล้วตั้งแต่บทแรก ๆ ประตูตรวจที่ดีต้องทำให้ run ล้มเมื่อพบปัญหา และต้องใช้เกณฑ์ที่ทีมแก้ได้จริง

image ของทีมประกอบด้วยส่วนที่ผู้อื่นเขียนเป็นส่วนใหญ่ SBOM ที่สร้างด้วย Trivy แสดงว่าแพ็กเกจกว่าสามในสี่มาจาก base image การเลือกและอัปเดต base image จึงสำคัญต่อความปลอดภัยไม่น้อยกว่าโค้ดของทีม SBOM ตามข้อเสนอของ NTIA มีฟิลด์ขั้นต่ำ เช่น ชื่อ รุ่น และตัวระบุที่ไม่ซ้ำของแต่ละส่วนประกอบ

Trivy จบด้วย exit code 0 แม้พบปัญหา workflow จึงต้องกำหนด exit-code: '1' ร่วมกับระดับความรุนแรง บทนี้ใช้เกณฑ์ CRITICAL ที่มีรุ่นแก้แล้ว การแก้ช่องโหว่เริ่มจากดูว่ามีรุ่นแก้หรือไม่ แพ็กเกจมาจากไหน และทางแก้ใดเปลี่ยนน้อยที่สุด การยกเว้นต้องมีเหตุผลที่ตรวจสอบได้ ส่วนรหัสผ่านของเครื่องปลายทางย้ายไปอยู่ใน GitHub Secrets และส่งเข้า deploy เป็นตัวแปรสภาพแวดล้อมของขั้นที่ต้องใช้ รูปแบบ ${VAR:?...} ของ Compose หยุด deploy เมื่อ secret ว่าง

สิ่งที่ส่งต่อไปยังโครงงานและ Capstone

สิ่งที่ได้นำไปใช้ที่
job image-scan และขั้นสแกนก่อน push (M9)บทที่ 12 pipeline ครบสาย commit → test → scan → build → deploy
GitHub Secret PROD_POSTGRES_PASSWORD และ ~/devops-deploy/.env ที่ไม่มีรหัสผ่านบทที่ 11 deploy gate · บทที่ 12 จุดตรวจความปลอดภัยครั้งที่ 4
scripts/check-secrets.sh ฉบับที่ไม่รายงานตัวเองบทที่ 12 จุดตรวจความปลอดภัยครั้งที่ 4
บันทึกการแก้ช่องโหว่ (งานข้อ 2)บทที่ 12 สอบปากเปล่า · บทที่ 13 portfolio
ความก้าวหน้างานต่อยอด (งานข้อ 3)บทที่ 13 สาธิตงานต่อยอด
Learning Log ครั้งที่ 10portfolio ที่ส่งในบทที่ 13

สรุปคำสั่งในบทนี้

คำสั่งหรือค่าตั้งหน้าที่
trivy image --format cyclonedx --output ไฟล์ IMAGEสร้าง SBOM รูปแบบ CycloneDX (ไม่สแกนช่องโหว่)
trivy image --severity CRITICAL,HIGH IMAGEสแกนช่องโหว่ แสดงเฉพาะระดับที่กำหนด
--ignore-unfixedแสดงเฉพาะช่องโหว่ที่มีรุ่นแก้แล้ว
--exit-code 1จบด้วย exit code 1 เมื่อพบรายการตามเงื่อนไข
trivy config .ตรวจค่าตั้ง เช่น Dockerfile
trivy fs --scanners secret .ค้นความลับในโฟลเดอร์
docker/build-push-action กับ load: truebuild image เก็บไว้ในเครื่อง runner โดยไม่ push
aquasecurity/trivy-action กับ image-ref, severity, exit-codeสแกน image ใน GitHub Actions
${{ secrets.ชื่อ }} ใต้ env ของขั้นส่งความลับเป็นตัวแปรสภาพแวดล้อมเฉพาะขั้นนั้น
gh secret set ชื่อ, gh secret listสร้าง secret โดยป้อนค่าแบบซ่อน และดูรายชื่อ secret
npm version patch --no-git-tag-versionเพิ่มเลขเวอร์ชันย่อยใน package.json และ lock file โดยไม่สร้าง tag
read -rs VAR && export VARป้อนค่าลับเข้าตัวแปรโดยไม่แสดงบนจอ

แบบฝึกหัดท้ายบท

ตอนที่ 1 ฝึกทบทวน

ชุด FP10306343-P10 จำนวน 12 ข้อ ข้อละ 1 คะแนน รวม 12 คะแนน ครอบคลุมการตั้งประตูสแกนใน pipeline และการอ่านรายงานช่องโหว่กับ SBOM ทำซ้ำได้และแสดงคำอธิบายหลังส่ง

เข้าสู่ระบบห้องเรียนด้วยบัญชีที่ลงทะเบียนในรายวิชา 10306343 แล้วตอบคำถามให้ครบ ระบบตรวจคำตอบและแสดงคำอธิบายเพื่อทบทวน เมื่อทำครบและส่งผล ระบบจะบันทึกคะแนนไว้กับบัญชีของผู้เรียน สามารถทำซ้ำเพื่อทบทวนได้ ส่วนคะแนนที่นำไปรวมในรายวิชาเป็นไปตามเกณฑ์และกำหนดส่งในสมุดคะแนน

แบบฝึกหัดเพิ่มเติมสำหรับอภิปรายและเขียนตอบ

แบบฝึกหัดแบ่งเป็นสามกลุ่ม ได้แก่ ความเข้าใจ การประยุกต์ และการวิเคราะห์ปัญหา แต่ละข้อระบุผลลัพธ์การเรียนรู้ที่ฝึก

ก. ความเข้าใจ

  • ก1 (LLO 10.1) อธิบายว่าเหตุใดการเพิ่มคำสั่ง trivy image IMAGE ลงใน workflow โดยไม่มีตัวเลือกอื่น จึงไม่ใช่ประตูตรวจความปลอดภัย
  • ก2 (LLO 10.2) อธิบายความหมายของ SBOM และระบุฟิลด์ขั้นต่ำตามรายงานของ NTIA อย่างน้อย 4 ฟิลด์
  • ก3 (LLO 10.2) อธิบายความต่างระหว่างช่องโหว่สถานะ fixed กับ affected และผลของ --ignore-unfixed ต่อแต่ละสถานะ

ข. การประยุกต์

  • ข1 (LLO 10.1) เขียนขั้นของ workflow ที่สแกน image myapp:ci ด้วย Trivy Action ให้ล้มเมื่อพบช่องโหว่ระดับ HIGH หรือ CRITICAL ทุกรายการ ไม่ว่ามีรุ่นแก้หรือไม่
  • ข2 (LLO 10.2) อ่านรายงานต่อไปนี้ (สร้างขึ้นเพื่อแบบฝึกหัด ไม่ใช่ผลสแกนจริง) แล้วตอบว่า (1) ถ้าใช้เกณฑ์ของบทนี้ run ล้มหรือไม่ เพราะรายการใด (2) เสนอวิธีแก้ของแต่ละรายการที่ทำให้ run ล้ม
แพ็กเกจช่องโหว่ระดับสถานะรุ่นที่ติดตั้งรุ่นที่แก้แล้วที่มา
libexampleCVE-0000-0001CRITICALfixed1.2.01.2.3base image
libotherCVE-0000-0002CRITICALaffected2.0.1—base image
json-helperCVE-0000-0003HIGHfixed0.9.00.9.5dependency ของแอป
path-utilCVE-0000-0004CRITICALfixed3.1.03.1.2dependency ของแอป
  • ข3 (LLO 10.1) เขียนส่วน env ของขั้น deploy ที่ส่ง secret ชื่อ PROD_DB_PASSWORD เข้าตัวแปร POSTGRES_PASSWORD และอธิบายว่าถ้าพิมพ์ชื่อ secret ผิด ระบบของทีมจะแสดงผลอย่างไร

ค. การวิเคราะห์ปัญหา

  • ค1 (LLO 10.1) pull request หนึ่งผ่าน job image-scan ทุกครั้งตลอดสองเดือน แม้ช่วงที่ทีมอื่นซึ่งใช้ base image เดียวกันรายงานว่าล้ม เมื่อเปิดไฟล์พบว่าขั้นสแกนไม่มี exit-code วิเคราะห์ผลกระทบ และเสนอวิธีป้องกันไม่ให้เกิดซ้ำนอกจากการแก้ไฟล์
  • ค2 (LLO 10.2) run ของ main ที่เคยผ่านเริ่มล้มที่ขั้นสแกนในวันจันทร์ ทั้งที่ไม่มีใครแก้โค้ดตั้งแต่วันศุกร์ วิเคราะห์สาเหตุที่เป็นไปได้ และเรียงลำดับสิ่งที่ทีมควรทำ
  • ค3 (LLO 10.2) สมาชิกคนหนึ่งเสนอให้เพิ่มรหัส CVE ทุกรายการที่ทำให้ run ล้มลงใน .trivyignore เพราะ "ใกล้ส่งงานแล้ว" วิเคราะห์ข้อเสนอนี้ตาม rubric CLO4 และเสนอเงื่อนไขที่การยกเว้นจะยอมรับได้

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

การศึกษาค้นคว้าเพิ่มเติม

  1. อ่าน Trivy docs: Others ส่วน exit code แล้วบันทึกใน Learning Log ว่าตัวเลือกใดทำให้ Trivy คืนค่าที่ไม่ใช่ 0
  2. อ่านหน้า Trivy Action แล้วจดรายชื่อ input ที่เกี่ยวกับระดับความรุนแรง การยกเว้น และรูปแบบรายงาน เพื่อใช้ใน Lab B.4
  3. อ่าน GitHub Docs: Using secrets in GitHub Actions แล้วสรุปข้อปฏิบัติเรื่องการส่งความลับให้ action และคำสั่ง
  4. อ่านหัวข้อ A03 Software Supply Chain Failures ใน OWASP Top 10:2025 และยกตัวอย่างความเสี่ยงหนึ่งข้อที่เกิดกับ pipeline ของทีมได้
  5. อ่านบทสรุปของ NIST SP 800-218 และรายงาน The Minimum Elements For a Software Bill of Materials เพื่อตอบแบบฝึกหัด ก2
  6. ทำแบบฝึกหัดท้ายบทให้ครบ เขียนบันทึกการแก้ช่องโหว่ (งานข้อ 2) รายงานความก้าวหน้างานต่อยอด (งานข้อ 3) และเขียน Learning Log ครั้งที่ 10 ผู้ที่สนใจเครื่องมือวิเคราะห์โค้ดหรือการจัดการความลับเพิ่มเติม ดูส่วนเสริมในภาคผนวก ก (SonarQube และ Vault)

เอกสารอ้างอิง

แหล่งข้อมูลเพิ่มเติมสำหรับตัวอย่างและกรณีอธิบาย

ตรวจเอกสารออนไลน์เมื่อ 23 ตุลาคม 2569 ตัวเลขและเหตุการณ์ที่ระบุว่าสมมติใช้เพื่ออธิบายกลไก ไม่ใช่ผลทดลองหรือสถิติขององค์กรจริง

เอกสารอ้างอิงของเนื้อหาเดิม

By Watcharin Sarachai