Utilizza cookie firmati

Questa pagina fornisce una panoramica sui cookie firmati e le istruzioni per utilizzarli con Cloud CDN. I cookie firmati forniscono accesso alle risorse a tempo limitato a un set di file, indipendentemente dal fatto che gli utenti abbiano un Account Google.

I cookie firmati sono un'alternativa agli URL firmati. Servono a proteggere l'accesso quando non è possibile firmare separatamente decine o centinaia di URL per ogni utente nella tua applicazione.

I cookie firmati ti consentono di eseguire queste operazioni:

  • Autorizzare un utente e fornirgli un token a tempo limitato per accedere ai tuoi contenuti protetti (anziché firmare ogni URL).
  • Limitare l'accesso dell'utente a un prefisso URL specifico, ad esempio https://media.example.com/videos/, e concedere all'utente autorizzato l'accesso solo ai contenuti protetti all'interno di quel prefisso URL.
  • Mantenere invariati gli URL e i manifest dei contenuti multimediali, semplificando la pipeline di packaging e migliorando la capacità di memorizzazione nella cache.

Se invece vuoi limitare l'accesso a URL specifici, valuta la possibilità di utilizzare URL firmati.

Prima di iniziare

Prima di utilizzare i cookie firmati, completa i seguenti passaggi:

  • Assicurati che Cloud CDN sia abilitato. Per istruzioni, consulta Utilizzo di Cloud CDN. Puoi configurare i cookie firmati su un backend prima di abilitare Cloud CDN, ma la configurazione non ha effetto finché Cloud CDN non è abilitato.

  • Se necessario, esegui l'aggiornamento all'ultima versione di Google Cloud CLI:

    gcloud components update
    

Per una panoramica, consulta URL e cookie firmati.

Configurazione delle chiavi per le richieste firmate

La creazione di chiavi per gli URL o i cookie firmati richiede diversi passaggi, descritti nelle sezioni seguenti.

Considerazioni sulla sicurezza

Cloud CDN non convalida le richieste nelle seguenti circostanze:

  • La richiesta non è firmata.
  • Il servizio di backend o il bucket di backend per la richiesta non ha Cloud CDN abilitato.

Le richieste firmate devono sempre essere convalidate all'origine prima della pubblicazione della risposta, perché le origini possono essere utilizzate per pubblicare un mix di contenuti firmati e non firmati e perché un client potrebbe accedere direttamente all'origine.

  • Cloud CDN non blocca le richieste senza un parametro di query Signature o un cookie HTTP Cloud-CDN-Cookie. Rifiuta le richieste con parametri di richiesta non validi (o con un formato errato).
  • Quando l'applicazione rileva una firma non valida, assicurati che risponda con un codice di risposta HTTP 403 (Unauthorized). I codici di risposta HTTP 403 non sono memorizzabili nella cache.
  • Le risposte alle richieste firmate e non firmate vengono memorizzate separatamente nella cache, pertanto una risposta positiva a una richiesta firmata valida non viene mai utilizzata per pubblicare una richiesta non firmata.
  • Se la tua applicazione invia un codice di risposta memorizzabile nella cache a una richiesta non valida, le richieste future valide potrebbero essere incorrettamente rifiutate.

Per i backend Cloud Storage, assicurati di rimuovere l'accesso pubblico, in modo che Cloud Storage possa rifiutare le richieste a cui manca una firma valida.

La seguente tabella riassume il comportamento.

La richiesta ha una firma Successo della cache Comportamento
No No Inoltra all'origine di backend.
No Pubblica dalla cache.
No Convalida la firma. Se è valida, inoltra all'origine di backend.
Convalida la firma. Se è valida, pubblica dalla cache.

Crea le chiavi per le richieste firmate

Per abilitare il supporto per i cookie e gli URL firmati di Cloud CDN, devi creare una o più chiavi in un servizio di backend, un bucket di backend o entrambi abilitati per Cloud CDN.

