บันทึก. แปล: เราขอนำเสนอให้คุณทราบถึงการแปลบทความโดยวิศวกรความปลอดภัยแอปพลิเคชันอาวุโสที่ ASOS.com บริษัทสัญชาติอังกฤษ ด้วยเหตุนี้ เขาจึงเริ่มเผยแพร่ชุดสิ่งพิมพ์ที่เกี่ยวข้องกับการปรับปรุงความปลอดภัยใน Kubernetes ผ่านการใช้ seccomp หากผู้อ่านชอบบทนำ เราจะติดตามผู้เขียนและดำเนินการต่อในเนื้อหาในอนาคตของเขาในหัวข้อนี้
บทความนี้เป็นบทความแรกในชุดโพสต์เกี่ยวกับวิธีสร้างโปรไฟล์ seccomp ด้วยจิตวิญญาณของ SecDevOps โดยไม่ต้องใช้เวทมนตร์และคาถา ในส่วนที่ XNUMX ฉันจะพูดถึงพื้นฐานและรายละเอียดภายในของการนำ seccomp ไปใช้งานใน Kubernetes
ระบบนิเวศของ Kubernetes นำเสนอวิธีการที่หลากหลายในการรักษาความปลอดภัยและแยกคอนเทนเนอร์ บทความนี้เกี่ยวกับโหมดคอมพิวเตอร์ที่ปลอดภัยหรือที่เรียกว่า วินาที. สิ่งสำคัญคือการกรองการเรียกของระบบที่พร้อมใช้งานสำหรับการดำเนินการโดยคอนเทนเนอร์
ทำไมมันถึงสำคัญ? คอนเทนเนอร์เป็นเพียงกระบวนการที่ทำงานบนเครื่องเฉพาะ และใช้เคอร์เนลเหมือนกับแอปพลิเคชันอื่นๆ หากคอนเทนเนอร์สามารถทำการเรียกของระบบได้ ในไม่ช้ามัลแวร์ก็จะใช้ประโยชน์จากสิ่งนี้เพื่อหลีกเลี่ยงการแยกคอนเทนเนอร์และส่งผลกระทบต่อแอปพลิเคชันอื่นๆ เช่น สกัดกั้นข้อมูล เปลี่ยนการตั้งค่าระบบ ฯลฯ
โปรไฟล์ seccomp กำหนดว่าการเรียกระบบใดควรได้รับอนุญาตหรือปิดใช้งาน รันไทม์ของคอนเทนเนอร์จะเปิดใช้งานเมื่อเริ่มทำงานเพื่อให้เคอร์เนลสามารถตรวจสอบการดำเนินการได้ การใช้โปรไฟล์ดังกล่าวช่วยให้คุณสามารถจำกัดเวกเตอร์การโจมตีและลดความเสียหายหากโปรแกรมใดๆ ภายในคอนเทนเนอร์ (นั่นคือ การขึ้นต่อกันของคุณ หรือการขึ้นต่อกันของพวกมัน) เริ่มทำสิ่งที่ไม่ได้รับอนุญาตให้ทำ
การทำความเข้าใจพื้นฐาน
โปรไฟล์ seccomp พื้นฐานประกอบด้วยสามองค์ประกอบ: defaultAction
, architectures
(หรือ archMap
) และ syscalls
:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
(
defaultAction
กำหนดชะตากรรมเริ่มต้นของการเรียกระบบใดๆ ที่ไม่ได้ระบุไว้ในส่วนนี้ syscalls
. เพื่อให้สิ่งต่าง ๆ ง่ายขึ้น เรามาเน้นไปที่ค่านิยมหลัก XNUMX ประการที่จะใช้:
-
SCMP_ACT_ERRNO
— บล็อกการดำเนินการเรียกของระบบ -
SCMP_ACT_ALLOW
- อนุญาต
ในส่วน architectures
สถาปัตยกรรมเป้าหมายจะแสดงรายการไว้ นี่เป็นสิ่งสำคัญเนื่องจากตัวกรองนั้นซึ่งใช้ในระดับเคอร์เนลนั้นขึ้นอยู่กับตัวระบุการเรียกของระบบ และไม่ได้ขึ้นอยู่กับชื่อที่ระบุในโปรไฟล์ รันไทม์ของคอนเทนเนอร์จะจับคู่กับตัวระบุก่อนใช้งาน แนวคิดก็คือการเรียกของระบบอาจมี ID ที่แตกต่างกันโดยสิ้นเชิง ขึ้นอยู่กับสถาปัตยกรรมของระบบ เช่น การเรียกของระบบ recvfrom
(ใช้เพื่อรับข้อมูลจากซ็อกเก็ต) มี ID = 64 บนระบบ x64 และ ID = 517 บน x86
ในส่วน syscalls
แสดงรายการการเรียกของระบบทั้งหมดและระบุสิ่งที่ต้องทำกับสิ่งเหล่านั้น ตัวอย่างเช่น คุณสามารถสร้างรายการที่อนุญาตพิเศษได้โดยการตั้งค่า defaultAction
บน SCMP_ACT_ERRNO
และการโทรในส่วน syscalls
กำหนด SCMP_ACT_ALLOW
. ดังนั้นคุณจึงอนุญาตเฉพาะการโทรที่ระบุไว้ในส่วนนี้เท่านั้น syscalls
และห้ามสิ่งอื่นๆ ทั้งหมด สำหรับบัญชีดำ คุณควรเปลี่ยนค่า defaultAction
และการกระทำที่ตรงกันข้าม
ตอนนี้เราควรพูดสองสามคำเกี่ยวกับความแตกต่างที่ไม่ชัดเจนนัก โปรดทราบว่าคำแนะนำด้านล่างนี้ถือว่าคุณกำลังปรับใช้แอปพลิเคชันทางธุรกิจหลายกลุ่มบน Kubernetes และคุณต้องการให้แอปพลิเคชันเหล่านี้ทำงานโดยมีสิทธิ์การใช้งานน้อยที่สุด
1. AllowPrivilegeEscalation=false
В securityContext
คอนเทนเนอร์มีพารามิเตอร์ AllowPrivilegeEscalation
. ถ้าจะติดตั้งใน false
คอนเทนเนอร์จะเริ่มต้นด้วย (on
) นิดหน่อย no_new_priv
ผลข้างเคียงของตัวเลือกนี้ถูกตั้งค่าเป็น true
(ค่าเริ่มต้น) คือรันไทม์ของคอนเทนเนอร์ใช้โปรไฟล์ seccomp ที่จุดเริ่มต้นของกระบวนการเริ่มต้น ดังนั้น การเรียกระบบทั้งหมดที่จำเป็นในการรันกระบวนการรันไทม์ภายใน (เช่น การตั้งค่า ID ผู้ใช้/กลุ่ม การลดความสามารถบางอย่าง) จะต้องเปิดใช้งานในโปรไฟล์
สู่ภาชนะที่ทำสิ่งเล็กๆ น้อยๆ echo hi
จะต้องได้รับอนุญาตดังต่อไปนี้:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"brk",
"capget",
"capset",
"chdir",
"close",
"execve",
"exit_group",
"fstat",
"fstatfs",
"futex",
"getdents64",
"getppid",
"lstat",
"mprotect",
"nanosleep",
"newfstatat",
"openat",
"prctl",
"read",
"rt_sigaction",
"statfs",
"setgid",
"setgroups",
"setuid",
"stat",
"uname",
"write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
...แทนที่จะเป็นสิ่งเหล่านี้:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"brk",
"close",
"execve",
"exit_group",
"futex",
"mprotect",
"nanosleep",
"stat",
"write"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
(
แต่ขอย้ำอีกครั้งว่าเหตุใดจึงเกิดปัญหานี้ โดยส่วนตัวแล้ว ฉันจะหลีกเลี่ยงการไวท์ลิสต์การโทรของระบบต่อไปนี้ (เว้นแต่มีความจำเป็นจริงๆ): capset
, set_tid_address
, setgid
, setgroups
и setuid
. อย่างไรก็ตาม ความท้าทายที่แท้จริงคือการอนุญาตให้กระบวนการที่คุณไม่สามารถควบคุมได้โดยสิ้นเชิง ถือเป็นการผูกโปรไฟล์เข้ากับการใช้งานรันไทม์ของคอนเทนเนอร์ กล่าวอีกนัยหนึ่ง วันหนึ่งคุณอาจพบว่าหลังจากอัปเดตสภาพแวดล้อมรันไทม์ของคอนเทนเนอร์ (โดยคุณหรือผู้ให้บริการคลาวด์) คอนเทนเนอร์ก็หยุดทำงานกะทันหัน
เคล็ดลับ # 1: รันคอนเทนเนอร์ด้วย AllowPrivilegeEscaltion=false
. วิธีนี้จะลดขนาดของโปรไฟล์ seccomp และทำให้โปรไฟล์เหล่านี้ไวต่อการเปลี่ยนแปลงในสภาพแวดล้อมรันไทม์ของคอนเทนเนอร์น้อยลง
2. การตั้งค่าโปรไฟล์ seccomp ในระดับคอนเทนเนอร์
โปรไฟล์ seccomp สามารถตั้งค่าได้ที่ระดับพ็อด:
annotations:
seccomp.security.alpha.kubernetes.io/pod: "localhost/profile.json"
...หรือที่ระดับคอนเทนเนอร์:
annotations:
container.security.alpha.kubernetes.io/<container-name>: "localhost/profile.json"
โปรดทราบว่าไวยากรณ์ข้างต้นจะเปลี่ยนไปเมื่อ Kubernetes seccomp
มีเพียงไม่กี่คนที่รู้ว่า Kubernetes มีมาโดยตลอด
ปัญหาคือว่าคอนเทนเนอร์นี้จะเริ่มต้นด้วยเสมอ AllowPrivilegeEscalation=true
นำไปสู่ปัญหาที่กล่าวไว้ในวรรค 1 และสิ่งนี้ไม่สามารถเปลี่ยนแปลงได้
ด้วยการใช้โปรไฟล์ seccomp ที่ระดับคอนเทนเนอร์ คุณจะหลีกเลี่ยงข้อผิดพลาดนี้และสามารถสร้างโปรไฟล์ที่ปรับให้เหมาะกับคอนเทนเนอร์เฉพาะได้ ซึ่งจะต้องทำจนกว่านักพัฒนาจะแก้ไขข้อบกพร่องและเวอร์ชันใหม่ (อาจเป็น 1.18?) จะพร้อมใช้งานสำหรับทุกคน
เคล็ดลับ # 2: ตั้งค่าโปรไฟล์ seccomp ที่ระดับคอนเทนเนอร์
ในทางปฏิบัติ กฎนี้มักจะทำหน้าที่เป็นคำตอบสากลสำหรับคำถาม: “เหตุใดโปรไฟล์ seccomp ของฉันจึงใช้งานได้กับ docker run
แต่ใช้งานไม่ได้หลังจากปรับใช้กับคลัสเตอร์ Kubernetes ใช่ไหม
3. ใช้รันไทม์/ค่าเริ่มต้นเป็นทางเลือกสุดท้ายเท่านั้น
Kubernetes มีสองตัวเลือกสำหรับโปรไฟล์ในตัว: runtime/default
и docker/default
. ทั้งสองใช้งานโดยรันไทม์ของคอนเทนเนอร์ ไม่ใช่ Kubernetes ดังนั้นอาจแตกต่างกันไปขึ้นอยู่กับสภาพแวดล้อมรันไทม์ที่ใช้และเวอร์ชัน
กล่าวอีกนัยหนึ่ง เนื่องจากการเปลี่ยนแปลงรันไทม์ คอนเทนเนอร์อาจมีสิทธิ์เข้าถึงชุดการเรียกระบบอื่น ซึ่งคอนเทนเนอร์อาจใช้หรือไม่ใช้ก็ได้ รันไทม์ส่วนใหญ่ใช้
รายละเอียด docker/default
เลิกใช้แล้วตั้งแต่ Kubernetes 1.11 ดังนั้นควรหลีกเลี่ยงการใช้
ในความคิดของฉันโปรไฟล์ runtime/default
เหมาะอย่างยิ่งสำหรับวัตถุประสงค์ที่สร้างขึ้น: การปกป้องผู้ใช้จากความเสี่ยงที่เกี่ยวข้องกับการดำเนินการคำสั่ง docker run
บนรถของพวกเขา อย่างไรก็ตาม เมื่อพูดถึงแอปพลิเคชันทางธุรกิจที่ทำงานบนคลัสเตอร์ Kubernetes ฉันกล้าโต้แย้งว่าโปรไฟล์ดังกล่าวเปิดกว้างเกินไป และนักพัฒนาควรมุ่งเน้นที่การสร้างโปรไฟล์สำหรับแอปพลิเคชันของตน (หรือประเภทของแอปพลิเคชัน)
เคล็ดลับ # 3: สร้างโปรไฟล์ seccomp สำหรับแอปพลิเคชันเฉพาะ หากเป็นไปไม่ได้ ให้สร้างโปรไฟล์สำหรับประเภทแอปพลิเคชัน เช่น สร้างโปรไฟล์ขั้นสูงที่รวม Web API ทั้งหมดของแอปพลิเคชัน Golang ใช้รันไทม์/ค่าเริ่มต้นเป็นทางเลือกสุดท้ายเท่านั้น
ในโพสต์ต่อๆ ไป ฉันจะพูดถึงวิธีสร้างโปรไฟล์ seccomp ที่ได้รับแรงบันดาลใจจาก SecDevOps ทำให้โปรไฟล์เป็นแบบอัตโนมัติ และทดสอบในไปป์ไลน์ กล่าวอีกนัยหนึ่ง คุณจะไม่มีข้อแก้ตัวที่จะไม่อัปเกรดเป็นโปรไฟล์เฉพาะแอปพลิเคชัน
4. การไม่จำกัดไม่ใช่ตัวเลือก
ของ PodSecurityPolicy
ซึ่งจะเปิดใช้งานในคลัสเตอร์ พ็อดทั้งหมดที่ไม่ได้กำหนดโปรไฟล์ seccomp จะใช้งานได้ seccomp=unconfined
.
การทำงานในโหมดนี้หมายความว่าฉนวนที่ปกป้องคลัสเตอร์หายไปทั้งชั้น ผู้เชี่ยวชาญด้านความปลอดภัยไม่แนะนำแนวทางนี้
เคล็ดลับ # 4: ไม่ควรเรียกใช้คอนเทนเนอร์ในคลัสเตอร์ seccomp=unconfined
โดยเฉพาะในสภาพแวดล้อมการผลิต
5. "โหมดการตรวจสอบ"
ประเด็นนี้ไม่ได้เกิดขึ้นเฉพาะกับ Kubernetes แต่ยังอยู่ในหมวดหมู่ "สิ่งที่ควรรู้ก่อนเริ่ม"
เมื่อมันเกิดขึ้น การสร้างโปรไฟล์ seccomp เป็นสิ่งที่ท้าทายมาโดยตลอดและต้องอาศัยการลองผิดลองถูกเป็นอย่างมาก ความจริงก็คือผู้ใช้ไม่มีโอกาสทดสอบในสภาพแวดล้อมการใช้งานจริงโดยไม่เสี่ยงที่จะ "ทิ้ง" แอปพลิเคชัน
หลังจากการเปิดตัวเคอร์เนล Linux 4.14 คุณสามารถเรียกใช้บางส่วนของโปรไฟล์ในโหมดตรวจสอบ โดยบันทึกข้อมูลเกี่ยวกับการเรียกของระบบทั้งหมดใน syslog แต่ไม่มีการบล็อก คุณสามารถเปิดใช้งานโหมดนี้ได้โดยใช้พารามิเตอร์ SCMT_ACT_LOG
:
SCMP_ACT_LOG: seccomp จะไม่ส่งผลกระทบต่อเธรดที่ทำการเรียกของระบบ หากเธรดนั้นไม่ตรงกับกฎใด ๆ ในตัวกรอง แต่ข้อมูลเกี่ยวกับการเรียกของระบบจะถูกบันทึกไว้
ต่อไปนี้เป็นกลยุทธ์ทั่วไปสำหรับการใช้คุณลักษณะนี้:
- อนุญาตการโทรของระบบที่จำเป็น
- บล็อกการโทรจากระบบที่คุณรู้ว่าจะไม่มีประโยชน์
- บันทึกข้อมูลเกี่ยวกับการโทรอื่นๆ ทั้งหมดในบันทึก
ตัวอย่างง่ายๆ มีลักษณะดังนี้:
{
"defaultAction": "SCMP_ACT_LOG",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
"names": [
"arch_prctl",
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [
"add_key",
"keyctl",
"ptrace"
],
"action": "SCMP_ACT_ERRNO"
}
]
}
แต่โปรดจำไว้ว่าคุณต้องบล็อกการโทรทั้งหมดที่คุณรู้ว่าจะไม่ถูกใช้และอาจเป็นอันตรายต่อคลัสเตอร์ พื้นฐานที่ดีในการรวบรวมรายชื่อคือเป็นทางการ
อย่างไรก็ตามมีสิ่งหนึ่งที่จับได้ แม้ว่า SCMT_ACT_LOG
รองรับโดยเคอร์เนล Linux ตั้งแต่ปลายปี 2017 และเข้าสู่ระบบนิเวศ Kubernetes เพียงไม่นานนี้ ดังนั้นหากต้องการใช้วิธีนี้คุณจะต้องมีเคอร์เนล Linux 4.14 และเวอร์ชัน runC ไม่ต่ำกว่า
เคล็ดลับ # 5: สามารถสร้างโปรไฟล์โหมดการตรวจสอบสำหรับการทดสอบในการใช้งานจริงได้โดยการรวมรายการขาวดำ และสามารถบันทึกข้อยกเว้นทั้งหมดได้
6. ใช้รายการที่อนุญาต
การไวท์ลิสต์ต้องใช้ความพยายามเพิ่มเติม เนื่องจากคุณต้องระบุทุกการโทรที่แอปพลิเคชันอาจต้องการ แต่วิธีนี้ช่วยปรับปรุงความปลอดภัยได้อย่างมาก:
ขอแนะนำอย่างยิ่งให้ใช้วิธีการไวท์ลิสต์เนื่องจากง่ายกว่าและเชื่อถือได้มากกว่า บัญชีดำจะต้องได้รับการอัปเดตทุกครั้งที่มีการเรียกระบบที่อาจเป็นอันตราย (หรือธง/ตัวเลือกที่เป็นอันตรายหากอยู่ในบัญชีดำ) นอกจากนี้ มักจะเป็นไปได้ที่จะเปลี่ยนการแสดงพารามิเตอร์โดยไม่ต้องเปลี่ยนสาระสำคัญ และด้วยเหตุนี้จึงสามารถข้ามข้อจำกัดของบัญชีดำได้
สำหรับแอปพลิเคชัน Go ฉันพัฒนาเครื่องมือพิเศษที่มาพร้อมกับแอปพลิเคชันและรวบรวมการโทรทั้งหมดที่เกิดขึ้นระหว่างการดำเนินการ ตัวอย่างเช่น สำหรับแอปพลิเคชันต่อไปนี้:
package main
import "fmt"
func main() {
fmt.Println("test")
}
...เปิดตัวเลย gosystract
เช่นนี้
go install https://github.com/pjbgf/gosystract
gosystract --template='{{- range . }}{{printf ""%s",n" .Name}}{{- end}}' application-path
... และเราจะได้ผลลัพธ์ดังนี้:
"sched_yield",
"futex",
"write",
"mmap",
"exit_group",
"madvise",
"rt_sigprocmask",
"getpid",
"gettid",
"tgkill",
"rt_sigaction",
"read",
"getpgrp",
"arch_prctl",
สำหรับตอนนี้ นี่เป็นเพียงตัวอย่าง รายละเอียดเพิ่มเติมเกี่ยวกับเครื่องมือจะตามมา
เคล็ดลับ # 6: อนุญาตเฉพาะสายที่คุณต้องการจริงๆ และบล็อกสายอื่นๆ ทั้งหมด
7. วางรากฐานที่ถูกต้อง (หรือเตรียมพร้อมสำหรับพฤติกรรมที่ไม่คาดคิด)
เคอร์เนลจะบังคับใช้โปรไฟล์ไม่ว่าคุณจะเขียนอะไรลงไปก็ตาม ถึงแม้จะไม่ใช่สิ่งที่คุณต้องการก็ตาม เช่น หากคุณบล็อกการเข้าถึงการโทรเช่น exit
หรือ exit_group
คอนเทนเนอร์จะไม่สามารถปิดระบบได้อย่างถูกต้องและแม้แต่คำสั่งง่ายๆเช่น echo hi
ในกรณีเช่นนี้ยูทิลิตี้สามารถช่วยได้ strace
- มันจะแสดงว่าปัญหาคืออะไร:
sudo strace -c -p 9331
ตรวจสอบให้แน่ใจว่าส่วนกำหนดค่ามีการเรียกของระบบทั้งหมดที่แอปพลิเคชันต้องการในขณะรันไทม์
เคล็ดลับ # 7: ใส่ใจในรายละเอียดและตรวจสอบให้แน่ใจว่าการเรียกระบบที่จำเป็นทั้งหมดอยู่ในรายการที่อนุญาต
นี่เป็นการสรุปส่วนแรกของชุดบทความเกี่ยวกับการใช้ seccomp ใน Kubernetes ตามจิตวิญญาณของ SecDevOps ในส่วนต่อไปนี้ เราจะพูดถึงสาเหตุที่สิ่งนี้สำคัญ และวิธีทำให้กระบวนการเป็นแบบอัตโนมัติ
ปล.จากผู้แปล
อ่านเพิ่มเติมในบล็อกของเรา:
- «
ความปลอดภัยสำหรับคอนเทนเนอร์ Docker "; - «
เครื่องมือรักษาความปลอดภัย 33+ Kubernetes "; - «
Docker และ Kubernetes ในสภาพแวดล้อมที่คำนึงถึงความปลอดภัย "; - «
9 แนวทางปฏิบัติที่ดีที่สุดสำหรับความปลอดภัยของ Kubernetes '
ที่มา: will.com