S3 хранилище сегодня не просто аналог сетевой папки. Оно отлично подходит для организации DataLake, хранения датасетов, артефактов CI/CD и MLOps, бэкапов, хостинга веб-сайтов.
Сложности возникают когда используешь вместо “отца” сервиса AWS S3, под который написан почти весь софт для работы по протоколу S3, любой другой.
Эта заметка – про особенности использования Object Storage от Yandex Cloud вместе с языком программирования R.
Upload – Загрузка файлов в Yandex Cloud Object Storage
Рабочий код приведен ниже:
library(digest)
library(base64enc)
library(dplyr)
library(httr2)
library(mime)
<- function(string_to_sign, key) {
calculate_signature <- charToRaw(key)
key_bytes ::hmac(key = key_bytes, object = charToRaw(string_to_sign), algo = "sha1", raw = TRUE) %>%
digest::base64encode()
base64enc
}
<- function(local_path, bucket_name, s3_key_id = Sys.getenv("AWS_ACCESS_KEY_ID"), s3_secret = Sys.getenv("AWS_SECRET_ACCESS_KEY"), s3_endpoint = "storage.yandexcloud.net", ...) {
upload_s3
<- paste0("/", bucket_name, "/", basename(local_path))
resource
<- local_path
abs_file <- mime::guess_type(abs_file)
content_type <- format(Sys.time(), "%a, %d %b %Y %H:%M:%S %z")
date_value <- paste0("PUT", "\n", "\n", content_type, "\n", date_value, "\n", resource)
string_to_sign <- calculate_signature(string_to_sign, s3_secret)
signature
<- list(
headers Host = s3_endpoint,
Date = date_value,
`Content-Type` = content_type,
Authorization = paste0("AWS ", s3_key_id, ":", signature)
)
<- paste0("https://", s3_endpoint)
s3_url
# Upload file
<- httr2::request(s3_url) %>%
req ::req_method("PUT") %>%
httr2::req_url_path(resource) %>%
httr2::req_headers(!!!headers) %>%
httr2::req_body_file(abs_file) %>%
httr2::req_perform()
httr2
}
<- "bucket_name"
bucket <- "local_path_to_file"
local_path
Sys.setlocale("LC_ALL", "C")
upload_s3(local_path, bucket)
Дам немного комментариев по коду.
Одним из краеугольных камней отправки файла в S3 – сформировать запрос с загружаемым файлом и правильно подписать его. Сформировать подпись оказалось сложнее всего.
Для чего нужна подпись? Это еще один способ защититься сервису от Replay атак – когда злоумышленник перехватывает сетевой запрос и отправляет его множество раз. Почему недостаточно в данном случае HTTPS, который защищает от Replay атак на веб-запросы?
Потому что, в современном мире корпораций часто применяют SSL MITM для контроля трафика сотрудников, а сами защищаемые сервисы могут быть за реверс-прокси, который также может снять TLS пока трафик не дошел до бекенда. И в том, и в другом случае появляются участки пути, на которых пакеты не защищены от модификации.
В httr2
недавно завезли функцию req_auth_aws_v4()
, которая предназначена для подписи, но заставить ее работать с Yandex Object Storage не получилось. В итоге, пришлось формировать заголовки HTTP вручную.
Установка новой локали Sys.setlocale("LC_ALL", "C")
нужна для получения времени в “правильной” с точки зрения S3 сервера локали. Если этого не сделать, то подпись получится некорректной.
Также, если в запросе не угадать с Content-Type
, то получим 400 ответ от сервера.
Download – Скачивание файлов из Yandex Cloud Object Storage
Скачивание файлов с S3 ничем не отличается от простой загрузки по HTTP/HTTPS.
download.file("https://storage.yandexcloud.net/example.csv", "example.csv")
Если нужна авторизация при загрузке файлов – используем код выше, заменив метод PUT
на GET
.