Per ogni servizio di backend o bucket di backend, puoi creare ed eliminare le chiavi in base alle tue esigenze di sicurezza. Ogni backend può avere fino a tre chiavi configurate alla volta. Ti consigliamo di ruotare periodicamente le chiavi eliminando la più vecchia, aggiungendo una nuova chiave e utilizzandola per firmare URL o cookie.

Puoi utilizzare lo stesso nome della chiave in più servizi di backend e bucket di backend perché ogni set di chiavi è indipendente dagli altri. I nomi delle chiavi possono contenere fino a 63 caratteri. Per assegnare un nome alle chiavi, utilizza i caratteri A-Z, a-z, 0-9, _ (trattino basso) e - (trattino).

Quando crei le chiavi, assicurati di proteggerle, perché chiunque ne sia in possesso può creare URL o cookie firmati che Cloud CDN accetta finché la chiave non viene eliminata da Cloud CDN. Le chiavi vengono archiviate sul computer in cui generi gli URL o i cookie firmati. Cloud CDN archivia le chiavi anche per verificare le firme delle richieste.

Per mantenere segrete le chiavi, i valori delle chiavi non sono inclusi nelle risposte a nessuna richiesta API. Se perdi una chiave, devi crearne una nuova.

Per creare una chiave di richiesta firmata, segui questi passaggi.

Console

  1. Nella console Google Cloud , vai alla pagina Cloud CDN.

    Vai a Cloud CDN

  2. Fai clic sul nome dell'origine a cui vuoi aggiungere la chiave.
  3. Nella pagina Dettagli origine, fai clic sul pulsante Modifica.
  4. Nella sezione Elementi di base dell'origine, fai clic su Avanti per aprire la sezione Regole host e percorso.
  5. Nella sezione Regole host e percorso, fai clic su Avanti per aprire la sezione Prestazioni della cache.
  6. Nella sezione Contenuti con limitazioni, seleziona Limita accesso con URL e cookie firmati.
  7. Fai clic su Aggiungi chiave di firma.

    1. Specifica un nome univoco per la nuova chiave di firma.
    2. Nella sezione Metodo di creazione chiave, seleziona Genera automaticamente. In alternativa, fai clic su Inserisci e poi specifica un valore per la chiave di firma.

      Per la prima opzione, copia il valore della chiave di firma generata automaticamente in un file privato, che puoi utilizzare per creare URL firmati.

    3. Fai clic su Fine.

    4. Nella sezione Durata massima delle voci di cache, inserisci un valore e poi seleziona un'unità di tempo.

  8. Fai clic su Fine.

gcloud

Lo strumento a riga di comando gcloud legge le chiavi da un file locale che specifichi. Il file della chiave deve essere creato generando 128 bit fortemente casuali, codificandoli con base64 e sostituendo il carattere + con - e il carattere / con _. Per saperne di più, consulta RFC 4648. È fondamentale che la chiave sia fortemente casuale. Su un sistema di tipo UNIX, puoi generare una chiave fortemente casuale e archiviarla nel file della chiave con il seguente comando:

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

Per aggiungere la chiave a un servizio di backend:

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

Per aggiungere la chiave a un bucket di backend:

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

Configura le autorizzazioni Cloud Storage

Se utilizzi Cloud Storage e hai limitato chi può leggere gli oggetti, devi concedere a Cloud CDN l'autorizzazione per leggere gli oggetti aggiungendo il service account Cloud CDN agli ACL di Cloud Storage.

Non è necessario creare il service account. Il service account viene creato automaticamente la prima volta che aggiungi una chiave a un bucket di backend in un progetto.

Prima di eseguire il comando seguente, aggiungi almeno una chiave a un bucket di backend nel tuo progetto. In caso contrario, il comando non va a buon fine e viene visualizzato un errore perché il service account di riempimento della cache di Cloud CDN non viene creato finché non aggiungi una o più chiavi per il progetto.

