Processus de signature V4 avec votre propre programme

Cette page décrit un algorithme vous permettant d'implémenter le processus de signature V4 afin de pouvoir créer des URL signées par une clé RSA Cloud Storage dans votre propre workflow, en utilisant le langage de programmation de votre choix.

Avant de commencer

Avant de créer un programme qui met en œuvre le processus de signature V4, vous devez suivre ces étapes :

  1. Créez un compte de service. Si vous disposez déjà d'un compte de service, vous pouvez ignorer cette étape. Pour en savoir plus sur les comptes de service, consultez la page Présentation des comptes de service.

  2. Accordez au compte de service des autorisations suffisantes pour qu'il puisse effectuer la requête adressée par l'URL signée.

    Par exemple, si votre URL signée permet à un utilisateur de lire des données d'objet, le compte de service doit lui-même être autorisé à lire les données d'objet.

  3. Si l'algorithme de signature que vous souhaitez utiliser n'est pas intégré à Google Cloud, générez une nouvelle clé privée ou utilisez une clé privée existante pour le compte de service. La clé peut être au format JSON ou PKCS12.

Algorithme de signature des URL

Votre programme doit inclure les étapes suivantes :

  1. Créez la requête canonique sous forme de chaîne. La requête canonique définit les éléments que les utilisateurs doivent inclure dans leur requête lorsqu'ils utilisent votre URL signée.

    Pour plus d'informations sur les segments et le format requis, consultez la section requêtes canoniques .

  2. Créez la chaîne à signer. La chaîne à signer sert de base pour la création d'une signature et inclut la valeur de hachage à encodage hexadécimal de la requête canonique.

    Pour en savoir plus sur le format de la chaîne à signer, consultez la page Signatures.

  3. Signez la chaîne à l'aide d'une signature RSA avec un hachage SHA-256. Le résultat de cette signature est votre signature de requête. Il existe plusieurs options pour la signature :

  4. Créez l'URL signée à l'aide de la concaténation suivante :

    HOSTNAME + PATH_TO_RESOURCE + "?" + CANONICAL_QUERY_STRING + "&X-Goog-Signature=" + REQUEST_SIGNATURE

    L'URL signée comprend les composants suivants :

    • HOSTNAME : il doit correspondre à https://storage.googleapis.com.

    • PATH_TO_RESOURCE : il doit correspondre à la valeur que vous avez utilisée lors de la création de la requête canonique.

    • CANONICAL_QUERY_STRING : il doit correspondre aux valeurs que vous avez utilisées lors de la création de la requête canonique.

    • REQUEST_SIGNATURE : résultat de l'utilisation d'une signature RSA à l'étape précédente.

    Voici un exemple d'URL générée :

    https://storage.googleapis.com/example-bucket/cat.jpeg?X-Goog-Algorithm=GOOG4-
    RSA-SHA256&X-Goog-Credential=example%40example-project.iam.gserviceaccount.com
    %2F20181026%2Fus%2Fstorage%2Fgoog4_request&X-Goog-Date=20181026T211942Z&X-Goog
    -expires=3600&X-Goog-Signedheaders=host&X-Goog-Signature=2d2a6f5055eb004b8690b
    9479883292ae7450cdc15f17d7f99bc49b916f9e7429106ed7e5858ae6b4ab0bbbdb1a8ccc364d
    ad3a0da2caebd30887a70c5b2569d089ceb8afbde3eed4dff5086f0db5483998c175980991fe89
    9fbd2cd8cb813b00165e8d56e0a8aa7b3d7a12ee1baa8400611040f05b50a1a8eab5ba223fe137
    5747748de950ec7a4dc50f8382a6ffd4994ac42498d7daa703d9a414d4475154d0e7edaa92d4f2
    507d92c1f7e8efa7cab64df68b5df48575b9259d8d0bdb5dc752bdf07bd162d98ff2924f2e4a26
    fa6b3cede73ad5333c47d146a21c2ab2d97115986a12c68ff37346d6c2ca83e56b8ec8ad956327
    10b489b75c35697d781c38e

