Защита рецептов: 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
This commit is contained in:
zuevav
2026-09-18 20:29:47 +03:00
parent 47f5015c3f
commit 7ce4c2c79f
49 changed files with 605 additions and 332 deletions
+11 -70
View File
@@ -19,6 +19,7 @@ import (
"context"
"database/sql"
"fmt"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway/fsql"
"sort"
"strings"
"sync"
@@ -63,15 +64,7 @@ func lkArchivePortfolio(ctx context.Context, s FancyMSSQLSettings, contract stri
today := time.Now()
isPast := day.Before(time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC))
rows, err := conn.QueryContext(cctx, `SELECT r.ID, b.NUM_S, ISNULL(b.SYS_NAME,''), b.NUM_1, ISNULL(r.REG_2,0), ISNULL(r.VALUE_ID,0),
CAST(ISNULL(r.A,0) AS float), CAST(ISNULL(r.D,0) AS float), CAST(ISNULL(r.EQ_D,0) AS float),
ISNULL(vs.ISIN,''), ISNULL(vs.NAME,''), ISNULL(vc.SYSNAME,''), ISNULL(sh.CLASS,0), CAST(ISNULL(sh.NOMINAL,0) AS float)
FROM OD_RESTS r JOIN OD_BALANCES b ON b.ID = r.BAL_ACC
LEFT JOIN OD_VALUES vs ON vs.ID = r.REG_2
LEFT JOIN OD_VALUES vc ON vc.ID = r.VALUE_ID
LEFT JOIN OD_SHARES sh ON sh.ID = r.REG_2
WHERE r.REG_3 = @c AND b.IS_BAL = 1 AND b.PART = 39 AND b.NUM_1 IN (50,51,52,55,58)
ORDER BY b.NUM_1, vs.NAME`, sql.Named("c", c.DOC))
rows, err := conn.QueryContext(cctx, fsql.Arch01, sql.Named("c", c.DOC))
if err != nil {
return nil, fmt.Errorf("остатки: %v", err)
}
@@ -91,12 +84,7 @@ func lkArchivePortfolio(ctx context.Context, s FancyMSSQLSettings, contract stri
for _, r := range rests {
ids = append(ids, fmt.Sprint(r.id))
}
trows, err := conn.QueryContext(cctx, fmt.Sprintf(`SELECT REST,
CAST(SUM(CASE WHEN TYPE_ = 1 THEN AMOUNT_ ELSE -AMOUNT_ END) AS float),
CAST(SUM(CASE WHEN TYPE_ = 1 THEN VALUE_ ELSE -VALUE_ END) AS float),
CAST(SUM(CASE WHEN TYPE_ = 1 THEN EQ_ ELSE -EQ_ END) AS float)
FROM OD_TURNS WHERE REST IN (%s) AND WIRDATE >= @d AND IS_PLAN = 'F' AND YEAR(WIRDATE) < 9000
GROUP BY REST`, strings.Join(ids, ",")), sql.Named("d", day.AddDate(0, 0, 1)))
trows, err := conn.QueryContext(cctx, fmt.Sprintf(fsql.Arch02, strings.Join(ids, ",")), sql.Named("d", day.AddDate(0, 0, 1)))
if err != nil {
return nil, fmt.Errorf("обороты: %v", err)
}
@@ -209,7 +197,7 @@ func spectreAsOf(ctx context.Context, conn *sql.DB) time.Time {
return spectreAsOfCache.v
}
var d sql.NullTime
_ = conn.QueryRowContext(ctx, `SELECT MAX(R_DATE) FROM OD_DOCS WHERE YEAR(R_DATE) < 9000`).Scan(&d)
_ = conn.QueryRowContext(ctx, fsql.Arch03).Scan(&d)
v := time.Time{}
if d.Valid {
v = time.Date(d.Time.Year(), d.Time.Month(), d.Time.Day(), 0, 0, 0, 0, time.UTC)
@@ -253,9 +241,7 @@ func spectreKind(class int) string {
func spectreQuote(ctx context.Context, conn *sql.DB, secID int64, day time.Time) (float64, int) {
var ofic sql.NullTime
var rate3, pfr, closeP sql.NullFloat64
err := conn.QueryRowContext(ctx, `SELECT TOP 1 OFICDATE, CAST(RATE3 AS float), CAST(PFR_PRICE AS float), CAST(CLOSE_ AS float)
FROM OD_VALUES_RATES WHERE VALUE_ID = @v AND OFICDATE <= @d AND (RATE3 > 0 OR PFR_PRICE > 0 OR CLOSE_ > 0)
ORDER BY OFICDATE DESC`, sql.Named("v", secID), sql.Named("d", day.AddDate(0, 0, 1))).Scan(&ofic, &rate3, &pfr, &closeP)
err := conn.QueryRowContext(ctx, fsql.Arch04, sql.Named("v", secID), sql.Named("d", day.AddDate(0, 0, 1))).Scan(&ofic, &rate3, &pfr, &closeP)
if err != nil {
return 0, 1 << 30
}
@@ -287,7 +273,7 @@ func spectreFx(ctx context.Context, conn *sql.DB, day time.Time) func(curID int6
return v
}
var rate sql.NullFloat64
_ = conn.QueryRowContext(ctx, `SELECT TOP 1 CAST(RATE AS float) FROM OD_VALUES_RATES WHERE VALUE_ID = @v AND MARKET = 0 AND OFICDATE <= @d AND RATE > 0 ORDER BY OFICDATE DESC`,
_ = conn.QueryRowContext(ctx, fsql.Arch05,
sql.Named("v", curID), sql.Named("d", day.AddDate(0, 0, 1))).Scan(&rate)
v := rate.Float64
if v <= 0 {
@@ -355,8 +341,7 @@ func lkArchiveOperationsLoad(ctx context.Context, s FancyMSSQLSettings, contract
cctx, cancel := context.WithTimeout(ctx, 40*time.Second)
defer cancel()
// Счета бумаг (ПОРТФЕЛЬ) и денег (касса) договора.
rows, err := conn.QueryContext(cctx, `SELECT r.ID, b.NUM_1, ISNULL(b.SYS_NAME,'') FROM OD_RESTS r JOIN OD_BALANCES b ON b.ID = r.BAL_ACC
WHERE r.REG_3 = @c AND b.PART = 39 AND ((b.NUM_1 = 58 AND b.SYS_NAME LIKE N'ПОРТФЕЛЬ%') OR b.NUM_1 IN (50,51,52,55))`, sql.Named("c", c.DOC))
rows, err := conn.QueryContext(cctx, fsql.Arch06, sql.Named("c", c.DOC))
if err != nil {
return nil, err
}
@@ -404,29 +389,7 @@ func lkArchiveOperationsLoad(ctx context.Context, s FancyMSSQLSettings, contract
// разбирать список счетов на каждой строке журнала — год данных читался
// 6–7 секунд, сейчас — десятки миллисекунд (оба индекса по D_REST/K_REST
// с WIRDATE отрабатывают поиском).
wrows, err := conn.QueryContext(cctx, fmt.Sprintf(`
WITH w AS (
SELECT wr.WIRDATE, wr.EQ_, wr.D_REST, wr.K_REST, wr.D_SUMMA, wr.K_SUMMA, wr.D_AMOUNT, wr.K_AMOUNT, wr.NAME,
wr.D_REST AS sec_rest, wr.K_REST AS money_rest
FROM OD_WIRING wr
WHERE wr.D_REST IN (%[1]s) AND wr.IS_PLAN = 'F' AND YEAR(wr.WIRDATE) < 9000
AND wr.WIRDATE >= @f AND wr.WIRDATE < @t AND (wr.D_AMOUNT <> 0 OR wr.K_AMOUNT <> 0)
UNION ALL
SELECT wr.WIRDATE, wr.EQ_, wr.D_REST, wr.K_REST, wr.D_SUMMA, wr.K_SUMMA, wr.D_AMOUNT, wr.K_AMOUNT, wr.NAME,
wr.K_REST, wr.D_REST
FROM OD_WIRING wr
WHERE wr.K_REST IN (%[1]s) AND wr.D_REST NOT IN (%[1]s) AND wr.IS_PLAN = 'F' AND YEAR(wr.WIRDATE) < 9000
AND wr.WIRDATE >= @f AND wr.WIRDATE < @t AND (wr.D_AMOUNT <> 0 OR wr.K_AMOUNT <> 0))
SELECT w.WIRDATE, CAST(ISNULL(w.EQ_,0) AS float), w.D_REST, w.K_REST,
CAST(ISNULL(w.D_SUMMA,0) AS float), CAST(ISNULL(w.K_SUMMA,0) AS float),
CAST(ISNULL(w.D_AMOUNT,0) AS float), CAST(ISNULL(w.K_AMOUNT,0) AS float),
ISNULL(w.NAME,''), ISNULL(sv.NAME,''), ISNULL(sv.ISIN,''), ISNULL(mv.SYSNAME,'')
FROM w
LEFT JOIN OD_RESTS sr ON sr.ID = w.sec_rest
LEFT JOIN OD_VALUES sv ON sv.ID = sr.REG_2
LEFT JOIN OD_RESTS mr ON mr.ID = w.money_rest
LEFT JOIN OD_VALUES mv ON mv.ID = mr.VALUE_ID
ORDER BY w.WIRDATE`, in), sql.Named("f", fromDay), sql.Named("t", toEnd))
wrows, err := conn.QueryContext(cctx, fmt.Sprintf(fsql.Arch07, in), sql.Named("f", fromDay), sql.Named("t", toEnd))
if err != nil {
return nil, fmt.Errorf("проводки: %v", err)
}
@@ -501,14 +464,7 @@ func spectreCashOps(ctx context.Context, conn *sql.DB, cashIDs []string, fromDay
}
var out []LKOperationRow
in := strings.Join(cashIDs, ",")
crows, err := conn.QueryContext(ctx, fmt.Sprintf(`SELECT w.WIRDATE, CAST(ISNULL(w.EQ_,0) AS float), w.D_REST, w.K_REST,
CAST(ISNULL(w.D_SUMMA,0) AS float), CAST(ISNULL(w.K_SUMMA,0) AS float), ISNULL(w.NAME,''), ISNULL(mv.SYSNAME,'')
FROM OD_WIRING w
JOIN OD_RESTS mr ON mr.ID = CASE WHEN w.D_REST IN (%[1]s) THEN w.D_REST ELSE w.K_REST END
LEFT JOIN OD_VALUES mv ON mv.ID = mr.VALUE_ID
WHERE (w.D_REST IN (%[1]s) OR w.K_REST IN (%[1]s)) AND w.IS_PLAN = 'F' AND YEAR(w.WIRDATE) < 9000
AND w.WIRDATE >= @f AND w.WIRDATE < @t
ORDER BY w.WIRDATE`, in), sql.Named("f", fromDay), sql.Named("t", toEnd))
crows, err := conn.QueryContext(ctx, fmt.Sprintf(fsql.Arch08, in), sql.Named("f", fromDay), sql.Named("t", toEnd))
if err != nil {
return nil
}
@@ -571,7 +527,7 @@ func spectreDealKind(name string, onDebit bool) string {
// spectreProceeds — выручка от продаж по (ISIN|день): [сумма в валюте, сумма в рублях].
func spectreProceeds(ctx context.Context, conn *sql.DB, doc int64, from, to time.Time) map[string][2]float64 {
out := map[string][2]float64{}
rows, err := conn.QueryContext(ctx, `SELECT r.ID FROM OD_RESTS r JOIN OD_BALANCES b ON b.ID = r.BAL_ACC WHERE r.REG_3 = @c AND b.SYS_NAME = N'ВЫРУЧКА'`, sql.Named("c", doc))
rows, err := conn.QueryContext(ctx, fsql.Arch09, sql.Named("c", doc))
if err != nil {
return out
}
@@ -589,22 +545,7 @@ func spectreProceeds(ctx context.Context, conn *sql.DB, doc int64, from, to time
return out
}
in := strings.Join(ids, ",")
wrows, err := conn.QueryContext(ctx, fmt.Sprintf(`
WITH w AS (
SELECT wr.WIRDATE, wr.EQ_, wr.D_REST, wr.K_REST, wr.D_SUMMA, wr.K_SUMMA, wr.D_REST AS sec_rest
FROM OD_WIRING wr
WHERE wr.K_REST IN (%[1]s) AND wr.IS_PLAN = 'F' AND YEAR(wr.WIRDATE) < 9000
AND wr.WIRDATE >= @f AND wr.WIRDATE < @t AND wr.EQ_ <> 0
UNION ALL
SELECT wr.WIRDATE, wr.EQ_, wr.D_REST, wr.K_REST, wr.D_SUMMA, wr.K_SUMMA, wr.K_REST
FROM OD_WIRING wr
WHERE wr.D_REST IN (%[1]s) AND wr.K_REST NOT IN (%[1]s) AND wr.IS_PLAN = 'F' AND YEAR(wr.WIRDATE) < 9000
AND wr.WIRDATE >= @f AND wr.WIRDATE < @t AND wr.EQ_ <> 0)
SELECT w.WIRDATE, CAST(ISNULL(w.EQ_,0) AS float), w.D_REST, w.K_REST,
CAST(ISNULL(w.D_SUMMA,0) AS float), CAST(ISNULL(w.K_SUMMA,0) AS float), ISNULL(sv.ISIN,'')
FROM w
LEFT JOIN OD_RESTS sr ON sr.ID = w.sec_rest
LEFT JOIN OD_VALUES sv ON sv.ID = sr.REG_2`, in), sql.Named("f", from), sql.Named("t", to))
wrows, err := conn.QueryContext(ctx, fmt.Sprintf(fsql.Arch10, in), sql.Named("f", from), sql.Named("t", to))
if err != nil {
return out
}