นาฬิกาไม่ตรงและข้อขัดข้องผ่านพร็อกซี: TLS, JWT, ลายเซ็นคำขอ และการวินิจฉัยในหนึ่งนาที
บทความ
- บทนำ: "เมื่อวานยังใช้ได้ แต่วันนี้ใบรับรองไม่ถูกต้อง"
- พื้นฐาน: เวลาสำคัญตรงไหน และทำไมโปรโตคอลถึงสนใจนาฬิกา
- เจาะลึก: นาฬิกาของใครสำคัญในห่วงโซ่ไคลเอนต์ — พร็อกซี — เซิร์ฟเวอร์
- ข้อผิดพลาดมีหน้าตาอย่างไรในไคลเอนต์ต่างๆ: ข้อความที่ต้องจำได้จากบรรทัดแรก
- ทำไมนาฬิกาถึงเพี้ยน: เวอร์ชวลไลเซชัน คอนเทนเนอร์ ไม่มีเดมอน แก้ด้วยมือ
- การวินิจฉัยในหนึ่งนาที: เปรียบเทียบกับแหล่งภายนอกและช่วงที่ยอมรับได้
- การตั้งค่าการซิงค์: systemd-timesyncd และ chrony และวิธีมั่นใจว่ามันทำงานจริง
- คอนเทนเนอร์และนาฬิกา: อะไรสืบทอดจากโฮสต์ อะไรไม่
- ข้อผิดพลาดทั่วไปและเคสจากประสบการณ์จริง
- เครื่องมือและเช็คลิสต์ "ก่อนจะโทษพร็อกซี"
- คำถามที่พบบ่อย: เวลา พร็อกซี และความล้มเหลว
- บทสรุป: เวลาคือรายการแรกในการวินิจฉัย
บทนำ: "เมื่อวานยังใช้ได้ แต่วันนี้ใบรับรองไม่ถูกต้อง"
มีประเภทของเหตุการณ์ที่สามารถพรางตัวได้ดีกว่าเรื่องอื่นๆ สคริปต์ที่วิ่งผ่านพร็อกซีไปยัง API หลายสิบแห่งเป็นเวลาหลายเดือน จู่ๆ ก็เริ่มล้มเหลวในทุกคำขอ ในล็อกมีข้อความว่า certificate has expired หรือ certificate is not yet valid วิศวกรเปิดใบรับรองของเว็บไซต์เป้าหมายในเบราว์เซอร์บนแล็ปท็อปของตัวเอง พบว่าอายุการใช้งานยังปกติ ห่วงโซ่ความเชื่อมั่นเขียวดี เขาเริ่มรันสคริปต์ใหม่โดยไม่ใช้พร็อกซีจากแล็ปท็อป ปรากฏว่าใช้งานได้ กลับไปใช้พร็อกซี ก็ล้มเหลวอีก ข้อสรุปที่ตามมาจึงดูเหมือนชัดเจน: พร็อกซีคือต้นเหตุ
และข้อสรุปนี้เกือบทุกครั้งผิด ปัญหาไม่ใช่เพราะพร็อกซี "สลับใบรับรอง" หรือ "ทำลาย TLS" แต่เป็นเพราะสคริปต์รันบนเครื่องอื่น — บนเซิร์ฟเวอร์ ใน VM หรือในคอนเทนเนอร์ — และบนเครื่องนั้นนาฬิกาของระบบคลาดเคลื่อนไป ไม่กี่นาที ไม่กี่ชั่วโมง หรือไม่กี่วัน พร็อกซีกลายเป็นเพียงตัวแปรที่สังเกตได้ง่ายที่สุดในสมการ และรับผลกระทบไปเต็มๆ
เราจะเจาะลึกหัวข้อนี้อย่างครบถ้วน คุณจะได้รู้ว่าโปรโตคอลสมัยใหม่พึ่งพาเวลาปัจจุบันตรงจุดไหนบ้าง และทำไมการเลื่อนของนาฬิกาไปคนละทิศถึงให้อาการต่างกัน คุณจะได้เห็นข้อความข้อผิดพลาดที่เจาะจงใน curl, Python และ Node.js เพื่อให้จำปัญหาได้จากบรรทัดแรกของเทรซ คุณจะเข้าใจว่าทำไมนาฬิกาถึงเพี้ยนบนเครื่องเสมือนและในคอนเทนเนอร์ และทำไมการที่เดมอนซิงค์เวลาติดตั้งอยู่แล้วไม่ได้การันตีว่ามันทำงาน คุณจะได้ขั้นตอนวินิจฉัยในหนึ่งนาที คอนฟิกที่ใช้งานได้จริงของ chrony และ systemd-timesyncd และเช็คลิสต์ที่ควรทำก่อนจะเขียนไปหาผู้ให้บริการพร็อกซี
ขอบเขตที่ชัดเจนตั้งแต่ต้น บทความนี้ไม่เกี่ยวกับการออกใบรับรอง ไม่เกี่ยวกับห่วงโซ่ความเชื่อมั่น และไม่เกี่ยวกับที่เก็บรากใบรับรอง เราพิจารณาสาเหตุเดียวของความล้มเหลว — เวลาระบบไม่ถูกต้อง — และทุกสิ่งที่ตามมาจากการทำงานผ่านพร็อกซี หัวข้อนี้แคบ แต่เป็นหัวข้อที่เผาเวลาการดีบักไปเป็นชั่วโมงสำหรับทีมที่ทำงานด้านการเชื่อมต่อ การมอนิเตอร์ การเก็บข้อมูลเปิด และการทำงานอัตโนมัติผ่านโครงสร้างพื้นฐานพร็อกซีอย่าง Proxeon
พื้นฐาน: เวลาสำคัญตรงไหน และทำไมโปรโตคอลถึงสนใจนาฬิกา
เริ่มจากคำถามง่ายๆ ทำไมโปรโตคอลเครือข่ายต้องรู้ว่าตอนนี้กี่โมง? คำตอบมีเพียงหนึ่งเดียว: เพื่อจำกัดอายุของความเชื่อมั่น ความลับใดๆ — ใบรับรอง โทเคน ลายเซ็น รหัสใช้ครั้งเดียว — ย่อมรั่วไหลได้ไม่วันใดก็วันหนึ่ง ถ้ามันมีวันหมดอายุ ความเสียหายจากการรั่วไหลก็ถูกจำกัด แต่การจำกัดอายุจะทำงานได้ก็ต่อเมื่อฝ่ายที่ตรวจสอบรู้เวลาปัจจุบัน และมันดึงเวลาจากที่เดียวเท่านั้น — จากนาฬิกาของเครื่องที่รันการตรวจสอบนั้น
ผลลัพธ์พื้นฐานที่ตามมาคือ การตรวจสอบเวลาทุกครั้งเป็นแบบท้องถิ่น เซิร์ฟเวอร์ไม่ถามไคลเอนต์ว่าตอนนี้กี่โมง ไคลเอนต์ไม่ถามเซิร์ฟเวอร์ แต่ละฝ่ายดูนาฬิกาของตัวเองแล้วสรุป ถ้านาฬิกาของสองฝ่ายต่างกัน พวกมันเห็นความจริงคนละแบบ และฝ่ายหนึ่งจะมองอีกฝ่ายว่าเป็นผู้ละเมิด
การตรวจสอบอายุใบรับรองใน TLS
ใบรับรอง X.509 ทุกใบมีสองฟิลด์: notBefore (มีผลตั้งแต่) และ notAfter (มีผลถึง) ในระหว่างการจับมือ TLS ไคลเอนต์จะเปรียบเทียบเวลาปัจจุบันบนนาฬิกาของตัวเองกับช่วงเวลานี้ เวลาที่เซิร์ฟเวอร์ออกใบรับรองไม่เกี่ยวข้องเลย สิ่งที่สำคัญคือไคลเอนต์คิดว่าตอนนี้กี่โมง
ทิศทางของการเลื่อนเป็นตัวกำหนดอาการ ถ้านาฬิกาไคลเอนต์ ช้ากว่า ใบรับรองที่เพิ่งออกใหม่จะดูเหมือนยังไม่ถูกออก: ข้อผิดพลาด "certificate is not yet valid" ถ้านาฬิกาไคลเอนต์ เร็วกว่า ใบรับรองจะดูเหมือนหมดอายุแล้ว: "certificate has expired" นี่คือสัญญาณวินิจฉัยแรกที่ควรจดจำ
ทำไมหัวข้อนี้ถึงร้อนแรงตอนนี้? เพราะใบรับรองมีอายุสั้นลงเรื่อยๆ ศูนย์ออกใบรับรองสาธารณะหลายแห่งออกใบรับรองอายุ 90 วัน มีรุ่นอายุ 6 วัน และข้อกำหนดในอุตสาหกรรมกำลังลดอายุสูงสุดลงอย่างเป็นระบบ: ตั้งแต่มีนาคม 2026 — 200 วัน จากนั้น 100 วัน และภายในปี 2029 — 47 วัน เมื่อก่อนนาฬิกาที่ช้าไปสองสัปดาห์แทบไม่เคยเจอใบรับรองใหม่ แต่ตอนนี้เว็บเป้าหมายอาจออกใบรับรองใหม่ทุกไม่กี่วัน และเครื่องที่ช้าไปสามวันจะปฏิเสธมันว่า "ยังไม่ถูกต้อง"
กลไกอื่นที่อยู่ในกลุ่มเดียวกัน ได้แก่ คำตอบ OCSP และรายการเพิกถอนที่มีหน้าต่างอายุของตัวเอง ตราประทับ Certificate Transparency ที่มีเวลากำกับ และตั๋วเซสชัน TLS 1.3 ที่มีอายุจำกัด ทั้งหมดถูกตรวจสอบโดยนาฬิกาท้องถิ่น
JWT: ฟิลด์ exp, nbf และ iat
JSON Web Token เป็นรูปแบบโทเคนเข้าถึงที่ใช้กันแพร่หลายที่สุดใน API ภายในตัว它有ฟิลด์เวลาแบบ Unix epoch สามฟิลด์: iat (ออกเมื่อไหร่), nbf (not before — ใช้ได้ตั้งแต่เมื่อไหร่) และ exp (หมดอายุเมื่อไหร่) ฝ่ายที่รับโทเคนจะเปรียบเทียบกับนาฬิกาของตัวเอง โดยปกติมีค่าผ่อนผัน (leeway) เล็กน้อย 30-60 วินาที
ตรงนี้เกิดความไม่สมมาตรที่น่าสนใจ ปกติเซิร์ฟเวอร์ออกโทเคนตามนาฬิกาของตัวเอง และไคลเอนต์แคชมันไว้จนถึง exp — แต่ตามนาฬิกาของตัวเอง ถ้าไคลเอนต์ช้ากว่าห้านาที มันจะคิดว่าโทเคนยังใช้ได้อีกห้านาทีหลังจากที่เซิร์ฟเวอร์เริ่มปฏิเสธแล้ว คุณจะเจอ 401 ที่อธิบายไม่ได้ตอนปลายอายุของทุกโทเคน แต่ถ้าไคลเอนต์เซ็น JWT เอง — เช่นสำหรับยืนยันตัวตนบัญชีบริการ — และนาฬิกาเร็วกว่า เซิร์ฟเวอร์จะเห็น iat อยู่ในอนาคตและปฏิเสธโทเคนว่าใช้ไม่ได้
ลายเซ็นคำขอที่มีการประทับเวลา
API หลายแห่งกำหนดให้เซ็นคำขอด้วย HMAC โดยรวมเวลาปัจจุบันไว้ในสตริงที่เซ็น ตัวอย่างที่รู้จักกันดีที่สุดคือ Signature Version 4 ที่ใช้ในที่เก็บออบเจกต์ที่เข้ากันได้กับ S3 เซิร์ฟเวอร์จะรับคำขอถ้าเวลาที่ประทับต่างจากนาฬิกาเซิร์ฟเวอร์ไม่เกิน 15 นาที มิฉะนั้นจะคืนข้อผิดพลาด RequestTimeTooSkewed เกตเวย์ชำระเงิน API ตลาดหลักทรัพย์และโบรกเกอร์ และบัสข้อมูลองค์กรใช้รูปแบบคล้ายกันโดยมีหน้าต่างตั้งแต่ 30 วินาทีถึงไม่กี่นาที การยืนยันตัวตน Kerberos ในเครือข่ายองค์กรโดยค่าเริ่มต้นยอมให้คลาดเคลื่อนได้ 5 นาที
ให้ความสนใจตรงนี้: ลายเซ็นถูกสร้างโดยไคลเอนต์ของคุณตามนาฬิกาของตัวเอง พร็อกซีไม่มีส่วนในกระบวนการนี้ทางกายภาพ ทำได้แค่ส่งไบต์ต่อ ดังนั้นข้อผิดพลาด "การประทับเวลาในลายเซ็นไม่ถูกต้อง" ผ่านพร็อกซีจึงเป็นคำถามถึงนาฬิกาของเครื่องคุณเสมอ
รหัสใช้ครั้งเดียว TOTP
รหัสยืนยันสองปัจจัยตามมาตรฐาน TOTP คำนวณจากความลับร่วมและหมายเลขช่วง 30 วินาที ฝ่ายตรวจสอบมักยอมให้บวกลบหนึ่งช่วง ถ้านาฬิกาของคุณเพี้ยนไปหนึ่งนาที รหัสจะไม่ถูกยอมรับเลย นี่ใช้กับอินทิเกรชันของเราเองด้วย ที่บัญชีบริการสร้าง TOTP ด้วยโปรแกรมเพื่อเข้าสู่พอร์ทัลองค์กร
ผู้ใช้เวลาที่ไม่ชัดเจนนัก
- แคช HTTP เฮดเดอร์ Date, Expires และ max-age ถูกเปรียบเทียบกับนาฬิกาท้องถิ่น เมื่อนาฬิกาเพี้ยน แคชก็ไม่ทำงาน หรือเสิร์ฟข้อมูลเก่า
- Cookies ที่มี Expires ไคลเอนต์ที่นาฬิกาช้าจะเก็บมันนานกว่า ไคลเอนต์ที่นาฬิกาเร็วจะทิ้งมันทันที
- HSTS และ pinning คำสั่ง max-age ก็ถูกนับตามนาฬิกาท้องถิ่นเช่นกัน
- การหมุนเวียนใบรับรองภายในคลัสเตอร์ คอมโพเนนต์ของออร์เคสเตรเตอร์ออกใบรับรองใหม่ตามกำหนดและเมื่อนาฬิกาบนโหนดเพี้ยน พวกมันก็หยุดเชื่อมั่นกันเอง
เจาะลึก: นาฬิกาของใครสำคัญในห่วงโซ่ไคลเอนต์ — พร็อกซี — เซิร์ฟเวอร์
ทีนี้ลองวางภาพนี้บนโทโพโลยีจริง เมื่อทำงานผ่านพร็อกซี ในห่วงโซ่มีอย่างน้อยสามเครื่องที่มีชุดนาฬิกาสามชุด: ไคลเอนต์ของคุณ เซิร์ฟเวอร์พร็อกซี และเซิร์ฟเวอร์เป้าหมาย คำถามวินิจฉัยสำคัญคือ: การตรวจสอบที่ล้มเหลวเกิดขึ้นบนเครื่องไหน?
HTTP CONNECT และ SOCKS5: พร็อกซีไม่เห็น TLS
เมื่อขอทรัพยากร HTTPS ผ่าน HTTP-พร็อกซี ไคลเอนต์ส่งคำสั่ง CONNECT พร้อมชื่อโฮสต์และพอร์ต พร็อกซีสร้างการเชื่อมต่อ TCP กับเป้าหมายแล้วส่งไบต์ต่อทั้งสองทางแบบตาบอด การจับมือ TLS เกิดขึ้นผ่านพร็อกซี ระหว่างไคลเอนต์ของคุณกับเซิร์ฟเวอร์เป้าหมาย พร็อกซีไม่เห็นใบรับรอง เปลี่ยนแปลงมันไม่ได้ และไม่มีส่วนในการตรวจสอบ เช่นเดียวกันกับ SOCKS5
ข้อสรุปเชิงปฏิบัติ: ข้อผิดพลาด "certificate is not yet valid" หรือ "certificate has expired" ผ่านทันเนลโปร่งใสไม่สามารถเกิดจากพร็อกซีได้ทางกายภาพ ต้นตอคือ นาฬิกาของเครื่องคุณ หรือที่เก็บรากใบรับรองที่เชื่อถือบนเครื่องนั้น หรือใบรับรองที่หมดอายุจริงบนปลายทาง สองอย่างแรกเป็นปัญหาท้องถิ่น และอย่างแรกตรวจได้ในสิบวินาที
เมื่อมีการตรวจสอบนาฬิกามากขึ้น: TLS ถึงพร็อกซีเอง
ถ้าคุณเชื่อมต่อกับพร็อกซีผ่านช่องทางที่ป้องกัน (รูปแบบ https:// สำหรับตัวพร็อกซี) ไคลเอนต์จะตรวจสอบสองใบรับรอง: ใบรับรองของพร็อกซีและใบรับรองของเว็บเป้าหมาย การตรวจสอบทั้งสองทำตามนาฬิกาของไคลเอนต์ อาการเหมือนกัน แต่ในเอาต์พุตแบบละเอียดของ curl จะเห็นว่าล้มเหลวในขั้นตอนไหน — "Proxy certificate" หรือ "Server certificate"
อะไรขึ้นกับนาฬิกาของพร็อกซีเอง
นาฬิกาของพร็อกซีมีผลต่อของจำกัด: ความถูกต้องของล็อกตัวเอง การทำงานของ TLS listener (เช่น การรับคำตอบ OCSP) และการตรวจสอบข้อมูลยืนยันตัวตนถ้าผู้ให้บริการใช้โทเคนที่มีอายุจำกัด โครงสร้างพื้นฐานของ Proxeon ซิงค์เวลาบนโหนดอย่างเป็นปกติ และในเหตุการณ์ส่วนใหญ่ นาฬิกาของพร็อกซีไม่เกี่ยวข้อง เพราะมันไม่มีส่วนในการตรวจใบรับรองปลายทาง ไม่มีส่วนสร้างลายเซ็นของคุณ และไม่มีส่วนตรวจสอบ JWT ของคุณ ทั้งหมดนี้ไคลเอนต์และเซิร์ฟเวอร์เป้าหมายเป็นคนทำ
นาฬิกาของเซิร์ฟเวอร์เป้าหมาย
บางครั้งนาฬิกาเพี้ยนที่ฝั่งตรงข้าม สัญญาณคือ ข้อผิดพลาดเกิดกับโฮสต์เดียวเท่านั้น แต่โฮสต์อื่นเข้าถึงได้ปกติ และเฮดเดอร์ Date ในคำตอบของโฮสต์นั้นต่างจากเซิร์ฟเวอร์อื่นอย่างเห็นได้ชัด คุณทำอะไรกับมันไม่ได้นอกจากแจ้งเจ้าของ และถ้า API อนุญาต ก็ชดเชยการเลื่อนในคำขอของคุณได้ — ไลบรารีบางตัวคำนวณค่าชดเชยจากเฮดเดอร์ Date ในคำตอบข้อผิดพลาดและเซ็นคำขอถัดไปโดยคำนึงถึงค่าคลาดเคลื่อน
ความหน่วงและ "การหมดอายุในคิว"
พร็อกซีเพิ่มความหน่วง — ตั้งแต่หลายสิบมิลลิวินาทีถึงหลายวินาทีเมื่อเชื่อมต่อใหม่ในช่องทางมือถือและการหมุน IP สำหรับหน้าต่าง 15 นาที นี่น้อยมาก แต่มีกับดักละเอียดในตรรกะการลองซ้ำ ถ้าคุณเซ็นคำขอ ใส่คิว แล้วส่งหลายนาทีต่อมาหลังชุดการลองซ้ำที่มีการรอ เวลาที่ประทับในลายเซ็นจะหมดอายุ ผ่านช่องทางที่ช้าหรือแออัด สิ่งนี้เกิดบ่อยขึ้น กฎคือ: เซ็นทันทีก่อนส่งและเซ็นใหม่ทุกครั้งที่ลองซ้ำ อย่าใช้ออบเจกต์ที่เซ็นแล้วซ้ำ
การเลื่อน: ขนาดและทิศทาง
รวบรวมความสัมพันธ์ระหว่างอาการกับทิศทางของการเลื่อนในรายการเดียว — มีประโยชน์เวลาอ่านล็อก:
- นาฬิกาไคลเอนต์ช้ากว่า: "certificate is not yet valid", JWT ที่มีข้อผิดพลาด nbf หรือ iat เมื่อเซ็นเอง, โทเคนที่แคชไว้ดูเหมือนยังใช้ได้แต่เซิร์ฟเวอร์ตอบ 401 "expired", ลายเซ็นถูกปฏิเสธว่าเก่าเกินไป, TOTP ไม่ถูกยอมรับ
- นาฬิกาไคลเอนต์เร็วกว่า: "certificate has expired", JWT ของตัวเองถูกปฏิเสธว่า iat อยู่ในอนาคต, ลายเซ็นถูกปฏิเสธว่า "มาจากอนาคต", โทเคนถูกล้างจากแคชก่อนเวลา, TOTP ไม่ถูกยอมรับ
- การเลื่อนน้อยกว่าหนึ่งวินาที: ไม่มีกลไกใดที่กล่าวมาสังเกตเห็น
- การเลื่อน 30 วินาที — 5 นาที: TOTP พัง, JWT ที่ leeway น้อยพัง, รูปแบบ HMAC ที่เข้มงวดพัง
- การเลื่อนมากกว่า 15 นาที: ลายเซ็นคำขอและ Kerberos เกือบทั้งหมดพัง
- การเลื่อนเป็นวัน: การเชื่อมต่อ TLS ไปยังเว็บที่มีใบรับรองอายุสั้นเริ่มล้มเหลว
ข้อผิดพลาดมีหน้าตาอย่างไรในไคลเอนต์ต่างๆ: ข้อความที่ต้องจำได้จากบรรทัดแรก
ความสามารถในการจำปัญหาจากข้อความข้อผิดพลาดประหยัดเวลามากกว่าวิธีการใดๆ ด้านล่างเป็นถ้อยคำลักษณะเฉพาะของไคลเอนต์ยอดนิยม แสดงตามที่ไลบรารีที่ใช้ OpenSSL ข้างใต้พิมพ์ออกมา หมายเลขบรรทัดในวงเล็บจะต่างกันไปตามเวอร์ชัน
curl
ข้อความพื้นฐานเมื่อเวลาไม่ถูกต้อง:
curl: (60) SSL certificate problem: certificate is not yet validcurl: (60) SSL certificate problem: certificate has expiredรหัส 60 คือ CURLE_PEER_FAILED_VERIFICATION ใช้ร่วมกับปัญหาการตรวจสอบใบรับรองทุกประเภท แยกสาเหตุเวลา จากปัญหาที่เก็บรากได้เฉพาะจากข้อความหลังเครื่องหมายโคลอน ด้วยแฟล็ก -v จะมีบรรทัดที่มีประโยชน์พร้อมรหัสตัวเลขของการตรวจสอบ OpenSSL: 9 ตรงกับ "not yet valid", 10 ตรงกับ "has expired":
* SSL certificate verify result: certificate is not yet valid (9), continuing anyway.บรรทัด "continuing anyway" จะปรากฏเฉพาะเมื่อใช้แฟล็ก -k ถ้าไม่ใช้ การเชื่อมต่อจะถูกตัด บน Windows ที่ใช้แบ็กเอนด์ Schannel ข้อความต่างออกไป:
curl: (60) schannel: SEC_E_CERT_EXPIRED (0x80090328) - The received certificate has expired.ตัวอย่างการตรวจสอบแบบเต็มผ่านพร็อกซีพร้อมเอาต์พุตละเอียด เพื่อดูทั้งขั้นตอน (พร็อกซีหรือเป้าหมาย) และผลการตรวจสอบ:
curl -v --proxy http://LOGIN:PASSWORD@PROXY_HOST:PORT https://example.com/ 2>&1 | grep -iE 'verify|expire|valid|date'Python: ssl, requests, httpx, aiohttp
โมดูลมาตรฐาน ssl โยนข้อยกเว้นที่มีส่วนต่อท้ายบอกชัด:
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate is not yet valid (_ssl.c:1010)ไลบรารี requests ห่อมันใน SSLError และเพิ่มส่วนห่อของพูลการเชื่อมต่อ ทำให้คำสำคัญไปอยู่ท้ายสุดของสตริงยาว:
requests.exceptions.SSLError: HTTPSConnectionPool(host='example.com', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1010)')))รายละเอียดสำคัญเมื่อทำงานผ่านพร็อกซี: ในทันเนล CONNECT ข้อผิดพลาดการตรวจสอบใบรับรองเป้าหมายมาในรูป SSLError ไม่ใช่ ProxyError ProxyError เกิดเมื่อเชื่อมต่อกับพร็อกซีเองไม่ได้ ดังนั้นประเภทข้อยกเว้นก็บอกแล้วว่าควรมองหาที่ไหน
httpx ใช้ถ้อยคำสั้นกว่า:
httpx.ConnectError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate has expired (_ssl.c:1010)aiohttp ใช้คลาสของตัวเอง:
aiohttp.client_exceptions.ClientConnectorCertificateError: Cannot connect to host example.com:443 ssl:True [SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: certificate is not yet valid (_ssl.c:1010)')]ข้อผิดพลาด JWT ใน PyJWT เมื่อตรวจสอบโทเคนฝั่งคุณ:
jwt.exceptions.ExpiredSignatureError: Signature has expiredjwt.exceptions.ImmatureSignatureError: The token is not yet valid (nbf)และนี่คือตัวอย่างที่ทำซ้ำได้ผ่านพร็อกซีที่ควรมีติดตัวไว้เป็นเทสต์เคส:
import requests, ssl, sys
PROXY = "http://LOGIN:PASSWORD@PROXY_HOST:PORT"
try:
r = requests.get("https://example.com/", proxies={"https": PROXY}, timeout=15)
print(r.status_code, r.headers.get("Date"))
except requests.exceptions.SSLError as e:
msg = str(e)
if "not yet valid" in msg or "has expired" in msg:
print("ดูเหมือนนาฬิกาของระบบคลาดเคลื่อน ตรวจสอบ date -u")
print(msg)อธิบายเล็กน้อย: อักขระขึ้นบรรทัดใหม่ในตัวอย่างนี้และต่อไปนี้แสดงเป็น HTML entity เพื่อให้มาร์กอัปอยู่ในบรรทัดเดียว เมื่อคัดลอกไปยังเอดิเตอร์จะกลายเป็นขึ้นบรรทัดใหม่ปกติ
Node.js: https, fetch, undici
Node พิมพ์ข้อความสั้นและให้ฟิลด์ code มาด้วย ซึ่งมีค่า:
Error: certificate is not yet valid
code: 'CERT_NOT_YET_VALID'Error: certificate has expired
code: 'CERT_HAS_EXPIRED'กับดักของ fetch ในตัว: ภายนอกคุณเห็นแค่ TypeError: fetch failed แต่สาเหตุจริงซ่อนอยู่ในพร็อพเพอร์ตี cause ล็อกมันอย่างชัดเจน:
try {
const res = await fetch("https://example.com/");
console.log(res.status, res.headers.get("date"));
} catch (e) {
console.error(e.message, e.cause && e.cause.code, e.cause && e.cause.message);
}สำหรับการเส้นทาง fetch ผ่านพร็อกซีใน Node ใช้ ProxyAgent จากแพ็กเกจ undici ข้อผิดพลาดใบรับรองเป้าหมายจะมาพร้อมรหัสเดียวกัน CERT_HAS_EXPIRED หรือ CERT_NOT_YET_VALID ใน cause สำหรับโมดูล https แบบคลาสสิกและไลบรารีอย่าง axios จะใช้เอเจนต์จากแพ็กเกจ https-proxy-agent รหัสข้อผิดพลาดเดียวกัน เพราะเกิดจาก OpenSSL ตัวเดียวกันใน Node
ไลบรารี jsonwebtoken แยกกรณีเวลาสองกรณีเมื่อตรวจสอบโทเคน:
TokenExpiredError: jwt expired
NotBeforeError: jwt not activeข้อผิดพลาดฝั่งเซิร์ฟเวอร์เมื่อลายเซ็นไม่ตรง
ตรงนี้ข้อความกำหนดโดย API แต่ถ้อยคำทั่วไปจำได้ ตัวอย่างของที่เก็บที่เข้ากันได้กับ S3:
<Code>RequestTimeTooSkewed</Code>
<Message>The difference between the request time and the current time is too large.</Message>ของ OAuth server — เฮดเดอร์ WWW-Authenticate ที่มี error="invalid_token" และคำอธิบายอย่าง "The token expired" หรือ "Token used before issued" ของ API การเงิน — ข้อความเกี่ยวกับ timestamp outside of recvWindow หรือ "Timestamp for this request is outside of the allowed window" ถ้าคุณเห็นวลีใดๆ เหล่านี้ นาฬิกาคือสิ่งแรกที่ต้องตรวจสอบ
ทำไมนาฬิกาถึงเพี้ยน: เวอร์ชวลไลเซชัน คอนเทนเนอร์ ไม่มีเดมอน แก้ด้วยมือ
ออสซิลเลเตอร์คริสตัลบนเมนบอร์ดไม่ใช่นาฬิกามาตรฐาน ความคลาดเคลื่อนทั่วไปอยู่ที่ 10-100 ppm คือดริฟต์ได้ถึง 1-9 วินาทีต่อวันโดยไม่แก้ไข ภายในหนึ่งเดือนโดยไม่ซิงค์ก็สะสมเป็นนาที นี่คือพื้นหลังที่มีในทุกเครื่อง แต่เหตุการณ์หลักไม่ได้เกิดจากดริฟต์ช้าๆ แต่เกิดจากการกระโดด เรามาดูแหล่งที่มากัน
เครื่องเสมือน: หยุดชั่วคราว สแนปชอต การโยกย้าย
เครื่องเสมือนไม่ได้เป็นเจ้าของนาฬิกาจริง มันรับเวลาผ่านแหล่งพาราเวอร์ชวล (kvm-clock, Hyper-V reference time, หรืออื่นๆ ตามไฮเปอร์ไวเซอร์) เมื่อเกสต์ถูกหยุดชั่วคราว บันทึกเป็นสแนปชอต หรือโยกย้ายระหว่างโฮสต์ นาฬิกาภายในจะ "หยุดนิ่ง" หลังจากกลับมาทำงาน เคอร์เนลอาจได้รับการแก้ไขจากไฮเปอร์ไวเซอร์หรือไม่ก็ได้ ขึ้นอยู่กับการตั้งค่าเกสต์เอเจนต์ สถานการณ์คลาสสิก: ทีมย้อนเซิร์ฟเวอร์ทดสอบกลับไปยังสแนปชอตสองสัปดาห์ก่อน และมันตื่นขึ้นมาพร้อมนาฬิกาที่ช้าไป 14 วัน ใบรับรองทั้งหมดที่ออกในช่วงสองสัปดาห์นั้นสำหรับมัน "ยังไม่ถูกต้อง"
ปัญหาอีกอย่างคือความขัดแย้งของแหล่งความจริงสองแหล่ง เกสต์เอเจนต์ของไฮเปอร์ไวเซอร์ดึงเวลาจากโฮสต์เป็นระยะ ในขณะที่เดมอน NTP ของเกสต์ก็ทำงาน ถ้าโฮสต์เองไม่ซิงค์ เกสต์จะได้สัญญาณที่ขัดแย้งกันสองอันและนาฬิกาอาจกระโดดไปมา คำแนะนำของคู่มือเวอร์ชวลไลเซชันส่วนใหญ่คือ: อย่างใดอย่างหนึ่งเท่านั้น — ซิงค์จากไฮเปอร์ไวเซอร์ หรือ NTP ข้างใน แต่ไม่ใช่ทั้งสองพร้อมกัน
แล็ปท็อปนักพัฒนา: สลีปและเดสก์ท็อป VM
Docker Desktop และ WSL2 ทำงานภายในเครื่องเสมือนแบบเบา หลังจากสลีปหรือไฮเบอร์เนตของแล็ปท็อป นาฬิกาของ VM นี้มักช้าไปเท่ากับเวลาที่สลีป — หนึ่งชั่วโมง หนึ่งคืน วันหยุดสุดสัปดาห์ โฮสต์จะดึงเวลาให้ แต่ VM ไม่เสมอ ผลคือ วันจันทร์เช้า สคริปต์ทั้งหมดในคอนเทนเนอร์ล้มบน TLS แต่ในเทอร์มินัลของโฮสต์ทุกอย่างปกติ สถานการณ์นี้เองที่มักสร้างคำร้องว่า "ทำงานได้โดยไม่ใช้พร็อกซีจากแล็ปท็อป แต่ไม่ทำงานผ่านพร็อกซีในคอนเทนเนอร์" — ทั้งที่พร็อกซีในทั้งสองกรณีอาจไม่มีส่วนในการตรวจสอบเลย
คอนเทนเนอร์
คอนเทนเนอร์ไม่มีนาฬิกาจริงของตัวเอง มันใช้ของโฮสต์ เราจะดูเรื่องนี้ในหัวข้อแยก แต่ตอนนี้บันทึกประเด็นหลัก: ถ้าโฮสต์เพี้ยน ทุกคอนเทนเนอร์เพี้ยน และแก้จากในคอนเทนเนอร์ไม่ได้
ไม่มีเดมอนซิงค์หรือเดมอนใช้ไม่ได้
อิมเมจขนาดเล็ก การติดตั้งแบบ "เปลือย" และระบบที่ประกอบเองมักไม่มีไคลเอนต์ NTP เลย และที่ที่มี มันอาจไม่ทำงานด้วยสาเหตุเป็นสิบ: UDP ขาออกพอร์ต 123 ถูกบล็อกโดยไฟร์วอลล์, ชื่อพูลรีโซลฟ์ไม่ได้, บริการติดตั้งอยู่แต่ถูกมาสก์, ติดตั้ง chrony และ timesyncd พร้อมกันแล้วขัดแย้งกัน, การเลื่อนตอนเริ่มต้นเกินขีดที่ ntpd แบบคลาสสิกปฏิเสธแก้ไขและออกจาก เซิร์ฟเวอร์ที่ทางออกเดียวสู่อินเทอร์เน็ตคือผ่าน HTTP-พร็อกซีของเครือข่ายองค์กรจะเปราะเป็นพิเศษ: NTP ผ่าน UDP ผ่านพร็อกซีแบบนี้ไปไม่ได้ และนาฬิกาก็ถูกทิ้งไว้ตามลำพัง
ไม่มี RTC และแบตเตอรี่หมด
คอมพิวเตอร์บอร์ดเดียวอย่าง Raspberry Pi และมินิพีซีหลายรุ่นไม่มีนาฬิกาฮาร์ดแวร์พร้อมแบตเตอรี่ ตอนบูตมันจะเริ่มจาก epoch หรือวันที่บันทึกครั้งล่าสุด และอยู่กับมันไปจนกว่าจะซิงค์สำเร็จครั้งแรก เซิร์ฟเวอร์เก่าๆ แบตเตอรี่ CMOS หมด และทุกครั้งที่ปิด BIOS จะรีเซ็ตวันที่กลับเป็นค่าจากโรงงาน ถ้าการซิงค์ใช้ไม่ได้ เครื่องจะทำงานด้วยวันที่สิบปีก่อน และการเชื่อมต่อ TLS ใดๆ ก็ doomed
แก้ด้วยมือและความสับสนเรื่องเขตเวลา
คำสั่ง date -s ที่ทำ "เพื่อทดสอบสมมติฐาน" แล้วลืม เป็นเรื่องที่พบบ่อย อีกอย่างคือ Windows และ Linux บูตคู่บนเครื่องเดียว: Windows เก็บเวลาท้องถิ่นในนาฬิกาฮาร์ดแวร์ Linux โดยค่าเริ่มต้นคาดหวัง UTC และหลังรีบูตเข้า OS อีกตัว เวลาจะเลื่อนไปตามขนาดเขตเวลา สำหรับเวลาไทยจะคลาดเคลื่อน 7 ชั่วโมง (สำหรับเวลามอสโก — สามชั่วโมง) และสุดท้ายความสับสนระหว่างเวลากับเขตเวลา: การเปลี่ยนตัวแปร TZ หรือไฟล์ /etc/localtime เปลี่ยนการแสดงผล แต่ไม่เปลี่ยนเวลาระบบ สิ่งที่ควรเปรียบเทียบคือ UTC เท่านั้น
การวินิจฉัยในหนึ่งนาที: เปรียบเทียบกับแหล่งภายนอกและช่วงที่ยอมรับได้
เป้าหมายของขั้นตอนนี้คือตอบคำถามเดียวใน 60 วินาที: นาฬิกาบนเครื่องที่รันคำขอผ่านพร็อกซีเดินถูกต้องหรือไม่ คำสั่งทั้งหมดทำบนเครื่องหรือในคอนเทนเนอร์เดียวกันที่ไคลเอนต์ล้ม ไม่ใช่บนแล็ปท็อปของคุณ
ขั้นที่ 1. ดู UTC และเปรียบเทียบกับแหล่งที่เชื่อถือได้
date -uเปรียบเทียบกับนาฬิกาโทรศัพท์ที่ซิงค์กับเครือข่ายมือถือ การคลาดเคลื่อนเป็นนาทีเห็นได้ทันที สำหรับวินาทีต้องใช้ขั้นถัดไป
ขั้นที่ 2. ดึงเวลาจากเฮดเดอร์ HTTP Date ผ่านพร็อกซีเดียวกัน
เซิร์ฟเวอร์ HTTP ที่ถูกต้องจะส่งเฮดเดอร์ Date ตามนาฬิกา (ที่มักซิงค์แล้ว) กลเม็ด: ขอผ่าน HTTP ธรรมดา ไม่ใช่ HTTPS เพราะถ้าเลื่อนมาก TLS จะไม่เชื่อมต่อและคุณจะไม่ได้เฮดเดอร์เลย:
date -u; curl -sI --proxy http://LOGIN:PASSWORD@PROXY_HOST:PORT http://example.com/ | grep -i '^date'สองบรรทัดควรตรงกันถึงระดับหนึ่งหรือสองวินาที (เฮดเดอร์มีการแบ่งระดับเป็นวินาที บวกความหน่วงของช่องทาง) เห็นเป็นนาทีขึ้นไป — วินิจฉัยเสร็จ กลเม็ดนี้มีค่าเป็นพิเศษตรงที่ไม่ต้องใช้ UDP ทำงานผ่านพร็อกซีใดก็ได้ และไม่ขึ้นกับ TLS
กลเม็ดเดียวกันบน Python — สะดวกในการฝังใน healthcheck ของบริการที่วิ่งผ่าน Proxeon หรือพร็อกซีอื่น:
import email.utils, time, requests
PROXY = "http://LOGIN:PASSWORD@PROXY_HOST:PORT"
r = requests.head("http://example.com/", proxies={"http": PROXY}, timeout=10)
server_ts = email.utils.parsedate_to_datetime(r.headers["Date"]).timestamp()
skew = time.time() - server_ts
print(f"การเลื่อนของนาฬิกาท้องถิ่น: {skew:+.1f} วินาที")
if abs(skew) > 30:
raise SystemExit("นาฬิกาคลาดเคลื่อน จำเป็นต้องซิงค์")ขั้นที่ 3. ถามระบบว่ามันคิดว่าตัวเองซิงค์แล้วหรือไม่
timedatectlมองหาสองบรรทัด: "System clock synchronized: yes" และ "NTP service: active" ถ้าเป็น "no" หรือ "inactive" ถือเป็นเหตุให้ไปที่หัวข้อการตั้งค่า ถ้าใช้ chrony:
chronyc trackingดูที่ "System time" (ขนาดและเครื่องหมายของการเลื่อน), "Stratum" (ไม่ควรเป็น 0) และ "Leap status" (ควรเป็น Normal ไม่ใช่ "Not synchronised")
ขั้นที่ 4. ถาม NTP ครั้งเดียวโดยไม่เปลี่ยนนาฬิกา
ถ้า UDP ขาออกเปิด สามารถวัดการเลื่อนได้อย่างแม่นยำโดยไม่เปลี่ยนอะไร:
chronyd -Q 'pool pool.ntp.org iburst'หรือถ้าติดตั้ง ntpdate:
ntpdate -q pool.ntp.orgเอาต์พุตจะแสดง offset เป็นวินาทีพร้อมเครื่องหมาย
ขั้นที่ 5. ยืนยันว่าเวลาคือสิ่งที่ทำลาย TLS
ยูทิลิตี openssl วิ่งผ่าน HTTP-พร็อกซีได้และพิมพ์ผลการตรวจสอบพร้อมรหัสตัวเลข:
openssl s_client -connect example.com:443 -proxy PROXY_HOST:PORT -servername example.com </dev/null 2>/dev/null | grep 'Verify return code'"Verify return code: 9 (certificate is not yet valid)" หรือ "10 (certificate has expired)" ในขณะที่ใบรับรองยังมีชีวิต เป็นหลักฐานที่ conclusive โปรดทราบว่า s_client ในบิลด์ส่วนใหญ่ไม่รองรับการยืนยันตัวตนบนพร็อกซีด้วยชื่อผู้ใช้และรหัสผ่าน สำหรับการตรวจสอบนี้ สะดวกใช้การยืนยันตัวตนตาม IP ซึ่ง Proxeon รองรับ ดึงวันที่ของใบรับรองเองได้แบบนี้:
openssl s_client -connect example.com:443 -proxy PROXY_HOST:PORT -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -datesช่วงที่ถือว่าปกติ
- ไม่เกิน 100 มิลลิวินาที — เครื่องสุขภาพดีที่มี NTP ทำงาน ไม่ต้องทำอะไร
- 100 มิลลิวินาที — 1 วินาที — ยอมรับได้สำหรับโปรโตคอลที่พูดถึงทั้งหมด แต่บ่งชี้แหล่งที่ไม่ดีหรือการโพลล์นานเกิน ควรตรวจคอนฟิก
- 1 — 30 วินาที — โซนเหลือง TLS และลายเซ็นที่มีหน้าต่างกว้างยังทำงานได้ TOTP และ JWT ที่ leeway น้อยอาจสะดุดแล้ว แก้ไขวันนี้
- มากกว่า 30 วินาที — โซนแดง ถือว่าข้อผิดพลาดการยืนยันตัวตนหรือ TLS ใดๆ ที่เกิดขึ้น เกิดจากนาฬิกา จนกว่าจะพิสูจน์เป็นอย่างอื่น
วิธีทำซ้ำปัญหาโดยเจตนา
เพื่อยืนยันว่าการจัดการข้อผิดพลาดของคุณรู้จักความล้มเหลวด้านเวลาได้ถูกต้อง ไม่ต้องแตะนาฬิกาของระบบ ไลบรารี libfaketime แทนเวลาสำหรับโปรเซสเดียว:
faketime '2024-01-01 12:00:00' curl -sS https://example.com/ -o /dev/nullคุณจะได้ "certificate is not yet valid" สำหรับเว็บใดก็ตามที่มีใบรับรองออกหลังจากวันที่ระบุ สำหรับการตรวจสอบใบรับรองโดยไม่ต้องใช้เครือข่าย OpenSSL รองรับแฟล็ก -attime กับ Unix time ซึ่งสะดวกสำหรับเทสต์ใน CI
การตั้งค่าการซิงค์: systemd-timesyncd และ chrony และวิธีมั่นใจว่ามันทำงานจริง
การติดตั้งแพ็กเกจไม่ใช่การซิงค์ ด้านล่างเป็นสองรูปแบบที่ใช้งานได้ และที่สำคัญกว่าคือวิธีตรวจสอบ
เลือก: timesyncd หรือ chrony
systemd-timesyncd — ไคลเอนต์ SNTP แบบมินิมัลลิสต์ที่เป็นส่วนหนึ่งของ systemd โพลล์เซิร์ฟเวอร์เดียวจากรายการ แก้ไขนาฬิกาได้ทั้งแบบกระโดดและแบบราบรื่น ไม่ทำงานเป็นเซิร์ฟเวอร์ ไม่รองรับแหล่งฮาร์ดแวร์ เพียงพอสำหรับเวิร์กสเตชันและเซิร์ฟเวอร์ที่ไม่สำคัญ ซึ่งต้องการความแม่นยำระดับหลายสิบมิลลิวินาที
chrony — การใช้งาน NTP แบบเต็มรูปแบบ ผสมหลายแหล่ง บรรจบเร็วหลังหยุดนาน ทำงานเรียบร้อยเมื่อเครือข่ายขาดเป็นช่วง รองรับ NTS (NTP พร้อมการยืนยันตัวตนผ่าน TLS) แจกจ่ายเวลาภายในเครือข่ายได้ เป็นตัวเลือกเริ่มต้นสำหรับเซิร์ฟเวอร์ เครื่องเสมือน และโหนดใดๆ ที่มีคำขอเซ็นลายเซ็นและทำงานผ่านพร็อกซี
สำคัญ: เดมอนสองตัวพร้อมกันคือแหล่งของความวุ่นวาย เมื่อติดตั้ง chrony ดิสทริบิวชันมักปิด timesyncd แต่ให้ตรวจสอบให้ชัดเจน
ตัวเลือก A: systemd-timesyncd
คอนฟิกในไฟล์ /etc/systemd/timesyncd.conf:
[Time]
NTP=ntp.your-provider.internal 0.pool.ntp.org 1.pool.ntp.org
FallbackNTP=2.pool.ntp.org 3.pool.ntp.org
PollIntervalMinSec=32
PollIntervalMaxSec=2048เปิดใช้และตรวจสอบ:
sudo timedatectl set-ntp true
sudo systemctl restart systemd-timesyncd
timedatectl timesync-statusในเอาต์พุต timesync-status ต้องมี Server เติมอยู่ Stratum ไม่เป็น 0 และฟิลด์ Offset เป็นมิลลิวินาที ถ้าไม่มีเซิร์ฟเวอร์หรือเป็น "Packet count: 0" แสดงว่าไม่มีคำตอบจาก NTP — เกือบแน่นอนว่า UDP 123 ถูกปิด
ตัวเลือก B: chrony
ไฟล์ /etc/chrony/chrony.conf (บนบางระบบ /etc/chrony.conf):
server ntp.your-provider.internal iburst prefer
pool pool.ntp.org iburst maxsources 4
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
logdir /var/log/chronyมาดูคำสั่งสำคัญทีละตัว เพราะมันกำหนดพฤติกรรมเมื่อเกิดความล้มเหลว:
- iburst — ตอนเริ่มส่งชุดคำขอต่อเนื่องและซิงค์ภายในไม่กี่วินาที ไม่ใช่ไม่กี่นาที
- makestep 1.0 3 — ถ้าการเลื่อนมากกว่า 1 วินาที ในสามการอัปเดตแรกอนุญาตให้กระโดด หลังจากนั้นปรับแบบราบรื่นเท่านั้น ถ้าไม่มีคำสั่งนี้ หลังสแนปชอตหรือสลีป chrony จะ "ไล่ทัน" นาฬิกาเป็นสัปดาห์ สำหรับเซิร์ฟเวอร์ที่สลีปหรือโยกย้ายบ่อย บางคนตั้ง makestep 1.0 -1 ให้กระโดดได้เสมอ
- rtcsync — เขียนเวลาไปยังนาฬิกาฮาร์ดแวร์เป็นระยะ เพื่อเริ่มจากค่าใกล้เคียงหลังรีบูต
- เซิร์ฟเวอร์ภายในของผู้ให้บริการก่อน — แพลตฟอร์มคลาวด์มักมีที่อยู่ NTP ภายในเครือข่าย เข้าถึงได้แม้ UDP ภายนอกปิด และมีความหน่วงต่ำสุด ดึงที่อยู่จากเอกสารของผู้ให้บริการของคุณ
เริ่มทำงาน:
sudo systemctl enable --now chrony
sudo chronyc makestep
chronyc tracking
chronyc sources -vวิธีรู้ว่าการซิงค์ทำงานจริง
นี่คือส่วนสำคัญที่สุดของหัวข้อ สัญญาณของการซิงค์ที่ทำงานใน chrony:
- ใน chronyc tracking ฟิลด์ Reference ID ไม่เป็นศูนย์ และ Stratum อยู่ในช่วง 2-4 สำหรับเซิร์ฟเวอร์ทั่วไป Stratum 0 หรือ 10 กับ Reference ID ว่างเปล่า หมายถึง "ทำงานกับตัวเอง"
- Leap status: Normal ค่า "Not synchronised" — นาฬิกาไม่ถูกแก้ไขโดยใคร
- System time แสดงการเลื่อนเป็นมิลลิวินาทีหรือไมโครวินาที ไม่ใช่วินาที
- ใน chronyc sources มีอย่างน้อยหนึ่งแหล่งที่มีเครื่องหมายดอกจัน (ที่เลือกใช้ปัจจุบัน) หรือเครื่องหมายบวก (มีส่วนร่วมในการรวม) เครื่องหมายคำถามกับทุกแหล่ง — ไม่มีคำตอบมา มองหาไฟร์วอลล์หรือ DNS
- Last offset และ RMS offset ลดลงตามเวลา ไม่ใช่เพิ่มขึ้น
สำหรับ timesyncd การตรวจสอบเทียบเท่าคือ "System clock synchronized: yes" ใน timedatectl และ Packet count ที่ไม่เป็นศูนย์ใน timesync-status
เมื่อ NTP ถูกบล็อกโดยไฟร์วอลล์
ถ้าเซิร์ฟเวอร์อนุญาตเฉพาะ HTTP(S) ขาออกผ่านพร็อกซี คุณมีสามทาง ทางแรก — ขอให้เปิด UDP 123 อย่างน้อยไปยังเซิร์ฟเวอร์ NTP ภายใน นี่เป็นคำขอที่เป็นมาตรฐานและปลอดภัยต่อผู้ดูแลเครือข่าย ทางที่สอง — ซิงค์จากไฮเปอร์ไวเซอร์หรือโฮสต์ ถ้าเป็น VM หรือคอนเทนเนอร์ ทางที่สาม — ทางประนีประนอมผ่านยูทิลิตี htpdate ที่ตั้งนาฬิกาตามเฮดเดอร์ Date ของคำตอบ HTTP ความแม่นยำระดับวินาที เพียงพอสำหรับ TLS, JWT และลายเซ็นคำขอ และมันวิ่งผ่าน HTTP-พร็อกซีได้ ไม่หรูหราที่สุด แต่เป็นตัวเลือกที่ใช้งานได้สำหรับเซกเมนต์ที่แยกออก
Windows
บนโฮสต์ Windows ตรวจสอบบริการเวลาดังนี้:
w32tm /query /status
w32tm /resyncในสภาพแวดล้อมโดเมน แหล่งคือโดเมนคอนโทรลเลอร์ นอกโดเมนคือเซิร์ฟเวอร์ NTP ที่ตั้งไว้ ฟิลด์ Last Successful Sync Time ควรใหม่
คอนเทนเนอร์และนาฬิกา: อะไรสืบทอดจากโฮสต์ อะไรไม่
คอนเทนเนอร์คือชุดเนมสเปซที่แยกออกจากกันบนเคอร์เนลร่วม การแยกเกี่ยวข้องกับโปรเซส เครือข่าย ระบบไฟล์ จุดเมานต์ แต่ นาฬิกาจริง (CLOCK_REALTIME) ไม่ถูกแยก เนมสเปซเวลาที่ปรากฏใน Linux 5.6 อนุญาตให้เลื่อนเฉพาะนาฬิกาโมโนโทนิกและเวลาตั้งแต่บูต — จำเป็นสำหรับการโยกย้ายคอนเทนเนอร์ และไม่มีผลต่อเวลาปฏิทิน จากนี้มีผลลัพธ์ที่เข้มงวดหลายประการ
อะไรสืบทอด
- เวลาปฏิทิน ทุกคอนเทนเนอร์เห็นเวลาเดียวกับโฮสต์เป๊ะ ถ้าโฮสต์เพี้ยน ทุกตัวเพี้ยน
- การซิงค์ chrony ที่ทำงานบนโฮสต์ "ซ่อม" เวลาในทุกคอนเทนเนอร์โดยอัตโนมัติ
- การกระโดด ถ้าโฮสต์แก้ไขนาฬิกาแบบกระโดด แอปในคอนเทนเนอร์จะเห็นการกระโดดทันที
อะไรไม่สืบทอด
- เขตเวลา ไฟล์ /etc/localtime และตัวแปร TZ อยู่ในอิมเมจ ในอิมเมจมินิมัลอย่าง Alpine แพ็กเกจ tzdata อาจไม่มี และคอนเทนเนอร์จะแสดง UTC เสมอ นี่ไม่ใช่ข้อผิดพลาดเวลา แต่เป็นข้อผิดพลาดการแสดงผล แต่มักทำให้เข้าใจผิดเวลาอ่านล็อก
- ที่เก็บรากใบรับรอง ไม่ใช่หัวข้อของบทความนี้ แต่เตือนไว้: มันก็อยู่ในอิมเมจเช่นกัน
- สิทธิ์แก้เวลา system call ตั้งเวลา requires CAP_SYS_TIME ซึ่งคอนเทนเนอร์โดยค่าเริ่มต้นไม่มี คำสั่ง date -s ในคอนเทนเนอร์จะล้มเหลวด้วย "Operation not permitted" การรันเดมอน NTP ในคอนเทนเนอร์ธรรมดาไม่มีประโยชน์ และการให้ CAP_SYS_TIME ก็เป็นแนวปฏิบัติที่ไม่ดี เพราะมันจะเริ่มจัดการนาฬิกาของทั้งโฮสต์
ผลลัพธ์เชิงปฏิบัติ
การวินิจฉัยจากหัวข้อก่อนทำได้และควรทำในคอนเทนเนอร์ — date -u และการขอเฮดเดอร์ Date ผ่านพร็อกซีจะแสดงภาพจริง แต่การแก้ไขเกิดขึ้นบนโฮสต์เสมอ ถ้าคอนเทนเนอร์ทำงานใน Kubernetes โฮสต์ที่ต้องการคือโหนดเฉพาะที่ pod ถูกจัดวาง มั่นใจว่า chrony อยู่ในอิมเมจพื้นฐานของโหนด และการมอนิเตอร์เก็บเมตริกการเลื่อนจากทุกโหนด
Docker Desktop และ WSL2 บนแล็ปท็อปเป็นเรื่องแยก เพราะ "โฮสต์" ของคอนเทนเนอร์คือเครื่องเสมือนที่ซ่อนอยู่ ซึ่งมีแนวโน้มช้ากว่าหลังสลีป สำหรับ WSL2 การแก้ไขเร็วคือดึงเวลาจากนาฬิกาฮาร์ดแวร์ที่ไฮเปอร์ไวเซอร์ดูแลอยู่:
sudo hwclock -sสำหรับ Docker Desktop วิธีที่รุนแรงแต่เชื่อถือได้คือรีสตาร์ตแอป หลังจาก VM เริ่มจะซิงค์กับโฮสต์ ในเวอร์ชันใหม่ปัญหาพบน้อยลง แต่ยังไม่หายไปหมด การมี healthcheck สำหรับการเลื่อนนาฬิกาใน dev จึงไม่ใช่ความหวาดระแวง แต่เป็นการประหยัดหนึ่งชั่วโมงในตอนเช้า
ตัวอย่าง: healthcheck ในไฟล์ compose
แนวคิดคือไม่ให้คอนเทนเนอร์ทำงานถ้านาฬิกาของโฮสต์เลื่อนเกินที่ยอมรับ การตรวจสอบใช้กลเม็ดเดียวกันกับเฮดเดอร์ Date ผ่าน HTTP ธรรมดาผ่านพร็อกซี เพื่อไม่ขึ้นกับ TLS:
healthcheck:
test: ["CMD", "python", "/app/clock_check.py"]
interval: 5m
timeout: 20s
retries: 1สคริปต์ clock_check.py — อันที่เราเขียนในหัวข้อการวินิจฉัย โดยมีเกณฑ์ 30 วินาที ออร์เคสเตรเตอร์จะทำเครื่องหมายคอนเทนเนอร์ว่าไม่ sehat และคุณจะเห็นสาเหตุในสถานะ ไม่ใช่กองข้อผิดพลาด TLS ในล็อกแอป
ข้อผิดพลาดทั่วไปและเคสจากประสบการณ์จริง
หัวข้อเกี่ยวกับสิ่งที่ไม่ควรทำ มีประโยชน์กว่าหัวข้อเกี่ยวกับสิ่งที่ควรทำ นี่คือการตัดสินใจที่ดูสมเหตุสมผลในขณะเกิดเหตุและเสียหายหนักในภายหลัง พร้อมเรื่องราวที่มีตัวเลขจากสถานการณ์จริง
ข้อผิดพลาด 1: ปิดการตรวจสอบใบรับรอง
แฟล็ก -k, verify=False, NODE_TLS_REJECT_UNAUTHORIZED=0 สคริปต์ "ทำงาน" อีกครั้ง งานปิด แต่ลายเซ็นคำขอและ JWT ยังไม่ตรง TOTP ยังไม่ถูกยอมรับ และคุณปิดกลไกเดียวที่ป้องกันช่องทาง นาฬิกายังเพี้ยนและอีกสัปดาห์จะพังอย่างอื่นอีก การปิดการตรวจสอบยอมรับได้เฉพาะคำสั่งเดียวเพื่อการวินิจฉัย และไม่เคยในโค้ด
ข้อผิดพลาด 2: เพิ่ม leeway หรือหน้าต่างลายเซ็น "เผื่อเหลือ"
เพิ่มค่าผ่อนผันการตรวจ JWT เป็น 10 นาที หมายถึงขยายหน้าต่างสำหรับการใช้โทเคนที่ถูกดักจับซ้ำขึ้น 20 เท่า ปัญหาจริง (นาฬิกา) ไม่หายไปไหน ค่าผ่อนผันที่ถูกต้องสำหรับ JWT คือ 30-60 วินาที สำหรับลายเซ็น HMAC คือค่าที่ผู้ให้บริการ API กำหนด
ข้อผิดพลาด 3: แก้เวลาด้วยมือแทนการซิงค์
date -s แก้ симптомไปสองชั่วโมง ดริฟต์และสแนปชอตถัดไปจะพากลับมา และตอนนั้นจะไม่มีใครจำได้ว่าเคยแก้เวลาด้วยมือ
ข้อผิดพลาด 4: เดมอนเวลาสองตัวพร้อมกัน
chrony กับ timesyncd หรือ chrony กับเกสต์เอเจนต์ไฮเปอร์ไวเซอร์ที่เปิดซิงค์ ดึงนาฬิกาไปคนละทาง ในล็อก chrony เห็นเป็นการแก้ไขขนาดใหญ่บ่อยๆ เหลือแหล่งความจริงเพียงหนึ่ง
ข้อผิดพลาด 5: เปรียบเทียบเวลาท้องถิ่นกับ UTC
ต่างกันพอดี 3, 5 หรือ 7 ชั่วโมง — เกือบทุกครั้งไม่ใช่นาฬิกาผิด แต่เป็นเขตเวลา เปรียบเทียบ date -u กับแหล่ง UTC เสมอ และในล็อกให้เขียนเวลาพร้อมระบุเขตชัดเจน
ข้อผิดพลาด 6: เชื่อสถานะ "installed"
Ansible role ติดตั้ง chrony งานเขียว แต่ UDP 123 ปิด และ chrony ซิงค์กับ "ตัวเอง" การตรวจสอบในคอนฟิกของโครงสร้างพื้นฐานควรดูที่ Leap status และการเลื่อน ไม่ใช่การมีแพ็กเกจ
ข้อผิดพลาด 7: ใช้คำขอที่เซ็นแล้วซ้ำเมื่อลองใหม่
โดยเฉพาะผ่านพร็อกซีที่มีการเชื่อมต่อใหม่ ทุกครั้งที่ลองควรได้เวลาที่ประทับใหม่และลายเซ็นใหม่
เคส A: สแนปชอตของสภาพแวดล้อมทดสอบ
ทีมเก็บข้อมูลเปิดราคาถือพูลเวิร์กเกอร์ในเครื่องเสมือนและย้อนสภาพแวดล้อมกลับไปยังสแนปชอต "สะอาด" ทุกสัปดาห์ หลังการย้อนครั้งหนึ่ง เวิร์กเกอร์ทั้งหมดที่วิ่งผ่านพร็อกซีเริ่มล้มด้วย "certificate is not yet valid" กับหนึ่งในสามของเว็บเป้าหมาย — เฉพาะที่ใช้ใบรับรอง 90 วันและเพิ่งออกใหม่ในช่วง 12 วันของอายุสแนปชอต จากแล็ปท็อปวิศวกรโดยไม่ใช้พร็อกซี เว็บเปิดได้ปกติ ใช้เวลาชั่วโมงครึ่งในการสลับพูลและแพ็กเกจกับพร็อกซี ก่อนที่ใครจะรัน date -u และเห็นว่าช้าไป 12 วัน การแก้ไข: chrony พร้อม makestep ในอิมเมจพื้นฐานของสภาพแวดล้อมและตรวจการเลื่อนในสคริปต์หลังกู้คืน ไม่เกิดซ้ำอีก
เคส B: เซกเมนต์ที่แยกและมินิพีซี
มินิพีซีที่ไม่มีนาฬิกาฮาร์ดแวร์ตั้งอยู่ในเซกเมนต์เครือข่ายที่มีเพียง HTTP ผ่านพร็อกซีองค์กรออกไปข้างนอกได้ หลังการดับไฟตามแผน มันบูตด้วยวันที่สร้างอิมเมจ — ห่างไป 14 เดือน การเชื่อมต่อ TLS ไม่ตั้งขึ้นเลย ซึ่งดูเหมือนพร็อกซีล้มเหลวทั้งหมด การแก้ไข: อนุญาต UDP 123 ไปยังเซิร์ฟเวอร์ NTP ภายในของบริษัท chrony ได้รับคำสั่ง makestep 1.0 -1 และในกรณีที่ไม่มี NTP ได้เพิ่มการเรียก htpdate ผ่านพร็อกซีตอนเริ่มต้น
เคส C: อัปโหลดไปยังที่เก็บออบเจกต์จาก Docker Desktop
นักพัฒนากำลังดีบักการอัปโหลดไฟล์ไปยังที่เก็บที่เข้ากันได้กับ S3 ผ่านพร็อกซีจากคอนเทนเนอร์บนแล็ปท็อป หลังพักกลางวัน คำขอทั้งหมดเริ่มคืน RequestTimeTooSkewed การเลื่อนของนาฬิกา VM ของ Docker Desktop — 23 นาที เกินหน้าต่าง 15 นาทีเล็กน้อย การรีสตาร์ต Docker Desktop แก้ได้ในหนึ่งนาที โปรเจกต์ได้ healthcheck การเลื่อนที่แสดงสาเหตุทันที
เคส D: 50 วินาทีและ TOTP
บริการทำอัตโนมัติการเข้าสู่พอร์ทัลองค์กรของตัวเองด้วยบัญชีบริการและยืนยันสองปัจจัยผ่าน TOTP รหัสเริ่มไม่ถูกยอมรับ เซิร์ฟเวอร์ตอบว่ารหัสไม่ถูกต้อง การเลื่อนของนาฬิกาโหนด — 50 วินาที เพราะ timesyncd ติดตั้งอยู่แต่ถูกมาสก์ตอนเปลี่ยนไปใช้ chrony ซึ่งไม่เคยถูกเปิด ด้วยค่าผ่อนผันหนึ่งช่วง 30 วินาที แค่นี้ก็เพียงพอ ไม่มีการเปลี่ยนโค้ดเลย แค่เปิด chrony
เครื่องมือและเช็คลิสต์ "ก่อนจะโทษพร็อกซี"
รวบรวมทุกอย่างในรูปแบบที่แนบกับตั๋วหรือใส่ใน runbook ได้ ก่อนอื่นเครื่องมือ แล้วตามด้วยลำดับ
เครื่องมือ
- date -u, timedatectl, chronyc, hwclock — สถานะนาฬิกาและการซิงค์
- curl -v --proxy, openssl s_client -proxy — ตรวจ TLS ผ่านพร็อกซีพร้อมรหัสการตรวจสอบ
- chronyd -Q, ntpdate -q — วัดการเลื่อนครั้งเดียวโดยไม่เปลี่ยนนาฬิกา
- เฮดเดอร์ Date ผ่าน HTTP — แหล่งเวลาสากลที่ทำงานผ่านพร็อกซีใดก็ได้
- libfaketime, openssl -attime — ทำซ้ำความล้มเหลวและทดสอบการจัดการข้อผิดพลาด
- htpdate — ซิงค์ฉุกเฉินในเซกเมนต์ที่ UDP ปิด
- เมตริก — เอ็กซ์พอร์ตเตอร์สำหรับ chrony หรือ timesyncd ส่งการเลื่อนเข้าระบบมอนิเตอร์ เกณฑ์แจ้งเตือน 1 วินาที วิกฤต 30 วินาที
เช็คลิสต์
- โค้ดที่ล้มทำงานที่ไหน? แล็ปท็อป เซิร์ฟเวอร์ VM คอนเทนเนอร์ pod ทำการตรวจสอบทั้งหมดที่นั่น
- date -u ตรงกับโทรศัพท์ไหม? คลาดเคลื่อนเป็นนาที — หยุด แก้นาฬิกาก่อน
- เฮดเดอร์ Date ผ่าน http:// ผ่านพร็อกซีตรงกับ UTC ท้องถิ่นภายในสองวินาทีไหม? ถ้าไม่ — นาฬิกา
- timedatectl บอก synchronized: yes, chronyc tracking แสดง Leap status Normal ไหม? ถ้าไม่ — การซิงค์ไม่ทำงาน ไม่ว่าอะไรจะติดตั้งอยู่
- ข้อความข้อผิดพลาดมี not yet valid, has expired, RequestTimeTooSkewed, jwt expired, jwt not active, timestamp ไหม? ทุกคำเหล่านี้เกี่ยวกับเวลา
- โดยไม่ใช้พร็อกซีจากเครื่องเดียวกัน ข้อผิดพลาดเหมือนกันไหม? ถ้าใช่ — พร็อกซีไม่เกี่ยว
- ผ่านพร็อกซีจากเครื่องอื่นไม่มีข้อผิดพลาดไหม? ถ้าใช่ — ปัญหาอยู่ที่เครื่องแรก
- openssl s_client ผ่านพร็อกซีแสดง Verify return code 9 หรือ 10 ในขณะที่วันที่ในใบรับรองยังดีอยู่ไหม? วินิจฉัยยืนยันแล้ว
- ข้อผิดพลาดเกิดกับโฮสต์เป้าหมายเดียวเท่านั้นไหม? เปรียบเทียบ Date กับโฮสต์อื่น — บางทีนาฬิกาเพี้ยนที่ฝั่งมัน
- ลายเซ็นถูกสร้างใหม่ทุกครั้งที่ลองซ้ำไหม? ถ้าไม่ — แก้ตรรกะการลองซ้ำ
- เมื่อเร็วๆ นี้มีสแนปชอต สลีป การโยกย้าย หรือรีบูตหลังปิดนานไหม? ทุก "ใช่" เกือบแน่นอนคือต้นเหตุ
- หลังจากทุกข้อเป็น "ไม่" แล้ว — จึงติดต่อผู้ให้บริการพร็อกซี พร้อมแนบเอาต์พุตของ date -u, chronyc tracking และ curl -v วิธีนี้ฝ่ายสนับสนุนของ Proxeon หรือบริการอื่นจะแก้ปัญหาในรอบเดียว ไม่ใช่ห้ารอบ
คำถามที่พบบ่อย: เวลา พร็อกซี และความล้มเหลว
พร็อกซีสามารถบิดเบือนเวลาในคำขอหรือใน TLS ของผมได้ไหม?
เมื่อใช้ทันเนลผ่าน CONNECT หรือ SOCKS5 — ไม่ได้ การจับมือ TLS เกิดระหว่างไคลเอนต์ของคุณกับเซิร์ฟเวอร์เป้าหมาย พร็อกซีเห็นแค่ไบต์ที่เข้ารหัส เวลาที่ประทับในลายเซ็นและ JWT สร้างโดยโค้ดของคุณบนเครื่องของคุณ สิ่งเดียวที่พร็อกซีเพิ่มได้คือความหน่วง และวัดเป็นมิลลิวินาที ไม่ใช่นาที
ทำไมจากแล็ปท็อปโดยไม่ใช้พร็อกซีใช้ได้ แต่จากเซิร์ฟเวอร์ผ่านพร็อกซีไม่ได้?
เพราะมันเป็นสองเครื่องที่มีชุดนาฬิกาต่างกัน การเปรียบเทียบผิดตั้งแต่ต้น เพราะมีสองตัวแปรเปลี่ยนพร้อมกัน เพื่อแยกผลของพร็อกซี ให้รันคำขอจากเซิร์ฟเวอร์โดยไม่ใช้พร็อกซี เพื่อแยกผลของเครื่อง ให้รันผ่านพร็อกซีจากแล็ปท็อป เกือบทุกครั้งจะพบว่าข้อผิดพลาดผูกกับเครื่อง
เวลาต้องแม่นยำแค่ไหนสำหรับการทำงานผ่านพร็อกซี?
สำหรับ TLS ความแม่นยำระดับชั่วโมงก็พอ ถ้าใบรับรองอายุยาว และระดับวันสำหรับอายุสั้น สำหรับลายเซ็นคำขอ หน้าต่างทั่วไปคือ 5-15 นาที สำหรับ JWT — ค่าผ่อนผัน 30-60 วินาที สำหรับ TOTP — บวกลบ 30 วินาที NTP ที่ทำงานให้ความแม่นยำระดับมิลลิวินาที มาตรฐานปฏิบัติคือการเลื่อนน้อยกว่า 1 วินาที และแจ้งเตือนเมื่อเกิน 30 วินาที
ผมสามารถรันไคลเอนต์ NTP ในคอนเทนเนอร์ได้ไหม?
ทางเทคนิค — ได้ก็เฉพาะกับ CAP_SYS_TIME และแล้วมันจะจัดการนาฬิกาของทั้งโฮสต์ ซึ่งละเมิดการแยกและขัดแย้งกับเดมอนบนโฮสต์ แนวทางที่ถูกคือซิงค์บนโฮสต์หรือโหนดคลัสเตอร์ ในคอนเทนเนอร์เหลือแค่การตรวจสอบการเลื่อน
ทำอย่างไรถ้า UDP 123 ปิดสนิท และทางออกคือผ่าน HTTP-พร็อกซีเท่านั้น?
ขอให้เปิด UDP อย่างน้อยไปยัง NTP ภายในของบริษัทหรือผู้ให้บริการคลาวด์ — นี่เป็นความต้องการพื้นฐานของโครงสร้างพื้นฐาน ระหว่างรอ ใช้การซิงค์จากไฮเปอร์ไวเซอร์หรือ htpdate ผ่านเฮดเดอร์ Date ผ่านพร็อกซี โปรดทราบว่า NTS ป้องกัน NTP ผ่าน TLS บนพอร์ต TCP 4460 แต่แพ็กเก็ตเวลายังวิ่งบน UDP ดังนั้น NTS ไม่ข้ามการบล็อก UDP
นาฬิกาเร็วกว่าหลายนาที แต่ TLS ทำงานได้ แล้วทำไมคำขอไปยัง API ถึงล้ม?
เพราะ TLS มีหน้าต่างเป็นวัน แต่ลายเซ็นและโทเคนมีหน้าต่างเป็นวินาทีและนาที นี่เป็นสถานการณ์ปกติ: ใบรับรองผ่านการตรวจสอบ แต่ลายเซ็น HMAC ที่เวลาประทับเร็วไปสามนาทีถูกเซิร์ฟเวอร์ที่มีหน้าต่างเข้มงวดปฏิเสธ ดูที่ข้อความข้อผิดพลาดจาก API ไม่ใช่แค่ความสำเร็จของการเชื่อมต่อ
จะรู้ได้อย่างไรว่านาฬิกาของเซิร์ฟเวอร์เป้าหมายเพี้ยน ไม่ใช่ของผม?
เปรียบเทียบเฮดเดอร์ Date จากคำตอบของโฮสต์ที่มีปัญหากับ Date สองสามโฮสต์อื่นและกับ date -u ของคุณ ถ้านาฬิกาของคุณตรงกับส่วนใหญ่และโฮสต์เดียวคลาดเคลื่อน — ปัญหาอยู่ฝั่งมัน SDK ไคลเอนต์บางตัวชดเชยการเลื่อนของเซิร์ฟเวอร์ได้อัตโนมัติ โดยคำนวณจากคำตอบข้อผิดพลาดเวลา
ต้องเซ็นคำขอใหม่เมื่อลองใหม่ผ่านพร็อกซีไหม?
ต้อง ลายเซ็นรวมเวลาที่ประทับและมีอายุในหน้าต่าง ถ้าระหว่างครั้งแรกกับการลองใหม่ผ่านไปหลายนาทีและมีการเชื่อมต่อใหม่ ลายเซ็นเก่าอาจหลุดหน้าต่าง สร้างลายเซ็นในขณะส่งแต่ละครั้ง
จะฝังการตรวจสอบนาฬิกาในมอนิเตอร์ได้อย่างไร?
เก็บการเลื่อนจาก chrony หรือ timesyncd เข้าระบบเมตริกและตั้งสองเกณฑ์: เตือนเมื่อ 1 วินาที วิกฤตเมื่อ 30 วินาที เพิ่ม healthcheck ของบริการตามเฮดเดอร์ Date ผ่านพร็อกซีที่ใช้อยู่ มันจับสถานการณ์ที่เดมอนเวลายังทำงานอยู่แต่เวลายังเพี้ยน เช่นหลังกู้คืนสแนปชอต
มีอะไรเปลี่ยนในปี 2026 ไหม?
การเปลี่ยนแปลงสำคัญคือการลดอายุใบรับรองสาธารณะและการออกใบรับรองใหม่อัตโนมัติอย่างแพร่หลาย สิ่งนี้ทำให้แม้การเลื่อนของนาฬิกาเพียงไม่กี่วันเป็นสาเหตุจริงของความล้มเหลว TLS ซึ่งเมื่อก่อนหายาก ในขณะเดียวกันสัดส่วน JWT อายุสั้นและหน้าต่างลายเซ็นที่เข้มงวดใน API ก็เพิ่มขึ้น พูดอีกอย่าง ความต้องการความแม่นยำของนาฬิกาในเครื่องไคลเอนต์เพิ่มขึ้น และการซิงค์เวลากำลังเลิกเป็น "การตั้งค่าเริ่มต้นที่ไม่มีใครคิดถึง"
บทสรุป: เวลาคือรายการแรกในการวินิจฉัย
สรุปกัน การตรวจสอบอายุใบรับรอง ฟิลด์ exp และ nbf ใน JWT เวลาที่ประทับในลายเซ็นคำขอ และรหัสใช้ครั้งเดียว — ทั้งหมดเป็นการเปรียบเทียบท้องถิ่นกับนาฬิกาของระบบ เมื่อทำงานผ่านพร็อกซี ในห่วงโซ่จะมีอีกหนึ่งเครื่อง และความสนใจก็เบนไปที่มันโดยธรรมชาติ ทั้งที่การจับมือ TLS ลายเซ็น และการตรวจสอบโทเคนไม่ผ่านทันเนลพร็อกซี พร็อกซีในสถานการณ์เหล่านี้เป็นเพียงการขนส่ง ส่วนนาฬิกาเพี้ยนด้วยเหตุผลทางโลกทั้งสิ้น: สแนปชอตและความหยุดชั่วคราวของเครื่องเสมือน สลีปของแล็ปท็อปที่มี Docker Desktop และ WSL2 การไม่มีหรือใช้ไม่ได้ของเดมอนซิงค์ UDP ที่ปิด แบตเตอรี่หมด การแก้ด้วยมือ
แผนปฏิบัติเรียบง่ายและพอดีกับสามขั้น ขั้นแรก — ทำความคุ้นเคยกับการจำข้อผิดพลาดด้านเวลาจากข้อความ: not yet valid, has expired, RequestTimeTooSkewed, jwt expired, jwt not active ขั้นที่สอง — ทำการตรวจสอบการเลื่อนของนาฬิกาเป็นรายการแรกของการวิเคราะห์เหตุการณ์พร็อกซีทุกครั้ง: date -u, เฮดเดอร์ Date ผ่าน HTTP ผ่านพร็อกซี, timedatectl หรือ chronyc tracking ใช้เวลาหนึ่งนาทีที่ประหยัดเป็นชั่วโมง ขั้นที่สาม — ทำให้ปัญหาไม่กลับมาอีก: chrony พร้อม iburst และ makestep ในอิมเมจพื้นฐาน หนึ่งแหล่งเวลาต่อเครื่อง การซิงค์บนโฮสต์สำหรับคอนเทนเนอร์ เมตริกการเลื่อนในมอนิเตอร์ และ healthcheck ในบริการที่วิ่งผ่านพร็อกซี
เมื่อทั้งหมดนี้ทำเสร็จ การติดต่อผู้ให้บริการพร็อกซีก็มีความหมาย: คุณมาพร้อมสมมติฐานที่ตัดออกแล้วและเอาต์พุตคำสั่งที่พร้อม และการวิเคราะห์ดำเนินไปตามเนื้อหา และในกรณีส่วนใหญ่ การติดต่อก็ไม่จำเป็นเลย — เพราะนาฬิกาจะเป็นสิ่งแรกที่คุณตรวจสอบ และสิ่งสุดท้ายที่จะทำให้คุณประหลาดใจ