Тестовую базу из продуктива делают все. Не демо-набор из трёх номенклатур, а свежую копию боевой: с её объёмами, её грязью и её пограничными случаями. Иначе не воспроизвести баг, который живёт только на реальных данных.
Вместе с объёмами в копию едет и всё остальное: ФИО, ИНН, адреса, телефоны, банковские реквизиты, зарплатные начисления. Дальше копия расходится по разработчикам и тестовым серверам, иногда доезжает до чьего-то ноутбука, и живёт там месяцами. Знакомая картина?
Я прошёл этот путь на нескольких базах, и вот что оказалось обиднее всего: почти все дыры вскрыла одна из них - свежая копия продуктива. Две базы до неё отработали чисто и убедили меня, что механизм готов. Ниже три дыры, и все три про то, как живая база отказывается быть похожей на представление о ней.
Почему нельзя просто затереть
Прежде чем писать своё, я честно перебрал очевидные пути.
| Вариант | Почему не сработало |
|---|---|
| Пустая или демо-база | Половина задач вслепую не решается: ни объёмов, ни грязи, ни краевых случаев |
| Доступ к боевой через VPN | Данные всё равно перед глазами, плюс администрирование доступов и чужое неудобное окружение |
| Ручная зачистка полей | Повторять на каждую копию, ломается ссылочность, и главное - необратимо |
| Синтетическая генерация | Трудоёмко, и синтетика не воспроизводит те самые краевые случаи, из-за которых баги и возникают |
Самый популярный пункт здесь третий, и он же самый неприятный. Затёртые данные назад не вернёшь, а значит результат, полученный на такой базе, потом не с чем сверить: бухгалтерия не проверит расчёт, потому что цифры в базе уже не те. Отсюда требование, с которого всё началось: данные должны становиться бессмысленными для постороннего, но возвращаться обратно.
Инженерно это псевдонимизация, а не обезличивание: пока существует файл соответствий, процесс обратим. Разница не терминологическая. Файл соответствий - это ключ, и хранить его надо как сами данные.
Первая дыра: поле, которое никто не искал
Определение класса данных начинается с имени реквизита. Увидел в имени ИНН - завёл правило. На двух базах подряд этого хватало.
На третьей идентификатор физлица звали ИдентификационныйКодЛичности. Ни одной из искомых подстрок в этом имени нет. Правило не создалось, реквизит в разбор не попал, и живые двенадцатизначные номера остались лежать в базе, которая по отчёту была полностью обезличена.
Отчёт при этом не соврал. Он честно перечислил всё, что заменил. Просто у отчёта нет графы «что я не понял».
Отсюда правило для любого, кто обезличивает базу чем угодно, хоть своим скриптом: после прогона надо открыть базу и посмотреть глазами. Не отчёт. Базу.
Та же дыра, только тихая, получается под ограничением доступа на уровне записей. Прогон под неполными правами увидит подмножество строк, обезличит его и отчитается зелёным - а остальное останется как было. Права надо проверять на входе и без полных просто не запускаться.
Вторая дыра: восемь миллионов кодов, похожих на ИНН
Обратная ошибка того же механизма, и вылезла она из оценки времени. Сканер набрал десятки таблиц и честно показал до старта: работы примерно на сто часов. Четверо суток. Срок волновал меньше. Интересовало, откуда столько.
Больше восьми миллионов записей дал один справочник товарных кодов. Персональных данных там нет ни одной. Он попал в работу из-за десятизначных кодов вида 3202900001, которые формально выглядят как ИНН юрлица.
Лечится контрольной суммой: у настоящего ИНН она сходится, у товарного кода обычно нет. Именно обычно - контрольная цифра одна, и случайное десятизначное число проходит её примерно в одном случае из десяти. То есть сумма снимает девять десятых шума. Оставшуюся десятую добирает уже проверка содержимого.
Но ровно здесь пришлось принять решение, которым я до сих пор не доволен.
// Слишком строго - пропустим настоящие персональные данные: в живых базах // номер с опечаткой не редкость, а он всё равно остаётся номером человека. // Слишком мягко - обезличим товарные коды. // // Разводим по длине. 12 цифр - формат ИНН физлица, посторонние коды такой // длины встречаются редко, берём даже с несошедшейся суммой. // 10 цифр - формат ИНН юрлица, но именно в эту длину попадают товарные // и складские коды, поэтому здесь требуем контрольную сумму. Если СтрДлина(Цифры) = 12 Тогда Возврат Истина; КонецЕсли; Возврат КонтрольнаяСуммаСходится(Цифры);
То есть для двенадцатизначных контроль сознательно ослаблен. Считаю, что размен правильный: лишняя подмена стоит мусора в тестовой базе, пропуск стоит утечки.
Слабое место этого решения нашлось быстро, и не там, где я ждал. Штрихкод UPC-A - ровно двенадцать цифр, и лежит он в справочнике штрихкодов любой торговой базы сотнями тысяч записей. Так что «посторонние коды такой длины встречаются редко» - утверждение, которое живёт ровно до первой розничной конфигурации. Отдельной галочки, чтобы выключить подмену двенадцатизначных, я не сделал, и это, наверное, зря.
Третья дыра: двенадцать тысяч имён на пятьдесят восемь тысяч человек
Эта нашлась не как ошибка. Она нашлась как «что-то сломалось»: на справочнике физлиц скорость упала в шесть раз и дальше не восстанавливалась.
Ничего не сломалось. Кончился словарь. Генератор ФИО собирал значение из конечного набора: 30 фамилий на 20 имён на 20 отчеств. Двенадцать тысяч комбинаций на пол, двадцать четыре на всю базу - при 58 124 живых людях в справочнике.
Дальше арифметика неумолимая. Больше половины людей в базе физически не могли получить уникальное имя. Генератор честно пытался: пересоливал значение и проверял снова, до полусотни попыток на человека. А после пятидесятой молча отдавал занятое имя второму человеку. До двенадцати одинаковых ФИО на разных людей в одной базе.
Обратимость от этого не страдала - журнал хранит связь по каждому объекту, вернулось всё. Страдало правдоподобие. А вместе с ним доверие: разработчик, который видит в справочнике двенадцать одинаковых Ковалёвых, начинает подозревать всю копию целиком.
Вывод шире, чем про словари. Качество синтетики - это инженерная задача. Словарь я расширил, и при исчерпании теперь добавляется видимый различитель («Ковалёв Алексей Фёдорович 2») вместо тихого дубля. Выглядит искусственно, зато разные люди остаются разными людьми.
Что из этого следует: имя реквизита врёт
Три истории выше - один и тот же дефект в трёх видах. Вся конструкция стояла на допущении, что имя реквизита достаточно надёжно говорит о содержимом. Допущение экономное, и на первых базах оно держалось.
Оно неверно, и врёт в обе стороны сразу:
ИдентификационныйКодЛичности- по имени не догадаешься, внутри персональные данные;- справочник товарных кодов - по имени тоже не догадаешься, зато содержимое по формату прошло как ИНН;
- справочник новостей с реквизитом, в имени которого есть «ИНН», а внутри идентификатор записи;
НаименованиеПолноеу контрагента-юрлица - имя обещает имя человека, внутри название организации. Синтетика превратила две компании в граждан с нормальными фамилиями.
Правильный признак - заглянуть внутрь. Взять из реквизита сотню значений и посмотреть, на что они похожи: если это цифры с точками, короткие и без пробелов, то перед нами коды, и класс имён назначать нельзя, как бы реквизит ни назывался.
Но честно скажу, где эта конструкция всё ещё дырявая. Заглядывание внутрь спасает от ложных срабатываний, а от пропуска не спасает: значения смотрят только у тех реквизитов, которые уже прошли отбор по имени. То есть первую дыру я закрыл дописав пять строк в тот самый список маркеров, который только что объявил ненадёжным. Никакой новой архитектуры. Честнее было бы читать содержимое всех строковых реквизитов подряд, но на базе в сто тысяч объектов это другая цена прогона, и я на неё пока не пошёл.
И ещё одно место, где я сделал прямо противоположное собственному правилу. Маркеры СерияДокумента и НомерДокумента пришлось из списка убрать: они цеплялись за НомерДокументаМодернизации у основных средств, а номер хозяйственного документа подменять нельзя, он часть учёта. Только серия и номер документа - это ещё и паспорт. Одно ложное срабатывание я разменял на класс настоящих персональных данных, и правильное решение тут - исключение по конкретному объекту метаданных. Удалять маркер целиком было ошибкой, и пока она не исправлена.
Двадцать две секунды
Самая дорогая по времени отладки история дня оказалась не про данные вовсе.
Прогон отработал, а вердикт был красный: файлы соответствий «не найдены». Их не было по тому пути, который обработка сама же показала пользователю. На диске при этом всё лежало - в соседнем каталоге, отличавшемся на 22 секунды: на экране ...154420, на диске ...154442.
Имя рабочей папки вычислялось дважды, из текущего времени, в двух разных местах кода. Между вычислениями прошли те самые 22 секунды. Если путь показывается пользователю, он должен быть вычислен один раз и запомнен, иначе показанный путь и фактический - разные вещи, и расходятся они ровно тогда, когда что-то идёт не так.
Рядом стоит история страшнее. Рабочая копия файла соответствий на сервере затиралась после выгрузки на клиент, а признаком успешной выгрузки считался непустой результат вызова. Проверять надо было по факту: существуют ли файлы и не нулевого ли они размера. Единственный ключ к данным нельзя удалять по косвенному признаку.
Из той же оперы главный инвариант, до которого я дошёл не сразу: соответствие «исходное - псевдоним» пишется до фиксации транзакции, которая ставит псевдоним в базу. Порядок выбран так, чтобы при обрыве в журнале соответствий оставались лишние строки. Такое чинится дедупликацией при возобновлении. Обратная ошибка - псевдоним в базе без записи в журнале - не чинится ничем. Полной гарантии тут, впрочем, не даст никто - буферизация файла и журнал транзакций СУБД живут своей жизнью, и на разных машинах в клиент-серверной схеме тем более. Но направление ошибки выбирается сознательно, и это половина дела.
Чего обезличивание не закрывает в принципе
Суммы, количества и даты остаются настоящими. Иначе база перестаёт быть похожей на себя, оборотно-сальдовая не сходится, и работать с копией нельзя. Но отсюда следует, что идентификация по совокупности косвенных признаков остаётся возможной: у кого ещё в этом месяце была ровно такая начисленная сумма.
Часть данных лежит в двоичном виде. Версии объектов, история данных, содержимое присоединённых файлов, секреты в безопасном хранилище - подменить там нечего, можно только удалить, и обратного хода по ним не будет.
Журнал регистрации средствами встроенного языка не чистится. Совсем. Это ручной шаг администратора: остановить сервер и удалить файлы журнала. Оговорка важная: журнал живёт отдельными файлами в каталоге кластера, а не внутри базы, поэтому в бэкапе СУБД и в выгрузке dt его нет. Риск возникает, только если копию отдают каталогом целиком - но тогда вместе с ней уезжают имена и действия живых пользователей.
Когда база уходит наружу
Всё выше про тестовую базу внутри периметра. Отдельная история начинается, когда копия уезжает подрядчику, и там обратимость перестаёт быть удобством.
Тестовую базу вы в конце просто удаляете. А доработку, сделанную внешней командой, надо принять: проверить расчёт на настоящих цифрах, сверить итоги, показать бухгалтерии. Если данные затёрты, сверять не с чем - вы принимаете работу на суррогате и надеетесь, что на боевых всё повторится. Обратимость превращает приёмку из «поверим» в «проверим».
И утечку ищут у того, чьё имя стоит на базе, а не у того, кто её потерял. Подрядчик может быть сколь угодно аккуратным, разбираться будете вы. Поэтому вопрос решается на входе - тем, что вообще уезжает наружу.
Практический минимум при передаче:
- файл соответствий не уезжает вместе с копией, никогда, ни в том же архиве, ни следом;
- пароль на него нигде не хранится - потеряли пароль, потеряли обратный ход;
- перед отправкой копию открывают и смотрят глазами;
- прогон повторяется на каждую новую копию: старый файл соответствий к новой копии не подойдёт;
- и прямой ход, и возврат гоняются в монопольном режиме, с выключенными регламентными заданиями. Объект, который кто-то поменял между двумя ходами, вернуть уже нечем.
Всё описанное выше делает обработка, она лежит отдельной публикацией: Анонимизатор базы данных PRO. Сразу про деньги, чтобы карточка не открывалась в ожидании файла за стартмани: это Маркетплейс, 42 700 руб. с НДС, что входит в цену и как продлеваются обновления - написано там же. Внутри ровно то, что разобрано выше: проход по всей базе, подмена персональных данных правдоподобной синтетикой с сохранением длин и контрольных сумм, журнал соответствий и обратный ход по нему. Ограничения из раздела "Чего обезличивание не закрывает в принципе" действуют и для неё, я их не обхожу.
Другие наши инструменты диагностики 1С:
- Карта объёмов базы 1С - с чего начинается решение «что вообще обезличивать»: какие таблицы крупные и куда ушли гигабайты.
- Матрица прав доступа для нейросети - кто из пользователей что видит в базе.
- Аудит паролей СУБД - тот же периметр с другой стороны: до кого дотягивается пароль от базы.
Вступайте в нашу телеграмм-группу Инфостарт