Безопасное xранилище данных 1с

Любой уважающий себя администратор сети может с уверенностью сказать что хранение паролей у него находится на высоком уровне. Как минимум они не будут их хранить у себя на рабочем столе компьютера или в виде записок на мониторах. Чего к сожалению нельзя сказать о программистах 1с. Зачастую они не уделяют этому вопросу должного внимания.
Практически во всех конфигурациях, что я встречал, механизмы (не типовые, а написанные чудо-разработчиками), требующие авторизация пользователя, выглядят примерно вот так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Процедура ПлохиеПримеры() //хранение в модуле Пользователь = "Какой-то пользователь"; Пароль = "XYUvamANeParol"; FTPСоединение = Новый FTPСоединение("craft1c.ru", 21, Пользователь, Пароль, , , , , УровеньИспользованияЗащищенногоСоединенияFTP.НеИспользовать); //В реквизитах формы, справочник с реквизитами "Пользователь", "Пароль" ЭлементСпр = Справочники.Пароли.НайтиПоНаименованию("Мой самый важный пароль"); FTPСоединение = Новый FTPСоединение("craft1c.ru", 21, ЭлементСпр.Пользователь, ЭлементСпр.Пароль, , , , , УровеньИспользованияЗащищенногоСоединенияFTP.НеИспользовать); // и т.д. КонецПроцедуры |
Конечно любой скажет: “Да какая разница, главное что работает, а доступ в конфигурацию имею только я! Да и кому нужен этот пароль или логин!. За последние 10 лет никто ничего не взламывал и еще 100 лет проработает!”. В принципе тут тяжело поспорить: и эта машина будет ехать. Но лучше все таки придерживаться стандартов и идти в ногу со временем.
На всем известном сайте https://its.1c.ru этой проблеме уделена целая статья “Безопасное хранение паролей”. Все нюансы и тонкости можете изучить в этой статье, здесь же остановимся на основных её принципах реализации.
Это решение есть во многих конфигурациях, в которое она перешла из конфигурации “Библиотека стандартных подсистем”.
Объект конфигурации. Все доступы хранятся в регистре сведений “БезопасноеХранилищеДанных”, где измерение “Владелец” может принимать типы “Строка, СправочникСсылка, ПланОбменаСсылка”, а ресурс “Данные” это тип “ХранилищеЗначения”. Причем по алгоритму в этом хранилище значений у нас будет лежать структура:

