Files
Bridge-and-Join-s/internal/lkgateway/lkclient.go
T
zuevav 7ce4c2c79f Защита рецептов: SQL DEPO/SPECTRE/QUIK вынесен в пакет fsql под garble
Реализация варианта B из аудита защиты знаний. Прямой garble всего bj-server
ломает html/template (он обращается к полям структур по имени, garble их
переименовывает). Решение — селективная обфускация: GOGARBLE обфусцирует ТОЛЬКО
пакет с секретами, шаблоны и обработчики не затрагиваются.

95 SQL-запросов к DEPO/SPECTRE/QUIK и трансферным таблицам FTV_M2M вынесены из
lkgateway в отдельный пакет internal/lkgateway/fsql как переменные (именно var,
не const: const компилятор вставил бы обратно в необфусцируемый пакет, и
шифрование бы не сработало — проверено). Сами обработчики, типы и параметры
sql.Named остались на месте; пакет держит только строки запросов.

build-release.sh собирает bj-server с `GOGARBLE=.../fsql garble -literals`
(нужен отдельный SDK go1.26 — модуль-кэшный тулчейн garble не подходит); если
garble/go1.26 не найдены — обычная сборка с предупреждением.

Проверено на реальном garble-бинаре (запускается, /admin и страницы на
структурах работают): рецепт чтения чужих систем из бинаря исчез —
OD_RESTS 13→0, OD_WIRING 5→0, OD_TURNS 1→0, OD_BALANCES 3→0, D_B_CONTRACTS 1→0,
OD_CLIENTS_PORTS 10→0, DepoLimits/MoneyLimits →0. Остаток FTV_M2M — упоминания
имён таблиц в операторских embed-шаблонах и логах, не сам рецепт.

Стенд собран обычной сборкой (без garble) — трассировки остаются читаемыми.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4YVo4UMopqT5DzicqpPjL
2026-09-18 20:29:47 +03:00

116 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package lkgateway
// lkclient.go — как один и тот же клиент называется в разных системах.
//
// В учётных записях кабинета клиент задан номером договора, как в DEPO: «БР-1545», «БРИИС-514».
// В экспорте QUIK тот же клиент — код латиницей без дефиса: BR1545, BRIIS514; внебиржевой
// раздел — с суффиксом OUT. В SPECTRE договор — строка D_B_CONTRACTS.NAME вида «БР-1545 ФИО».
import (
"context"
"database/sql"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
"strings"
"sync"
"time"
)
// lkContractNumber — номер договора без ФИО: «БР-1545 Иванов Иван» → «БР-1545».
func lkContractNumber(contract string) string {
c := strings.TrimSpace(contract)
if i := strings.IndexByte(c, ' '); i > 0 {
c = c[:i]
}
return strings.ToUpper(c)
}
// lkQuikCodes — коды клиента в QUIK по номеру договора: основной и внебиржевой раздел.
func lkQuikCodes(contract string) []string {
num := lkContractNumber(contract)
if num == "" {
return nil
}
repl := strings.NewReplacer("-", "", "А", "A", "Б", "B", "В", "V", "Д", "D", "И", "I", "С", "S", "Р", "R", "У", "U", "Т", "T", "К", "K", "М", "M", "Н", "N", "О", "O", "Е", "E", "П", "P", "Ф", "F", "Ц", "C")
code := repl.Replace(num)
return []string{code, code + "OUT"}
}
// spectreContract — договор клиента в SPECTRE.
type spectreContract struct {
DOC int64
Investor int64
Name string
Open time.Time
Closed bool
}
var spectreContractCache = struct {
mu sync.Mutex
m map[string]spectreContract
at map[string]time.Time
}{m: map[string]spectreContract{}, at: map[string]time.Time{}}
// spectreOpen — подключение к копии SPECTRE тем же логином, что и к DEPO.
func spectreOpen(s FancyMSSQLSettings) (*sql.DB, error) {
db := strings.TrimSpace(s.SpectreDB())
return fancyOpen(s, db)
}
// SpectreDB — база SPECTRE рядом с DEPO: по умолчанию BJ_SPECTRE_DATA.
func (s FancyMSSQLSettings) SpectreDB() string {
if v := strings.TrimSpace(s.SpectreDatabase); v != "" {
return v
}
return "BJ_SPECTRE_DATA"
}
// spectreContractFor — договор SPECTRE по номеру договора клиента (кэш 10 минут).
func spectreContractFor(ctx context.Context, s FancyMSSQLSettings, contract string) (spectreContract, bool) {
num := lkContractNumber(contract)
if num == "" || strings.TrimSpace(s.Server) == "" {
return spectreContract{}, false
}
spectreContractCache.mu.Lock()
if c, ok := spectreContractCache.m[num]; ok && time.Since(spectreContractCache.at[num]) < 10*time.Minute {
spectreContractCache.mu.Unlock()
return c, c.DOC != 0
}
spectreContractCache.mu.Unlock()
conn, err := spectreOpen(s)
if err != nil {
return spectreContract{}, false
}
defer conn.Close()
cctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
var c spectreContract
var open, closed sql.NullTime
var state sql.NullInt64
err = conn.QueryRowContext(cctx, fsql.Cl01, sql.Named("n", num)).
Scan(&c.DOC, &c.Investor, &c.Name, &open, &closed, &state)
if err == nil {
c.Name = strings.TrimSpace(c.Name)
if open.Valid {
c.Open = open.Time
}
c.Closed = closed.Valid && closed.Time.Year() < 9000 && closed.Time.Before(time.Now())
}
spectreContractCache.mu.Lock()
spectreContractCache.m[num], spectreContractCache.at[num] = c, time.Now()
spectreContractCache.mu.Unlock()
return c, err == nil && c.DOC != 0
}
// lkClientName — ФИО клиента для показа: из учётной записи, иначе из номера договора,
// где учётная система хранит номер вместе с ФИО («БР-719 Аминов Вячеслав Маркович»).
func lkClientName(u LKUser) string {
if n := strings.TrimSpace(u.FullName); n != "" {
return n
}
c := strings.TrimSpace(u.Contract)
if i := strings.IndexByte(c, ' '); i > 0 {
return strings.TrimSpace(c[i:])
}
return ""
}