Como usar URLs assinados

Nesta página, você terá uma visão geral dos URLs assinados e das instruções para usá-los com o Cloud CDN. Os URLs assinados fornecem acesso a recursos por tempo limitado para qualquer pessoa que tenha o URL, independentemente de o usuário ter uma Conta do Google.

Um URL assinado fornece permissão e tempo limitados para fazer uma solicitação. Os URLs assinados contêm informações de autenticação nas strings de consulta, permitindo que usuários sem credenciais executem ações específicas em um recurso. Ao gerar um URL assinado, você especifica um usuário ou uma conta de serviço que precisa ter permissão suficiente para fazer a solicitação associada a esse URL.

Depois que a geração for concluída, qualquer pessoa que tenha o URL assinado poderá usá-lo para executar as ações especificadas, como a leitura de um objeto, dentro do período especificado.

Os URLs assinados também são compatíveis com um parâmetro URLPrefix opcional, permitindo o acesso a vários URLs com base em um prefixo comum.

Se você quiser acessar o escopo de um prefixo de URL específico, considere usar cookies assinados.

Antes de começar

Antes de usar URLs assinados, faça o seguinte:

  • Verifique se o Cloud CDN está ativado. Para instruções, consulte Como usar o Cloud CDN. É possível configurar URLs assinados em um back-end antes de ativar o Cloud CDN, mas isso não terá efeito até que o Cloud CDN seja ativado.

  • Se necessário, atualize para a versão mais recente do SDK do Cloud:

    gcloud components update
    

Para uma visão geral, consulte URLs e cookies assinados.

Como configurar chaves de solicitação assinadas

A criação de chaves para seus URLs ou cookies assinados requer vários passos, descritos nas seções a seguir.

Considerações sobre segurança

É preciso configurar os servidores da Web de origem para validar as assinaturas em todas as solicitações assinadas e aceitar ou rejeitar solicitações não assinadas.

  • O Cloud CDN não bloqueia solicitações sem um parâmetro de consulta Signature. Rejeita solicitações com parâmetros de solicitação inválidos (ou com outros tipos de erros).
  • Quando o aplicativo detectar uma assinatura inválida, verifique se ele retorna um código de resposta HTTP 403 (Unauthorized). Os códigos de resposta de HTTP 403 não são armazenáveis em cache.
  • Se o aplicativo enviar um código de resposta armazenável em cache para uma solicitação inválida, as solicitações futuras válidas poderão ser rejeitadas incorretamente.

Para os back-ends do Cloud Storage, certifique-se de ter removido o acesso público, que permite que o Cloud Storage rejeite as solicitações que não têm uma assinatura válida.

Como criar chaves de solicitação assinadas

Para ativar o suporte a URLs e cookies assinados do Cloud CDN, crie uma ou mais chaves em um serviço de back-end com Cloud CDN ativado, um bucket de back-end ou ambos.

Para cada serviço ou bucket de back-end, é possível criar e excluir chaves, conforme as necessidades de segurança. Cada back-end pode ter até três chaves configuradas de cada vez. Sugerimos rotacionar periodicamente suas chaves excluindo a mais antiga, adicionando uma nova e usando-a ao assinar URLs ou cookies.

É possível usar o mesmo nome de chave em vários serviços e buckets de back-end, porque cada conjunto de chaves é independente dos outros. Os nomes das chaves podem ter até 63 caracteres. Para nomear as chaves, use os caracteres AZ, az, 0-9, _ (sublinhado) e - (hífen).

Ao criar chaves, certifique-se de mantê-las seguras, porque qualquer pessoa que tenha uma das chaves pode criar URLs ou cookies assinados aceitos pelo Cloud CDN até que a chave seja excluída. As chaves são armazenadas no computador em que os URLs ou cookies assinados são gerados. O Cloud CDN também armazena as chaves para verificar as assinaturas das solicitações.

Para manter as chaves em segredo, os valores de chave não são incluídos nas respostas a solicitações de API. Se você perder uma chave, será necessário criar uma nova.

Para criar chaves, siga estas etapas:

