Usa cookies firmadas

En esta página, se proporciona una descripción general de las cookies firmadas y las instrucciones para usarlas con Cloud CDN.

Las cookies firmadas otorgan acceso a recursos por tiempo limitado a un conjunto de archivos, sin importar si los usuarios tienen Cuentas de Google.

Descripción general

Las cookies firmadas son una alternativa a las URL firmadas. Las cookies firmadas protegen el acceso cuando no es posible firmar decenas o cientos de URL por separado para cada usuario en la aplicación.

Las cookies firmadas te permiten hacer lo siguiente:

  • Autorizar a un usuario y proporcionarle un token por tiempo limitado para acceder al contenido protegido (en lugar de firmar cada URL)
  • Determinar el alcance del acceso del usuario a un prefijo de URL específico, como https://media.example.com/videos/, y otorgar al usuario autorizado acceso a contenido protegido dentro de ese prefijo de URL
  • Mantener las URL y manifiestos de medios sin cambios, lo que simplifica la canalización de empaquetado y mejora la capacidad de almacenamiento en caché

En cambio, si necesitas acceso a URL específicas, considera usar URL firmadas.

Antes de comenzar

Antes de usar cookies firmadas, ten en cuenta lo siguiente:

Configura las claves de solicitud firmadas

Crear claves para URL firmadas o cookies firmadas requiere varios pasos, que se describen en las siguientes secciones.

Crea claves de solicitud firmadas

Para habilitar la compatibilidad con las cookies firmadas y las URL firmadas de Cloud CDN, crea una o más claves en un servicio de backend o un depósito de backend habilitados de Cloud CDN, o en ambos.

Para cada servicio de backend o depósito de backend, puedes crear y borrar claves según las necesidades de seguridad. Cada backend puede tener hasta tres claves configuradas a la vez. Te recomendamos alternar las claves de forma periódica. Para ello, borra la clave más antigua, agrega una clave nueva y úsala cuando firmes las URL o las cookies.

Puedes usar el mismo nombre de clave en varios servicios de backend y depósitos de backend, porque cada conjunto de claves es independiente de los demás. Los nombres de claves pueden tener hasta 63 caracteres. Usa los caracteres A-Z, a-z, 0-9, _ (guion bajo) y - (guion) para asignar un nombre a las claves.

Cuando crees claves, asegúrate de mantenerlas seguras, ya que cualquier persona que tenga una de las claves puede crear URL firmadas o cookies firmadas que Cloud CDN acepte hasta que la clave se borre de Cloud CDN. Las claves se almacenan en la computadora en la que generas las URL firmadas o cookies firmadas. Cloud CDN también almacena las claves para verificar las firmas de las solicitudes.

Para mantener las claves en secreto, los valores de las claves no se incluyen en las respuestas a ninguna solicitud a la API. Si pierdes una clave, debes crear una nueva.

Console

Para crear claves mediante Cloud Console, haz lo siguiente:

  1. Ve a la página de Cloud CDN en Google Cloud Console.
    Ir a la página de Cloud CDN
  2. Haz clic en Agregar origen.
  3. Selecciona un balanceador de cargas de HTTP(S) como el origen.
  4. Selecciona servicios de backend o depósitos de backend. Para cada uno, haz lo siguiente:
    1. Haz clic en Configurar y, luego, en Agregar clave de firma.
    2. En Nombre, asigna un nombre a la clave de firma nueva.
    3. En Método de creación de clave, elige Generar de forma automática o Permitirme ingresar.
    4. Si ingresas tu propia clave, escríbela en el campo de texto.
    5. Haz clic en Listo.
    6. En Antigüedad máxima de la entrada de caché, proporciona un valor y selecciona una Unidad de tiempo en la lista desplegable. Puedes elegir entre segundo, minuto, hora y día. El tiempo máximo es de tres (3) días.
  5. Haz clic en Guardar.
  6. Haz clic en Agregar.

