Cloudflare ดูแล Secret ของเราอย่างไร
คนที่มีหน้าที่ดูแลระบบจัดการกุญแจ ไม่จำเป็นต้องรู้ว่ากุญแจนั้นมีค่าอะไร
หนึ่งในเรื่องที่ผมให้ความสำคัญมากเวลาออกแบบระบบ คือการจัดการ Secret ไม่ว่าจะเป็น Database Password, API Key, JWT Signing Key หรือ Private Key สิ่งเหล่านี้เปรียบเสมือนกุญแจที่ใช้เปิดประตูเข้าไปยังส่วนสำคัญของระบบ
โดยปกติเรามักเก็บ Secret ไว้ใน Environment Variable หรือไฟล์ .env บน Server ซึ่งก็เป็นวิธีที่ใช้งานได้ แต่มีข้อจำกัดสำคัญอย่างหนึ่งคือ ถ้าวันหนึ่ง Server ถูกยึดครอง คนที่เข้าถึงเครื่องได้ก็อาจเข้าถึง Secret ทั้งหมดที่เราฝากไว้ในนั้นได้เช่นกัน ผมจึงเริ่มสนใจแนวคิดที่ว่า ถ้าเราไม่จำเป็นต้องเก็บ Secret ไว้บน Server ของเราเลยจะเป็นอย่างไร
ช่วงนี้ผมกำลังศึกษา Cloudflare Workers และไปเจอกับบริการที่ชื่อว่า Cloudflare Secrets Store ซึ่งเป็นระบบจัดเก็บ Secret ระดับ Account สามารถจัดเก็บไว้ส่วนกลางและนำไปใช้ซ้ำกับบริการต่าง ๆ ภายใน Account ได้ โดยไม่ต้องผูกกับ Worker ตัวใดตัวหนึ่งตั้งแต่แรก ปัจจุบันยังอยู่ในสถานะ Open Beta แต่สิ่งที่ทำให้ผมสนใจเป็นพิเศษไม่ใช่แค่การที่ Cloudflare เข้ารหัสข้อมูลให้เรา แต่เป็นวิธีที่เขาออกแบบระบบจัดการกุญแจเหล่านี้ด้วย
เข้ารหัสข้อมูล แล้วยังเข้ารหัสกุญแจอีกชั้นหนึ่ง
รายละเอียดเรื่องการจัดการกุญแจไม่ได้อยู่ในหน้า Documentation หลักของ Secrets Store แต่อยู่ใน บล็อกเปิดตัวเมื่อวันที่ 9 เมษายน 2025 ซึ่งอธิบายถึงสิ่งที่เรียกว่า Two-level Key Hierarchy
เมื่อเราส่ง Secret เข้าไปจัดเก็บ Cloudflare จะเข้ารหัสข้อมูลก่อนบันทึกลงฐานข้อมูล โดยใช้ Data Encryption Key (DEK) สำหรับเข้ารหัสค่า Secret จริง ๆ จากนั้นจึงใช้ Key Encryption Key (KEK) เข้ารหัสตัว DEK อีกชั้นหนึ่ง ส่วน Root Key จะถูกเก็บรักษาอยู่ภายในระบบที่ปลอดภัยและไม่ถูกนำออกมาใช้งานภายนอก พูดง่าย ๆ คือ กุญแจที่ใช้เข้ารหัสข้อมูลก็ถูกปกป้องด้วยกุญแจอีกชั้นหนึ่งด้วย ดังนั้นแม้จะมีใครเข้าถึงข้อมูลที่จัดเก็บอยู่ใน Storage ได้ทั้งหมด ก็ไม่ได้หมายความว่าจะสามารถอ่านข้อความ Secret ได้ทันที
ในบล็อกเดียวกันยังอธิบายว่า Cloudflare มีการ Refresh DEK อยู่บ่อย ๆ เพื่อลดขอบเขตความเสียหายที่อาจเกิดขึ้นหาก DEK ตัวใดตัวหนึ่งรั่วไหล ส่วนการหมุนเวียน KEK เป็นระยะและการแยก DEK ตาม Account ของลูกค้า ยังถูกพูดถึงในฐานะแผนงานในอนาคต ไม่ใช่ความสามารถที่เปิดใช้งานอยู่แล้วในขณะนั้น
คนดูแลระบบ ไม่จำเป็นต้องอ่านกุญแจได้
อีกเรื่องที่ผมชอบคือ Cloudflare แยกสิทธิ์ในการจัดการ Secret ออกจากสิทธิ์ในการนำ Secret ไปใช้งาน ตัวอย่างเช่น เราอาจมี Secret อยู่ส่วนกลางหลายตัว ทั้ง DATABASE_URL, JWT_PRIVATE_KEY, STRIPE_SECRET_KEY และ POSTMARK_API_KEY ซึ่ง Worker หลายตัวภายใน Account อาจต้องใช้งาน Secret เหล่านี้ร่วมกัน
ตาม เอกสาร Access Control การนำ Secret ไปใช้งานต้องผ่านเงื่อนไขสองอย่างพร้อมกัน คือสิทธิ์ของผู้ใช้งานหรือ API Token ที่เป็นผู้ร้องขอ และ Scope ของ Secret ที่กำหนดว่าอนุญาตให้บริการประเภทใดนำไปใช้งานได้ ปัจจุบัน Scope ที่รองรับมี workers และ ai-gateway ซึ่งหมายถึงการอนุญาตให้ผูก Secret เข้ากับ Worker หรือ AI Gateway ตามลำดับ หาก Scope ไม่อนุญาตให้บริการนั้นใช้งาน การผูก Secret จะถูกปฏิเสธ แม้ผู้ร้องขอจะมี Role ที่ถูกต้องก็ตาม
ส่วน Role บน Dashboard ก็แบ่งหน้าที่ออกเป็นหลายระดับ เพื่อไม่ให้คนที่มีหน้าที่จัดการ Secret จำเป็นต้องมีสิทธิ์นำ Secret ไปผูกกับบริการอื่นด้วย โดยแบ่งเป็น 4 ระดับ ได้แก่
- Secrets Store Admin สามารถสร้าง แก้ไข ทำซ้ำ ลบ และดู Metadata ของ Secret ได้ แต่ไม่มีสิทธิ์ผูก Secret เข้ากับ Worker
- Secrets Store Deployer สามารถดู Metadata และนำ Secret ไปผูกกับ Worker หรือ AI Gateway ได้ แต่ไม่มีสิทธิ์สร้าง แก้ไข หรือลบ Secret
- Secrets Store Reporter สามารถดูได้เฉพาะ Metadata เท่านั้น
- Super Administrator สามารถจัดการ Secret และผูกเข้ากับบริการต่าง ๆ ได้ทั้งหมด
จุดสำคัญคือการมีสิทธิ์ดู Metadata ไม่ได้หมายความว่าจะสามารถอ่านข้อความ Secret ได้ด้วย สิทธิ์ API แบบ Read จะเข้าถึงได้เพียงข้อมูลอย่างชื่อ, ID, Scope และ Comment เท่านั้น ไม่สามารถอ่านค่า Secret หรือผูก Secret เข้ากับ Resource อื่นได้
นอกจากนี้ เอกสารการจัดการ Secret ยังบอกว่า หลังจากสร้าง Secret แล้ว จะไม่สามารถย้อนกลับไปอ่านหรือถอดรหัสค่าเดิมผ่าน API หรือ Dashboard ได้อีก มีเพียงบริการที่ได้รับอนุญาตและผูกกับ Secret นั้นเท่านั้นที่สามารถเข้าถึงค่าได้ในระหว่างการทำงาน ส่วน เอกสาร Workers Integration ก็เขียนไว้ชัดเจนว่าเมื่อบันทึก Secret แล้ว ค่าจะไม่แสดงให้ดูอีก
ใน บล็อกเปิดตัว Cloudflare ยังบอกด้วยว่าหลังจากสร้าง Secret แล้ว ไม่ว่าจะเป็น Developer, Administrator หรือแม้แต่พนักงานของ Cloudflare เอง ก็ไม่สามารถกลับมาอ่านค่า Secret นั้นได้ มีเพียงบริการที่ได้รับอนุญาตเท่านั้นที่สามารถนำค่าไปใช้งานในขณะที่ระบบกำลังทำงานอยู่
ผมว่าการออกแบบแบบนี้น่าสนใจมาก เพราะคนที่มีหน้าที่ดูแลระบบจัดการกุญแจ ไม่จำเป็นต้องรู้ว่ากุญแจนั้นมีค่าอะไร
อย่างไรก็ตาม มีรายละเอียดอีกอย่างที่ต้องระวัง เพราะสิทธิ์ของ Role บน Dashboard กับสิทธิ์ของ API Token ไม่ได้แบ่งเหมือนกันทั้งหมด โดยสิทธิ์ Edit ของ API Token ครอบคลุมทั้งการสร้าง แก้ไข ลบ และการผูก Secret เข้ากับ Worker ซึ่งไม่ได้แยกเป็นคนละสิทธิ์เหมือน Secrets Store Admin และ Deployer บน Dashboard ดังนั้นหาก CI/CD ต้อง Deploy Worker ที่ผูกกับ Secrets Store ไว้ API Token ที่ใช้ก็ต้องมีสิทธิ์ Edit ไม่ใช่แค่ Read เท่านั้น
แต่ Secret ก็ยังมีโอกาสรั่วไหลได้
ถึงแม้ Secret จะถูกเข้ารหัสและจัดเก็บอย่างปลอดภัย แต่ Worker ที่ได้รับอนุญาตให้ใช้งาน Secret ก็ยังสามารถอ่านค่า Secret นั้นได้ตามวัตถุประสงค์ เพราะ เอกสาร Workers Integration เปิดให้เรียกใช้ get() ผ่าน Binding แล้วนำค่าที่ได้ไปใช้งานในโค้ดได้ เช่น การนำไปใส่ใน Header ตอนเรียก API ภายนอก นั่นหมายความว่าในระหว่างที่ Worker ทำงาน ค่า Secret ในรูปแบบ Plaintext จะอยู่ในหน่วยความจำและอยู่ภายใต้การควบคุมของโค้ดที่กำลังทำงาน ไม่ได้อยู่เฉพาะในระบบจัดเก็บที่เข้ารหัสอีกต่อไป
ถ้ามีใครสามารถแทรกโค้ดอันตรายเข้าไปใน Worker ได้ เขาก็อาจเขียนโค้ดเพื่อส่ง Secret ออกไปยัง Server ของตัวเองได้เช่นกัน เรื่องนี้เป็นความเสี่ยงที่ผมเคยจดไว้ในบทความ เมื่อ Server ถูกเจาะ Secret ที่เก็บไว้จะปลอดภัยแค่ไหน และการใช้ Secrets Store ก็ไม่ได้ทำให้ความเสี่ยงในส่วนนี้หายไป
ดังนั้นการป้องกันไม่ได้จบแค่การเลือกใช้ Secret Manager แต่ต้องดูแล Git Repository, CI/CD, Deployment Token และสิทธิ์ในการ Deploy ควบคู่กันไปด้วย โดยเฉพาะการแยกสิทธิ์ระหว่าง Developer ที่มีหน้าที่พัฒนา Worker กับผู้ที่มีสิทธิ์ Deploy ขึ้น Production เพราะหากใครก็ตามสามารถเปลี่ยนโค้ดและ Deploy ขึ้น Production ได้โดยไม่มีข้อจำกัด ระบบจัดการ Secret ที่ออกแบบมาดีแค่ไหนก็อาจถูกใช้เป็นช่องทางในการขโมยข้อมูลได้อยู่ดี
จาก Secret ของแต่ละ Worker มาเป็น Secret ส่วนกลาง
ก่อนหน้านี้ Cloudflare มี Workers Secrets อยู่แล้ว ซึ่งเป็นระบบจัดเก็บ Secret แยกตาม Worker โดยค่าที่ตั้งไว้จะไม่แสดงให้ดูอีกหลังจากบันทึก ทั้งใน Wrangler และ Dashboard แต่ข้อจำกัดคือ Secret แต่ละตัวจะผูกอยู่กับ Worker ตัวนั้น ไม่สามารถนำไปใช้ร่วมกับ Worker ตัวอื่นได้โดยตรง
ใน บล็อกเปิดตัว Secrets Store Cloudflare เล่าถึงที่มาของปัญหานี้ไว้ว่า Workers Secrets มีมาตั้งแต่ปี 2020 และเมื่อระบบเริ่มมี Worker หลายตัว ก็เกิดปัญหาการสร้าง Secret ซ้ำกันในหลาย Script เพราะแต่ละตัวเข้าถึงได้เฉพาะ Secret ของตัวเอง เมื่อถึงเวลาต้องหมุนเวียนหรือเปลี่ยนค่า Secret ก็ต้องตามไปแก้ไขในหลายจุด ทำให้ไม่มีแหล่งข้อมูลกลางที่เป็น Single Source of Truth
Secrets Store จึงเข้ามาเพิ่มความสามารถในการจัดเก็บ Secret ระดับ Account ทำให้หลาย Worker สามารถนำ Secret ชุดเดียวกันไปใช้งานได้ โดยไม่ต้องสร้างซ้ำในแต่ละโปรเจกต์ สำหรับระบบที่มี Worker หลายตัว หรือมีหลายบริการที่ต้องใช้ API Key ร่วมกัน วิธีนี้ช่วยลดความซ้ำซ้อนและทำให้การจัดการ Secret เป็นระบบมากขึ้น
ปัจจุบัน Secrets Store ยังอยู่ในสถานะ Open Beta และมีข้อจำกัดบางอย่าง โดย เอกสารการจัดการ Secret บอกว่าในช่วง Open Beta สามารถสร้างได้หนึ่ง Store ต่อหนึ่ง Account และจัดเก็บได้ไม่เกิน 100 Secret ต่อ Account ส่วนบริการที่รองรับการนำ Secret ไปใช้งานในปัจจุบันมี Workers และ AI Gateway ตามที่อธิบายไว้ใน หน้า Overview โดย Cloudflare มีแผนที่จะเพิ่มการรองรับบริการอื่น ๆ ในอนาคต