Usa URL firmadas

Descripción general

Las URL firmadas de Cloud CDN te permiten entregar respuestas desde las memorias caché distribuidas globalmente de Google Cloud Platform, incluso cuando necesitas que las solicitudes cuenten con autorización.

Las URL firmadas brindan a un cliente acceso temporal a un recurso privado sin necesitar autorización adicional. Para lograr esto, se genera un hash de los elementos seleccionados en una solicitud y tales elementos se firman de forma criptográfica mediante el uso de la clave altamente aleatoria que generas. Cuando una solicitud entrega la URL firmada que proporcionaste, la solicitud se considera autorizada para recibir el contenido solicitado. Cuando Cloud CDN recibe una solicitud con una firma incorrecta para un servicio habilitado, la solicitud se rechaza y nunca va al backend para su manejo.

Por lo general, cualquier persona que tenga una URL firmada puede utilizarla. Sin embargo, una URL firmada generalmente está diseñada para que la utilice el cliente al que se le dio la URL. Para mitigar el riesgo de que otro cliente utilice la URL, las URL firmadas caducan en el momento que elijas. Para minimizar el riesgo de que una URL firmada se comparta, configúrala para que caduque lo antes posible.

Cómo se firman las URL

Antes de que puedas firmar las URL, crea una o más claves criptográficas en un servicio de backend, depósito de backend, o ambos. A continuación, firma una URL y genera un hash de forma criptográfica para esa URL con la herramienta de línea de comandos de gcloud o tu propio código.

Distribuye las URL firmadas

Cuando el manejo de una URL firmada está habilitado en un backend, Cloud CDN ofrece un manejo especial a las solicitudes con URL firmadas. Específicamente, las solicitudes con un parámetro de búsqueda Signature se consideran firmadas. Cuando se recibe una solicitud de este tipo, Cloud CDN verifica lo que se indica a continuación:

  1. Que el método HTTP sea GET o HEAD.
  2. Que el parámetro Expires esté establecido en una hora futura.
  3. Que la firma de la solicitud coincida con la firma procesada mediante el uso de la clave con nombre.

Si cualquiera de estas verificaciones falla, se entrega una respuesta 403 Forbidden. De lo contrario, la solicitud se dirige al backend o se entrega desde la memoria caché. Todas las solicitudes firmadas válidas para una URL base particular (la parte antes del parámetro KeyName) compartirán la misma entrada de caché, Las respuestas a las solicitudes firmadas y sin firmar no comparten entradas de caché. Las respuestas se almacenan en caché y se entregan hasta el plazo de vencimiento establecido.

El contenido que requiere solicitudes firmadas a menudo se marca como no almacenable en caché con el uso del encabezado Cache-Control. Para hacer que tales objetos sean compatibles con Cloud CDN sin que se necesiten cambios de backend, Cloud CDN anula el encabezado Cache-Control cuando responde a las solicitudes que tienen URL firmadas válidas. Cloud CDN trata el contenido como almacenable en caché, y utiliza el parámetro max-age establecido en la configuración de Cloud CDN. La respuesta entregada todavía tiene los encabezados Cache-Control que generó el backend.

Configura claves de URL firmadas

La habilitación de URL firmadas en la instalación de Cloud CDN requiere varios pasos, que se describen a continuación.

Antes de comenzar

Antes de utilizar URL firmadas, asegúrate de que Cloud CDN esté habilitado. Para obtener instrucciones, consulta la Documentación de Google Cloud CDN. Puedes configurar las URL firmadas en un backend antes de habilitar Cloud CDN, pero no tendrán efecto hasta que Cloud CDN esté habilitado.

Si es necesario, actualiza a la última versión del SDK de Cloud:

gcloud components update

Crea claves

Puedes habilitar la compatibilidad en las URL firmadas de Cloud CDN; para ello, crea una o más claves en el servicio de backend, el depósito de backend habilitado de Cloud CDN, o en ambos.