gcloud storage buckets add-iam-policy-binding gs://BUCKET \
  --member=serviceAccount:service-PROJECT_NUMBER@cloud-cdn-fill.iam.gserviceaccount.com \
  --role=roles/storage.objectViewer

Sostituisci PROJECT_NUMBER con il numero del progetto e BUCKET con il bucket di archiviazione.

Il service account di Cloud CDN service-PROJECT_NUMBER@cloud-cdn-fill.iam.gserviceaccount.com non viene visualizzato nell'elenco dei service account del tuo progetto. Questo perché il service account di Cloud CDN è di proprietà di Cloud CDN, non del tuo progetto.

Per saperne di più sui numeri di progetto, consulta Individua l'ID progetto e il numero di progetto nella documentazione della guida della console Google Cloud .

Personalizza la durata massima della cache

Cloud CDN memorizza nella cache le risposte per le richieste firmate indipendentemente dall'intestazione Cache-Control del backend. Il tempo massimo per cui le risposte possono essere memorizzate nella cache senza convalida è impostato dal flag signed-url-cache-max-age, che è impostato su un'ora per impostazione predefinita e può essere modificato come mostrato qui.

Per impostare la durata massima della cache per un servizio di backend o un bucket di backend, esegui uno dei seguenti comandi:

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

Elenca i nomi delle chiavi per le richieste firmate

Per elencare le chiavi in un servizio di backend o in un bucket di backend, esegui uno dei seguenti comandi:

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

Elimina le chiavi per le richieste firmate

Quando gli URL firmati da una determinata chiave non devono più essere rispettati, esegui uno dei seguenti comandi per eliminare la chiave dal servizio di backend o dal bucket di backend:

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

Creazione di una policy

Le policy per i cookie firmati sono una serie di coppie key-value (delimitate dal carattere :), simili ai parametri di query utilizzati in un URL firmato. Per vedere degli esempi, consulta Emissione di cookie per gli utenti.

Le policy rappresentano i parametri per i quali una richiesta è valida. Le policy sono firmate utilizzando un codice HMAC (Hash-based Message Authentication Code) che Cloud CDN convalida a ogni richiesta.

Definizione del formato e dei campi delle policy

Esistono quattro campi obbligatori che devi definire nel seguente ordine:

  • URLPrefix
  • Expires
  • KeyName
  • Signature

Le coppie key-value in una policy sui cookie firmata sono sensibili alle maiuscole.

URLPrefix

URLPrefix indica un prefisso URL con codifica base64 sicuro per URL che comprende tutti i percorsi per cui la firma deve essere valida.

Un URLPrefix codifica uno schema (http:// o https://), un FQDN e un percorso facoltativo. Terminare il percorso con / è facoltativo, ma consigliato. Il prefisso non deve includere parametri di query o frammenti come ? o #.

Ad esempio, https://media.example.com/videos corrisponde alle richieste di entrambi i seguenti elementi:

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

Il percorso del prefisso viene utilizzato come sottostringa di testo, non strettamente come percorso di directory. Ad esempio, il prefisso https://example.com/data concede l'accesso a entrambi i seguenti elementi:

  • /data/file1
  • /database

Per evitare questo errore, ti consigliamo di terminare tutti i prefissi con /, a meno che tu non scelga intenzionalmente di terminare il prefisso con un nome file parziale come https://media.example.com/videos/123 per concedere l'accesso a quanto segue:

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

Se l'URL richiesto non corrisponde a URLPrefix, Cloud CDN rifiuta la richiesta e restituisce un errore HTTP 403 al client.

Expires

Expires deve essere un timestamp Unix (il numero di secondi trascorsi dal 1° gennaio 1970).

KeyName

KeyName è il nome della chiave creata per il bucket di backend o il servizio di backend. I nomi delle chiavi sono sensibili alle maiuscole.

Signature

Signature è la firma HMAC-SHA-1 con codifica base64 sicura per URL dei campi che compongono la policy sui cookie. Questo campo viene convalidato a ogni richiesta; le richieste con una firma non valida vengono rifiutate con un errore HTTP 403.

Creazione programmatica di cookie firmati

I seguenti esempi di codice mostrano come creare in modo programmatico cookie firmati.

Go

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

// signCookie creates a signed cookie for an endpoint served by Cloud CDN.
//
// - urlPrefix must start with "https://" and should include the path prefix
// for which the cookie will authorize access to.
// - 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 signCookie(urlPrefix, keyName string, key []byte, expiration time.Time) (string, error) {
	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
}