Console

  1. No Console do Google Cloud, acesse a página do Cloud CDN.

    Acessar a página do Cloud CDN

  2. Clique em Adicionar origem.
  3. Selecione um balanceador de carga HTTP(S) como origem.
  4. Selecione serviços ou buckets de back-end. Para cada um deles, faça o seguinte:
    1. Clique em Configurar e, em seguida, em Adicionar chave de assinatura.
    2. Em Nome, atribua um nome para a nova chave de assinatura.
    3. Em Método de criação de chave, selecione Gerar automaticamente ou Quero inserir.
    4. Se você estiver digitando sua própria chave, digite-a no campo de texto.
    5. Clique em Concluído.
    6. Em Idade máxima da entrada do cache, forneça um valor e selecione uma Unidade de tempo na lista suspensa. É possível escolher entre segundo, minuto, hora, e dia. O período máximo de tempo é de três (3) dias.
  5. Clique em Save.
  6. Clique em Adicionar.

gcloud

A ferramenta de linha de comando gcloud lê as chaves de um arquivo local que você especificou. Para criar o arquivo de chave, gere 128 bits fortemente aleatórios, codifique-os com Base64 e substitua o caractere + por - e o caractere / por _. Para mais informações, consulte a RFC 46485 (em inglês). É essencial que a chave seja fortemente aleatória. Em um sistema semelhante ao UNIX, é possível gerar uma chave fortemente aleatória e armazená-la no arquivo de chave com o seguinte comando:

head -c 16 /dev/urandom | base64 | tr +/ -_ > KEY_FILE_NAME

Para adicionar a chave a um serviço de back-end:

gcloud compute backend-services \
   add-signed-url-key BACKEND_NAME \
   --key-name KEY_NAME \
   --key-file KEY_FILE_NAME

Para adicionar a chave a um bucket de back-end:

gcloud compute backend-buckets \
   add-signed-url-key BACKEND_NAME \
   --key-name KEY_NAME \
   --key-file KEY_FILE_NAME

Como configurar permissões do Cloud Storage

Se você usar o Cloud Storage e tiver restringido quem pode ler os objetos, será necessário conceder permissão ao Cloud CDN para ler os objetos adicionando a conta de serviço do Cloud CDN às ACLs do Cloud Storage.

Não é necessário criar a conta de serviço. Ela é criada automaticamente na primeira vez que você adiciona uma chave a um bucket de back-end em um projeto.

Antes de executar o comando a seguir, adicione pelo menos uma chave a um bucket de back-end no projeto. Caso contrário, o comando falhará com um erro, já que a conta de serviço de preenchimento de cache do Cloud CDN não será criada até que você inclua uma ou mais chaves para o projeto. Substitua PROJECT_NUM pelo número do projeto e BUCKET pelo bucket de armazenamento.

gsutil iam ch \
  serviceAccount:service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com:objectViewer \
  gs://BUCKET

A conta de serviço do Cloud CDN, service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com, não aparece na lista de contas de serviço no seu projeto. Isso acontece porque a conta de serviço do Cloud CDN é de propriedade dele, não do seu projeto.

Para mais informações sobre números de projeto, consulte Localizar o ID e o número do projeto na documentação de ajuda do Console do Google Cloud.

Como personalizar opcionalmente o tempo máximo de cache

O Cloud CDN armazena em cache as respostas para solicitações assinadas, independentemente do cabeçalho Cache-Control do back-end. O tempo máximo em que as respostas podem ser armazenadas em cache sem revalidação é definido pela sinalização signed-url-cache-max-age, que tem como padrão uma hora e pode ser modificada conforme mostrado aqui.

Para definir o tempo máximo de cache para um serviço ou um bucket de back-end, execute um dos comandos a seguir:

gcloud compute backend-services update BACKEND_NAME
  --signed-url-cache-max-age MAX_AGE
gcloud compute backend-buckets update BACKEND_NAME
  --signed-url-cache-max-age MAX_AGE

Como listar nomes de chave de solicitação assinadas

Para listar as chaves em um serviço ou bucket de back-end, execute um dos comandos a seguir:

gcloud compute backend-services describe BACKEND_NAME
gcloud compute backend-buckets describe BACKEND_NAME

Como excluir chaves de solicitação assinadas

Quando os URLs assinados por uma chave específica não forem mais aceitos, execute um dos seguintes comandos para excluir a chave do serviço ou do bucket de back-end:

gcloud compute backend-services \
   delete-signed-url-key BACKEND_NAME --key-name KEY_NAME
gcloud compute backend-buckets \
   delete-signed-url-key BACKEND_NAME --key-name KEY_NAME

Como assinar URLs