gcloud

La herramienta de línea de comandos de gcloud lee las claves de un archivo local que especifiques. Para crear un archivo de claves, genera 128 bits altamente aleatorios, codifícalos con base64 y, luego, reemplaza el carácter + por -/ por _. Para obtener más información, consulta RFC 4648. Es fundamental que la clave sea altamente aleatoria. En un sistema similar a UNIX, puedes generar una clave altamente aleatoria y almacenarla en el archivo de claves con el siguiente comando:

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

Para agregar la clave a un servicio de backend, usa lo siguiente:

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

Para agregar la clave a un depósito de backend, usa lo siguiente:

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

Configura los permisos de Cloud Storage

Si usas Cloud Storage y restringiste quién puede leer los objetos, debes otorgar permiso a Cloud CDN a fin de que los lea. Para ello, agrega la cuenta de servicio de Cloud CDN a las LCA de Cloud Storage.

No necesitas crear la cuenta de servicio. La cuenta de servicio se crea de forma automática la primera vez que agregas una clave a un depósito de backend en un proyecto.

Usa el siguiente comando, en el que [PROJECT_NUM] es el número de proyecto y [BUCKET] es el depósito de almacenamiento.

Ejecuta el comando después de agregar al menos una clave a un depósito de backend en el proyecto. De lo contrario, el comando falla con un error porque la cuenta de servicio de llenado de caché de Cloud CDN no se crea hasta que agregas una o más claves para el proyecto.

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

La cuenta de servicio de Cloud CDN, service-PROJECT_NUM@cloud-cdn-fill.iam.gserviceaccount.com, no aparece en la lista de cuentas de servicio del proyecto. Esto se debe a que la cuenta de servicio de Cloud CDN es propiedad de Cloud CDN, no del proyecto.

Para obtener más información sobre los números de proyecto, consulta Localiza el ID del proyecto y el número de proyecto en la documentación de ayuda de Cloud Platform Console.

Configura VM de Compute Engine

Debes configurar las VM para validar las firmas en cada solicitud firmada que entregan y aceptar o rechazar solicitudes sin firmar. Cloud CDN no bloquea las solicitudes sin un parámetro de consulta Signature, y puedes configurar tu proyecto para permitir solicitudes que omiten el servicio de backend.

Cuando tu aplicación detecte una firma no válida, asegúrate de que la aplicación responda con un código de respuesta HTTP 403 (no autorizado). Los códigos de respuesta HTTP 403 no se pueden almacenar en caché. Si la aplicación envía un código de respuesta que se puede almacenar en caché, es posible que las futuras solicitudes válidas se rechacen de forma incorrecta.

Optimiza de manera opcional el tiempo máximo de caché

Cloud CDN almacena en caché las respuestas para las solicitudes firmadas, sin importar el encabezado Cache-Control del backend. El tiempo máximo que pueden almacenarse sin revalidación se establece mediante la marca signed-url-cache-max-age, que se configura de forma predeterminada en 1 hora y puede modificarse como se muestra a continuación.

A fin de configurar el tiempo máximo de caché para un servicio de backend o un depósito de backend, usa lo siguiente:

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]

Enumera los nombres de claves de solicitud firmadas

Para enumerar las claves en un servicio de backend o un depósito de backend, usa estos comandos.

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

Borra claves de solicitud firmadas

Cuando ya no deben respetarse las URL que tienen la firma de una clave particular, borra esa clave del servicio de backend o del depósito de 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]

Crea una política

Las políticas de cookies firmadas son una serie de pares key=value (delimitados por el carácter :), similares a los parámetros de consulta usados en una URL firmada. Para ver ejemplos, consulta Emite cookies para los usuarios.

Las políticas representan los parámetros para los que una solicitud es válida. Las políticas se firman mediante un código de autenticación de mensajes basado en hash (HMAC), que Cloud CDN valida en cada solicitud.