Por cada servicio de backend o depósito de backend, puedes crear y borrar claves de URL firmadas de acuerdo con las necesidades de seguridad. Cada backend puede tener hasta tres (3) claves configuradas a la vez. Te recomendamos alternar periódicamente las claves; para ello, borra las más antiguas, luego agrega una clave nueva y utilízala cuando firmes las URL. Puedes utilizar 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. Utiliza los caracteres A-Z, a-z, 0-9, _ (guion bajo) y - (guion) para asignar un nombre a tus claves.

Cuando crees claves, asegúrate de mantenerlas seguras, ya que cualquiera que tenga una de tus claves puede crear URL 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. Cloud CDN también almacena las claves para verificar las firmas de las solicitudes.

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

Console


Sigue estos pasos para crear claves con el uso de Console:

  1. Ve a la página de Cloud CDN de Google Cloud Platform Console.
    Ir a la página de Cloud CDN
  2. Haz clic en Agregar origen.
  3. Selecciona un balanceador de cargas HTTP(S) como el origen.
  4. Decide qué servicios de backend o depósitos de backend deben admitir las URL firmadas. Para cada uno haz lo siguiente:
    1. Haz clic en Configurar y, a continuación, haz clic 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 automáticamente o Permitirme ingresar.
    4. Si ingresas tu propia clave, escribe la clave en el campo de texto.
    5. Haga 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 URL firmadas desde el archivo local que especificas. Para crear un archivo de claves, genera 128 bits altamente aleatorios, codifícalos con base64 y, a continuación, reemplaza el carácter.

  • por - y reemplaza el carácter / 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 o un depósito de backend:

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

Configura los permisos de Google Cloud Storage

Si utilizas Google Cloud Storage y has restringido quién puede leer los objetos, debes dar permiso a Cloud CDN para leer los objetos; 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 automáticamente la primera vez que agregas una clave de URL firmada a un depósito de backend en un proyecto.

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

El comando debe ejecutarse después de agregar al menos una clave de URL firmada a un depósito de backend en el proyecto. De lo contrario, el comando falla con un error.

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

Para obtener más información sobre el número de proyecto, lee Cómo localizar el ID del proyecto y el número de proyecto en la documentación de Ayuda de Cloud Platform Console.

Configura las instancias de VM de Google Compute Engine

Las VM deben validar las firmas en cada solicitud firmada que entregan y deben prepararse para aceptar o rechazar las solicitudes sin firmar. Cloud CDN no bloquea las solicitudes sin un parámetro de búsqueda Signature, y es posible que el proyecto esté configurado para permitir solicitudes que omiten el servicio de backend configurado con anterioridad.

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

Como se describió anteriormente, Cloud CDN almacena en caché las respuestas de URL firmadas, independientemente del encabezado Cache-Control del backend. El tiempo máximo que pueden almacenarse sin revalidación se establece mediante la marca de antigüedad máxima de la caché de la URL firmada, que se configura de forma predeterminada en 1 hora y puede modificarse como se muestra a continuación.

Para configurar el tiempo máximo de caché para un servicio de backend o un depósito de backend:

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

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

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

Borra claves

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]

Firma las URL

El último paso es firmar las URL y distribuirlas. Puedes firmar las URL con el comando gcloud compute sign-url o con el código que escribas; para ello, sigue los ejemplos que se incluyen a continuación. Si necesitas una gran cantidad de URL firmadas, el código personalizado brinda un mejor rendimiento.

Crea URL firmadas

Utiliza estas instrucciones para crear URL firmadas.

En este paso, se da por sentado que ya creaste las claves.

Console


No puedes crear URL firmadas con Console. Puedes utilizar la herramienta de línea de comandos de gcloud o escribir el código personalizado; para ello, sigue los ejemplos que se indican a continuación.

gcloud


La herramienta de línea de comandos de gcloud incluye un comando para firmar las URL. El comando implementa el algoritmo que se describe en la sección sobre cómo escribir tu propio código.

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

Este comando lee y decodifica el valor de la clave codificada base64url en [KEY_FILE_NAME], luego genera una URL firmada que puedes utilizar para las solicitudes GET o HEAD de la URL específica.