Java

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SignedCookies {

  public static void main(String[] args) throws Exception {
    // TODO(developer): Replace these variables before running the sample.

    // The name of the signing key must match a key added to the back end bucket or service.
    String keyName = "YOUR-KEY-NAME";
    // Path to the URL signing key uploaded to the backend service/bucket.
    String keyPath = "/path/to/key";
    // The Unix timestamp that the signed URL expires.
    long expirationTime = ZonedDateTime.now().plusDays(1).toEpochSecond();
    // URL prefix to sign as a string. URL prefix must start with either "http://" or "https://"
    // and must not include query parameters.
    String urlPrefix = "https://media.example.com/videos/";

    // Read the key as a base64 url-safe encoded string, then convert to byte array.
    // Key used in signing must be in raw form (not base64url-encoded).
    String base64String = new String(Files.readAllBytes(Paths.get(keyPath)),
        StandardCharsets.UTF_8);
    byte[] keyBytes = Base64.getUrlDecoder().decode(base64String);

    // Create signed cookie from policy.
    String signedCookie = signCookie(urlPrefix, keyBytes, keyName, expirationTime);
    System.out.println(signedCookie);
  }

  // Creates a signed cookie for the specified policy.
  public static String signCookie(String urlPrefix, byte[] key, String keyName,
      long expirationTime)
      throws InvalidKeyException, NoSuchAlgorithmException {

    // Validate input URL prefix.
    try {
      URL validatedUrlPrefix = new URL(urlPrefix);
      if (!validatedUrlPrefix.getProtocol().startsWith("http")) {
        throw new IllegalArgumentException(
            "urlPrefix must start with either http:// or https://: " + urlPrefix);
      }
      if (validatedUrlPrefix.getQuery() != null) {
        throw new IllegalArgumentException("urlPrefix must not include query params: " + urlPrefix);
      }
    } catch (MalformedURLException e) {
      throw new IllegalArgumentException(
          "urlPrefix malformed: " + urlPrefix);
    }

    String encodedUrlPrefix = Base64.getUrlEncoder().encodeToString(urlPrefix.getBytes(
        StandardCharsets.UTF_8));
    String policyToSign = String.format("URLPrefix=%s:Expires=%d:KeyName=%s", encodedUrlPrefix,
        expirationTime, keyName);

    String signature = getSignatureForUrl(key, policyToSign);
    return String.format("Cloud-CDN-Cookie=%s:Signature=%s", policyToSign, signature);
  }

  // Creates signature for input string with private key.
  private static String getSignatureForUrl(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(StandardCharsets.UTF_8)));
  }
}

Python

import argparse
import base64
from datetime import datetime, timezone
import hashlib
import hmac
from urllib.parse import parse_qs, urlsplit