O último passo é a assinatura e a distribuição de URLs. Você pode assinar URLs usando o comando gcloud compute sign-url ou o código que você mesmo escreveu. Caso você precise de vários URLs assinados, códigos personalizados oferecem um desempenho melhor.

Como criar URLs assinados

Use estas instruções para criar URLs assinados usando o comando gcloud compute sign-url. Nesta etapa, presume-se que você já tenha criado as chaves.

Console

Não é possível criar URLs assinados usando o Console do Cloud. Você pode usar a ferramenta de linha de comando gcloud ou gravar um código personalizado usando os exemplos a seguir.

gcloud

A ferramenta de linha de comando gcloud inclui um comando para assinar URLs. O comando implementa o algoritmo descrito na seção sobre como escrever seu próprio código.

gcloud compute sign-url \
  --key-name KEY_NAME \
  --key-file KEY_FILE_NAME \
  --expires-in TIME_UNTIL_EXPIRATION \
  [--validate] \
  "URL"

Esse comando lê e decodifica o valor da chave codificada base64url (em inglês) de KEY_FILE_NAME e gera um URL assinado que pode ser usado para solicitações GET ou HEAD no URL fornecido.

Exemplo:

gcloud compute sign-url \
  --key-name my-test-key \
  --expires-in 30m \
  --key-file sign-url-key-file \
  "https://example.com/media/video.mp4"

O URL precisa ser um URL válido que tenha um componente de caminho. Por exemplo, http://example.com é inválido, mas https://example.com/ e https://example.com/whatever são URLs válidos.

Se a sinalização --validate opcional for fornecida, esse comando enviará uma solicitação HEAD com o URL resultante e imprimirá o código de resposta HTTP. Caso o URL assinado esteja correto, o código de resposta será o mesmo que o código de resultado enviado pelo back-end. Se o código de resposta não for o mesmo, verifique novamente o KEY_NAME e o conteúdo do arquivo especificado e veja se o valor de TIME_UNTIL_EXPIRATION corresponde, pelo menos, a alguns segundos.

Se a sinalização --validate não for fornecida, o seguinte não será verificado:

  • As entradas
  • O URL gerado
  • O URL assinado gerado

Como criar programaticamente URLs assinados

Os exemplos de código a seguir demonstram como criar URLs assinados de maneira programática.

Go

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"time"
)

// SignURL creates a signed URL for an endpoint on Cloud CDN.
//
// - url must start with "https://" and should not have the "Expires", "KeyName", or "Signature"
// query parameters.
// - key should be in raw form (not base64url-encoded) which is 16-bytes long.
// - keyName must match a key added to the backend service or bucket.
func signURL(url, keyName string, key []byte, expiration time.Time) string {
	sep := "?"
	if strings.Contains(url, "?") {
		sep = "&"
	}
	url += sep
	url += fmt.Sprintf("Expires=%d", expiration.Unix())
	url += fmt.Sprintf("&KeyName=%s", keyName)

	mac := hmac.New(sha1.New, key)
	mac.Write([]byte(url))
	sig := base64.URLEncoding.EncodeToString(mac.Sum(nil))
	url += fmt.Sprintf("&Signature=%s", sig)
	return url
}

// SignURLWithPrefix creates a signed URL prefix for an endpoint on Cloud CDN.
// Prefixes allow access to any URL with the same prefix, and can be useful for
// granting access broader content without signing multiple URLs.
//
// - urlPrefix must start with "https://" and should not include query parameters.
// - key should be in raw form (not base64url-encoded) which is 16-bytes long.
// - keyName must match a key added to the backend service or bucket.
func signURLWithPrefix(urlPrefix, keyName string, key []byte, expiration time.Time) (string, error) {
	if strings.Contains(urlPrefix, "?") {
		return "", fmt.Errorf("urlPrefix must not include query params: %s", urlPrefix)
	}

	encodedURLPrefix := base64.URLEncoding.EncodeToString([]byte(urlPrefix))
	input := fmt.Sprintf("URLPrefix=%s&Expires=%d&KeyName=%s",
		encodedURLPrefix, expiration.Unix(), keyName)

	mac := hmac.New(sha1.New, key)
	mac.Write([]byte(input))
	sig := base64.URLEncoding.EncodeToString(mac.Sum(nil))

	signedValue := fmt.Sprintf("%s&Signature=%s", input, sig)

	return signedValue, nil
}

