From da302c6d698ede8982b84df5a81ac3eb52aa7094 Mon Sep 17 00:00:00 2001 From: fontvielle Date: Thu, 22 Oct 2026 12:16:57 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9B=D0=B5=D0=BD=D1=82=D0=B0=20=D1=81=D0=BE?= =?UTF-8?q?=D0=B1=D1=8B=D1=82=D0=B8=D0=B9:=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BE=20=D1=81=D1=80=D0=BE=D0=BA=D0=B0?= =?UTF-8?q?=D1=85=20=D1=87=D1=83=D0=B6=D0=B8=D1=85=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit После запуска (вместе с первой проверкой сроков) из ленты и главной уходят уведомления «Сертификат ключа организации…» по сертификатам не нашей организации и «Сертификат НРД/биржи в справочнике ИШ…» — на 9 их 22, все по чужим (Сбербанк и др.). Свои остаются. RuntimeConfig.RemoveNews. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q7eREaYwUt1i4fNLZcGnjn --- internal/lkgateway/certexpiry.go | 32 +++++++++++++++++++++++++ internal/lkgateway/onboarding_test.go | 34 +++++++++++++++++++++++++++ internal/lkgateway/runtimeconfig.go | 21 +++++++++++++++++ 3 files changed, 87 insertions(+) diff --git a/internal/lkgateway/certexpiry.go b/internal/lkgateway/certexpiry.go index 600f54d..c04c940 100644 --- a/internal/lkgateway/certexpiry.go +++ b/internal/lkgateway/certexpiry.go @@ -220,6 +220,37 @@ func certExpiryText(c NSDStoreCert, kind string, days int, until time.Time) (str who, until.Format("02.01.2006")) } +var certNewsSerialRe = regexp.MustCompile(`серийный номер ([0-9A-Fa-f]+)`) + +// certNewsCleanup — убрать из ленты уведомления о сроках чужих сертификатов (до 22.10.2026 «своим» считался +// любой сертификат участника в справочнике ИШ) и сертификатов НРД/биржи: остаются только по своим. +func certNewsCleanup(rc *RuntimeConfig) { + s := rc.Snapshot() + if strings.TrimSpace(ourOrgINN(s)) == "" || len(s.NSDCerts.LastStore) == 0 { + return // не знаем, чьи сертификаты наши, — ничего не трогаем + } + own := map[string]bool{} + for _, c := range ownStoreCerts(s) { + own[normSerial(c.Serial)] = true + } + n := rc.RemoveNews(func(it NewsItem) bool { + if it.Kind != "system" || !strings.HasPrefix(it.Title, "Сертификат ") { + return false + } + if strings.Contains(it.Title, "в справочнике ИШ") { + return true // сертификаты НРД/биржи — больше не уведомляем + } + if !strings.HasPrefix(it.Title, "Сертификат ключа организации") { + return false + } + m := certNewsSerialRe.FindStringSubmatch(it.Body) + return m != nil && !own[normSerial(m[1])] + }) + if n > 0 { + log.Printf("cert-expiry: из ленты убрано %d уведомлений о сроках чужих сертификатов", n) + } +} + // StartCertExpiryWatch — проверка сроков раз в 6 часов (первая — через 3 минуты после запуска). func StartCertExpiryWatch(rc *RuntimeConfig) func() { stop := make(chan struct{}) @@ -231,6 +262,7 @@ func StartCertExpiryWatch(rc *RuntimeConfig) func() { case <-stop: return case <-t.C: + certNewsCleanup(rc) certExpiryTick(rc) t.Reset(6 * time.Hour) } diff --git a/internal/lkgateway/onboarding_test.go b/internal/lkgateway/onboarding_test.go index da8de81..a4075d7 100644 --- a/internal/lkgateway/onboarding_test.go +++ b/internal/lkgateway/onboarding_test.go @@ -837,3 +837,37 @@ func TestKeyInstallProblem(t *testing.T) { } } } + +// Из ленты уходят уведомления о сроках чужих сертификатов и сертификатов НРД/биржи; свои остаются. +func TestCertNewsCleanup(t *testing.T) { + rc, err := NewRuntimeConfig(filepath.Join(t.TempDir(), "setup.json")) + if err != nil { + t.Fatal(err) + } + nsd := rc.Snapshot().NSD + nsd.OrgINN = "7703807489" + _ = rc.UpdateNSD(nsd) + certs := rc.Snapshot().NSDCerts + certs.LastStore = []NSDStoreCert{ + {Kind: "x509", Subject: "O=ООО ИК Фонтвьель,CN=Лапшина,INN=007703807489", SubjectCN: "Лапшина", Serial: "AA"}, + {Kind: "x509", Subject: "O=ПАО Сбербанк,CN=Исакова,INN=007707083893", SubjectCN: "Исакова", Serial: "BB"}, + } + _ = rc.UpdateNSDCerts(certs) + for i, it := range []NewsItem{ + {Title: "Сертификат ключа организации истекает через 30 дн.", Body: "Сертификат «Лапшина» (издатель «УЦ», серийный номер AA) действует"}, + {Title: "Сертификат ключа организации истекает сегодня", Body: "Сертификат «Исакова» (издатель «УЦ», серийный номер BB) действует"}, + {Title: "Сертификат Московская Биржа в справочнике ИШ истекает через 3 дн.", Body: "…"}, + {Title: "Служба «служба ключей Валидаты» не работает", Body: "…"}, + } { + it.ID, it.Kind = fmt.Sprintf("n%d", i), "system" + _ = rc.AddNews(it) + } + certNewsCleanup(rc) + var titles []string + for _, it := range rc.Snapshot().News.Items { + titles = append(titles, it.Title) + } + if len(titles) != 2 || !strings.Contains(strings.Join(titles, "|"), "30 дн.") || !strings.Contains(strings.Join(titles, "|"), "Валидаты") { + t.Fatalf("осталось: %q", titles) + } +} diff --git a/internal/lkgateway/runtimeconfig.go b/internal/lkgateway/runtimeconfig.go index 3c8358c..ba6e632 100644 --- a/internal/lkgateway/runtimeconfig.go +++ b/internal/lkgateway/runtimeconfig.go @@ -664,6 +664,27 @@ func (r *RuntimeConfig) AddNews(item NewsItem) error { return r.save() } +// RemoveNews удаляет из ленты события, для которых drop вернул true; сколько удалено. +func (r *RuntimeConfig) RemoveNews(drop func(NewsItem) bool) int { + r.mu.Lock() + kept := r.data.News.Items[:0:0] + for _, it := range r.data.News.Items { + if !drop(it) { + kept = append(kept, it) + } + } + n := len(r.data.News.Items) - len(kept) + if n > 0 { + r.data.News.Items = kept + r.data.UpdatedAt = time.Now() + } + r.mu.Unlock() + if n > 0 { + _ = r.save() + } + return n +} + // DismissNews помечает новость скрытой по ID (не удаляет — для аудита). func (r *RuntimeConfig) DismissNews(id string) error { r.mu.Lock()