Formato y campos de la política

Existen cuatro campos, todos son obligatorios y debes definirlos en el siguiente orden:

  • URLPrefix
  • Expires
  • KeyName
  • Signature

En los pares key=value de una política de cookies firmada, se distingue entre mayúsculas y minúsculas.

URLPrefix

URLPrefix denota un prefijo de URL seguro codificado en base64 que incluye todas las rutas de acceso para las que la cookie debe ser válida.

Un URLPrefix codifica un esquema (http:// o https://), un FQDN y una ruta de acceso opcional. Finalizar la ruta con una / es opcional, pero se recomienda. El prefijo no debe incluir parámetros de consulta o fragmentos como ? o #.

Por ejemplo, https://media.example.com/videos hace coincidir las solicitudes con lo siguiente:

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

La ruta de acceso del prefijo se usa como una substring de texto, no estrictamente una ruta de acceso del directorio. Por ejemplo, el prefijo https://example.com/data otorga acceso a lo siguiente:

  • /data/file1
  • /database

Recomendamos terminar todos los prefijos con / a fin de evitar este error, a menos que decidas finalizar el prefijo con un nombre de archivo parcial, como https://media.example.com/videos/123, para otorgar acceso a lo siguiente:

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

Si la URL solicitada no coincide con URLPrefix, Cloud CDN rechaza la solicitud y muestra un error HTTP 403 al cliente.

Expires

Expires debe ser una marca de tiempo de Unix (la cantidad de segundos desde el 1 de enero de 1970).

KeyName

KeyName es el nombre de la clave creada para el depósito o servicio de backend. En los nombres de clave, se distingue entre mayúsculas y minúsculas.

Signature

Signature es la firma HMAC-SHA-1 codificada en base64 segura para URL de los campos que conforman la política de cookies. Esto se valida en cada solicitud. Las solicitudes con una firma no válida se rechazan con un error HTTP 403.

Crea cookies firmadas de manera programática

En los siguientes ejemplos de código, se demuestra cómo crear cookies firmadas de manera programática.

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)

Emite cookies a los usuarios

Tu aplicación debe generar y emitir a cada usuario (cliente) una única cookie HTTP que contenga una política firmada de forma correcta.

  1. Crea un certificador HMAC-SHA-1 en el código de la aplicación.

  2. Firma la política mediante la clave elegida y anota el nombre de la clave que agregaste al backend, como mySigningKey.

  3. Crea una política de cookies con el siguiente formato. Ten en cuenta que en el nombre y el valor, se distingue entre mayúsculas y minúsculas.

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

    Ejemplo de encabezado 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
    

    Los atributos Domain y Path de la cookie determinan si el cliente envía la cookie a Cloud CDN.

Recomendaciones y requisitos

  • Establece de forma explícita Domain y Path para que coincidan con el dominio y el prefijo de la ruta de acceso desde los que deseas publicar el contenido protegido, que puede diferir del dominio y la ruta en la que se emite la cookie (example.com en comparación con media.example.com o /browse en comparación con /videos).

  • Asegúrate de tener solo una cookie con un nombre determinado para el mismo Domain y Path.

  • Asegúrate de no emitir cookies en conflicto, ya que esto podría impedir el acceso al contenido en otras sesiones del navegador (ventanas o pestañas).

  • Configura las marcas Secure y HttpOnly cuando corresponda. Secure garantiza que la cookie se envíe solo a través de conexiones HTTPS. HttpOnly evita que la cookie esté disponible para JavaScript.

  • Los atributos Expires y Max-Age de la cookie son opcionales. Si los omites, la cookie existe mientras exista la sesión del navegador (pestaña o ventana).

  • En un llenado de caché o error de caché, la cookie firmada se pasa a través del origen definido en el servicio de backend. Asegúrate de validar el valor de la cookie firmada en cada solicitud antes de entregar contenido.