// readKeyFile reads the base64url-encoded key file and decodes it.
func readKeyFile(path string) ([]byte, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read key file: %+v", err)
	}
	d := make([]byte, base64.URLEncoding.DecodedLen(len(b)))
	n, err := base64.URLEncoding.Decode(d, b)
	if err != nil {
		return nil, fmt.Errorf("failed to base64url decode: %+v", err)
	}
	return d[:n], nil
}

func generateSignedURLs(w io.Writer) error {
	// The path to a file containing the base64-encoded signing key
	keyPath := os.Getenv("KEY_PATH")

	// Note: consider using the GCP Secret Manager for managing access to your
	// signing key(s).
	key, err := readKeyFile(keyPath)
	if err != nil {
		return err
	}

	// Generate a signed URL for a specific URL
	url := signURL("https://example.com/media/1234.m3e8", "my-key", key, time.Now().Add(time.Hour*24))
	fmt.Fprintln(w, url)

	// Generate a signed URL for
	urlPrefix, err := signURLWithPrefix("https://www.google.com/", "my-key", key, time.Unix(1549751401, 0))
	if err != nil {
		return err
	}

	fmt.Fprintln(w, urlPrefix)

	return nil
}

Ruby

def signed_url url:, key_name:, key:, expiration:
  # url        = "URL of the endpoint served by Cloud CDN"
  # key_name   = "Name of the signing key added to the Google Cloud Storage bucket or service"
  # key        = "Signing key as urlsafe base64 encoded string"
  # expiration = Ruby Time object with expiration time

  require "base64"
  require "openssl"
  require "time"

  # Decode the URL safe base64 encode key
  decoded_key = Base64.urlsafe_decode64 key

  # Get UTC time in seconds
  expiration_utc = expiration.utc.to_i

  # Determine which separator makes sense given a URL
  separator = "?"
  separator = "&" if url.include? "?"

  # Concatenate url with expected query parameters Expires and KeyName
  url = "#{url}#{separator}Expires=#{expiration_utc}&KeyName=#{key_name}"

  # Sign the url using the key and url safe base64 encode the signature
  signature         = OpenSSL::HMAC.digest "SHA1", decoded_key, url
  encoded_signature = Base64.urlsafe_encode64 signature

  # Concatenate the URL and encoded signature
  signed_url = "#{url}&Signature=#{encoded_signature}"
end

.NET

        /// <summary>
        /// Creates signed URL for Google Cloud SDN
        /// More details about order of operations is here:
        /// <see cref="https://cloud.google.com/cdn/docs/using-signed-urls#programmatically_creating_signed_urls"/>
        /// </summary>
        /// <param name="url">The Url to sign. This URL can't include Expires and KeyName query parameters in it</param>
        /// <param name="keyName">The name of the key used to sign the URL</param>
        /// <param name="encodedKey">The key used to sign the Url</param>
        /// <param name="expirationTime">Expiration time of the signature</param>
        /// <returns>Signed Url that is valid until {expirationTime}</returns>
        public static string CreateSignedUrl(string url, string keyName, string encodedKey, DateTime expirationTime)
        {
            var builder = new UriBuilder(url);

            long unixTimestampExpiration = ToUnixTime(expirationTime);

            char queryParam = string.IsNullOrEmpty(builder.Query) ? '?' : '&';
            builder.Query += $"{queryParam}Expires={unixTimestampExpiration}&KeyName={keyName}".ToString();

            // Key is passed as base64url encoded
            byte[] decodedKey = Base64UrlDecode(encodedKey);

            // Computes HMAC SHA-1 hash of the URL using the key
            byte[] hash = ComputeHash(decodedKey, builder.Uri.AbsoluteUri);
            string encodedHash = Base64UrlEncode(hash);

            builder.Query += $"&Signature={encodedHash}";
            return builder.Uri.AbsoluteUri;
        }

        private static long ToUnixTime(DateTime date)
        {
            var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
            return Convert.ToInt64((date - epoch).TotalSeconds);
        }

        private static byte[] Base64UrlDecode(string arg)
        {
            string s = arg;
            s = s.Replace('-', '+'); // 62nd char of encoding
            s = s.Replace('_', '/'); // 63rd char of encoding

            return Convert.FromBase64String(s); // Standard base64 decoder
        }

        private static string Base64UrlEncode(byte[] inputBytes)
        {
            var output = Convert.ToBase64String(inputBytes);

            output = output.Replace('+', '-')      // 62nd char of encoding
                           .Replace('/', '_');     // 63rd char of encoding

            return output;
        }

        private static byte[] ComputeHash(byte[] secretKey, string signatureString)
        {
            var enc = Encoding.ASCII;
            using (HMACSHA1 hmac = new HMACSHA1(secretKey))
            {
                hmac.Initialize();

                byte[] buffer = enc.GetBytes(signatureString);

                return hmac.ComputeHash(buffer);
            }
        }

