Utilizzo dei cookie firmati

Questa pagina fornisce una panoramica dei cookie firmati e le istruzioni per utilizzarli con Cloud CDN. I cookie firmati consentono di accedere a risorse a tempo limitato a un insieme di file, indipendentemente dal fatto che gli utenti dispongano di Account Google.

I cookie firmati sono un'alternativa agli URL firmati. I cookie firmati proteggono l'accesso quando si registrano separatamente decine o centinaia di URL per ciascun utente nella tua applicazione.

I cookie firmati ti consentono di:

  • Autorizza un utente e forniscigli un token a tempo limitato per accedere ai tuoi contenuti protetti (anziché firmare ogni URL).
  • Limita l'accesso dell'utente a un prefisso URL specifico, ad esempio https://media.example.com/videos/, e concedi all'utente autorizzato l'accesso ai contenuti protetti solo all'interno del prefisso dell'URL.
  • Mantieni invariati gli URL e i manifest multimediali, semplificando la pipeline di pacchetti e migliorando la memorizzazione nella cache.

Se preferisci limitare l'ambito all'accesso a URL specifici, valuta la possibilità di utilizzare gli URL firmati.

Prima di iniziare

Prima di utilizzare i cookie firmati, procedi nel seguente modo:

  • Assicurati che Cloud CDN sia abilitato; per le istruzioni, vedi Utilizzare Cloud CDN. Puoi configurare i cookie firmati su un backend prima di abilitare Cloud CDN, ma questo non ha alcun effetto fino a quando non viene attivato Cloud CDN.

  • Se necessario, esegui l'aggiornamento all'ultima versione dell'interfaccia a riga di comando di Google Cloud:

    gcloud components update
    

Per una panoramica, vedi URL e cookie firmati.

Configurazione delle chiavi di richiesta firmate

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

Considerazioni sulla sicurezza

Cloud CDN non convalida le richieste nei seguenti casi:

  • 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 essere sempre convalidate all'origine prima di pubblicare la risposta. Questo 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 ricerca Signature o un cookie HTTP Cloud-CDN-Cookie. La richiesta rifiuta le richieste con parametri di richiesta non validi (o comunque con formato non valido).
  • Se la tua applicazione rileva una firma non valida, assicurati che risponda con un codice di risposta HTTP 403 (Unauthorized). I codici di risposta di HTTP 403 non sono memorizzabili nella cache.
  • Le risposte alle richieste firmate e non firmate vengono memorizzate separatamente nella cache, quindi una risposta riuscita a una richiesta firmata valida non viene mai utilizzata per gestire 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 rifiutate erroneamente.

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

La tabella riportata di seguito riassume il comportamento.

La richiesta ha una firma Successo della cache Comportamento
No No Inoltra all'origine del backend.
No Pubblicare i contenuti dalla cache.
No Convalida firma. Se valido, inoltra all'origine del backend.
Convalida firma. Se valido, pubblica dalla cache.

Creazione di chiavi di richiesta firmate

Puoi attivare il supporto per gli URL e i cookie firmati di Cloud CDN creando una o più chiavi in un servizio di backend con Cloud CDN, un bucket di backend o entrambi.

Per ogni servizio di backend o bucket di backend, puoi creare ed eliminare chiavi in base alle tue esigenze di sicurezza. Ogni backend può avere fino a tre chiavi configurate alla volta. Consigliamo di ruotare periodicamente le chiavi eliminando le meno recenti, aggiungendone una nuova e utilizzando la nuova chiave quando si firmano 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 essere composti da 63 caratteri al massimo. 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 tenerle al sicuro perché chiunque ne abbia una può creare URL firmati o cookie firmati che Cloud CDN accetta fino alla rimozione della chiave da Cloud CDN. Le chiavi vengono memorizzate sul computer in cui vengono generati gli URL o i cookie firmati. Cloud CDN archivia anche le chiavi per verificare le firme delle richieste.

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

Per creare le chiavi, segui questi passaggi.

Console

  1. In Google Cloud Console, vai alla pagina Cloud CDN.

    Vai alla pagina Cloud CDN

  2. Fai clic su Aggiungi origine.
  3. Seleziona un bilanciatore del carico HTTP(S) come origine.
  4. Seleziona Servizi di backend o Bucket di backend. Per ciascuno di essi:
    1. Fai clic su Configura, quindi su Aggiungi chiave di firma.
    2. In Nome, assegna un nome alla nuova chiave di firma.
    3. In Metodo di creazione della chiave, seleziona Genera automaticamente o Consentimi.
    4. Se stai inserendo la tua chiave, digita la chiave nel campo di testo.
    5. Fai clic su Fine.
    6. In Età massima della voce della cache, fornisci un valore e seleziona un'Unità di tempo dall'elenco a discesa. Puoi scegliere tra secondi, minuti, ore e giorni. La durata massima è di tre (3) giorni.
  5. Fai clic su Salva.
  6. Fai clic su Aggiungi.

gcloud

Lo strumento a riga di comando gcloud legge le chiavi da un file locale specificato da te. Il file della chiave deve essere creato generando 128 bit fortemente casuali, codificato con base64 e quindi sostituendo il carattere + con - e il carattere / con _. Per ulteriori informazioni, consulta RFC 4648. È fondamentale che la chiave sia a caso. In un sistema simile a UNIX, puoi generare una chiave fortemente casuale e memorizzarla nel file della chiave con il comando seguente:

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

