Подписанные (pre-signed) URL
В Object Storage реализовано несколько механизмов для управления доступом к ресурсам. Алгоритм взаимодействия этих механизмов см. в Обзор способов управления доступом в Object Storage.
С помощью подписанных URL произвольный пользователь интернета может выполнять в Object Storage различные операции, например:
- Скачать объект.
- Загрузить объект.
- Создать бакет.
Подписанный URL — это URL, содержащий в своих параметрах данные для авторизации запроса. Составить подписанный URL может пользователь, обладающий статическими ключами доступа.
Раздел содержит общие принципы построения подписанного URL с использованием AWS Signature V4
Примечание
SDK для различных языков программирования и другие инструменты для работы с AWS S3 содержат готовые методы генерирования подписанного URL, которые можно использовать и для Object Storage.
Общий вид подписанного URL
https://<имя_бакета>.storage.yandexcloud.net/<ключ_объекта>?
X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=<access_key-id>%2F<YYYYMMDD>%2Fru-central1%2Fs3%2Faws4_request
&X-Amz-Date=<время_в_формате_ISO_8601>
&X-Amz-Expires=<время_жизни_ссылки>
&X-Amz-SignedHeaders=<список_подписанных_заголовков>
&X-Amz-Signature=<подпись>
Параметры подписанного URL:
| Параметр | Описание |
|---|---|
X-Amz-Algorithm |
Идентифицирует версию подписи и алгоритм ее вычисления. Значение — AWS4-HMAC-SHA256. |
X-Amz-Credential |
Идентификатор для подписи. Строка формата <access-key-id>/<YYYYMMDD>/ru-central1/s3/aws4_request, где <YYYYMMDD> должна совпадать с датой, установленной в заголовке X-Amz-Date. |
X-Amz-Date |
Время в формате ISO860120180719T000000Z. Указанная дата должна по значению (не по формату) совпадать с датой в параметре X-Amz-Credential. |
X-Amz-Expires |
Время в секундах, в течение которого ссылка действительна. Начало отсчета — момент, указанный в X-Amz-Date. Максимальное значение — 2592000 секунд (30 дней). |
X-Amz-SignedHeaders |
Заголовки запроса, которые вы хотите подписать, разделенные точкой с запятой (;).Обязательно подписывайте заголовок Host и все заголовки X-Amz-*, которые используются в запросе. Другие заголовки подписывать не обязательно, однако чем больше вы подпишете заголовков, тем безопаснее будет запрос. |
X-Amz-Signature |
Подпись запроса. |
Составление подписанного URL
Примечание
Для публичных бакетов необязательно генерировать подписанные ссылки. Из бакета с публичным доступом файлы можно получить как по протоколу HTTP, так и по протоколу HTTPS, даже если для бакета не настроен хостинг сайта.
Чтобы получить подписанный URL:
- Составьте канонический запрос.
- Составьте строку для подписи.
- Сформируйте подписывающий ключ.
- Вычислите подпись с помощью ключа.
- Сформируйте подписанный URL.
Для составления подписанного URL необходимо обладать статическими ключами доступа.
Канонический запрос
Общий вид канонического запроса:
<HTTPVerb>\n
<CanonicalURL>\n
<CanonicalQueryString>\n
<CanonicalHeaders>\n
<SignedHeaders>\n
UNSIGNED-PAYLOAD
HTTPVerb
HTTP метод, которым будет отправлен запрос: GET, PUT, HEAD или DELETE.
CanonicalURL
URL-кодированный ключ объекта. Например, /folder/object.ext.
Примечание
Не нормализуйте путь. Например, объект может иметь ключ some//strange//key//example и нормализация пути /<bucket-name>/some/strange/key/example сделает ключ некорректным.
CanonicalQueryString
Каноническая строка запроса должна включать все query параметры конечного URL, кроме X-Amz-Signature. Параметры в строке должны быть URL-кодированы и отсортированы по алфавиту.
Пример:
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=JK38EXAMPLEAKDID8%2F20190801%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date=20190801T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host
CanonicalHeaders
Список заголовков запроса со значениями.
Требования:
- Каждый заголовок отделяется символом новой строки
\n. - Имена заголовков должны быть в нижнем регистре.
- Заголовки должны быть отсортированы по алфавиту.
- Не должно быть лишних пробелов.
- Список должен содержать заголовок
hostи все заголовкиx-amz-*, которые используются в запросе.
Дополнительно в список можно добавить любой из заголовков запроса. Чем больше вы подпишете заголовков, тем безопаснее будет запрос.
Пример:
host:sample-bucket.storage.yandexcloud.net
x-amz-date:20190801T000000Z
SignedHeaders
Список имен заголовков запроса в нижнем регистре, отсортированный по алфавиту и разделенный точками с запятыми.
Пример:
host;x-amz-date
Завершение канонического запроса
Завершать канонический запрос всегда должна строка UNSIGNED-PAYLOAD.
Строка для подписи
Общий вид строки для подписи:
"AWS4-HMAC-SHA256" + "\n" +
<timestamp> + "\n" +
<scope> + "\n" +
Hex(Hash-SHA256(<CanonicalRequest>))
Где:
AWS4-HMAC-SHA256— алгоритм хэширования.timestamp— текущее время в формате ISO 8601, например,20190801T000000Z. Указанная дата должна по значению (не по формату) совпадать с датой вscope.scope—<YYYYMMDD>/ru-central1/s3/aws4_request.CanonicalRequest— сформированный ранее канонический запрос. В строку для подписи помещается SHA256 -хэш канонического запроса в шестнадцатеричном представлении.
Подписывающий ключ
Чтобы сгенерировать подписывающий ключ:
-
Закодируйте дату с помощью секретного ключа:
DateKey = sign("AWS4" + "SecretKey", "yyyymmdd") -
Закодируйте регион с помощью полученного на предыдущем шаге ключа
DateKey:RegionKey = sign(DateKey, "ru-central1") -
Закодируйте сервис с помощью полученного на предыдущем шаге ключа
RegionKey:ServiceKey = sign(RegionKey, "s3") -
Получите подписывающий ключ:
SigningKey = sign(ServiceKey, "aws4_request")
Примечание
Подписывающий ключ действителен в течение нескольких минут, после этого его использование может привести к ошибке 403 — RequestTimeTooSkewed. Регулярно обновляйте подписывающий ключ, чтобы продлить его срок действия.
Для генерации подписывающего ключа AWS SDK использует локальное системное время.
Если локальное системное время некорректно
Вы можете использовать функцию получения реального времени, например v4.SignSDKRequestWithCurrentTime
client := s3.New(sess)
client.Handlers.Sign.Swap(v4.SignRequestHandler.Name, request.NamedHandler{
Name: v4.SignRequestHandler.Name,
Fn: func(r *request.Request) {
v4.SignSDKRequestWithCurrentTime(r, currentTimeFn)
},
})
Подпись строки с помощью ключа
Чтобы получить подпись строки, необходимо использовать механизм HMAC с хэширующей функцией SHA256, а полученный результат преобразовать в шестнадцатеричное представление.
signature = Hex(sign(SigningKey, StringToSign))
Подписанный URL
Чтобы составить подписанный URL, к URL ресурса Object Storage добавьте параметры, необходимые для авторизации запроса, в том числе параметр X-Amz-Signature с вычисленной подписью в значении.
Значения остальных параметров должны совпадать с аналогичными значениями, указанными ранее в каноническом запросе и в строке для подписи.
Пример составления подписанного URL для скачивания объекта
Составим подписанный URL для скачивания объекта object-for-share.txt из бакета в течение часа:
-
Статический ключ:
access_key_id = 'JK38EXAMP********' secret_access_key = 'ExamP1eSecReTKeykdo********' -
Канонический запрос:
GET /object-for-share.txt X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=YCAJEK0Iv6x********eLTAdg%2F20231208%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date=20231208T184504Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host host:<имя_бакета>.storage.yandexcloud.net host UNSIGNED-PAYLOAD -
Строка для подписи:
AWS4-HMAC-SHA256 20231208T184504Z 20231208/ru-central1/s3/aws4_request e823d75aad02c1317589bd5373fe9e20d5ef44499237703ff23e5600******** -
Подписывающий ключ:
sign(sign(sign(sign("AWS4" + "ExamP1eSecReTKeykdokKK38800","20190801"),"ru-central1"),"s3"),"aws4_request")Функция
signвведена для обозначения способа вычисления ключа с помощью механизма HMAC с хэширующей функцией SHA256 . -
Подпись:
b10c16a1997bb524bf59974512f1a6561cf2953c29dc3efbdb920790******** -
Подписанный URL:
https://<имя_бакета>.storage.yandexcloud.net/object-for-share.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=YCAJEK0Iv6xqy-pEQcueLTAdg%2F20231208%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date=20231208T195434Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=b10c16a1997bb524bf59974512f1a6561cf2953c29dc3efbdb920790********
Отрабатывать процесс формирования запроса и подписи вы можете с помощью AWS CLI в режиме отладки.
Чтобы отправить подписанный запрос к S3 API, вы можете использовать утилиту curl
Примеры кода для генерации подписанных URL
В подразделе приведены примеры кода для генерации подписанных URL.
Чтобы показать принцип формирования и подписи запросов к Object Storage, в этих примерах не используются AWS SDK. Примеры с использованием Yandex Cloud CLI, AWS CLI и AWS SDK см. на страницах:
- Получение подписанной ссылки (pre-signed URL) на скачивание объекта
- Получение подписанной ссылки (pre-signed URL) на загрузку объекта
import datetime
import hashlib
import hmac
access_key = '<идентификатор_статического_ключа>'
secret_key = '<содержимое_статического_ключа>'
object_key = '<ключ_объекта>'
bucket = '<имя_бакета>'
host = 'storage.yandexcloud.net'
now = datetime.datetime.now(datetime.UTC)
datestamp = now.strftime('%Y%m%d')
timestamp = now.strftime('%Y%m%dT%H%M%SZ')
def sign(key, msg):
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256).digest()
canonical_request = """GET
/{bucket}/{object_key}
X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential={access_key}%2F{datestamp}%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date={timestamp}&X-Amz-Expires=3600&X-Amz-SignedHeaders=host
host:{host}
host
UNSIGNED-PAYLOAD""".format(
bucket=bucket,
object_key=object_key,
access_key=access_key,
datestamp=datestamp,
timestamp=timestamp,
host=host)
print()
print("Canonical request:\n" + canonical_request)
print()
string_to_sign = """AWS4-HMAC-SHA256
{timestamp}
{datestamp}/ru-central1/s3/aws4_request
{request_hash}""".format(
timestamp=timestamp,
datestamp=datestamp,
request_hash=hashlib.sha256(canonical_request.encode('utf-8')).hexdigest())
print()
print("String to be signed:\n" + string_to_sign)
print()
signing_key = sign(sign(sign(sign(('AWS4' + secret_key).encode('utf-8'), datestamp), 'ru-central1'), 's3'), 'aws4_request')
signature = hmac.new(signing_key, string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest()
print()
print("Signature: " + signature)
print()
signed_link = "https://" + host + '/' + bucket + '/' + object_key + "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=" + access_key + "%2F" + datestamp + "%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date=" + timestamp + "&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=" + signature + "\n"
print()
print("Signed Link:\n" + signed_link)
<?php
date_default_timezone_set('UTC');
$keyid = "<идентификатор_статического_ключа>";
$secretkey = "<содержимое_статического_ключа>";
$path = "<ключ_объекта>";
$objectname = "/".implode("/", array_map("rawurlencode", explode("/", $path)));
$host = "<имя_бакета>.storage.yandexcloud.net";
$region = "ru-central1";
$timestamp = time();
$dater = strval(date('Ymd', $timestamp));
$dateValue = strval(date('Ymd', $timestamp))."T".strval(date('His', $timestamp))."Z";
// Generate the canonical request
$canonical_request = "GET\n".$objectname."\nX-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=".$keyid."%2F".$dater."%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date=".$dateValue."&X-Amz-Expires=3600&X-Amz-SignedHeaders=host\nhost:".$host."\n\nhost\nUNSIGNED-PAYLOAD";
echo "<b>Canonical request: </b><br>".$canonical_request."<br><br>";
// Generate the string to be signed
$string_to_sign = "AWS4-HMAC-SHA256\n".$dateValue."\n".$dater."/".$region."/s3/aws4_request\n".openssl_digest($canonical_request, "sha256", $binary = false);
echo "<b>String to be signed: </b><br>".$string_to_sign."<br><br>";
// Generate the signing key
$signing_key = hash_hmac('sha256', 'aws4_request', hash_hmac('sha256', 's3', hash_hmac('sha256', 'ru-central1', hash_hmac('sha256', $dater, 'AWS4'.$secretkey, true), true), true), true);
echo "<b>Signing key: </b><br>".$signing_key."<br><br>";
// Generate the signature
$signature = hash_hmac('sha256', $string_to_sign, $signing_key);
echo "<b>Signature: </b><br>".$signature."<br><br>";
// Generate the pre-signed link
$signed_link = "https://".$host.$objectname."?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=".$keyid."%2F".$dater."%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date=".$dateValue."&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=".$signature."\n";
echo '<b>Signed link: </b><br>'.'<a href = "'.$signed_link.'" target = "_blank">'.$signed_link.'</a>';
?>