Java

/**
 * Creates a signed URL for a Cloud CDN endpoint with the given key
 * URL must start with http:// or https://, and must contain a forward
 * slash (/) after the hostname.
 * @param url the Cloud CDN endpoint to sign
 * @param key url signing key uploaded to the backend service/bucket, as a 16-byte array
 * @param keyName the name of the signing key added to the back end bucket or service
 * @param expirationTime the date that the signed URL expires
 * @return a properly formatted signed URL
 * @throws InvalidKeyException when there is an error generating the signature for the input key
 * @throws NoSuchAlgorithmException when HmacSHA1 algorithm is not available in the environment
 */
public static String signUrl(String url,
                             byte[] key,
                             String keyName,
                             Date expirationTime)
        throws InvalidKeyException, NoSuchAlgorithmException {

  final long unixTime = expirationTime.getTime() / 1000;

  String urlToSign = url
                      + (url.contains("?") ? "&" : "?")
                      + "Expires=" + unixTime
                      + "&KeyName=" + keyName;

  String encoded = SignedUrls.getSignature(key, urlToSign);
  return urlToSign + "&Signature=" + encoded;
}

public static String getSignature(byte[] privateKey, String input)
    throws InvalidKeyException, NoSuchAlgorithmException {

  final String algorithm = "HmacSHA1";
  final int offset = 0;
  Key key = new SecretKeySpec(privateKey, offset, privateKey.length, algorithm);
  Mac mac = Mac.getInstance(algorithm);
  mac.init(key);
  return  Base64.getUrlEncoder().encodeToString(mac.doFinal(input.getBytes()));
}

Python

def sign_url(url, key_name, base64_key, expiration_time):
    """Gets the Signed URL string for the specified URL and configuration.

    Args:
        url: URL to sign as a string.
        key_name: name of the signing key as a string.
        base64_key: signing key as a base64 encoded string.
        expiration_time: expiration time as a UTC datetime object.

    Returns:
        Returns the Signed URL appended with the query parameters based on the
        specified configuration.
    """
    stripped_url = url.strip()
    parsed_url = urllib.parse.urlsplit(stripped_url)
    query_params = urllib.parse.parse_qs(
        parsed_url.query, keep_blank_values=True)
    epoch = datetime.datetime.utcfromtimestamp(0)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    url_pattern = u'{url}{separator}Expires={expires}&KeyName={key_name}'

    url_to_sign = url_pattern.format(
            url=stripped_url,
            separator='&' if query_params else '?',
            expires=expiration_timestamp,
            key_name=key_name)

    digest = hmac.new(
        decoded_key, url_to_sign.encode('utf-8'), hashlib.sha1).digest()
    signature = base64.urlsafe_b64encode(digest).decode('utf-8')

    signed_url = u'{url}&Signature={signature}'.format(
            url=url_to_sign, signature=signature)

    print(signed_url)

def sign_url_prefix(url, url_prefix, key_name, base64_key, expiration_time):
    """Gets the Signed URL string for the specified URL prefix and configuration.

    Args:
        url: URL of request.
        url_prefix: URL prefix to sign as a string.
        key_name: name of the signing key as a string.
        base64_key: signing key as a base64 encoded string.
        expiration_time: expiration time as a UTC datetime object.

    Returns:
        Returns the Signed URL appended with the query parameters based on the
        specified URL prefix and configuration.
    """
    stripped_url = url.strip()
    parsed_url = urllib.parse.urlsplit(stripped_url)
    query_params = urllib.parse.parse_qs(
        parsed_url.query, keep_blank_values=True)
    encoded_url_prefix = base64.urlsafe_b64encode(
            url_prefix.strip().encode('utf-8')).decode('utf-8')
    epoch = datetime.datetime.utcfromtimestamp(0)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    policy_pattern = u'URLPrefix={encoded_url_prefix}&Expires={expires}&KeyName={key_name}'
    policy = policy_pattern.format(
            encoded_url_prefix=encoded_url_prefix,
            expires=expiration_timestamp,
            key_name=key_name)

    digest = hmac.new(
            decoded_key, policy.encode('utf-8'), hashlib.sha1).digest()
    signature = base64.urlsafe_b64encode(digest).decode('utf-8')

    signed_url = u'{url}{separator}{policy}&Signature={signature}'.format(
            url=stripped_url,
            separator='&' if query_params else '?',
            policy=policy,
            signature=signature)

    print(signed_url)

