Processo de assinatura V4 com o seu próprio programa

Esta página descreve um algoritmo para implementar o processo de assinatura v4 de modo que possa criar URLs assinados de chaves RSA do Cloud Storage no seu próprio fluxo de trabalho, usando uma linguagem de programação à sua escolha.

Antes de começar

Antes de criar um programa que implemente o processo de assinatura da V4, tem de concluir os seguintes passos:

  1. Crie uma conta de serviço. Se já tiver uma conta de serviço, pode ignorar este passo. Para mais informações sobre contas de serviço, consulte o artigo Vista geral das contas de serviço.

  2. Conceda à conta de serviço autorização suficiente para que possa executar o pedido que o URL assinado vai fazer.

    Por exemplo, se o URL assinado permitir que um utilizador leia dados de objetos, a conta de serviço tem de ter autorização para ler os dados de objetos.

  3. Se o algoritmo de assinatura que pretende usar não estiver incorporado no Trusted Cloud, gere uma nova chave privada ou tenha uma chave privada existente para a conta de serviço. A chave pode estar no formato JSON ou PKCS12.

Algoritmo para assinar URLs

O seu programa deve incluir os seguintes passos:

  1. Construa o pedido canónico como uma string. O pedido canónico define os elementos que os utilizadores têm de incluir no respetivo pedido quando usam o seu URL assinado.

    Consulte o artigo Pedidos canónicos para ver detalhes sobre as partes e o formato necessários.

  2. Construa a string-to-sign. A string a assinar é a base para criar uma assinatura e inclui o valor hash codificado em hexadecimal do pedido canónico.

    Consulte Assinaturas para ver detalhes sobre o formato da string a assinar.

  3. Assine a string a assinar com uma assinatura RSA com SHA-256. O resultado desta assinatura é a sua assinatura de pedido. Existem várias opções para assinar:

    • Pode usar o método signBlob do IAM fornecido por Trusted Cloud.

    • Pode usar uma linguagem de programação que tenha uma biblioteca para realizar assinaturas RSA.

  4. Crie o URL assinado através da seguinte concatenação:

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

    O URL assinado tem os seguintes componentes:

    • HOSTNAME: deve ser https://storage.s3nsapis.fr.

    • PATH_TO_RESOURCE: isto deve corresponder ao valor que usou na construção do pedido canónico.

    • CANONICAL_QUERY_STRING: isto deve corresponder aos valores que usou na criação do pedido canónico.

    • REQUEST_SIGNATURE: esta é a saída da utilização de uma assinatura RSA no passo anterior, codificada em hexadecimal.

    Segue-se um exemplo de um URL concluído:

    https://storage.s3nsapis.fr/example-bucket/cat.jpeg?X-Goog-Algorithm=GOOG4-
    RSA-SHA256&X-Goog-Credential=example%40example-project.s3ns.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

Programa de exemplo Python

Pode usar as bibliotecas de cliente do Cloud Storage para criar URLs assinados para muitas linguagens de programação comuns. Consulte o processo de assinatura V4 com as ferramentas do Cloud Storage para ver exemplos.

O exemplo seguinte mostra uma implementação do algoritmo para assinar URLs que não usa as bibliotecas cliente do Cloud Storage. O exemplo usa a linguagem de programação Python, mas pode ser adaptado à linguagem da sua escolha.

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

O que se segue?