NixOS และค่าลับ
(isabelroses.com)- หากเก็บค่าลับไว้เป็นข้อความล้วนในคอนฟิก Nix, รีโพซิทอรี Git แบบส่วนตัว หรือ
git-cryptบน NixOS จะมีความเสี่ยงที่ Nix store ซึ่งทุกคนอ่านได้ทำให้ผู้ที่เข้าถึงเครื่องสามารถอ่านค่าลับได้ - sops-nix เข้ารหัสไฟล์ค่าลับด้วยกฎใน
.sops.yamlและเวิร์กโฟลว์การแก้ไขของsopsจากนั้นตอน activate จะถอดรหัสด้วยคีย์ SSH ของโฮสต์และวางข้อความล้วนไว้บน tmpfs ที่/run/secrets/<name> - agenix ระบุ public key ผู้รับของค่าลับแต่ละรายการใน
secrets.nixและถอดรหัสไฟล์.ageลงบน tmpfs ที่/run/agenix/<name>โดยต้อง rekey เมื่อมีโฮสต์ใหม่หรือมีการเปลี่ยนคีย์ - วิธีวางค่าลับไว้ในไฟล์ระบบโดยตรงใช้ได้กับเซิร์ฟเวอร์เดี่ยวหรือแล็ปท็อป แต่ถ้าอ่านตอน evaluation เช่น
builtins.readFile "/var/lib/myservice/token"ค่านั้นจะเข้าไปอยู่ใน Nix store จึงควรหลีกเลี่ยง - ถ้าเพิ่งเริ่มต้นและมีโทเคนอิสระเพียงไม่กี่ตัว agenix จะมีขั้นตอนน้อยกว่า แต่ถ้าเป็นโฮสต์อย่างเมลเซิร์ฟเวอร์ที่มีค่าลับเกี่ยวข้องกันจำนวนมาก sops-nix ที่รวมหลายค่าไว้ในไฟล์เดียวจะเหมาะกว่า
ความเสี่ยงพื้นฐานเมื่อจัดการค่าลับบน NixOS
- การจัดการค่าลับบน NixOS แบ่งได้เป็น
sops-nix,agenix/ragenix, การใช้ไฟล์ระบบ, รีโพซิทอรี Git แบบส่วนตัว,git-cryptและการเขียนลงในคอนฟิก Nix โดยตรง - ไม่ควรใช้รีโพซิทอรี Git แบบส่วนตัว,
git-cryptหรือการเขียนลงในคอนฟิก Nix โดยตรง หากมีการใช้เครื่องร่วมกันหรือมีแผนจะเปิดเผยคอนฟิก- Nix store เปิดให้อ่านได้โดยทุกคน ดังนั้นผู้ที่มีสิทธิ์เข้าถึงเครื่องจะอ่านค่าลับได้
- ความเสี่ยงนี้สำคัญเป็นพิเศษในช่วงที่มีช่องโหว่อย่าง CVE-2026-31431(copyfail) และ CVE-2026-43284 และ CVE-2026-43500(dirtyfrag)
- เคยมีกรณีที่ค่าลับรั่วไหลจากคอนฟิกที่เผยแพร่แล้วอย่างน้อยสองครั้ง โดยยังมีตัวอย่างอยู่ที่ 1, 2
sops-nix
- sops-nix อาจรู้สึกว่าตั้งค่ายากในครั้งแรกที่ใช้งาน แต่เอกสารถูกปรับปรุงขึ้นมาก และการที่
sopsรองรับการเข้ารหัสและถอดรหัสค่าลับด้วยคีย์ SSH โดยตรงก็เป็นพัฒนาการสำคัญ - อย่างไรก็ตาม
sops-nixยังตามไม่ทันการรองรับคีย์ SSH นี้ โดยมีงานที่เกี่ยวข้องอยู่ใน sops-nix#779, sops-nix#922 - เวิร์กโฟลว์การใช้งานคือสร้างกฎเข้ารหัสและถอดรหัสใน
.sops.yamlแล้วใช้คำสั่งsopsเพื่อแก้ไขไฟล์ค่าลับ- ตัวอย่างเช่น กำหนด SSH public key เป็นผู้รับ
ageสำหรับพาธsecrets/*.yaml - เมื่อรัน
sops secrets/shush.yamlโปรแกรมจะแก้ไขไฟล์ด้วย editor ที่เลือกไว้ และสามารถใส่ค่า YAML อย่างhello: sopsได้ - เมื่อออกจาก editor ค่าเหล่านั้นจะถูกเข้ารหัสเป็นรูปแบบ
ENC[AES256_GCM,...]และกลับมาแก้ไขซ้ำได้ด้วยคำสั่งเดิม
- ตัวอย่างเช่น กำหนด SSH public key เป็นผู้รับ
- ในคอนฟิก NixOS โมดูล
sops-nixจะจัดการงานส่วนใหญ่ให้- ใช้
defaultSopsFile = ./secrets/shush.yaml;เพื่อกำหนดไฟล์ค่าลับเริ่มต้น - ใช้
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];เพื่อระบุคีย์ SSH ของโฮสต์ - กำหนดสิทธิ์ไฟล์ใน
secrets."hello"ด้วยowner,group,mode
- ใช้
- ตอน activate
sops-nixจะถอดรหัสไฟล์ด้วยคีย์ SSH ของโฮสต์และวางข้อความล้วนไว้ที่/run/secrets/<name>- พาธนี้อยู่บน tmpfs จึงทำให้ค่าลับไม่แตะดิสก์
- บริการที่ต้องใช้ค่านั้นเพียงอ่านจากพาธดังกล่าว
- ฟีเจอร์ template มีประโยชน์กับคอนฟิกร่วมกันหรือคอนฟิกที่ผู้ใช้อื่นต้องอ้างอิง
- หากไฟล์คอนฟิกของบริการต้องการทั้งข้อความล้วนและค่าลับบางส่วน ก็ไม่จำเป็นต้องเข้ารหัสทั้งไฟล์
- ตัวอย่างเช่น เก็บ
SMTP_USER=isabelไว้เป็นข้อความล้วน และใส่ placeholder ของค่าลับเป็นSMTP_PASSWORD=${config.sops.placeholder."mailserver/smtp_password"}
agenix
- agenix ต่างจาก
sops-nixตรงที่ตั้งค่าค่าลับและคีย์ที่เข้าถึงได้ในsecrets.nixจึงให้ความรู้สึกที่ใกล้กับ Nix มากกว่า - ใน
secrets.nixจะ bind SSH public key ของผู้ใช้และโฮสต์ แล้วกำหนดว่าไฟล์.ageแต่ละไฟล์ให้ public key ใดเข้าถึงได้บ้าง- เช่น
"secret1.age".publicKeys = [ isabel host1 ];,"secret2.age".publicKeys = [ isabel host2 ];ทำให้กำหนดรายชื่อผู้รับของแต่ละค่าลับต่างกันได้
- เช่น
- ต้องสร้างไฟล์ค่าลับด้วย CLI ของ
agenix- ใช้คำสั่ง
agenix -e secret1.ageเพื่อสร้างหรือกลับมาแก้ไขsecret1.ageภายหลัง
- ใช้คำสั่ง
- วิธีเชื่อมเข้ากับคอนฟิกของโฮสต์คล้ายกับ
sops-nixแต่เนื่องจากค่าลับแต่ละรายการเป็นไฟล์แยกกัน จึงมีพื้นผิวที่เล็กกว่า- ใช้
age.secrets.secret1.file = ./secrets/secret1.age;เพื่อระบุไฟล์ - ใช้
owner,group,modeเพื่อกำหนดเจ้าของและสิทธิ์
- ใช้
- ตอนบูต ระบบจะถอดรหัสไฟล์
.ageแต่ละไฟล์ด้วยคีย์ SSH ของโฮสต์แล้ววางไว้ที่/run/agenix/<name>- พาธนี้ก็อยู่บน tmpfs เช่นกัน
- จุดที่มักสะดุดบ่อยที่สุดคือ rekeying
- เมื่อเพิ่มโฮสต์ใหม่หรือเปลี่ยนคีย์ ต้องเข้ารหัสค่าลับใหม่ทุกตัวที่มีรายการ
publicKeysเปลี่ยนไปในsecrets.nix agenix --rekeyช่วยจัดการเรื่องนี้ แต่ต้องมี private key ของผู้รับปัจจุบันอย่างน้อยหนึ่งรายเพื่ออ่าน ciphertext เดิม- ในการใช้งานจริง การ rekey มักเกิดบนเครื่องที่เชื่อถือได้มากที่สุด ไม่ใช่โฮสต์ใหม่ที่กำลังจะนำขึ้นระบบ
- เมื่อเพิ่มโฮสต์ใหม่หรือเปลี่ยนคีย์ ต้องเข้ารหัสค่าลับใหม่ทุกตัวที่มีรายการ
วิธีใช้ไฟล์ระบบอย่างเดียว
- การวางค่าลับไว้ในไฟล์ระบบโดยตรงมีต้นทุนคือคอนฟิกจะไม่สามารถอธิบายเครื่องได้ครบถ้วนอีกต่อไป
- เมื่อติดตั้งใหม่ ต้องนำไฟล์ทั้งหมดกลับไปวางในตำแหน่งและสิทธิ์ที่ถูกต้องอีกครั้ง
- ในงานกู้ระบบ เช่น การกู้เซิร์ฟเวอร์ตอนตีสอง เรื่องนี้อาจกลายเป็นหายนะครั้งใหญ่
- แพตเทิร์นที่ควรหลีกเลี่ยงคือรูปแบบอย่าง
builtins.readFile "/var/lib/myservice/token"- วิธีนี้อ่านไฟล์ตั้งแต่ตอน evaluation และนำเนื้อหาเข้าไปไว้ใน Nix store
- เนื่องจาก Nix store เปิดให้อ่านได้โดยทุกคน จึงกลายเป็นปัญหาแบบเดียวกับที่เตือนไว้ตั้งแต่ต้นทุกประการ
- สิ่งที่ควรทำแทนคือส่งพาธให้บริการ แทนการส่งค่าจริง
- ตัวอย่างเช่น ใช้ออปชันอย่าง services.*.environmentFiles
- สำหรับเซิร์ฟเวอร์เดี่ยวหรือแล็ปท็อปอาจพอรับได้ แต่ถ้าเป็น fleet หรือเป็นสภาพแวดล้อมที่ต้องการสร้างใหม่ทั้งหมดจากคอนฟิกเพียงอย่างเดียว
sops-nixหรือagenixจะเหมาะกว่า - ถ้าแต่ละเครื่องมีค่าเพียงหนึ่งหรือสองตัวที่ไม่ควรอยู่ในรีโพซิทอรีจริง ๆ ก็อาจใช้ได้ แต่ภาระในการนำกลับมาใส่อีกครั้งในอนาคตจะตกกับตัวเองในวันหน้า
เปรียบเทียบ sops-nix กับ agenix
- เหตุผลหลักในการเลือก
sops-nixคือสามารถรวมข้อมูลให้มากที่สุดเท่าที่ทำได้ไว้ในไฟล์เดียว- หากเป็นกรณีอย่างเมลเซิร์ฟเวอร์ที่มีค่าลับเกี่ยวข้องกันจำนวนมาก ก็สามารถเก็บค่าลับได้มากกว่าในไฟล์เดียว แทนที่จะแยกเป็นราว 5 ไฟล์แบบ
agenix - เป็นเครื่องมือที่ทรงพลังและเหมาะกับการใช้งานต่อเนื่อง โดยเฉพาะโฮสต์อย่างเมลเซิร์ฟเวอร์ที่มีค่าลับจำนวนมากซึ่งควรพิจารณาเป็นตัวเลือกแรก
- หากเป็นกรณีอย่างเมลเซิร์ฟเวอร์ที่มีค่าลับเกี่ยวข้องกันจำนวนมาก ก็สามารถเก็บค่าลับได้มากกว่าในไฟล์เดียว แทนที่จะแยกเป็นราว 5 ไฟล์แบบ
agenixเด่นในเรื่อง ความเรียบง่าย- ไม่มี YAML schema ที่ต้องเรียนรู้
- ไม่มี
.sops.yamlที่ต้องซิงก์ secrets.nixเป็น Nix ล้วน จึงใช้การ bind แบบlet ... inเดียวกับที่ใช้กับโฮสต์และผู้ใช้กับคีย์ได้ตรง ๆ- แบบจำลองความคิดคือ “ค่าลับหนึ่งรายการ, หนึ่งไฟล์, หนึ่งรายการผู้รับ” ซึ่งเข้ากันได้ดีกับรูปแบบการควบคุมการเข้าถึง
- เพราะมีชิ้นส่วนที่ต้องขยับน้อยที่สุดแต่ยังให้การควบคุมการเข้าถึงคีย์ตามโฮสต์ จึงเป็นตัวเลือกที่แนะนำได้ง่ายสำหรับคนที่เพิ่งถามเรื่องการจัดการค่าลับบนเครื่อง NixOS
- ทั้งสองเครื่องมือแก้ปัญหาเดียวกันได้ และความต่างในตอนนี้ส่วนใหญ่เป็นเรื่อง การใช้งาน
- หากเพิ่งเริ่มต้นและมีหลายบริการที่ต้องใช้ชุดค่าลับที่เกี่ยวข้องกัน
sops-nixจะขยายต่อได้ดีกว่า - หากเพิ่งเริ่มต้นและส่วนใหญ่มีแค่โทเคนอิสระไม่กี่ตัว
agenixจะพาไปถึงเป้าหมายได้ด้วยขั้นตอนที่น้อยกว่า - หากกำลังจะเลือกเครื่องมือจัดการค่าลับตัวแรก การเริ่มจาก
agenixเพื่อให้คุ้นกับเวิร์กโฟลว์ก่อน แล้วค่อยย้ายไปsops-nixเมื่อเริ่มรู้สึกถึงความไม่สะดวกของแนวทาง “ค่าลับหนึ่งรายการต่อหนึ่งไฟล์” จะเป็นทางเลือกที่ดีกว่า
- หากเพิ่งเริ่มต้นและมีหลายบริการที่ต้องใช้ชุดค่าลับที่เกี่ยวข้องกัน
- มีการแก้ไขข้อมูลเกี่ยวกับการทนทานต่อควอนตัมแล้ว
1 ความคิดเห็น
ความเห็นจาก Lobste.rs
ซีเคร็ตที่เข้ารหัสกับคีย์ของมันไม่ได้อยู่บนดิสก์ทั้งคู่อยู่แล้วหรือ? หรือว่าอย่างใดอย่างหนึ่งถูกเก็บไว้ในที่อย่าง TPM?
เพิ่งเริ่มใช้ Nix ได้ไม่นาน และในการดีพลอยแบบ self-hosting ขนาดเล็กก็ใช้วิธีง่าย ๆ คือใส่ไฟล์ลง filesystem ด้วย
scpลองค้นเพิ่มนิดหน่อยก็เหมือนจะสามารถป้องกัน SSH private key ด้วย TPM ได้ และก็สงสัยว่าทำใน VM ได้ไหม แต่พอเห็นว่าอาจมีการรองรับ vTPM ก็เหมือนตอบคำถามตัวเองได้แล้ว
ฝั่งเซิร์ฟเวอร์ยังมีทางเข้าถึงแบบ NixOps ด้วย ดูวิธีที่ Colmena ทำได้ที่นี่: https://colmena.cli.rs/0.4/features/keys.html
แก่นสำคัญคือให้เครื่องที่เชื่อถือได้และมีซีเคร็ตเป็นคน push ไปยังเซิร์ฟเวอร์ระยะไกล คล้ายกับที่ตอนนี้คุณทำด้วย
scpแต่ทำให้กระบวนการนั้นเป็นสไตล์ Nix มากขึ้นช่วงไม่กี่คืนที่ผ่านมาเพิ่งใช้เวลาตั้งค่าแนว agenix บน system flake ของตัวเองใหม่ เลยพูดได้เฉพาะเรื่อง agenix สำหรับคนที่สนใจ ฉันเลือก agenix-rekey เพราะไม่ต้องมีไฟล์
.ymlที่เก็บซีเคร็ต และสามารถตั้งค่าซีเคร็ตทั้งหมดไว้ใน Nix ได้เหมือนที่ทำไว้อยู่แล้วซีเคร็ตที่เข้ารหัสจะอยู่ใน Nix store และเหมือนกับอย่างอื่นใน Nix store คืออ่านได้ทั่วระบบ ส่วน SSH private key ที่ใช้ถอดรหัสซีเคร็ตนั้น โดยทั่วไปก็คือ private key ของ SSH server จริง ๆ และจะเลือก ไม่ใช้แบบนั้น ก็ได้ ซีเคร็ตจะถูกถอดรหัสตอน activation ซึ่งก็ประมาณช่วงบูต แล้วนำไปวางไว้ที่
/run/agenix/<user>เครื่องมือชื่อ secrix ไปไกลกว่านั้นอีก โดยผูกซีเคร็ตเข้ากับบริการ systemd แล้วผูกบริการนั้นกลับเข้ากับบริการที่ต้องใช้ซีเคร็ต ดังนั้นซีเคร็ตจะถูกถอดรหัสเฉพาะตอนที่บริการนั้นกำลังรันอยู่เท่านั้น แต่ในทางปฏิบัติ ผู้ใช้ NixOS ไม่ค่อยเปิดปิดบริการบ่อย ๆ เลย ทำให้ส่วนใหญ่ลงเอยที่บริการรันตลอดอยู่ดี อาจเหมาะกับซีเคร็ตที่ใช้ตอน activation ของระบบ เช่น การสร้างผู้ใช้
agenix-rekey ทำให้การ rekey น่ารำคาญน้อยลง และแทนที่
secrets.nixด้วย flake output มันใช้ YubiKey split identity เป็นครึ่งหนึ่งของคีย์ การ rekey คือยืนยันตัวตนด้วยคีย์นั้นและอีกครึ่งหนึ่งเพื่อถอดรหัสซีเคร็ต แล้วจึงเข้ารหัสคีย์ใหม่ด้วย SSH public key ของเซิร์ฟเวอร์ Public key ถูกสร้างขึ้นตอนติดตั้งระบบ และฉันใช้ nixos-anywhere พร้อม--copy-host-keysเพื่อดึงคีย์ที่สร้างใน installation closure มา ซีเคร็ตที่เข้ารหัสจะเก็บไว้ในรีโป แต่เก็บไว้ในซับโมดูลแยกที่เข้าถึงได้เฉพาะ trusted builderอีกอย่าง vTPM โดยมากไม่ได้อิงฮาร์ดแวร์ และผู้ให้บริการหลายเจ้าถึงจะมี TPM ให้ก็มักเป็นแค่ TPM v1.2 ไม่ใช่ TPM v2 ซึ่งผู้ให้บริการของฉัน BinaryLane ก็เป็นแบบนั้น มันเพิ่มชั้นความปลอดภัยขึ้นมาอีกชั้นก็จริง แต่ไม่ใช่ HSM วิเศษแบบ TPM จริง และไม่สามารถป้องกันการถูกเจาะจากผู้ให้บริการหรือโฮสต์โนดได้ ถ้าผู้ให้บริการจะรองรับ vTPM ที่อิง HSM จริง ต้นทุนก็น่าจะสูงมาก และ AWS ก็ดูเหมือนจะคิดราคาแบบ AWS
agenix+agenix-rekey+age-plugin-1pmaster key อยู่ใน 1Password ดังนั้นฉันจึงแก้ไข ดู หรือ rekey รหัสผ่านของเซิร์ฟเวอร์ไหนก็ได้โดยไม่ต้องเก็บ credential ใด ๆ ไว้บนดิสก์โน้ตบุ๊ก
คุณสามารถให้สิทธิ์เข้าถึงกับเซิร์ฟเวอร์ที่ต้องการเข้าถึงคีย์ตอนรันได้ บอก agenix-rekey ว่าเซิร์ฟเวอร์นั้นเห็นคีย์อะไรได้บ้าง แล้วรัน
agenix rekeyจากนั้นคีย์เวอร์ชันที่เข้ารหัสและเซิร์ฟเวอร์นั้นถอดรหัสได้จะถูกบันทึกลงใน Nix store ส่วน SSH private key ของเซิร์ฟเวอร์นั้นอยู่แค่บนเซิร์ฟเวอร์นั้นและไม่ออกไปข้างนอกเลย agenix-rekey ต้องใช้แค่ public key ในการ rekeyดังนั้นกรณีที่ซีเคร็ตรั่วก็คือบัญชี 1Password ของฉันถูกแฮ็ก หรือไม่ก็เซิร์ฟเวอร์ที่ใช้ซีเคร็ตนั้นถูกแฮ็ก
/etc/ssh/ssh_host_ed25519_keyแล้วใส่ลงใน ramfs ที่ mount ไว้ที่/run/agenix.dดังนั้นก็ใช่เลย ทั้งข้อมูลที่เข้ารหัส คีย์เข้ารหัส และข้อมูลที่ถอดรหัสแล้ว ล้วนเข้าถึงได้จาก filesystem
https://github.com/oddlama/agenix-rekey
อีกอย่าง credential ที่อยู่ยาว ๆ ก็มีน้อยลงเรื่อย ๆ ด้วย ตอนนี้กำลังค่อย ๆ เลิก การคัดลอก credential ระยะยาว แล้วหันไปใช้ credential แบบอิงฮาร์ดแวร์ ซึ่งอาจใช้โดยตรงหรือเอาไปแลกเป็น credential อายุสั้นแทน