Creación de un mini SDK de PHP para firmar solicitudes a la API de Oracle Cloud Infrastructure

La idea de escribir esta biblioteca surgió cuando quería aprovechar al máximo la  oferta gratuita de  Oracle Cloud Infrastructure, es decir, 10 GB de almacenamiento de objetos y 10 TB de tráfico saliente por mes. La diferencia con AWS S3 es  enorme . Desafortunadamente, Oracle Cloud  no tiene  un SDK disponible para el lenguaje de programación aún más popular para el desarrollo de sitios web. La buena noticia es que el servicio es  parcialmente compatible  con Amazon S3, lo que significa que puede utilizar herramientas de desarrollo existentes y bien documentadas  , incluso para PHP.





Para aquellos que estén ansiosos por ver el código, bienvenidos a  https://github.com/hitrov/oci-api-php-request-sign .





De hecho, con las herramientas disponibles, puede realizar casi todas las operaciones imaginables: crear, leer y eliminar depósitos y objetos (archivos). Las papeleras de reciclaje pueden ser públicas (con o sin archivos de lista) y privadas. Es posible cargar archivos en una canasta privada, teniendo solo una URL "secreta" (generada manualmente usando la  CLI  o la interfaz web - consola de Oracle Cloud). De hecho, esto puede ser suficiente para muchos escenarios, especialmente si genera nombres de archivo de fuerza bruta en caso de que no quiera exponerlos al público.





Estaba interesado en la capacidad de "compartir" archivos, es decir, compartir enlaces públicos a archivos y, por supuesto, restringir el acceso si es necesario. Con una pequeña cantidad de archivos, puede hacerlo manualmente, pero estamos reunidos aquí para tener acceso mediante programación. AWS S3 llama a esto una URL pre-firmada , mientras que Oracle la  llama  Solicitud pre-autenticada .





Instalación de AWS PHP SDK





composer require aws/aws-sdk-php
      
      



A continuación, se mostrará dónde obtener acceso ( AWS_ACCESS_KEY_ID



y AWS_SECRET_ACCESS_KEY



.





Se puede ver el espacio de nombres





require('vendor/autoload.php');

$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');
var_dump($request);
      
      



object(GuzzleHttp\Psr7\Request)#146 (7) {
  ["method":"GuzzleHttp\Psr7\Request":private]=>
  string(3) "GET"
  ["uri":"GuzzleHttp\Psr7\Request":private]=>
  object(GuzzleHttp\Psr7\Uri)#148 (7) {
    ["scheme":"GuzzleHttp\Psr7\Uri":private]=>
    string(5) "https"
    ...
    ["host":"GuzzleHttp\Psr7\Uri":private]=>
    string(64) "{namespace}.compat.objectstorage.eu-frankfurt-1.oraclecloud.com"
    ...
    ["path":"GuzzleHttp\Psr7\Uri":private]=>
    string(21) "/{bucket}/fff.txt"
    ["query":"GuzzleHttp\Psr7\Uri":private]=>
    string(329) "X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=&&&%2F20210211%2Feu-frankfurt-1%2Fs3%2Faws4_request&X-Amz-Date=20210211T093350Z&X-Amz-SignedHeaders=host&X-Amz-Expires=1200&X-Amz-Signature=1e1b1***6ac992"
    ...
  }
}
      
      



Esta operación devuelve una solicitud de PSR-7 en respuesta  , desde la cual puede generar una URL del formulario





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







Pero, lamentablemente, esto no permite revocar el acceso, por ejemplo, si el enlace es "de larga duración".





,  API,  , .





, \\  ,  DNS   Email. API Reference and Endpoints.





, , , , - () Oracle Cloud  User Settings





Acciones de perfil de Oracle Cloud
Oracle Cloud

API Keys — Add API Key





Claves API - Agregar clave API
API Keys - Add API Key

Download private key ( ),  Add





Descargar clave privada y agregar
Download Private Key and Add

,





Ejemplo de archivo de configuración
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..aaaaaaaaba3pv6wkcr4jqae5f15p2b2m2yt2j6rx32uzr4h25vqstifsfdsq
OCI_USER_ID=ocid1.user.oc1..aaaaaaaat5nvwcna5j6aqzjcaty5eqbb6qt2jvpkanghtgdaqedqw3rynjq
OCI_KEY_FINGERPRINT=20:3b:97:13:55:1c:5b:0d:d3:37:d8:50:4e:c5:3a:34
OCI_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.com
content-length: 76
content-type: application/json
x-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).





Me ayudó el  tutorial de llamadas REST de Oracle Cloud Infrastructure (OCI) con artículo curl . Algunos de los nombres de los métodos se tomaron prestados del SDK oficial de  GoLang . Casos de prueba:  desde el mismo lugar .








All Articles