S3 в 1С — это работа с объектным хранилищем прямо из кода конфигурации: сложить скан, вложение или выгрузку в бакет и забрать обратно по HTTP. Запрос «s3 1с» и «1с s3 хранилище» почти всегда про одно и то же — как из 1С programmatically положить двоичные данные в S3-совместимое хранилище и получить список объектов, не поднимая для этого сервер взаимодействия. Ниже — два рабочих способа с реальным кодом BSL: прямой вызов S3 REST с ручной подписью AWS SigV4 и вариант через локальный микросервис-компаньон.
Сразу разведём два похожих сценария, чтобы не искать код не в той статье. Если вам нужно хранилище файлов сервера взаимодействия (обсуждения, вложения в чатах 1С) — это настраивается не кодом, а SQL-скриптом в таблице storage_server: об этом отдельный разбор — сервер взаимодействия 1С + Amazon S3. Здесь же речь про программную работу с S3 из прикладного кода: ваша обработка сама кладёт и читает объекты бакета.
Два способа подключить S3 к 1С
S3 — это HTTP REST API поверх бакетов. Проблема одна: каждый запрос надо подписать по алгоритму AWS Signature Version 4, иначе облако ответит 403 SignatureDoesNotMatch. Отсюда две стратегии:
- Прямой REST + SigV4 в BSL. 1С сама строит подпись и ходит в
https://{bucket}.s3.amazonaws.com. Ноль внешних зависимостей, но подпись придётся реализовать побайтово. - Микросервис-компаньон. Рядом с 1С крутится маленький Python-сервис (с готовым SDK boto3), а 1С шлёт ему простой JSON. Подпись берёт на себя SDK, но появляется отдельный процесс.
Что выгоднее в вашем случае — зависит от платформы кластера 1С, требований к проду и сложности сценариев. Переключите вкладки и сравните по пунктам:
Способ 1. Прямой вызов S3 REST с подписью SigV4
Это «академический», но самый автономный вариант: никаких exe и портов, только штатное HTTPСоединение. Вся сложность — в правильном формировании подписи. Разберём её по шагам, а потом соберём в код.
Начинается всё с канонического запроса и строки для подписи. Порядок строк и заголовков здесь фиксирован спецификацией — расхождение на один символ ломает подпись:
&НаСервере
Функция СформироватьStringToSign(ТекущаяДата)
short_date = Формат(ТекущаяДата, "ДФ=yyyyMMdd");
dateISO8601 = Формат(ТекущаяДата, "ДФ=yyyyMMddTHHmmssZ");
// Хеш пустого тела: для GET без содержимого это константа SHA-256("")
ХешТела = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
CanonicalRequest =
"GET" + Символы.ПС +
"/" + Символы.ПС +
"prefix=" + EncodeURL(Папка + "/") + Символы.ПС + // canonical query
"host:" + Bucket + ".s3.amazonaws.com" + Символы.ПС + // canonical headers
"x-amz-content-sha256:" + ХешТела + Символы.ПС +
"x-amz-date:" + dateISO8601 + Символы.ПС +
"" + Символы.ПС +
"host;x-amz-content-sha256;x-amz-date" + Символы.ПС + // signed headers
ХешТела;
StringToSign =
"AWS4-HMAC-SHA256" + Символы.ПС +
dateISO8601 + Символы.ПС +
short_date + "/" + Region + "/s3/aws4_request" + Символы.ПС +
SHA256Hex(CanonicalRequest);
Возврат StringToSign;
КонецФункции
Функция SHA256Hex — это штатный SHA-256 с представлением результата в hex через фабрику XDTO (проверенный приём, работает на любой платформе):
&НаСервере
Функция SHA256Hex(Строка)
Хеширование = Новый ХешированиеДанных(ХешФункция.SHA256);
Хеширование.Добавить(Строка);
ТипHex = ФабрикаXDTO.Тип("http://www.w3.org/2001/XMLSchema", "hexBinary");
Возврат НРег(ФабрикаXDTO.Создать(ТипHex, Хеширование.ХешСумма).ЛексическоеЗначение);
КонецФункции
Дальше — самое тонкое место: вывод ключа подписи. Секретный ключ не подписывает строку напрямую. Из него четырьмя раундами HMAC-SHA256 выводится одноразовый kSigning, привязанный к дате, региону и сервису. В учебном исходнике это сделано через COM-объект Windows:
// ⚠️ COM-вариант — работает ТОЛЬКО на Windows-сервере 1С
Криптография = Новый COMОбъект("System.Security.Cryptography.HMACSHA256");
Текст = Новый COMОбъект("System.Text.UTF8Encoding");
Криптография.Key = Текст.GetBytes_4("AWS4" + СекретныйКлюч);
kDate = Криптография.ComputeHash_2(Текст.GetBytes_4(short_date));
Криптография.Key = kDate;
kRegion = Криптография.ComputeHash_2(Текст.GetBytes_4(Region));
// ... и так далее до kSigning
Проблема очевидна: COM-объект System.Security.Cryptography есть только в Windows. На Linux-кластере 1С (а это большинство продовых серверов) такой код упадёт. Начиная с платформы 8.3.21 HMAC считается штатной глобальной функцией HMAC() — она кроссплатформенная, и именно её стоит использовать в новых проектах:
&НаСервере
Функция ВДвоичные(Стр)
Возврат ПолучитьДвоичныеДанныеИзСтроки(Стр, "UTF-8", Ложь);
КонецФункции
&НаСервере
Функция ВHex(Данные) // ДвоичныеДанные → hex-строка
ТипHex = ФабрикаXDTO.Тип("http://www.w3.org/2001/XMLSchema", "hexBinary");
Возврат НРег(ФабрикаXDTO.Создать(ТипHex, Данные).ЛексическоеЗначение);
КонецФункции
&НаСервере
Функция ПодписатьSigV4(СекретныйКлюч, short_date, StringToSign)
// 64 — размер блока SHA-256; результат каждого HMAC становится ключом следующего
kDate = HMAC(ВДвоичные("AWS4" + СекретныйКлюч), ВДвоичные(short_date), ХешФункция.SHA256, 64);
kRegion = HMAC(kDate, ВДвоичные(Region), ХешФункция.SHA256, 64);
kService = HMAC(kRegion, ВДвоичные("s3"), ХешФункция.SHA256, 64);
kSigning = HMAC(kService, ВДвоичные("aws4_request"),ХешФункция.SHA256, 64);
signature = HMAC(kSigning, ВДвоичные(StringToSign), ХешФункция.SHA256, 64);
Возврат ВHex(signature);
КонецФункции
Если платформа старше 8.3.21 или вы хотите код, гарантированно переносимый на Linux, штатный HMAC() можно заменить собственной реализацией HMAC на БуферДвоичныхДанных — классический алгоритм с ipad/opad (0x36/0x5C) через ЗаписатьПобитовоеИсключительноеИли и двойное ХешированиеДанных. Это чистый BSL, без COM и без требований к версии; именно так сделано в зрелой продовой версии этого клиента. Внешне функция та же — принимает ключ и данные, возвращает `ДвоичныеДанные`, — поэтому цепочка вывода ключа из шага 3 не меняется.
Осталось собрать заголовок Authorization и отправить запрос. Обратите внимание: секретный ключ по сети не уходит — в заголовке только публичный AccessKeyId, область действия и подпись:
&НаСервере
Функция ПолучитьСписокФайлов()
Соединение = Новый HTTPСоединение(Bucket + ".s3.amazonaws.com", , , , , 30);
// x-amz-date ОБЯЗАНА быть в UTC — берём универсальное время, а не локальное
ТекДата = ТекущаяУниверсальнаяДата();
short_date = Формат(ТекДата, "ДФ=yyyyMMdd");
dateISO8601 = Формат(ТекДата, "ДФ=yyyyMMddTHHmmssZ");
ХешТела = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
Signature = ПодписатьSigV4(СекретныйКлюч, short_date, СформироватьStringToSign(ТекДата));
Authorization =
"AWS4-HMAC-SHA256 " +
"Credential=" + AccessKeyID + "/" + short_date + "/" + Region + "/s3/aws4_request," +
"SignedHeaders=host;x-amz-content-sha256;x-amz-date," +
"Signature=" + Signature;
Заголовки = Новый Соответствие;
Заголовки.Вставить("Authorization", Authorization);
Заголовки.Вставить("Host", Bucket + ".s3.amazonaws.com");
Заголовки.Вставить("x-amz-date", dateISO8601);
Заголовки.Вставить("x-amz-content-sha256", ХешТела);
Запрос = Новый HTTPЗапрос("/?prefix=" + Папка + "/", Заголовки);
Ответ = Соединение.ВызватьHTTPМетод("GET", Запрос);
Если Ответ.КодСостояния = 200 Тогда
Возврат Ответ.ПолучитьТелоКакСтроку(); // XML со списком объектов
КонецЕсли;
Возврат Неопределено;
КонецФункции
Для загрузки файла в бакет принцип тот же, но метод PUT, а в x-amz-content-sha256 кладётся уже реальный SHA-256 тела файла (не константа пустой строки), и тело передаётся как ДвоичныеДанные. Точно так же собираются HEAD (проверить существование объекта) и DELETE (удалить) — меняется только HTTP-метод в подписи и запросе, вся обвязка подписи общая. В продовом варианте эти четыре операции (Put/Get/Head/Delete) выносят в один переиспользуемый общий модуль, а ключи доступа и регион держат не в коде, а в регистре сведений с ограничением прав на чтение — тогда секрет не виден в конфигурации и не утекает в выгрузку.
Способ 2. Через локальный микросервис-компаньон
Если переписывать SigV4 на BSL не хочется, а сценарии сложнее «положил-забрал» (multipart-загрузка, пресайн-ссылки, ретраи) — подпись отдают готовому SDK. Рядом с 1С запускается маленький Python-сервис s3server на 127.0.0.1:9988, а 1С общается с ним простым JSON. 1С здесь — тонкий UI поверх внешнего сервиса:
&НаСервере
Функция ВыполнитьHTTPЗапрос(s3method, Метод = "POST", КлючиМетода = Неопределено)
Попытка
HTTPСоединение = Новый HTTPСоединение("127.0.0.1", 9988, , , , 10);
HTTPЗапрос = Новый HTTPЗапрос();
HTTPЗапрос.Заголовки.Вставить("Content-type", "application/json");
Если Метод = "POST" Тогда
HTTPЗапрос.УстановитьТелоИзСтроки(СформироватьТелоЗапроса(s3method, КлючиМетода));
КонецЕсли;
Возврат HTTPСоединение.ВызватьHTTPМетод(Метод, HTTPЗапрос);
Исключение
Возврат Неопределено; // сервис не поднят
КонецПопытки;
КонецФункции
&НаСервере
Функция СформироватьТелоЗапроса(s3method, КлючиМетода)
Тело = Новый Соответствие;
Тело.Вставить("s3method", s3method); // list_files | download_files | upload_file
Тело.Вставить("access_key", access_key);
Тело.Вставить("secret_key", secret_key);
Тело.Вставить("region_name", region_name);
Тело.Вставить("bucket_name", bucket_name);
Тело.Вставить("folder_name", folder_name);
Если ЗначениеЗаполнено(endpoint_url) Тогда
Тело.Вставить("endpoint_url", endpoint_url); // ← для S3-совместимых хранилищ
КонецЕсли;
Если НЕ КлючиМетода = Неопределено Тогда
Для каждого Стр Из КлючиМетода Цикл
Тело.Вставить(Стр.Ключ, Стр.Значение);
КонецЦикла;
КонецЕсли;
Возврат ОбъектВJSON(Тело);
КонецФункции
Список файлов, массовая загрузка на диск и выгрузка файла — это просто вызовы с разным s3method. Сервис отвечает единым конвертом {"error": "...", "data": [...]}, который 1С разбирает обёрткой JSONВОбъект. Ключевое преимущество — параметр endpoint_url: подставив свой адрес, тот же код работает с MinIO, Yandex Object Storage и любым другим S3-совместимым хранилищем без единой правки логики подписи.
Сам сервис не обязан быть «большим». В рабочем варианте это две короткие вещи: класс на boto3, который делает всю работу с S3, и лёгкий HTTP-роутер на стандартной библиотеке Python (http.server) — без FastAPI и прочих фреймворков. Вот ядро на boto3, где вся подпись SigV4 берётся из SDK:
from boto3.session import Session
class AmazonS3:
def __init__(self, message):
session = Session(
aws_access_key_id=message['access_key'],
aws_secret_access_key=message['secret_key'],
region_name=message['region_name'],
)
endpoint = message.get('endpoint_url') # ← MinIO / Yandex / VK Cloud
self.s3 = (session.resource('s3', endpoint_url=endpoint)
if endpoint else session.resource('s3'))
self.bucket_name = message['bucket_name']
def list_files(self):
bucket = self.s3.Bucket(self.bucket_name)
return [{'filename': o.key} for o in bucket.objects.all()]
def upload_file(self, full_name, key):
self.s3.Bucket(self.bucket_name).upload_file(full_name, key)
А роутер — это буквально BaseHTTPRequestHandler: GET возвращает версию сервиса (по нему 1С проверяет, что он жив), POST принимает JSON, вызывает нужный метод класса и отдаёт конверт {data, error}. Порт 9988, ноль внешних веб-зависимостей.
Запуск компаньона в Docker вместо exe
Раньше такой сервис собирали в один s3server.exe через PyInstaller и запускали рядом с 1С. Минусы очевидны: отдельный бинарник, который надо распаковать и держать живым, и регулярные ложные срабатывания антивируса на самодельный exe. Docker снимает обе проблемы — сервис едет как обычный контейнер:
# Dockerfile
FROM python:3.8
ENV PYTHONUNBUFFERED=1
WORKDIR /app
COPY requirements.txt /app/
RUN pip install -r requirements.txt # boto3, botocore, yandexcloud
COPY . /app/
# docker-compose.yml
services:
web:
build: .
restart: always
command: python s3server.py
ports:
- "9988:9988"
docker compose up — и компаньон слушает тот же 127.0.0.1:9988, а restart: always поднимет его после перезагрузки. Для 1С со стороны кода ничего не меняется: она по-прежнему шлёт JSON на порт 9988. Это тот же приём, что мы применяем для публикации Python-сервисов рядом с 1С в контейнере, — как микросервис на FastAPI для 1С.
Написать и упаковать такой сервис-компаньон — работа на вечер, и это ровно тот навык связки 1С с Python-бэкендом, который мы разбираем на курсе «Python для 1С‑разработчика»: как вынести тяжёлую или «неудобную для BSL» логику в микросервис и аккуратно ходить в него из 1С.
S3-совместимые хранилища: MinIO, Yandex Object Storage, VK Cloud
С 2022 года прямой доступ к Amazon для российской аудитории 1С стал менее удобным, но это почти не важно: S3 давно стал стандартом де-факто, и российские облака его поддерживают. Практически весь код выше остаётся тем же — меняются только эндпоинт и ключи:
- Yandex Object Storage — эндпоинт
storage.yandexcloud.net, регионru-central1; статический ключ доступа выдаёт сервисный аккаунт в консоли Yandex Cloud. - VK Cloud / Selectel — свой эндпоинт и ключи из панели управления; в подписи меняются host и регион.
- MinIO — self-hosted S3 у себя на сервере: удобно для тестов и закрытого контура, эндпоинт указывает на ваш адрес.
Для варианта с прямой подписью подставляйте нужный host и регион в canonical headers и в scope. Для микросервиса — просто заполняйте endpoint_url. Если вы переносите в облако не сами файлы, а сервер взаимодействия целиком (с историей сообщений) — это отдельная процедура: переезд сервера взаимодействия 1С в Yandex Cloud.
Подводные камни, на которых теряют часы
- Время должно быть в UTC. Заголовок
x-amz-date— по Гринвичу. БеритеТекущаяУниверсальнаяДата(), а неТекущаяДата(). Если часы сервера разошлись с S3 больше чем на 15 минут — получите403 RequestTimeTooSkewedдаже при абсолютно верной подписи. - Хеш тела обязателен.
x-amz-content-sha256для запроса без тела — это SHA-256 пустой строки (e3b0c442…), а для PUT — реальный хеш содержимого. Тот же хеш идёт и в конец канонического запроса. - COM ≠ прод. Подпись через
System.Security.Cryptographyпривязывает вас к Windows. На Linux-кластере используйте штатныйHMAC()(8.3.21+) — код выше. - Ключи — не в код. В учебных примерах
secret_keyлежит в открытой константе. В бою секрет храните в защищённом хранилище (безопасное хранилище данных, отдельная константа с ограничением прав), а доступ к обработке — по роли. Утёкший Secret Access Key = полный доступ к бакету. - URL-кодирование по правилам AWS. Канонический запрос кодирует query по своим правилам (пробел, спецсимволы) — «почти как обычный encodeURL, но не совсем». Расхождение здесь — самая частая причина
SignatureDoesNotMatch.
Что выбрать
Нужен автономный серверный код без внешних процессов — регламентная выгрузка, бэкап, работа внутри одного контура — берите прямую подпись SigV4 (на Linux — со штатным HMAC). Нужна скорость разработки и богатая логика S3 — берите микросервис-компаньон и держите S3-специфику в Python. Оба подхода — это, по сути, одна и та же связка «1С ↔ внешний REST», и именно её мы ставим на поток на курсе «Python для 1С‑разработчика»: от HTTP-клиента до собственного микросервиса рядом с 1С.