Skip to main content

Security Model

SecurityContextConstraints (SCC)​

K8s มาตรฐานปล่อยให้ container รันเป็น root ได้ถ้า image ไม่ได้ตั้ง USER ไว้ — OpenShift บังคับ non-root เป็น default ผ่านกลไกที่เรียกว่า SCC ซึ่งเป็น concept ที่ไม่มีใน K8s เปล่า (คล้าย PodSecurityPolicy เดิมของ K8s ที่ถูก deprecate ไปแล้ว หรือ Pod Security Admission ที่มาทีหลัง แต่ SCC มาก่อนและทำงานต่างกันในรายละเอียด)

oc get scc # ดู SCC ทั้งหมดใน cluster
oc describe scc restricted # SCC default ที่ project ส่วนใหญ่ใช้

SCC default (restricted-v2 ในเวอร์ชันใหม่) บังคับ: รันด้วย UID ที่สุ่มจาก range ที่ project ได้รับ (ไม่ใช่ UID คงที่ที่ image กำหนด), ห้าม privilege escalation, ห้าม mount hostPath, drop Linux capability เกือบทั้งหมด

🧠 จำง่ายๆ

"SCC = ยามตรวจก่อนเข้าประตู, SecurityContext ใน Pod spec = สิ่งที่ Pod 'ขอ'" — Pod spec "ขอ" สิทธิ์อะไรก็ได้ตามต้องการ แต่ SCC คือยามที่ตรวจว่า "ขอได้แค่ไหนจริงๆ" ถ้าขอเกินสิทธิ์ที่ SCC อนุญาต Pod จะถูกปฏิเสธไม่ให้สร้างเลย (ไม่ใช่แค่เตือน)

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

UID ที่ Pod ได้รับจาก SCC restricted ไม่ใช่เลขเดียวกันทุกครั้ง — แต่ละ Project จะได้รับ UID range เฉพาะของตัวเอง (เช่น project A ได้ 1000000000-1000009999, project B ได้ 1000010000-1000019999) ที่สุ่มมาจาก UID ว่างแล้วจองไว้ตอนสร้าง project เหตุผลที่ทำแบบนี้ (แทนที่จะให้ทุก Pod รันเป็น UID เดียวกันหมด เช่น 1000) คือ ป้องกัน UID collision ข้าม project บน Node เดียวกัน — ถ้า 2 project รัน container คนละตัวด้วย UID เดียวกันบน Node เดียวกัน (แม้จะอยู่คนละ Pod/namespace) ในทางทฤษฎี process หนึ่งอาจเข้าถึงไฟล์ของอีก process ได้ถ้ามี vulnerability บางอย่างที่ bypass container isolation (เช่น shared volume ที่ mount ผิดพลาด) การให้แต่ละ project มี UID range แยกกันคือ defense-in-depth อีกชั้นที่ K8s เปล่า ไม่มีมาให้โดย default — เป็นเหตุผลที่ลึกกว่าแค่ "บังคับ non-root" ที่คนทั่วไปรู้

ทำไม Image ที่รันเป็น root ถึงพังบน OpenShift​

Image จำนวนมากจาก Docker Hub ถูก build มาโดยสมมติว่าจะรันเป็น root (เขียนไฟล์ไปที่ /var/lib/... หรือ bind port ต่ำกว่า 1024 ที่ต้องใช้สิทธิ์ root) พอมารันบน OpenShift ที่ บังคับ UID สุ่ม จะเจอ Permission denied ทันที

# ต้องแก้ Dockerfile ให้รองรับ "arbitrary UID" แบบนี้
RUN chgrp -R 0 /app && chmod -R g=u /app # ให้ group root (gid 0) เข้าถึงได้เท่ากับ owner
USER 1001 # รันด้วย UID ที่ไม่ใช่ root (เลขอะไรก็ได้ ไม่สำคัญ
# เพราะ OpenShift จะ override เป็น UID สุ่มอยู่ดี)
🧠 จำง่ายๆ

