
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)
calculate_signature <- function(string_to_sign, key) {
key_bytes <- charToRaw(key)
digest::hmac(key = key_bytes, object = charToRaw(string_to_sign), algo = "sha1", raw = TRUE) %>%
base64enc::base64encode()
}
upload_s3 <- 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", ...) {
resource <- paste0("/", bucket_name, "/", basename(local_path))
abs_file <- local_path
content_type <- mime::guess_type(abs_file)
date_value <- format(Sys.time(), "%a, %d %b %Y %H:%M:%S %z")
string_to_sign <- paste0("PUT", "\n", "\n", content_type, "\n", date_value, "\n", resource)
signature <- calculate_signature(string_to_sign, s3_secret)
headers <- list(
Host = s3_endpoint,
Date = date_value,
`Content-Type` = content_type,
Authorization = paste0("AWS ", s3_key_id, ":", signature)
)
s3_url <- paste0("https://", s3_endpoint)
# Upload file
req <- httr2::request(s3_url) %>%
httr2::req_method("PUT") %>%
httr2::req_url_path(resource) %>%
httr2::req_headers(!!!headers) %>%
httr2::req_body_file(abs_file) %>%
httr2::req_perform()
}
bucket <- "bucket_name"
local_path <- "local_path_to_file"
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.