Configurazione delle autorizzazioni di 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 l'account di servizio Cloud CDN agli ACL di Cloud Storage.

Non devi creare l'account di servizio. L'account di servizio viene creato automaticamente la prima volta che aggiungi una chiave a un bucket di backend in un progetto.

Prima di eseguire il seguente comando, aggiungi almeno una chiave a un bucket di backend nel progetto. In caso contrario, il comando non riesce a restituire un errore perché l'account di servizio di riempimento della cache di Cloud CDN non viene creato finché non aggiungi una o più chiavi per il progetto. Sostituisci PROJECT_NUM con il numero di progetto e BUCKET con il bucket di archiviazione.

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

L'account di servizio Cloud CDN service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com non viene visualizzato nell'elenco degli account di servizio del progetto. Questo perché l'account di servizio di Cloud CDN è di proprietà di Cloud CDN, non del tuo progetto.

Per ulteriori informazioni sui numeri del progetto, vedi Trovare l'ID progetto e il numero del progetto nella documentazione di assistenza di Google Cloud Console.

In via facoltativa, puoi personalizzare il tempo di cache massimo

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

Per impostare il tempo massimo di 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

Elenco dei nomi delle chiavi di richiesta firmata

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

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

Eliminazione delle chiavi di richiesta firmate

Quando gli URL firmati da una determinata chiave non dovrebbero 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 norma

I criteri relativi ai cookie firmati sono una serie di coppie key-value (delimitati dal carattere :), simili ai parametri di ricerca utilizzati in un URL firmato. Ad esempio, vedi Inviare cookie agli utenti.

I criteri rappresentano i parametri per cui una richiesta è valida. I criteri vengono firmati utilizzando un codice HMAC (Hash-based Message Authentication Code) convalidato da ciascuna richiesta in ciascuna richiesta.

Definizione dei campi e del formato del criterio

È necessario definire quattro campi obbligatori nel seguente ordine:

  • URLPrefix
  • Expires
  • KeyName
  • Signature

Le coppie key-value in un criterio relativo ai cookie firmati sono sensibili alle maiuscole.

Prefisso URL

URLPrefix indica un prefisso URL con codifica Base64 sicuro per l'URL che include tutti i percorsi per i quali la firma deve essere valida.

URLPrefix codifica uno schema (http:// o https://), un nome di dominio completo e un percorso facoltativo. Terminare il percorso con un / è un'operazione facoltativa, ma consigliata. Il prefisso non deve includere parametri di ricerca o frammenti quali ? o #.

Ad esempio, https://media.example.com/videos abbina le richieste a 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 rigorosamente per il percorso della directory. Ad esempio, il prefisso https://example.com/data concede l'accesso a entrambe le seguenti opzioni:

  • /data/file1
  • /database

Per evitare questo errore, ti consigliamo di terminare tutti i prefissi con /, a meno che tu non decida intenzionalmente di terminare il prefisso con un nome file parziale, ad esempio https://media.example.com/videos/123, per concedere l'accesso ai seguenti elementi:

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

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

Scadenza

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

Nome chiave

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

Firma

Signature è la firma di base HMAC-SHA-1 con URL-safe base64 dei campi che compongono il criterio cookie. Questa operazione viene convalidata per ogni richiesta; le richieste con una firma non valida vengono rifiutate con un errore HTTP 403.

Creazione di cookie firmati in modo programmatico

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
}

// 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 generateSignedCookie(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
	}

	var (
		// domain and path should match the user-facing URL for accessing
		// content.
		domain     = "media.example.com"
		path       = "/segments/"
		keyName    = "my-key"
		expiration = time.Hour * 2
	)

	signedValue, err := signCookie(fmt.Sprintf("https://%s%s", domain,
		path), keyName, key, time.Now().Add(expiration))
	if err != nil {
		return err
	}

	// Use Go's http.Cookie type to construct a cookie.
	cookie := &http.Cookie{
		Name:   "Cloud-CDN-Cookie",
		Value:  signedValue,
		Path:   path, // Best practice: only send the cookie for paths it is valid for
		Domain: domain,
		MaxAge: int(expiration.Seconds()),
	}

	// We print this to stdout in this example. In a real application, use the
	// SetCookie method on a http.ResponseWriter to write the cookie to the
	// user.
	fmt.Fprintln(w, cookie)

	return nil
}

Python

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

    Args:
        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 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.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_policy = u'Cloud-CDN-Cookie={policy}:Signature={signature}'.format(
            policy=policy, signature=signature)
    print(signed_policy)

Convalida dei cookie firmati

Il processo di convalida di un cookie firmato consiste essenzialmente nello stesso modo in cui viene generato un cookie firmato. Ad esempio, supponiamo che tu voglia 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_NAMEper generare in modo indipendente la firma e quindi convalidare la corrispondenza con la stessa.SIGNATURE.

Invio di cookie agli utenti

L'applicazione deve generare e inviare a ogni utente (client) un singolo cookie HTTP contenente un criterio firmato correttamente:

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

  2. Firma il criterio con la chiave scelta, annotando il nome della chiave che hai aggiunto al backend, ad esempio mySigningKey.

  3. Crea un criterio dei cookie con il seguente formato, tenendo presente che sia il nome sia il valore sono sensibili alle maiuscole:

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

    Esempio di intestazione Set-Cookie:

    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 dominio e al prefisso del percorso da cui intendi pubblicare i 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 gli stessi Domain e Path.

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

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

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

  • In caso di riempimento o di mancato riempimento 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 contenuti.