Функции и процедуры. Работа с этим регистров сведений построена на 1 функции “ПрочитатьДанныеИзБезопасногоХранилища” и 2 процедур “ЗаписатьДанныеВБезопасноеХранилище”, “УдалитьДанныеИзБезопасногоХранилища”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
#Область БезопасноеХранилище //////////////////////////////////////////////////////////////////////////////// // Процедуры и функции для работы с хранилищем паролей. // Записывает конфиденциальные данные в безопасное хранилище. // Вызывающий код должен самостоятельно устанавливать привилегированный режим. // // Безопасное хранилище недоступно для чтения пользователям (кроме администраторов), // а доступно только коду, который делает обращения только к своей части данных и // в том контексте, который предполагает чтение или запись конфиденциальных данных. // // Параметры: // Владелец - ПланОбменаСсылка, СправочникСсылка, Строка - ссылка на объект информационной базы, // представляющий объект-владелец сохраняемого пароля или строка до 128 символов. // Для объектов других типов в качестве владельца рекомендуется использовать ссылку на // элемент метаданных этого типа в справочнике ИдентификаторыОбъектовМетаданных // или ключ в виде строки с учетом имен подсистем. // Например, для БСП: // Владелец = ОбщегоНазначения.ИдентификаторОбъектаМетаданных("РегистрСведений.АдресныеОбъекты"); // если нужно 1 хранилище на подсистему БСП: // Владелец = "СтандартныеПодсистемы.УправлениеДоступом"; // если нужно более 1 хранилища на подсистему БСП: // Владелец = "СтандартныеПодсистемы.УправлениеДоступом.<Уточнение>"; // // Данные - Произвольный - данные помещаемые в безопасное хранилище. Неопределенно - удаляет все данные. // Для удаления данных по ключу следует использовать процедуру УдалитьДанныеИзБезопасногоХранилища. // Ключ - Строка - ключ сохраняемых настроек, по умолчанию "Пароль". // Ключ должен соответствовать правилам, установленным для идентификаторов: // * Первым символом ключа должна быть буква или символ подчеркивания (_). // * Каждый из последующих символов может быть буквой, цифрой или символом подчеркивания (_). // // Пример: // Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи) // Если ТекущийПользовательМожетИзменятьПароль Тогда // УстановитьПривилегированныйРежим(Истина); // ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище(ТекущийОбъект.Ссылка, Логин, "Логин"); // ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище(ТекущийОбъект.Ссылка, Пароль); // УстановитьПривилегированныйРежим(Ложь); // КонецЕсли; // КонецПроцедуры // Процедура ЗаписатьДанныеВБезопасноеХранилище(Владелец, Данные, Ключ = "Пароль") Экспорт ОбщегоНазначенияКлиентСервер.Проверить(ЗначениеЗаполнено(Владелец), СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку( НСтр("ru = 'Недопустимое значение параметра %1 в %2. |параметр должен содержать ссылку; передано значение: %3 (тип %4).'"), "Владелец", "ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище", Владелец, ТипЗнч(Владелец))); ОбщегоНазначенияКлиентСервер.Проверить(ТипЗнч(Ключ) = Тип("Строка"), СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку( НСтр("ru = 'Недопустимое значение параметра %1 в %2. |параметр должен содержать строку; передано значение: %3 (тип %4).'"), "Ключ", "ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище", Ключ, ТипЗнч(Ключ))); ЭтоОбластьДанных = РазделениеВключено() И ДоступноИспользованиеРазделенныхДанных(); Если ЭтоОбластьДанных Тогда БезопасноеХранилищеДанных = РегистрыСведений.БезопасноеХранилищеДанныхОбластейДанных.СоздатьМенеджерЗаписи(); Иначе БезопасноеХранилищеДанных = РегистрыСведений.БезопасноеХранилищеДанных.СоздатьМенеджерЗаписи(); КонецЕсли; БезопасноеХранилищеДанных.Владелец = Владелец; БезопасноеХранилищеДанных.Прочитать(); Если Данные <> Неопределено Тогда Если БезопасноеХранилищеДанных.Выбран() Тогда ДанныеДляСохранения = БезопасноеХранилищеДанных.Данные.Получить(); Если ТипЗнч(ДанныеДляСохранения) <> Тип("Структура") Тогда ДанныеДляСохранения = Новый Структура(); КонецЕсли; ДанныеДляСохранения.Вставить(Ключ, Данные); ДанныеДляХранилищеЗначения = Новый ХранилищеЗначения(ДанныеДляСохранения, Новый СжатиеДанных(6)); БезопасноеХранилищеДанных.Данные = ДанныеДляХранилищеЗначения; БезопасноеХранилищеДанных.Записать(); Иначе ДанныеДляСохранения = Новый Структура(Ключ, Данные); ДанныеДляХранилищеЗначения = Новый ХранилищеЗначения(ДанныеДляСохранения, Новый СжатиеДанных(6)); БезопасноеХранилищеДанных.Данные = ДанныеДляХранилищеЗначения; БезопасноеХранилищеДанных.Владелец = Владелец; БезопасноеХранилищеДанных.Записать(); КонецЕсли; Иначе БезопасноеХранилищеДанных.Удалить(); КонецЕсли; КонецПроцедуры // Возвращает данные из безопасного хранилища. // Вызывающий код должен самостоятельно устанавливать привилегированный режим. // // Безопасное хранилище недоступно для чтения пользователям (кроме администраторов), // а доступно только коду, который делает обращения только к своей части данных и // в том контексте, который предполагает чтение или запись конфиденциальных данных. // // Параметры: // Владелец - ПланОбменаСсылка, СправочникСсылка, Строка - ссылка на объект информационной базы, // представляющий объект-владелец сохраняемого пароля или строка до 128 символов. // Ключи - Строка - содержит список имен сохраненных данных, указанных через запятую. // ОбщиеДанные - Булево - Истина, если требуется в модели сервиса получить данные из общих данных в разделенном режиме. // // Возвращаемое значение: // Произвольный, Структура, Неопределенно - данные из безопасного хранилища. Если указан один ключ, // то возвращается его значение, иначе структура. // Если данные отсутствуют - Неопределенно. // // Пример: // Процедура ПриЧтенииНаСервере(ТекущийОбъект) // // Если ТекущийПользовательМожетИзменятьПароль Тогда // УстановитьПривилегированныйРежим(Истина); // Логин = ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища(ТекущийОбъект.Ссылка, "Логин"); // Пароль = ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища(ТекущийОбъект.Ссылка); // УстановитьПривилегированныйРежим(Ложь); // Иначе // Элементы.ГруппаЛогинИПароль.Видимость = Ложь; // КонецЕсли; // // КонецПроцедуры // Функция ПрочитатьДанныеИзБезопасногоХранилища(Владелец, Ключи = "Пароль", ОбщиеДанные = Неопределено) Экспорт ОбщегоНазначенияКлиентСервер.Проверить(ЗначениеЗаполнено(Владелец), СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку( НСтр("ru = 'Недопустимое значение параметра %1 в %2. |параметр должен содержать ссылку; передано значение: %3 (тип %4).'"), "Владелец", "ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища", Владелец, ТипЗнч(Владелец))); Если РазделениеВключено() И ДоступноИспользованиеРазделенныхДанных() Тогда Если ОбщиеДанные = Истина Тогда ИмяБезопасноеХранилищеДанных = "БезопасноеХранилищеДанных"; Иначе ИмяБезопасноеХранилищеДанных = "БезопасноеХранилищеДанныхОбластейДанных"; КонецЕсли; Иначе ИмяБезопасноеХранилищеДанных = "БезопасноеХранилищеДанных"; КонецЕсли; Результат = ДанныеИзБезопасногоХранилища(Владелец, ИмяБезопасноеХранилищеДанных, Ключи); Если Результат <> Неопределено И Результат.Количество() = 1 Тогда Возврат ?(Результат.Свойство(Ключи), Результат[Ключи], Неопределено); КонецЕсли; Возврат Результат; КонецФункции // Удаляет конфиденциальные данные в безопасное хранилище. // Вызывающий код должен самостоятельно устанавливать привилегированный режим. // // Безопасное хранилище недоступно для чтения пользователям (кроме администраторов), // а доступно только коду, который делает обращения только к своей части данных и // в том контексте, который предполагает чтение или запись конфиденциальных данных. // // Параметры: // Владелец - ПланОбменаСсылка, СправочникСсылка, Строка - ссылка на объект информационной базы, // представляющий объект-владелец сохраняемого пароля или строка до 128 символов. // Ключи - Строка - содержит список имен удаляемых данных, указанных через запятую. // Неопределено - удаляет все данные. // // Пример: // Процедура ПередУдалением(Отказ) // // // Проверка значения свойства ОбменДанными.Загрузка отсутствует, так как удалять данные // // из безопасного хранилища нужно даже при удалении объекта при обмене данными. // // УстановитьПривилегированныйРежим(Истина); // ОбщегоНазначения.УдалитьДанныеИзБезопасногоХранилища(Ссылка); // УстановитьПривилегированныйРежим(Ложь); // // КонецПроцедуры // Процедура УдалитьДанныеИзБезопасногоХранилища(Владелец, Ключи = Неопределено) Экспорт ОбщегоНазначенияКлиентСервер.Проверить(ЗначениеЗаполнено(Владелец), СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку( НСтр("ru = 'Недопустимое значение параметра %1 в %2. |параметр должен содержать ссылку; передано значение: %3 (тип %4).'"), "Владелец", "ОбщегоНазначения.УдалитьДанныеИзБезопасногоХранилища", Владелец, ТипЗнч(Владелец))); Если РазделениеВключено() И ДоступноИспользованиеРазделенныхДанных() Тогда БезопасноеХранилищеДанных = РегистрыСведений.БезопасноеХранилищеДанныхОбластейДанных.СоздатьМенеджерЗаписи(); Иначе БезопасноеХранилищеДанных = РегистрыСведений.БезопасноеХранилищеДанных.СоздатьМенеджерЗаписи(); КонецЕсли; БезопасноеХранилищеДанных.Владелец = Владелец; БезопасноеХранилищеДанных.Прочитать(); Если ТипЗнч(БезопасноеХранилищеДанных.Данные) = Тип("ХранилищеЗначения") Тогда ДанныеДляСохранения = БезопасноеХранилищеДанных.Данные.Получить(); Если Ключи <> Неопределено И ТипЗнч(ДанныеДляСохранения) = Тип("Структура") Тогда СписокКлючей = СтрРазделить(Ключи, ",", Ложь); Если БезопасноеХранилищеДанных.Выбран() И СписокКлючей.Количество() > 0 Тогда Для каждого КлючДляУдаления Из СписокКлючей Цикл Если ДанныеДляСохранения.Свойство(КлючДляУдаления) Тогда ДанныеДляСохранения.Удалить(КлючДляУдаления); КонецЕсли; КонецЦикла; ДанныеДляХранилищеЗначения = Новый ХранилищеЗначения(ДанныеДляСохранения, Новый СжатиеДанных(6)); БезопасноеХранилищеДанных.Данные = ДанныеДляХранилищеЗначения; БезопасноеХранилищеДанных.Записать(); Возврат; КонецЕсли; КонецЕсли; КонецЕсли; БезопасноеХранилищеДанных.Удалить(); КонецПроцедуры #КонецОбласти |
Плюс одна дополнительная функция “ДанныеИзБезопасногоХранилища”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#Область БезопасноеХранилище Функция ДанныеИзБезопасногоХранилища(Владелец, ИмяБезопасноеХранилищеДанных, Ключ) Результат = Новый Структура(Ключ); Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ | БезопасноеХранилищеДанных.Данные КАК Данные |ИЗ | РегистрСведений." + ИмяБезопасноеХранилищеДанных + " КАК БезопасноеХранилищеДанных |ГДЕ | БезопасноеХранилищеДанных.Владелец = &Владелец"; Запрос.УстановитьПараметр("Владелец", Владелец); РезультатЗапроса = Запрос.Выполнить().Выбрать(); Если РезультатЗапроса.Следующий() Тогда Если ЗначениеЗаполнено(РезультатЗапроса.Данные) Тогда СохраненныеДанные = РезультатЗапроса.Данные.Получить(); Если ЗначениеЗаполнено(СохраненныеДанные) Тогда ЗаполнитьЗначенияСвойств(Результат, СохраненныеДанные); КонецЕсли; КонецЕсли; КонецЕсли; Возврат Результат; КонецФункции #КонецОбласти |
Вот в принципе и вся архитектура хранения паролей. Один из важных нюансов, описанных в статье на сайте 1с: “Установка привилегированного режима производится непосредственно перед вызовом функций, а не внутри них, что бы исключить получение или запись любых паролей в сеансе с любыми правами“.
Используя этот механизм можно построить свою схему установки паролей, чтения и т.д. Ну и что бы стало совсем уж все понятно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
&НаСервере Процедура ТестНаСервере() // Вставить содержимое обработчика. УстановитьПривилегированныйРежим(Истина); ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище("CRM_ВыгрузкаЦенFTP", "СекретныйПользователь" , "Пользователь"); ОбщегоНазначения.ЗаписатьДанныеВБезопасноеХранилище("CRM_ВыгрузкаЦенFTP", "СамЫЙ123КрутойПароль7UpChakChak" , "Пароль"); Пароли = ОбщегоНазначения.ПрочитатьДанныеИзБезопасногоХранилища("CRM_ВыгрузкаЦенFTP", "Пользователь, Пароль"); Если Пароли <> Неопределено Тогда Сообщить(Пароли.Пользователь); Сообщить(Пароли.Пароль); КонецЕсли; ОбщегоНазначения.УдалитьДанныеИзБезопасногоХранилища("CRM_ВыгрузкаЦенFTP", "Пользователь, Пароль"); УстановитьПривилегированныйРежим(Ложь); КонецПроцедуры |
Так же можете посмотреть и решение в типовой конфигурации “Библиотека стандартных подсистем”, скачав её с сайта 1с или по этой ссылке craft1c_Демонстрационная конфигурация Библиотека стандартных подсистем, редакция 3.0 (3.0.2.264).
Надеюсь что благодаря этому механизму я все меньше и меньше буду видеть логины и пароли в модулях конфигураций. Всем удачи! Программируйте красиво!
о! самое время разобраться с такими паролями в своей конфе.