среда, 6 августа 2025 г.

Проксирование S3

Появилась задача проксировать посетителей сайта прямо в наш S3. Мы использовали S3 от ceph и там было всё хорошо. Использовали политику с доступом по User-Agent:
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": [
          "s3:GetObject"
        ],
        "Effect": "Allow",
        "Resource": "arn:aws:s3:::my-bucket-name/*",
        "Condition": {
          "StringEquals": {
          "aws:UserAgent": [
            "<какой-то рандомный токен>"
          ]
        }
        },
        "Principal": "*"
      }
    ]
}
После чего коллеги, ответственные за фронт, проксировали силами Nginx, подставляя указанный User-Agent в заголовки.

Но однажды у нас появился S3-сервер на SeaweedFS, да еще и не самой свежей версии :)
Там доступ по User-Agent сходу прикрутить не удалось.

А как вы прекрасно знаете, авторизоваться по ACCESS_KEY/SECRET_KEY не очень-то просто, вот пример со stackoverflow:
#!/bin/sh
file=path/to/file
bucket=your-bucket
resource="/${bucket}/${file}"
contentType="application/x-compressed-tar"
dateValue="`date +'%a, %d %b %Y %H:%M:%S %z'`"
stringToSign="GET
${contentType}
${dateValue}
${resource}"
s3Key=xxxxxxxxxxxxxxxxxxxx
s3Secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
signature=`/bin/echo -en "$stringToSign" | openssl sha1 -hmac ${s3Secret} -binary | base64`
curl -H "Host: ${bucket}.s3.amazonaws.com" \
-H "Date: ${dateValue}" \
-H "Content-Type: ${contentType}" \
-H "Authorization: AWS ${s3Key}:${signature}" \
https://${bucket}.s3.amazonaws.com/${file}
Конечно, такое можно прикрутить в Nginx (с помощью встроенного в Nginx модуля Perl), есть про это статья на Опеннете ("Проксирование запросов к S3 с помощью nginx и angie").
Решение показалось не изящным, плюс недостатки нашего S3-сервера пришлось бы решать коллегам из фронтенда, а перекладывание работы на других -- не наш метод.

Поэтому мы решили проксировать у себя. Прямо на S3-сервере с SeaweedFS. С помощью s3-proxy от oxyno-zeta.

Качаем бинарник (и, при желании, упаковываем в deb-пакет, если надо) отсюда https://github.com/oxyno-zeta/s3-proxy.

Кладём в /opt/s3-proxy/s3-proxy, а конфиг в /opt/s3-proxy/conf/s3proxy.yml
log:
  level: info
  format: text

metrics:
  disableRouterPath: false

server:
  listenAddr: ip-адрес-сервера
  port: 8080

authProviders:
  basic:
    provider1:
      realm: basic

targets:
  target1:
    mount:
      path:
        - /my-bucket-name/
    resources:
      - path: /my-bucket-name/*
        provider: provider1
        basic:
          credentials:
            - user: username1
              password:
                value: somePass
    bucket:
      name: my-bucket-name
      prefix:
      region: eu-west-1
      s3Endpoint: localhost:8333
      disableSSL: true
      credentials:
        accessKey:
          value: xxxXXXxxx
        secretKey:
          value: XXXxxxXXX

Готовим systemd:

[Unit]
Description=S3 Proxy
After=network.target
Documentation=https://oxyno-zeta.github.io/s3-proxy/

[Service]
Type=simple
User=root
WorkingDirectory=/opt/s3-proxy
ExecStart=/opt/s3-proxy/s3-proxy
Restart=on-failure

[Install]
WantedBy=multi-user.target

Проверяем:
$ wget http://username1:somePass@servername:8080/my-bucket-name/9.jpeg
--2025-08-06 16:59:26--  http://username1:*password*@servername:8080/my-bucket-name/9.jpeg
Распознаётся servername (servername)… 1.2.3.4
Подключение к servername (servername)|1.2.3.4|:8080... соединение установлено.
HTTP-запрос отправлен. Ожидание ответа… 401 Unauthorized
Выбранная аутентификация: Basic realm="basic"
Повторное использование соединения с servername:8080.
HTTP-запрос отправлен. Ожидание ответа… 200 OK
Длина: 197222 (193K) [image/jpeg]
Сохранение в: «9.jpeg»

9.jpeg     100%[======================================================================================================>] 192,60K  --.-KB/s    за 0,003s

2025-08-06 16:59:26 (66,9 MB/s) - «9.jpeg» сохранён [197222/197222]
s3-proxy умеет гораздо больше, на их wiki всё написано.

Теперь сообщаем коллегам, что к нам в S3 надо ходить не с User-Agent, а на новый порт 8080 с Basic Auth username1/somePass.