PHP

/**
 * Decodes base64url (RFC4648 Section 5) string
 *
 * @param string $input base64url encoded string
 *
 * @return string
 */
function base64url_decode($input)
{
    $input .= str_repeat('=', (4 - strlen($input) % 4) % 4);
    return base64_decode(strtr($input, '-_', '+/'), true);
}

/**
* Encodes a string with base64url (RFC4648 Section 5)
* Keeps the '=' padding by default.
*
* @param string $input   String to be encoded
* @param bool   $padding Keep the '=' padding
*
* @return string
*/
function base64url_encode($input, $padding = true)
{
    $output = strtr(base64_encode($input), '+/', '-_');
    return ($padding) ? $output : str_replace('=', '',  $output);
}

/**
 * Creates signed URL for Google Cloud CDN
 * Details about order of operations: https://cloud.google.com/cdn/docs/using-signed-urls#creating_signed_urls
 *
 * Example function invocation (In production store the key safely with other secrets):
 *
 *     <?php
 *     $base64UrlKey = 'wpLL7f4VB9RNe_WI0BBGmA=='; // head -c 16 /dev/urandom | base64 | tr +/ -_
 *     $signedUrl = sign_url('https://example.com/foo', 'my-key', $base64UrlKey, time() + 1800);
 *     echo $signedUrl;
 *     ?>
 *
 * @param string $url             URL of the endpoint served by Cloud CDN
 * @param string $keyName         Name of the signing key added to the Google Cloud Storage bucket or service
 * @param string $base64UrlKey    Signing key as base64url (RFC4648 Section 5) encoded string
 * @param int    $expirationTime  Expiration time as a UNIX timestamp (GMT, e.g. time())
 *
 * @return string
 */
function sign_url($url, $keyName, $base64UrlKey, $expirationTime)
{
    // Decode the key
    $decodedKey = base64url_decode($base64UrlKey);

    // Determine which separator makes sense given a URL
    $separator = (strpos($url, '?') === false) ? '?' : '&';

    // Concatenate url with expected query parameters Expires and KeyName
    $url = "{$url}{$separator}Expires={$expirationTime}&KeyName={$keyName}";

    // Sign the url using the key and encode the signature using base64url
    $signature = hash_hmac('sha1', $url, $decodedKey, true);
    $encodedSignature = base64url_encode($signature);

    // Concatenate the URL and encoded signature
    return "{$url}&Signature={$encodedSignature}";
}

Como gerar URLs assinados personalizados

Quando você escreve seu próprio código para gerar URLs assinados, seu objetivo é criar URLs com o seguinte formato: ou algoritmo: todos os parâmetros de URL diferenciam maiúsculas de minúsculas e precisam estar nesta ordem:

https://example.com/foo?Expires=EXPIRATION&KeyName=KEY_NAME&Signature=SIGNATURE