Exemple de programme en Python

Vous pouvez créer des URL signées pour de nombreux langages de programmation courants à l'aide des bibliothèques clientes Cloud Storage. Pour obtenir des exemples, consultez la page Processus de signature V4 avec les outils Cloud Storage.

L'exemple suivant décrit une mise en œuvre de l'algorithme de signature d'URL qui n'utilise pas les bibliothèques clientes Cloud Storage. L'exemple utilise le langage de programmation Python mais peut être adapté au langage de votre choix.

import binascii
import collections
import datetime
import hashlib
import sys
from urllib.parse import quote

# pip install google-auth
from google.oauth2 import service_account

# pip install six
import six


def generate_signed_url(
    service_account_file,
    bucket_name,
    object_name,
    subresource=None,
    expiration=604800,
    http_method="GET",
    query_parameters=None,
    headers=None,
):
    if expiration > 604800:
        print("Expiration Time can't be longer than 604800 seconds (7 days).")
        sys.exit(1)

    escaped_object_name = quote(six.ensure_binary(object_name), safe=b"/~")
    canonical_uri = f"/{escaped_object_name}"

    datetime_now = datetime.datetime.now(tz=datetime.timezone.utc)
    request_timestamp = datetime_now.strftime("%Y%m%dT%H%M%SZ")
    datestamp = datetime_now.strftime("%Y%m%d")

    google_credentials = service_account.Credentials.from_service_account_file(
        service_account_file
    )
    client_email = google_credentials.service_account_email
    credential_scope = f"{datestamp}/auto/storage/goog4_request"
    credential = f"{client_email}/{credential_scope}"

    if headers is None:
        headers = dict()
    host = f"{bucket_name}.storage.googleapis.com"
    headers["host"] = host

    canonical_headers = ""
    ordered_headers = collections.OrderedDict(sorted(headers.items()))
    for k, v in ordered_headers.items():
        lower_k = str(k).lower()
        strip_v = str(v).lower()
        canonical_headers += f"{lower_k}:{strip_v}\n"

    signed_headers = ""
    for k, _ in ordered_headers.items():
        lower_k = str(k).lower()
        signed_headers += f"{lower_k};"
    signed_headers = signed_headers[:-1]  # remove trailing ';'

    if query_parameters is None:
        query_parameters = dict()
    query_parameters["X-Goog-Algorithm"] = "GOOG4-RSA-SHA256"
    query_parameters["X-Goog-Credential"] = credential
    query_parameters["X-Goog-Date"] = request_timestamp
    query_parameters["X-Goog-Expires"] = expiration
    query_parameters["X-Goog-SignedHeaders"] = signed_headers
    if subresource:
        query_parameters[subresource] = ""

    canonical_query_string = ""
    ordered_query_parameters = collections.OrderedDict(sorted(query_parameters.items()))
    for k, v in ordered_query_parameters.items():
        encoded_k = quote(str(k), safe="")
        encoded_v = quote(str(v), safe="")
        canonical_query_string += f"{encoded_k}={encoded_v}&"
    canonical_query_string = canonical_query_string[:-1]  # remove trailing '&'

    canonical_request = "\n".join(
        [
            http_method,
            canonical_uri,
            canonical_query_string,
            canonical_headers,
            signed_headers,
            "UNSIGNED-PAYLOAD",
        ]
    )

    canonical_request_hash = hashlib.sha256(canonical_request.encode()).hexdigest()

    string_to_sign = "\n".join(
        [
            "GOOG4-RSA-SHA256",
            request_timestamp,
            credential_scope,
            canonical_request_hash,
        ]
    )

    # signer.sign() signs using RSA-SHA256 with PKCS1v15 padding
    signature = binascii.hexlify(
        google_credentials.signer.sign(string_to_sign)
    ).decode()

    scheme_and_host = "{}://{}".format("https", host)
    signed_url = "{}{}?{}&x-goog-signature={}".format(
        scheme_and_host, canonical_uri, canonical_query_string, signature
    )

    return signed_url

Étapes suivantes