def sign_cookie(
    url_prefix: str,
    key_name: str,
    base64_key: str,
    expiration_time: datetime,
) -> str:
    """Gets the Signed cookie value for the specified URL prefix and configuration.

    Args:
        url_prefix: URL prefix to sign.
        key_name: name of the signing key.
        base64_key: signing key as a base64 encoded string.
        expiration_time: expiration time as time-zone aware datetime.

    Returns:
        Returns the Cloud-CDN-Cookie value based on the specified configuration.
    """
    encoded_url_prefix = base64.urlsafe_b64encode(
        url_prefix.strip().encode("utf-8")
    ).decode("utf-8")
    epoch = datetime.fromtimestamp(0, timezone.utc)
    expiration_timestamp = int((expiration_time - epoch).total_seconds())
    decoded_key = base64.urlsafe_b64decode(base64_key)

    policy = f"URLPrefix={encoded_url_prefix}:Expires={expiration_timestamp}:KeyName={key_name}"

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

    signed_policy = f"Cloud-CDN-Cookie={policy}:Signature={signature}"

    return signed_policy

Convalida dei cookie firmati

La procedura di convalida di un cookie firmato è essenzialmente uguale a quella di generazione di un cookie firmato. Ad esempio, supponiamo di voler convalidare la seguente intestazione del cookie firmato:

Cookie: Cloud-CDN-Cookie=URLPrefix=URL_PREFIX:Expires=EXPIRATION:KeyName=KEY_NAME:Signature=SIGNATURE; Domain=media.example.com; Path=/; Expires=Tue, 20 Aug 2019 02:26:49 GMT; HttpOnly

Puoi utilizzare la chiave segreta denominata da KEY_NAME per generare in modo indipendente la firma e poi verificare che corrisponda a SIGNATURE.

Emissione di cookie per gli utenti

La tua applicazione deve generare ed emettere per ogni utente (client) un singolo cookie HTTP contenente una policy firmata correttamente:

  1. Crea un signer HMAC-SHA-1 nel codice dell'applicazione.

  2. Firma la policy utilizzando la chiave scelta e prendi nota del nome della chiave che hai aggiunto al backend, ad esempio mySigningKey.

  3. Crea una policy sui cookie con il seguente formato, tenendo presente che sia il nome che il valore sono sensibili alle maiuscole:

    Name: Cloud-CDN-Cookie
    Value: URLPrefix=$BASE64URLECNODEDURLORPREFIX:Expires=$TIMESTAMP:KeyName=$KEYNAME:Signature=$BASE64URLENCODEDHMAC
    

    Intestazione Set-Cookie di esempio:

    Set-Cookie: Cloud-CDN-Cookie=URLPrefix=aHR0cHM6Ly9tZWRpYS5leGFtcGxlLmNvbS92aWRlb3Mv:Expires=1566268009:KeyName=mySigningKey:Signature=0W2xlMlQykL2TG59UZnnHzkxoaw=; Domain=media.example.com; Path=/; Expires=Tue, 20 Aug 2019 02:26:49 GMT; HttpOnly
    

    Gli attributi Domain e Path nel cookie determinano se il client invia il cookie a Cloud CDN.

Consigli e requisiti

  • Imposta in modo esplicito gli attributi Domain e Path in modo che corrispondano al prefisso del dominio e del percorso da cui intendi pubblicare i tuoi contenuti protetti, che potrebbero differire dal dominio e dal percorso in cui viene emesso il cookie (example.com rispetto a media.example.com o /browse rispetto a /videos).

  • Assicurati di avere un solo cookie con un determinato nome per lo stesso Domain e Path.

  • Assicurati di non emettere cookie in conflitto, perché ciò potrebbe impedire l'accesso ai contenuti in altre sessioni del browser (finestre o schede).

  • Imposta i flag Secure e HttpOnly, se applicabili. Secure garantisce che il cookie venga inviato solo tramite connessioni HTTPS. HttpOnly impedisce di rendere il cookie disponibile per JavaScript.

  • Gli attributi dei cookie Expires e Max-Age sono facoltativi. Se li ometti, il cookie esiste finché esiste la sessione del browser (scheda, finestra).

  • In caso di riempimento o fallimento della cache, il cookie firmato viene passato all'origine definita nel servizio di backend. Assicurati di convalidare il valore del cookie firmato in ogni richiesta prima di pubblicare i contenuti.