Por ejemplo:

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

La URL debe ser una URL válida que tenga un componente de ruta de acceso. Por ejemplo, http://example.com no es válida, pero https://example.com/ y https://example.com/whatever sí lo son.

Si se proporciona la marca --validate opcional, este comando envía una solicitud HEAD con la URL resultante y, a continuación, imprime el código de respuesta HTTP. Si la URL firmada es correcta, el código de respuesta es el mismo que el código de resultado que envía el backend. Si el código de respuesta no es el mismo, vuelve a verificar [KEY_NAME] y el contenido del archivo especificado, y asegúrate de que el valor de [TIME_UNTIL_EXPIRATION] sea al menos de varios segundos. Si no se proporciona --validate, no se verifican las entradas ni la URL generadas.

Si no se proporciona la marca --validate, no se verifica la URL firmada que se generó.

Crea de manera programática URL firmadas

En las muestras de códigos que figuran a continuación, se ilustra cómo crear de manera programática URL firmadas.

Go

// 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 should be 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
}

// 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 main() {
	key, err := readKeyFile("/path/to/key")
	if err != nil {
		log.Fatal(err)
	}
	url := SignURL("https://example.com", "MY-KEY", key, time.Now().Add(time.Hour*24))
	fmt.Println(url)
}

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-dot-devsite.googleplex.com/cdn/docs/signed-urls#creatingkeys"/>
/// </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)

Algoritmo para firmar URL

Cuando escribes tu propio código para generar URL firmadas, el objetivo es crear URL con el siguiente formato:

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

Todos los parámetros de URL distinguen entre mayúsculas y minúsculas, y deben estar en el orden mencionado anteriormente.

  1. Asegúrate de que la URL para la firma no tenga los parámetros de consulta Expires, KeyName ni Signature.

  2. Determina cuándo caduca la URL y agrega un parámetro de búsqueda Expires con el plazo de vencimiento deseado en horario UTC (la cantidad de segundos desde 1970-01-01 00:00:00 UTC). Para maximizar la seguridad, establece el valor en el período más corto posible para el caso práctico. Mientras mayor sea la validez de una URL, más grande será el riesgo de que el usuario al cual le diste la URL la comparta con otros, de manera accidental o por otro motivo.

  3. Configura el nombre de la clave. La URL debe contar con la firma de una clave de URL firmada del servicio de backend o del depósito de backend que entregará la URL. Es mejor utilizar la clave de URL firmada que se haya agregado más recientemente para realizar la rotación de la clave. Agrega la clave de URL firmada a la URL; para ello, adjunta &KeyName=[KEY_NAME], en el que [KEY_NAME] es el nombre de la clave elegida que se creó en Crea claves.

  4. Firma la URL. Crea la URL firmada; para ello, sigue estos pasos. Asegúrate de que los parámetros de búsqueda estén en el orden que se muestra inmediatamente antes del Paso 1, y asegúrate de que no se produzca ningún cambio en URL firmada.

    a. Genera un hash de toda la URL (que incluye http:// o https:// al principio y &KeyName... al final) con HMAC-SHA1, mediante el uso de una clave secreta que se corresponda con el nombre de clave elegido con anterioridad. Utiliza la clave secreta de 16 bytes sin procesar, no la clave codificada base64url. Decodifícala si es necesario.

    b. Codifica en Base64url el resultado.

    c. Adjunta &Signature= a la URL, seguido de la firma codificada.

Distribuye y usa las URL firmadas

La URL que se muestra desde la herramienta de línea de comandos de gcloud o que genera el código personalizado puede distribuirse según tus necesidades. Te recomendamos firmar únicamente las URL de HTTPS, ya que HTTPS proporciona un transporte seguro que impedirá que el componente de firma de la URL firmada sea interceptado. Del mismo modo, puedes distribuir las URL firmadas a través de protocolos de transporte seguros como, por ejemplo, TLS/HTTPS.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Documentación de Cloud CDN