Работа с Yandex Cloud Object Storage из R

… или будни дата-инженеRа

R
YandexCloud
Автор

i2z1@ddslab.ru

Дата

26.03.2025

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.