"Image ต้องเขียนให้ 'ไม่สนใจว่าใครเป็นคนรัน' ไม่ใช่ 'ต้องเป็น root เท่านั้นถึงรันได้'" — หลักการ คือทำให้ไฟล์ที่ container ต้องเขียนได้ เข้าถึงได้ด้วย group (ไม่ใช่เจาะจง user คนเดียว) เพราะ OpenShift ควบคุม UID ได้ แต่ GID 0 (root group) ยังคงเดิมเสมอ ไม่ว่า UID จะสุ่มมา เป็นอะไร

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

เหตุผลที่ chgrp -R 0 (group เป็น root group, GID 0) ใช้ได้ผล เพราะ OpenShift รับประกันว่า container ทุกตัวจะมี GID 0 (root group) อยู่เสมอ แม้ UID จะถูกสุ่มเปลี่ยนก็ตาม — นี่คือ "สัญญา" ที่ Red Hat ออกแบบไว้ให้ image builder อาศัยได้ ("Support Arbitrary User IDs" guideline อย่างเป็นทางการ) การตั้ง permission ด้วย chmod g=u (ให้ group มีสิทธิ์เท่ากับ owner) จึงทำให้ ไม่ว่า UID จะเป็นเลขอะไรก็ตาม ตราบใดที่อยู่ใน group 0 ก็เข้าถึงไฟล์ได้เหมือนเดิม — เป็นแพทเทิร์น มาตรฐานที่ Red Hat official image ทุกตัว (UBI base images) implement ตามนี้ แต่ community image ทั่วไปจาก Docker Hub ส่วนใหญ่ไม่ได้ทำตาม จึงต้องแก้ Dockerfile เองเวลาย้ายมารัน OpenShift

Project-level RBAC​

OpenShift มี default role ที่ผูกมากับทุก Project: admin (จัดการทุกอย่างใน project รวม RBAC), edit (แก้ resource ได้แต่แก้ RBAC ไม่ได้), view (อ่านอย่างเดียว) — ต่างจาก K8s เปล่าที่ต้อง สร้าง Role/RoleBinding เองทั้งหมดตั้งแต่ศูนย์

oc adm policy add-role-to-user edit jane -n my-team
oc adm policy who-can create pods -n my-team # เช็คว่าใครมีสิทธิ์ทำอะไรได้บ้าง
🧠 จำง่ายๆ

"OpenShift มีตำแหน่งงานสำเร็จรูปให้เลือก (admin/edit/view), K8s เปล่าต้องเขียน job description เองทุกตำแหน่ง" — ลดงานตั้งค่า RBAC ซ้ำๆ สำหรับ pattern การใช้งานทั่วไปที่ซ้ำกัน ทุก project

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

oc adm policy who-can เป็นคำสั่งที่ engineer จำนวนมากไม่รู้ว่ามีอยู่ — มันตอบคำถาม "ใคร มีสิทธิ์ทำ action นี้ได้บ้าง" โดยตรง (reverse lookup จาก action ไปหา user/group) ต่างจาก kubectl auth can-i ที่ตอบคำถามตรงข้าม ("user นี้ทำ action นี้ได้ไหม" — ต้องรู้ user ล่วงหน้า) เวลา debug ปัญหา "ทำไม deploy ไม่ผ่าน permission denied" who-can ช่วยตอบได้เร็วกว่ามากว่า ควรขอสิทธิ์จากใคร หรือ role ไหนที่ต้อง bind เพิ่ม แทนที่จะต้องไล่เดา role ทีละตัวแล้วเช็คด้วย can-i ทีละรอบ

English Summary

SecurityContextConstraints (SCC) enforce non-root by default — each Project gets its own random UID range (not a shared fixed UID) as a defense-in-depth measure preventing cross-project UID collisions on shared Nodes. Images built assuming root access break under OpenShift's random-UID model; the fix is making files group-writable (chgrp 0 + chmod g=u), relying on the guarantee that every container always runs under GID 0 regardless of its assigned UID — an official Red Hat "arbitrary user ID" convention that Docker Hub images rarely follow. Project-level RBAC ships with default admin/edit/view roles, saving the from-scratch Role/RoleBinding setup vanilla Kubernetes requires; oc adm policy who-can answers "who can do X" directly, which is often faster for debugging permission issues than repeatedly checking kubectl auth can-i per user.