Идея написать эту библиотеку возникла, когда захотелось в полной
мере воспользоваться всеми преимуществамибесплатного
предложенияOracle Cloud Infrastructure, а именно 10 ГБ
хранилища объектов (Object Storage) и 10
ТБ исходящего трафика в месяц. Разница сAWSS3 простоогромнейшая. К
сожалению,OracleCloudне имеетв
наличииSDKдля всё еще самого популярного языка программирования для
разработки веб-сайтов. Хорошая новость состоит в том, что
сервисчастично
совместимсAmazonS3, а это означает, что можно применить уже
имеющиеся и отлично задокументированныеинструменты
разработчика, в том числе дляPHP.
Тем, кому не терпится увидеть код, добро пожаловатьhttps://github.com/hitrov/oci-api-php-request-sign.
Действительно, с имеющимися инструментами можно выполнять почти
все операции, которые можно представить - для создания, чтения и
удаления корзин (buckets) и объектов (файлов). Корзины могут быть
как публичными (с возможностью листинга файлов и без) и приватными.
Есть возможность загружать файлы в приватную корзину, имея лишь
секретныйURL(сгенерированный вручную с помощьюCLIили веб-интерфейса
- консолиOracleCloud). На самом деле этого уже может быть
достаточно для многих сценариев, особенно если генерировать стойкие
к подбору имена файлов - в случае, если вы не хотите выставлять их
на публику.
Меня интересовала возможность расшаривать файлы, то есть
делиться общедоступными ссылками на файлы, и, конечно же,
ограничивать доступ при необходимости. При небольшом количестве
файлов можно делать это вручную, но мы собрались здесь, чтобы иметь
программный доступ. ВAWS S3этоназываетсяPre-Signed
URL,ауOracle -Pre-Authenticated
Request.
Установка AWS PHP SDK
composer require aws/aws-sdk-php
Ниже будет показано, где взять доступы
(AWS_ACCESS_KEY_ID
и
AWS_SECRET_ACCESS_KEY
.
Namespace же можно увидеть
$namespaceName = 'frpegp***';$bucketName = 'test******05';$region = 'eu-frankfurt-1';$endpoint = "https://$namespaceName.compat.objectstorage.$region.oraclecloud.com";$s3 = new Aws\S3\S3Client([ 'version' => 'latest', 'region' => $region, 'endpoint' => $endpoint, 'signature_version' => 'v4', 'use_path_style_endpoint' => true, 'credentials' => [ 'key' => 'AKI***YYJ', // remove if you have env var AWS_ACCESS_KEY_ID 'secret' => 'ndK***cIf', , // remove if you have env var AWS_SECRET_ACCESS_KEY ],]);$cmd = $s3->getCommand('GetObject', [ 'Bucket' => $bucketName, 'Key' => 'fff.txt']);$request = $s3->createPresignedRequest($cmd, '+20 minutes');
К сожалению, данная операция, хотя и не вызывает ошибку, отдавая
в ответPSR-7request, но
возвращаемый имURLвида
https://{namespace}.compat.objectstorage.eu-frankfurt-1.oraclecloud.com/{bucket}/fff.txt?X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=***%2F20210210%2Feu-frankfurt-1%2Fs3%2Faws4_request&X-Amz-Date=20210210T185244Z&X-Amz-SignedHeaders=host&X-Amz-Expires=1200&X-Amz-Signature=a167a***9a857
просто не работает.
<?xml version="1.0" encoding="UTF-8"?><Error> <Message>The required information to complete authentication was not provided.</Message> <Code>SignatureDoesNotMatch</Code></Error>
Поскольку совместимость для данной операции не была заявлена,
странно было бы ожидать иного, но попробовать стоило :)
Здесь я попробую очень кратко описать, что необходимо для
подписи запроса кAPI, ведь все изложено довольно подробноздесь, пусть и с
примерами для иных языков программирования.
Разумеется, подпись будет работать для всех запросов начиная от
создания\остановки\бэкапаавтономной базы
данных, управленияDNSи заканчивая
отправкойEmail. Всё что
указано в API Reference and
Endpoints.
Прежде всего, для того, чтобы начать работу, нужны ключи
доступа, в веб-интерфейсе (консоли)OracleCloud необходимо зайти
вUserSettings
Действия
в профиле Oracle Cloud
API KeysAdd API Key
API Keys - Add API Key
Downloadprivatekey(сохраняем в надежном месте), затемAdd
Download
Private Key and Add
Сохраняем все значения из текстового поля, они нам понадобятся
через минуту
Configuration File example
Для того, чтобы воспользоваться AWS PHP SDK, вам необходимы
Customer Secret Keys (они же AWS_ACCESS_KEY_ID
и
AWS_SECRET_ACCESS_KEY
в понимании Amazon.
Установка Oracle Cloud Infrastructure mini PHP SDK(никаких
внешних зависимостей!)
composer require hitrov/oci-api-php-request-sign
Пакет использует стандартнуюPSR-4 автозагрузку классов.
require 'vendor/autoload.php';use Hitrov\OCI\Signer;
Для авторизации нужно задать переменные среды (замените на
значения, взятые из текстового поля, проставьте путь к файлу с
приватным ключом).
OCI_TENANCY_ID=ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsqOCI_USER_ID=ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjqOCI_KEY_FINGERPRINT=20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34OCI_PRIVATE_KEY_FILENAME=/path/to/privatekey.pem
В этом случае конструктор не принимает аргументов.
$signer = new Signer;
Переменным среды есть несколько альтернативhttps://github.com/hitrov/oci-api-php-request-sign#alternatives-for-providing-credentials,
не стану дублировать это здесь.
МыпопробуемвыполнитьCreatePreauthenticatedRequest.
Вся сложность (если можно так выразиться) абстрагирована в один
публичный метод
public function getHeaders( string $url, string $method = 'GET', ?string $body = null, ?string $contentType = 'application/json', string $dateString = null): array
Пример использования
$curl = curl_init();$url = 'https://objectstorage.eu-frankfurt-1.oraclecloud.com/n/{namespaceName}/b/{bucketName}/p/';$method = 'POST';$body = '{"accessType": "ObjectRead", "name": "read-access-to-image.png", "objectName": "path/to/image.png", "timeExpires": "2021-03-01T00:00:00-00:00"}';$headers = $signer->getHeaders($url, $method, $body, 'application/json');var_dump($headers);$curlOptions = [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 5, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => $headers,];if ($body) { // not needed for GET or HEAD requests $curlOptions[CURLOPT_POSTFIELDS] = $body;}curl_setopt_array($curl, $curlOptions);$response = curl_exec($curl);echo $response;curl_close($curl);
array(6) { [0]=> string(35) "date: Mon, 08 Feb 2021 20:49:22 GMT" [1]=> string(50) "host: objectstorage.eu-frankfurt-1.oraclecloud.com" [2]=> string(18) "content-length: 76" [3]=> string(30) "content-type: application/json" [4]=> string(62) "x-content-sha256: X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=" [5]=> string(538) "Authorization: Signature version=\"1\",keyId=\"ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq/20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34\",algorithm=\"rsa-sha256\",headers=\"date (request-target) host content-length content-type x-content-sha256\",signature=\"LXWXDA8VmXXc1NRbMmXtW61IS97DfIOMAnlj+Gm+oBPNc2svXYdhcXNJ+oFPoi9qJHLnoUiHqotTzuVPXSG5iyXzFntvkAn3lFIAja52iwwwcJflEIXj/b39eG2dCsOTmmUJguut0FsLhCRSX0eylTSLgxTFGoQi7K/m18nafso=\""}
{ "accessUri": "/p/AlIlOEsMok7oE7YkN30KJUDjDKQjk493BKbuM-ANUNGdBBAHzHT_5lFlzYC9CQiA/n/{namespaceName}/b/{bucketName}/o/path/to/image.png", "id": "oHJQWGxpD+2PhDqtoewvLCf8/lYNlaIpbZHYx+mBryAad/q0LnFy37Me/quKhxEi:path/to/image.png", "name": "read-access-to-image.png", "accessType": "ObjectRead", "objectName": "path/to/image.png", "timeCreated": "2021-02-09T11:52:45.053Z", "timeExpires": "2021-03-01T00:00:00Z"}
Вот и всё!
По большому счету, клиентский код более ни в чем не нуждается.
Остальное для тех, кому любопытно в образовательных целях.
1) Прежде всего, нам необходимо собрать список подписываемых
заголовков (SIGNING_HEADERS_NAMES). Он всегда содержит
-
date
-
(request-target)
-
host
ДляPOST|PUT|PATCHзапросов добавляются еще три
-
content-length
-
content-type
-
x-content-sha256
$signingHeadersNames = $signer->getSigningHeadersNames('POST');
2) SHA256 хэш тела запроса кодированный вbase64
$bodyHashBase64 = $signer->getBodyHashBase64($body);
3) Сформировать строку для подписи, в нашем случае она будет
выглядеть следующим образом
date: Mon, 08 Feb 2021 20:51:33 GMT(request-target): post /n/{namespaceName}/b/{bucketName}/p/host: objectstorage.eu-frankfurt-1.oraclecloud.comcontent-length: 76content-type: application/jsonx-content-sha256: X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=
$signingString = $signer->getSigningString($url, $method, $body, 'application/json');
Хэш мы получили в (2).Важно, что дата и время не должны
отличаться от текущих на более, чем 5 минут.
4) Подписать строку из (3) приватным ключом с помощью
алгоритмаRSA-SHA256
$signature = $signer->calculateSignature($signingString, $privateKeyString);
5) Сформировать KEY_ID данными, которые вы скопировали при
создании API Key, это строка, разделенная слешами
"{OCITENANCYID}/{OCIUSERID}/{OCIKEY_FINGERPRINT}"
$keyId = $signer->getKeyId();
6) Теперь мы готовы сгенерировать заголовок авторизации(версия
1
останется таковой до отдельного уведомления
отOracle)
Authorization: Signature
version=\"1\",keyId=\"{KEY_ID}\",algorithm=\"rsa-sha256\",headers=\"{SIGNING_HEADERS_NAMES_STRING}\",signature=\"{SIGNATURE}\"
гдеSIGNING_HEADERS_NAMES_STRING это список из (1), разделенный
пробелами.
date (request-target) host content-length content-type
x-content-sha256
$signingHeadersNamesString = implode(' ', $signingHeadersNames);$authorizationHeader = $signer->getAuthorizationHeader($keyId, $signingHeadersNamesString, $signature);
Пример вывода
Authorization: Signature version=\"1\",keyId=\"ocid1.tenancy.oc1..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq/ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq/20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34\",algorithm=\"rsa-sha256\",headers=\"date (request-target) host content-length content-type x-content-sha256\",signature=\"LXWXDA8VmXXc1NRbMmXtW61IS97DfIOMAnlj+Gm+oBPNc2svXYdhcXNJ+oFPoi9qJHLnoUiHqotTzuVPXSG5iyXzFntvkAn3lFIAja52iwwwcJflEIXj/b39eG2dCsOTmmUJguut0FsLhCRSX0eylTSLgxTFGoQi7K/m18nafso=\"
Реальные заголовки запроса - см.
выводvar_dump()
выше - должны содержать всё из (3), за
исключением поля(request-target)и его значения. И, конечно же,
заголовок авторизации (6).
МнепомогластатьяOracle Cloud Infrastructure
(OCI) REST call walkthrough with curl. Некоторые имена
методов позаимствованы из официальногоGoLangSDK. Тест-кейсы
оттуда же.