Подписанные (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.kz/<ключ_объекта>?
X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=<access_key-id>%2F<YYYYMMDD>%2Fkz1%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>/kz1/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%2Fkz1%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.kz
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>/kz1/s3/aws4_request.CanonicalRequest— сформированный ранее канонический запрос. В строку для подписи помещается SHA256 -хэш канонического запроса в шестнадцатеричном представлении.
Подписывающий ключ
Чтобы сгенерировать подписывающий ключ:
-
Закодируйте дату с помощью секретного ключа:
DateKey = sign("AWS4" + "SecretKey", "yyyymmdd") -
Закодируйте регион с помощью полученного на предыдущем шаге ключа
DateKey:RegionKey = sign(DateKey, "kz1") -
Закодируйте сервис с помощью полученного на предыдущем шаге ключа
RegionKey:ServiceKey = sign(RegionKey, "s3") -
Получите подписывающий ключ:
SigningKey = sign(ServiceKey, "aws4_request")
Подпись строки с помощью ключа
Чтобы получить подпись строки, необходимо использовать механизм 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%2Fkz1%2Fs3%2Faws4_request&X-Amz-Date=20231208T184504Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host host:<имя_бакета>.storage.yandexcloud.kz host UNSIGNED-PAYLOAD -
Строка для подписи:
AWS4-HMAC-SHA256 20231208T184504Z 20231208/kz1/s3/aws4_request e823d75aad02c1317589bd5373fe9e20d5ef44499237703ff23e5600******** -
Подписывающий ключ:
sign(sign(sign(sign("AWS4" + "ExamP1eSecReTKeykdokKK38800","20190801"),"kz1"),"s3"),"aws4_request")Функция
signвведена для обозначения способа вычисления ключа с помощью механизма HMAC с хэширующей функцией SHA256 . -
Подпись:
b10c16a1997bb524bf59974512f1a6561cf2953c29dc3efbdb920790******** -
Подписанный URL:
https://<имя_бакета>.storage.yandexcloud.kz/object-for-share.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=YCAJEK0Iv6xqy-pEQcueLTAdg%2F20231208%2Fkz1%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.kz'
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}%2Fkz1%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}/kz1/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), 'kz1'), '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 + "%2Fkz1%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.kz";
$region = "kz1";
$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."%2Fkz1%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', 'kz1', 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."%2Fkz1%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>';
?>