diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b3ce0..504e04a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ Biçim [Keep a Changelog](https://keepachangelog.com/) esas alınmıştır. ## [Unreleased] +### Eklendi +- **Kontrol makinesi saatiyle bağımsız karşılaştırma.** Gerçek VM bulgusu: snapshot/rollback sonrası sunucu saati 13 gün geride iken `chronyc tracking` ("0.000000155 seconds slow", Ref time sunucunun kendi yanlış saatinde) ve `sources` sapmayı göstermedi; 0.1.1 `durum` `ntp_senkron=yes sapma_ms=0.0` dedi, plan `saat` içermedi. Artık sunucunun `date +%s` değeri Ansible kontrol makinesininkiyle karşılaştırılır (chrony'den bağımsız): fark > 300 sn ise `durum` `saat_sapma_kontrolcuye_gore_sn=N` verir ve `ntp_senkron=no` olur, `preview`/`apply` plana `saat` ekler ve uyarır. `apply` önce chrony'yi yeniden başlatıp (bayat ölçümü atar) `makestep` dener; fark sürerse ve `utility_ntp_clock_from_controller=true` ise (varsayılan `false`, açık seçenek) `date -s` ile kontrol makinesinin saatine ayarlar. Chrony yeniden başlatma sonrası taze kaynakla saati doğruluyorsa kontrolcü saatine DOKUNULMAZ (kontrol makinesi yanlış olabilir) ve uyarılır. +- LAB testleri 21-27 (SAHTE `date` ve `chronyc`). + +### Denenmedi +- Gerçek VM'de: chrony yeniden başlatma + `makestep` ile bayat durumun düzelmesi ve `date -s` yolu. Bayat chrony nedeni varsayımdır (aşağıya bakın), LAB'da gerçek chronyd yok. + ### Değişti - `scripts/release-publish.sh`: Release ve özet doğrulamasından sonra paket indeksini günceller (`sshpocket-index/scripts/index-publish.sh`, `SSHPOCKET_INDEX_DIR`); betik yoksa etiketten önce durur; indeks hatası yayımlanmış Release'i geçersiz kılmaz. Normal sürümde `components.lock` PR'ı gerekmez. Paket içeriği değişmedi (sürüm gerektirmez). Gerçek `index-publish.sh` ile DENENMEDİ (atrap betikle test edildi). diff --git a/component.json b/component.json index f178d66..116d8a1 100644 --- a/component.json +++ b/component.json @@ -17,8 +17,8 @@ "effects": [], "requires_sudo": false, "confirm": "none", - "warnings": ["Salt okunur: saat dilimi, LANG, chrony durumu, ofseti ve gerçek sapmayı (seçili kaynak yoksa erişilebilir kaynakların farkı) okur; hiçbir şeyi değiştirmez. ntp_senkron=yes için chrony çalışırken seçili kaynak ve sapma <= 1 sn gerekir. chrony kurulu değilse ntp_servis=inactive/unknown, ofset ve sapma unknown görünür."], - "outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.durum", "keys": ["timezone", "locale", "ntp_servis", "ntp_senkron", "offset_ms", "kaynak_sayisi", "sapma_ms", "secili_kaynak"] } + "warnings": ["Salt okunur: saat dilimi, LANG, chrony durumu, ofseti ve gerçek sapmayı (seçili kaynak yoksa erişilebilir kaynakların farkı) okur; hiçbir şeyi değiştirmez. ntp_senkron=yes için chrony çalışırken seçili kaynak ve sapma <= 1 sn, ayrıca sunucu saatinin kontrol makinesinden en çok 300 sn farklı olması gerekir (saat_sapma_kontrolcuye_gore_sn; chrony snapshot/rollback sonrası bayat ve yanıltıcı olabilir). chrony kurulu değilse ntp_servis=inactive/unknown, ofset ve sapma unknown görünür."], + "outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.durum", "keys": ["timezone", "locale", "ntp_servis", "ntp_senkron", "offset_ms", "kaynak_sayisi", "sapma_ms", "secili_kaynak", "saat_sapma_kontrolcuye_gore_sn"] } }, "preview": { "cmd": "ansible-playbook playbooks/preview.yml --check --diff", @@ -28,7 +28,8 @@ "warnings": [ "Önizleme yalnızca dosya farkını ve plan adlarını gösterir: timedatectl, locale-gen, paket kurulumu ve chrony yeniden başlatması --check'te çalışmaz, gösterilmez.", "Boş bırakılan utility_timezone / utility_locale / utility_ntp_servers o alana hiç dokunulmayacağı anlamına gelir.", - "chrony çalışıyorsa ve saat 1 sn'den fazla sapmışsa plana 'saat' girer: apply tek seferlik adımlar. chrony bu koşuda kurulacaksa sapma önizlemede ölçülemez." + "chrony çalışıyorsa ve saat 1 sn'den fazla sapmışsa plana 'saat' girer: apply tek seferlik adımlar. chrony bu koşuda kurulacaksa sapma önizlemede ölçülemez. Sunucu saati kontrol makinesine göre 300 sn'den fazla farklıysa (chrony göstermese de) plana yine 'saat' girer.", + "Kontrol makinesi saati referans alınır: o yanlışsa karşılaştırma da yanlış uyarır." ], "outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.preview", "keys": ["degisen", "plan"] } }, @@ -45,6 +46,7 @@ "auditd açıksa saat dilimi değişimi 'time-change' kaydı üretir (beklenen). ufw giden trafiği reddediyorsa (hardening_ufw_outgoing_deny) NTP için 123/udp çıkışı gerekir.", "locale-gen ve chrony kurulumu gerekirse apt ile paket kurar (locales, chrony); geri_yukle paketleri kaldırmaz.", "Saat 1 sn'den fazla sapmışsa (chrony çalışıyorken ölçülür) apply kaynaklar erişilebilir olunca TEK SEFERLİK `chronyc makestep` yapar (en çok 60 sn bekler; kalıcı makestep ayarı yazılmaz). Adımlama zaman sıçramasıdır: log damgaları, cron işleri, auditd 'time-change' kaydı, ssh/TOTP/sertifika geçerliliği etkilenebilir. Kapatmak için utility_ntp_makestep=false.", + "Sunucu saati kontrol makinesinden 300 sn'den fazla farklıysa chrony önce yeniden başlatılır, makestep denenir; fark sürerse ve utility_ntp_clock_from_controller=true ise 'date -s' ile kontrol makinesinin saatine ayarlanır (chrony taze kaynakla doğruluyorsa dokunulmaz).", "Saat dilimi değişince systemd-timedated yeniden başlatılır (timedatectl önbelleği eski değeri tutmasın)." ], "outputs": { "format": "keyvalue-line", "prefix": "SSHPOCKET_RESULT utility.apply", "keys": ["degisen", "plan", "yedek"] } @@ -67,6 +69,7 @@ { "name": "utility_timezone", "type": "string", "default": "", "group": "Zaman", "description": "Saat dilimi (örn. Europe/Istanbul). Boş = saat dilimine dokunma. /usr/share/zoneinfo altında var olmalı." }, { "name": "utility_locale", "type": "string", "default": "", "group": "Dil", "description": "Sistem dili: yalnızca LANG (örn. en_US.UTF-8). Boş = dokunma. LC_* yönetilmez. Gerekirse locales kurulur ve locale üretilir." }, { "name": "utility_ntp_servers", "type": "list[string]", "default": [], "group": "NTP", "description": "NTP kaynakları (en çok 8): 'host', 'server host' ya da 'pool host'; iburst otomatik eklenir. Boş = dağıtım varsayılanı (dosya yazılmaz, chrony kurulmaz). Dağıtımın varsayılan kaynakları kalır." }, + { "name": "utility_ntp_clock_from_controller", "type": "bool", "default": false, "group": "NTP", "description": "Sunucu saati kontrol makinesine göre 300 sn'den fazla farklıysa ve chrony saati bir kaynakla doğrulayamıyorsa apply saati KONTROL MAKİNESİNİN saatine 'date -s' ile ayarlar. Kontrol makinesinin saati yanlışsa sunucu da yanlış olur. false = yalnız chrony." }, { "name": "utility_ntp_makestep", "type": "bool", "default": true, "group": "NTP", "description": "Saat 1 sn'den fazla sapmışsa apply chrony çalışırken TEK SEFERLİK 'chronyc makestep' yapar (kalıcı ayar yazılmaz). false = sapmaya dokunma." }, { "name": "utility_backup", "type": "bool", "default": true, "group": "Yedek", "description": "Değiştirmeden önce /var/backups/utility// altına yedek al. false = yedek alınmaz ve geri_yukle yeni değişikliği geri alamaz." }, { "name": "utility_restore_from", "type": "string", "default": "", "group": "Yedek", "description": "geri_yukle için yedek adı (örn. 20261105T101500Z). Boş = en son yedek." } diff --git a/playbooks/durum.yml b/playbooks/durum.yml index c0ebd43..f111101 100644 --- a/playbooks/durum.yml +++ b/playbooks/durum.yml @@ -1,5 +1,5 @@ # Salt okunur durum: saat dilimi, LANG, NTP servisi/senkron/ofset/kaynak sayısı. Hiçbir şey değiştirmez, sudo gerektirmez. -# Sonuç satırı: SSHPOCKET_RESULT utility.durum timezone=… locale=… ntp_servis=… ntp_senkron=yes|no offset_ms=… kaynak_sayisi=… sapma_ms=… secili_kaynak=yes|no +# Sonuç satırı: SSHPOCKET_RESULT utility.durum timezone=… locale=… ntp_servis=… ntp_senkron=yes|no offset_ms=… kaynak_sayisi=… sapma_ms=… secili_kaynak=yes|no saat_sapma_kontrolcuye_gore_sn= - name: utility durum (salt okunur) hosts: utility gather_facts: false @@ -56,13 +56,17 @@ - name: Read clock drift ansible.builtin.include_role: {name: ntp, tasks_from: drift} + # chrony'den bağımsız ikinci ölçüm: hedef saati - kontrol makinesi saati + - name: Compare with the controller clock + ansible.builtin.include_role: {name: ntp, tasks_from: skew} + # chronyc -c tracking alanları: 1 ref-id, 2 ad, 3 katman, 4 ref zamanı, 5 sistem saati farkı (saniye, işaretli) ... - name: Result line vars: utility_d_clock: "{{ utility_clock }}" # chrony çalışıyorsa 'senkron' = timedatectl yes + seçili kaynak + sapma <= 1 sn; çalışmıyorsa (timesyncd) timedatectl'e bakılır utility_d_synced: >- - {{ (utility_d_sync.stdout | default('') | trim == 'yes') and + {{ (utility_d_sync.stdout | default('') | trim == 'yes') and not (utility_ctrl_skew_big | bool) and (not (utility_d_clock.up | default(false)) or (utility_d_clock.selected | default(false) and utility_d_clock.drift_s | default(none) is not none and utility_d_clock.drift_s | float <= 1.0)) }} utility_d_offset: >- @@ -78,3 +82,4 @@ kaynak_sayisi={{ (utility_d_sources.stdout_lines | default([]) | length) if utility_d_sources.rc == 0 else 0 }} sapma_ms={{ ((utility_d_clock.drift_s | float * 1000) | round(1)) if utility_d_clock.drift_s | default(none) is not none else 'unknown' }} secili_kaynak={{ 'yes' if utility_d_clock.selected | default(false) else 'no' }} + saat_sapma_kontrolcuye_gore_sn={{ utility_ctrl_skew_s if utility_ctrl_skew_s | string != '' else 'unknown' }} diff --git a/roles/common/tasks/preflight.yml b/roles/common/tasks/preflight.yml index e1bdd3d..8026bd4 100644 --- a/roles/common/tasks/preflight.yml +++ b/roles/common/tasks/preflight.yml @@ -39,6 +39,7 @@ - (utility_ntp_servers | default([])) | length <= 8 - utility_backup | bool in [true, false] - utility_ntp_makestep | default(true) | bool in [true, false] + - utility_ntp_clock_from_controller | default(false) | bool in [true, false] - (utility_timezone | default('')) == '' or (utility_timezone | default('')) is match('^[A-Za-z0-9_+-]+(/[A-Za-z0-9_+-]+){0,2}$') - (utility_locale | default('')) == '' or (utility_locale | default('')) is match('^(C|POSIX|C\.UTF-8|[a-z]{2,3}_[A-Z]{2}\.(UTF-8|ISO-8859-[0-9]{1,2}))$') - >- diff --git a/roles/common/tasks/warnings.yml b/roles/common/tasks/warnings.yml index d9cac04..48115ee 100644 --- a/roles/common/tasks/warnings.yml +++ b/roles/common/tasks/warnings.yml @@ -34,7 +34,19 @@ UYARI (utility): saat {{ utility_clock.drift_s }} sn sapmalı (ölçüm: {{ utility_clock.source }}); apply chrony ile TEK SEFERLİK adımlayacak (`chronyc makestep`). Adımlama zaman sıçraması demektir: log zaman damgaları, cron işleri, auditd 'time-change' kaydı, ssh/TOTP/sertifika geçerliliği etkilenebilir. Sapma kalıcı bir sorunsa (kaynak yok, snapshot'tan açılış) önce kaynakları kontrol edin. Kalıcı 'makestep' ayarı YAZILMAZ. - when: "'saat' in utility_plan and utility_clock is defined" + when: >- + 'saat' in utility_plan and utility_clock is defined and utility_ntp_makestep | default(true) | bool and + utility_clock.drift_s | default(none) is not none and utility_clock.drift_s | float > utility_ntp_step_threshold_s | default(1.0) | float + +- name: Warn about the controller clock difference + ansible.builtin.debug: + msg: >- + UYARI (utility): sunucu saati kontrol makinesine göre {{ utility_ctrl_skew_s }} sn farklı (negatif = sunucu geride); chrony'nin kendi ölçümü + bunu göstermese de (snapshot/rollback sonrası bayat durum). apply önce chrony'yi yeniden başlatıp `makestep` dener; + {{ 'utility_ntp_clock_from_controller=true: chrony doğrulayamazsa saat kontrol makinesinin saatine ayarlanacak (`date -s`; kontrol makinesinin saati yanlışsa sunucu da yanlış olur).' + if utility_ntp_clock_from_controller | default(false) | bool else 'kontrol makinesi saatine ayarlama kapalı (utility_ntp_clock_from_controller=false).' }} + Adımlama zaman sıçramasıdır: log zaman damgaları, cron, auditd 'time-change', ssh/TOTP/sertifika geçerliliği etkilenir. + when: utility_ctrl_skew_big | default(false) | bool - name: Note chrony installation and clock drift ansible.builtin.debug: diff --git a/roles/ntp/defaults/main.yml b/roles/ntp/defaults/main.yml index 8e30466..d71975c 100644 --- a/roles/ntp/defaults/main.yml +++ b/roles/ntp/defaults/main.yml @@ -2,6 +2,10 @@ utility_ntp_servers: [] # saat sapması: eşikten büyükse apply TEK SEFERLİK `chronyc makestep` yapar (kalıcı makestep ayarı yazılmaz). false = sapmaya dokunma utility_ntp_makestep: true +# chrony saati düzeltemezse (ya da doğrulayamazsa) apply saati KONTROL MAKİNESİNİN saatine `date -s` ile ayarlar. Kontrol makinesinin saati yanlışsa sunucu yanlışlanır; varsayılan false +utility_ntp_clock_from_controller: false # iç sabitler (manifest değişkeni değil) utility_ntp_step_threshold_s: 1.0 utility_ntp_step_timeout_s: 60 +# hedef-kontrolcü saat farkı bu değeri (sn) aşarsa chrony ne derse desin sapma sayılır +utility_ntp_ctrl_threshold_s: 300 diff --git a/roles/ntp/tasks/plan.yml b/roles/ntp/tasks/plan.yml index 680611c..560f651 100644 --- a/roles/ntp/tasks/plan.yml +++ b/roles/ntp/tasks/plan.yml @@ -54,16 +54,20 @@ utility_backup_paths: "{{ utility_backup_paths + (['/etc/chrony/sources.d/utility.sources'] if utility_ntp_need_file | bool else []) }}" when: utility_ntp_need_pkg | bool or utility_ntp_need_file | bool or utility_ntp_need_service | bool -# SAAT SAPMASI (utility_ntp_servers'tan bağımsız): chrony çalışıyorsa ve sapma eşikten büyükse plana 'saat' girer; apply tek seferlik adımlar. +# SAAT SAPMASI (utility_ntp_servers'tan bağımsız): iki bağımsız ölçüm — chrony sapması ve hedef/kontrol makinesi saat farkı (skew.yml). +# Biri eşiği aşarsa plana 'saat' girer; apply düzeltir (chrony makestep ve/veya açık seçenekle kontrolcü saati). - name: Plan clock step - when: utility_ntp_makestep | bool block: - name: Read the drift ansible.builtin.include_tasks: drift.yml + - name: Compare with the controller clock + ansible.builtin.include_tasks: skew.yml + - name: Record the clock step plan ansible.builtin.set_fact: utility_plan: "{{ utility_plan + ['saat'] }}" utility_ntp_need_step: true when: >- - utility_clock.drift_s | default(none) is not none and utility_clock.drift_s | float > utility_ntp_step_threshold_s | float + (utility_ntp_makestep | bool and utility_clock.drift_s | default(none) is not none and utility_clock.drift_s | float > utility_ntp_step_threshold_s | float) + or (utility_ctrl_skew_big | bool and (utility_ntp_makestep | bool or utility_ntp_clock_from_controller | bool)) diff --git a/roles/ntp/tasks/skew.yml b/roles/ntp/tasks/skew.yml new file mode 100644 index 0000000..4aacfac --- /dev/null +++ b/roles/ntp/tasks/skew.yml @@ -0,0 +1,18 @@ +# Hedef saatini KONTROL MAKİNESİNİN (Ansible controller) saatiyle karşılaştırır; chrony'den BAĞIMSIZ ikinci ölçüm (salt okunur; --check'te de çalışır). +# utility_ctrl_skew_s = hedef epoch - kontrolcü epoch (saniye; negatif = hedef geride), ölçülemezse ''. SSH gecikmesi (saniyeler) eşiğin (300 sn) yanında önemsiz. +# Neden: chrony'nin kendi sayıları (tracking/sources) VM snapshot/rollback sonrası bayat ama içten tutarlı olabilir ve sapmayı göstermeyebilir. +- name: Read the target clock + ansible.builtin.command: date +%s + register: utility_target_epoch + changed_when: false + failed_when: false + check_mode: false + +- name: Compare with the controller clock + ansible.builtin.set_fact: + utility_ctrl_skew_s: >- + {{ (utility_target_epoch.stdout | trim | int - lookup('ansible.builtin.pipe', 'date +%s') | int) + if utility_target_epoch.rc == 0 and (utility_target_epoch.stdout | trim) is match('^[0-9]+$') else '' }} + utility_ctrl_skew_big: >- + {{ utility_target_epoch.rc == 0 and (utility_target_epoch.stdout | trim) is match('^[0-9]+$') and + ((utility_target_epoch.stdout | trim | int - lookup('ansible.builtin.pipe', 'date +%s') | int) | abs) > (utility_ntp_ctrl_threshold_s | default(300) | int) }} diff --git a/roles/ntp/tasks/step.yml b/roles/ntp/tasks/step.yml index e66a23c..7fe9efc 100644 --- a/roles/ntp/tasks/step.yml +++ b/roles/ntp/tasks/step.yml @@ -1,10 +1,15 @@ -# SAAT ADIMLAMA (yalnızca gerçek koşu): chrony yeni kaynaklarla çalışırken sapma eşikten büyükse TEK SEFERLİK `chronyc makestep`. -# Kaynaklar erişilebilir olana kadar bekler (utility_ntp_step_timeout_s); kalıcı makestep ayarı YAZILMAZ. -# chrony bu koşuda kurulduysa/değiştiyse ('paket' ya da 'ntp' planda) ölçülemese bile beklenir: erişilemeyen kaynak burada görünür. +# SAAT ADIMLAMA (yalnızca gerçek koşu). İki aşama: +# A) chrony (utility_ntp_makestep): sapma eşikten büyükse TEK SEFERLİK `chronyc makestep`; kaynaklar erişilebilir olana kadar bekler. +# chrony "sapma yok" derken kontrol makinesine göre saat çok farklıysa (snapshot/rollback sonrası bayat durum) chrony ÖNCE yeniden başlatılır. +# B) kontrolcü saati (utility_ntp_clock_from_controller, varsayılan false): A sonrası fark sürüyorsa ve chrony saati KAYNAKLA doğrulayamıyorsa +# `date -s` ile kontrol makinesinin saatine ayarlanır. Kalıcı makestep ayarı YAZILMAZ. - name: Restart chrony now if its configuration changed (before measuring) ansible.builtin.meta: flush_handlers -- name: Step the clock +- name: Compare with the controller clock (before) + ansible.builtin.include_tasks: skew.yml + +- name: Step the clock with chrony when: not ansible_check_mode and utility_ntp_makestep | bool block: - name: Measure the drift again @@ -16,10 +21,22 @@ changed_when: false failed_when: false + # bayat chrony: kendi ölçümüne göre sapma yok ama kontrolcüye göre büyük sapma var - name: Decide whether to step ansible.builtin.set_fact: utility_clock_now: "{{ (utility_clock_now_raw.stdout if utility_clock_now_raw.rc == 0 and (utility_clock_now_raw.stdout | default('')) | length > 0 else '{}') | from_json }}" utility_clock_touched: "{{ 'paket' in utility_plan or 'ntp' in utility_plan }}" + - name: Detect stale chrony state + ansible.builtin.set_fact: + utility_clock_stale: >- + {{ utility_clock_now.up | default(false) and utility_ctrl_skew_big | bool and + utility_clock_now.drift_s | default(none) is not none and utility_clock_now.drift_s | float <= utility_ntp_step_threshold_s | float }} + + - name: Restart chrony to drop stale measurements + ansible.builtin.systemd: + name: chrony + state: restarted + when: utility_clock_stale | bool - name: Wait for a usable source and step the clock once ansible.builtin.script: @@ -31,17 +48,19 @@ failed_when: >- utility_step_raw.rc != 0 or (utility_step_raw.stdout | default('{}') | from_json).error is defined or ((utility_step_raw.stdout | default('{}') | from_json).ok | default(false) | bool == false and - (utility_step_raw.stdout | default('{}') | from_json).reason | default('') == 'adimlanamadi') + (utility_step_raw.stdout | default('{}') | from_json).reason | default('') == 'adimlanamadi' and + not (utility_ntp_clock_from_controller | bool)) when: >- utility_clock_now.up | default(false) and ((utility_clock_now.drift_s | default(none) is not none and utility_clock_now.drift_s | float > utility_ntp_step_threshold_s | float) - or utility_clock_touched | bool) + or utility_clock_touched | bool or utility_clock_stale | bool) - name: Note the clock step in the plan ansible.builtin.set_fact: utility_plan: "{{ utility_plan + ['saat'] }}" when: >- - utility_step_raw is not skipped and (utility_step_raw.stdout | from_json).stepped | default(false) and 'saat' not in utility_plan + 'saat' not in utility_plan and (utility_clock_stale | bool or + (utility_step_raw is not skipped and (utility_step_raw.stdout | from_json).stepped | default(false))) - name: Report the clock step ansible.builtin.debug: @@ -53,8 +72,83 @@ - name: Warn when no source became usable ansible.builtin.debug: msg: >- - UYARI (utility): chrony'nin erişilebilir kaynağı yok ({{ utility_step_wait }} sn beklendi); saat sapması ölçülemedi ve düzeltilemedi. + UYARI (utility): chrony'nin erişilebilir kaynağı yok ({{ utility_ntp_step_timeout_s }} sn beklendi); saat sapması chrony ile ölçülemedi ve düzeltilemedi. Kaynaklara ağ erişimi (123/udp, ufw giden kuralı, DNS) ve `chronyc sources` çıktısını kontrol edin. - vars: - utility_step_wait: "{{ utility_step_timeout_s | default(utility_ntp_step_timeout_s) }}" when: utility_step_raw is not skipped and (utility_step_raw.stdout | from_json).reason | default('') == 'kaynak_yok' + +# B) kontrolcü saati — yalnızca açık seçenekle +- name: Set the clock from the controller + when: not ansible_check_mode and utility_ntp_clock_from_controller | bool + block: + - name: Compare with the controller clock (after chrony) + ansible.builtin.include_tasks: skew.yml + + - name: Remember the skew before the manual step + ansible.builtin.set_fact: + utility_ctrl_skew_before: "{{ utility_ctrl_skew_s }}" + + # chrony bu koşuda bayat bulunup YENİDEN BAŞLATILDIYSA ve şimdi taze bir kaynakla saati doğruluyorsa kontrolcüye değil chrony'ye güvenilir + - name: Check whether chrony can verify the clock against a source + ansible.builtin.script: + cmd: >- + utility_clock.py {{ {'mode': 'status', 'threshold': utility_ntp_step_threshold_s} | to_json | quote }} + executable: python3 + register: utility_clock_fin_raw + changed_when: false + failed_when: false + + - name: Decide whether the controller clock applies + ansible.builtin.set_fact: + utility_clock_fin: "{{ (utility_clock_fin_raw.stdout if utility_clock_fin_raw.rc == 0 and (utility_clock_fin_raw.stdout | default('')) | length > 0 else '{}') | from_json }}" + - name: Controller clock is the only reference + ansible.builtin.set_fact: + utility_clock_use_ctrl: >- + {{ utility_ctrl_skew_big | bool and + not (utility_clock_stale | default(false) | bool and utility_clock_fin.up | default(false) and utility_clock_fin.selected | default(false) and + utility_clock_fin.drift_s | default(none) is not none and utility_clock_fin.drift_s | float <= utility_ntp_step_threshold_s | float) }} + + - name: Set the clock to the controller time (date -s) + ansible.builtin.command: "date -u -s @{{ lookup('ansible.builtin.pipe', 'date +%s') | int }}" + register: utility_date_set + changed_when: true + when: utility_clock_use_ctrl | bool + + - name: Restart chrony after the manual step + ansible.builtin.systemd: + name: chrony + state: restarted + failed_when: false + when: utility_clock_use_ctrl | bool and (utility_clock_fin.up | default(false) | bool) + + - name: Compare with the controller clock (verify) + ansible.builtin.include_tasks: skew.yml + when: utility_clock_use_ctrl | bool + + - name: Fail when the controller clock did not stick + ansible.builtin.fail: + msg: "utility: saat kontrolcü saatine ayarlanamadı (kalan fark {{ utility_ctrl_skew_s }} sn)." + when: utility_clock_use_ctrl | bool and utility_ctrl_skew_big | bool + + - name: Note the controller step in the plan + ansible.builtin.set_fact: + utility_plan: "{{ utility_plan + ['saat'] }}" + when: utility_clock_use_ctrl | bool and 'saat' not in utility_plan + + - name: Report the controller step + ansible.builtin.debug: + msg: >- + utility: saat KONTROL MAKİNESİNİN saatine ayarlandı (`date -s`; önceki fark {{ utility_ctrl_skew_before | default('?') }} sn). Kontrol makinesinin saati + yanlışsa sunucu da yanlıştır. Log, cron ve audit kayıtlarında zaman sıçraması beklenen. + when: utility_clock_use_ctrl | bool + +# A+B sonrası fark sürüyorsa (kontrolcü seçeneği kapalı ya da chrony kaynakla doğruluyor): düzeltme yok, açık uyarı +- name: Final controller comparison + ansible.builtin.include_tasks: skew.yml + when: not ansible_check_mode + +- name: Warn about the remaining controller skew + ansible.builtin.debug: + msg: >- + UYARI (utility): sunucu saati kontrol makinesine göre hâlâ {{ utility_ctrl_skew_s }} sn farklı. chrony saati kaynakla doğruluyorsa kontrol makinesinin saati + yanlış olabilir (dokunulmadı). Aksi halde `chronyc sources -v` ve `chronyc tracking` bayat olabilir; düzeltmek için utility_ntp_clock_from_controller=true. + when: not ansible_check_mode and utility_ctrl_skew_big | bool diff --git a/test/e2e/lab.bats b/test/e2e/lab.bats index cce1fca..ecfdfd6 100644 --- a/test/e2e/lab.bats +++ b/test/e2e/lab.bats @@ -174,7 +174,7 @@ stub_on() { stub_on run "$RUN" durum [ "$status" -eq 0 ] - [[ "$(result)" == *" ntp_senkron=no "* && "$(result)" == *" sapma_ms=1152060000.0 secili_kaynak=no" ]] + [[ "$(result)" == *" ntp_senkron=no "* && "$(result)" == *" sapma_ms=1152060000.0 secili_kaynak=no saat_sapma_kontrolcuye_gore_sn="* ]] } @test "saat sapması: preview 'saat' planlar ve hiçbir şey adımlamaz" { @@ -204,7 +204,7 @@ stub_on() { run lab sh -c 'wc -l < /run/utility-stub/steps' [ "$output" = 1 ] run "$RUN" durum - [[ "$(result)" == *" ntp_senkron=yes "* && "$(result)" == *" secili_kaynak=yes" ]] + [[ "$(result)" == *" ntp_senkron=yes "* && "$(result)" == *" secili_kaynak=yes saat_sapma_kontrolcuye_gore_sn="* ]] } @test "saat sapması: makestep düzeltmezse apply net hatayla düşer" { @@ -215,4 +215,73 @@ stub_on() { lab rm -f /usr/local/bin/chronyc } +# --- kontrolcü saatiyle karşılaştırma (GERÇEK VM bulgusu: chrony bayat durumda sapmayı göstermedi). SAHTE date + SAHTE chronyc --- +ctrl_on() { # $1=skew sn, $2=chrony: stale (seçili, ofset~0) | none (kaynak yok) + docker cp "$BATS_TEST_DIRNAME/lab/chronyc-stub.sh" utility-lab:/usr/local/bin/chronyc + docker cp "$BATS_TEST_DIRNAME/lab/date-stub.sh" utility-lab:/usr/local/bin/date + lab sh -c "mkdir -p /run/utility-stub && cd /run/utility-stub && echo $1 > skew && rm -f steps datesets" + if [ "$2" = stale ]; then lab sh -c 'cd /run/utility-stub && echo 0.0002 > offset && echo 377 > reach && echo 1 > selected' + else lab sh -c 'cd /run/utility-stub && echo 0 > offset && echo 0 > reach && echo 0 > selected'; fi +} +ctrl_off() { lab rm -f /usr/local/bin/chronyc /usr/local/bin/date; } + +@test "kontrolcü farkı: chrony 'sapma yok' dese de durum ntp_senkron=no ve farkı gösterir" { + ctrl_on -1123200 stale + run "$RUN" durum + [ "$status" -eq 0 ] + [[ "$(result)" == *" sapma_ms=0.2 "* && "$(result)" == *" ntp_senkron=no "* ]] + [[ "$(result)" =~ saat_sapma_kontrolcuye_gore_sn=-11232[0-9][0-9]$ ]] || [[ "$(result)" =~ saat_sapma_kontrolcuye_gore_sn=-11231[0-9][0-9]$ ]] +} + +@test "kontrolcü farkı: preview 'saat' planlar, uyarır, hiçbir şey değiştirmez" { + run "$RUN" preview --check + [ "$status" -eq 0 ] + [ "$(result)" = "SSHPOCKET_RESULT utility.preview degisen=1 plan=saat" ] + [[ "$output" == *"kontrol makinesine göre"* && "$output" == *"utility_ntp_clock_from_controller=false"* ]] + run lab sh -c 'test ! -e /run/utility-stub/datesets && echo temiz' + [ "$output" = temiz ] +} + +@test "kontrolcü farkı: makestep=false ve seçenek kapalıyken plan boş, yalnız uyarı" { + run "$RUN" preview --check -e utility_ntp_makestep=false + [ "$(result)" = "SSHPOCKET_RESULT utility.preview degisen=0 plan=yok" ] + [[ "$output" == *"kontrol makinesine göre"* ]] +} + +@test "kontrolcü farkı: chrony taze kaynakla doğruluyorsa kontrolcü saatine DOKUNULMAZ (seçenek açık olsa bile)" { + run "$RUN" apply -e utility_ntp_clock_from_controller=true + [ "$status" -eq 0 ] + [[ "$output" == *"hâlâ"* ]] + run lab sh -c 'test ! -e /run/utility-stub/datesets && echo temiz' + [ "$output" = temiz ] +} + +@test "kontrolcü farkı: kaynak yoksa seçenek açıkken date -s ile kontrolcü saatine ayarlar; ikinci apply boş" { + ctrl_on -1123200 none + run "$RUN" apply -e utility_ntp_clock_from_controller=true + [ "$status" -eq 0 ] + [ "$(result)" = "SSHPOCKET_RESULT utility.apply degisen=1 plan=saat yedek=hayir" ] + [[ "$output" == *"KONTROL MAKİNESİNİN saatine ayarlandı"* ]] + run lab sh -c 'wc -l < /run/utility-stub/datesets' + [ "$output" = 1 ] + run "$RUN" apply -e utility_ntp_clock_from_controller=true + [ "$(result)" = "SSHPOCKET_RESULT utility.apply degisen=0 plan=yok yedek=hayir" ] +} + +@test "kontrolcü farkı: seçenek kapalıyken kaynak yoksa saat DEĞİŞMEZ, açık uyarı verir" { + ctrl_on -1123200 none + run "$RUN" apply + [ "$status" -eq 0 ] + [[ "$output" == *"hâlâ"* ]] + run lab sh -c 'test ! -e /run/utility-stub/datesets && echo temiz' + [ "$output" = temiz ] +} + +@test "kontrolcü farkı: eşik altı fark (60 sn) planlanmaz" { + ctrl_on -60 none + run "$RUN" preview --check + [ "$(result)" = "SSHPOCKET_RESULT utility.preview degisen=0 plan=yok" ] + ctrl_off +} + teardown_file() { "$BATS_TEST_DIRNAME/lab/down.sh" --clean >&3 || true; } diff --git a/test/e2e/lab/date-stub.sh b/test/e2e/lab/date-stub.sh new file mode 100755 index 0000000..cf06b2e --- /dev/null +++ b/test/e2e/lab/date-stub.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# LAB için SAHTE date: konteynerde saat ayarlanamaz. /run/utility-stub/skew (sn) kadar kaydırılmış epoch verir; `-s @N` kaymayı sıfırlar ve datesets'e yazar. +# Yalnızca utility'nin kontrolcü-karşılaştırma ve `date -s` akışını sınar. Gerçek saat adımlamayı DEĞİL. +S=/run/utility-stub; skew=$(cat "$S/skew" 2>/dev/null || echo 0) +case "$*" in + "+%s") echo $(( $(/bin/date +%s) + skew )) ;; + "-u -s @"*) echo "$*" >> "$S/datesets"; echo 0 > "$S/skew" ;; + *) exec /bin/date "$@" ;; +esac