Para gerar URLs assinados, siga estas etapas:

  1. Certifique-se de que o URL para assinatura não tenha um parâmetro de consulta Signature.

  2. Determine quando o URL expira e anexe um parâmetro de consulta Expires com o prazo de validade desejado em horário UTC [em inglês] (o número de segundos desde 1970-01-01 00:00:00 UTC). Para aumentar a segurança, defina o valor como o menor período de tempo possível no seu caso de uso. Quanto maior for o período de validade de um URL assinado, maior será o risco de ele ser compartilhado com outras pessoas, acidentalmente ou não, pelo usuário que o recebeu.

  3. Defina o nome da chave. O URL precisa ser assinado com uma chave do serviço ou bucket de back-end que veiculará o URL. É melhor usar a chave adicionada mais recentemente para a rotação de chaves. Adicione a chave ao URL anexando &KeyName=KEY_NAME. Substitua KEY_NAME pelo nome da chave escolhida criada em Como criar chaves de solicitação assinadas.

  4. Assine o URL. Crie o URL assinado seguindo estes passos. Verifique se os parâmetros da consulta estão na ordem mostrada imediatamente antes da Etapa 1 e certifique-se de que não haja mudança de maiúsculas/minúsculas no URL assinado.

    a. Gere hash para o URL inteiro (incluindo http:// ou https:// no início e &KeyName... no final) com HMAC-SHA1 usando a chave secreta que corresponde ao nome da chave escolhido anteriormente. Use a chave secreta bruta de 16 bytes, não a chave base64url codificada (em inglês). Decodifique-a, se necessário.

    b. Use codificação base64url para codificar o resultado.

    c. Anexe &Signature= ao URL, seguido pela assinatura codificada.

Como usar prefixos de URL para URLs assinados

Em vez de assinar o URL de solicitação completo com os parâmetros de consulta Expires e KeyName, assine apenas os parâmetros de consulta URLPrefix, Expires e KeyName. Isso permite que uma determinada combinação de parâmetros de consulta URLPrefix, Expires, KeyName e Signature seja reutilizada textualmente em vários URLs que correspondem ao URLPrefix. Assim, não é preciso criar uma nova assinatura para cada URL distinto.

No exemplo a seguir, o texto destacado mostra os parâmetros assinados. O Signature é anexado como o parâmetro de consulta final, como de costume.

https://media.example.com/videos/id/master.m3u8?userID=abc123&starting_profile=1&URLPrefix=aHR0cHM6Ly9tZWRpYS5leGFtcGxlLmNvbS92aWRlb3Mv&Expires=1566268009&KeyName=mySigningKey&Signature=8NBSdQGzvDftrOIa3WHpp646Iis=

URLPrefix indica um prefixo de URL codificado em Base64 e seguro que abrange todos os caminhos válidos para o cookie.

Um URLPrefix codifica um esquema (http:// ou https://), FQDN e um caminho opcional. Encerrar o caminho com um / é opcional, mas recomendado. O prefixo não pode incluir parâmetros de consulta ou fragmentos como ? ou #.

Por exemplo, https://media.example.com/videos corresponde a solicitações para os seguintes itens:

  • https://media.example.com/videos?video_id=138183&user_id=138138
  • https://media.example.com/videos/137138595?quality=low

O caminho do prefixo é usado como uma substring de texto, não estritamente como um caminho de diretório. Por exemplo, o prefixo https://example.com/data concede acesso aos dois itens a seguir:

  • /data/file1
  • /database

Para evitar esse erro, recomendamos encerrar todos os prefixos com /, a menos que você opte ativamente por encerrar o prefixo com um nome de arquivo parcial, como https://media.example.com/videos/123, para conceder acesso ao seguinte:

  • /videos/123_chunk1
  • /videos/123_chunk2
  • /videos/123_chunkN

Se o URL solicitado não corresponder a URLPrefix, o Cloud CDN rejeitará a solicitação e retornará um erro HTTP 403 ao cliente.

Como remover o acesso público ao bucket do Cloud Storage

Para que URLs assinados protejam adequadamente o conteúdo, é importante que o servidor de origem não conceda acesso público a esse conteúdo. Ao usar um bucket do Cloud Storage, uma abordagem comum é tornar os objetos temporariamente públicos para teste. Depois de ativar os URLs assinados, é importante remover as permissões READ allUsers (e allAuthenticatedUsers, se aplicável) no bucket. Em outras palavras, o papel Visualizador de objetos do Storage do IAM.

Depois de desabilitar o acesso público no bucket, os usuários individuais ainda poderão acessar o Cloud Storage sem URLs assinados se tiverem permissão de acesso, como a permissão OWNER (PROPRIETÁRIO).

Para remover o acesso de LEITURA público allUsers em um bucket do Cloud Storage, reverta a ação descrita em Como tornar todos os objetos em um bucket publicamente legíveis.

Como distribuir e usar URLs assinados

O URL retornado da ferramenta de linha de comando gcloud ou produzido pelo seu código personalizado pode ser distribuído de acordo com suas necessidades. Recomendamos assinar somente URLs de HTTPS, porque o HTTPS fornece um transporte seguro que impede a interceptação do componente de Signature do URL assinado. Da mesma forma, certifique-se de distribuir os URLs assinados, por protocolos de transporte seguros, como TLS/HTTPS.