In questa pagina viene descritto come proteggere l'app con le intestazioni IAP firmate. Quando è configurato, Identity-Aware Proxy (IAP) utilizza i token web JSON (JWT) per assicurarsi che una richiesta alla tua app sia autorizzata. In questo modo, la tua app è protetta dai seguenti rischi:
- IAP è disattivato accidentalmente
- Firewall configurati in modo errato
- Accesso non autorizzato dall'interno del progetto
Per proteggere l'app, devi utilizzare le intestazioni firmate per tutti i tipi di app.
In alternativa, se hai un'app dell'ambiente standard App Engine, puoi utilizzare l'API Users.
I controlli di integrità di Compute Engine e GKE non includono le intestazioni JWT e IAP non elabora i controlli di integrità. Se il controllo di integrità restituisce errori di accesso, assicurati di aver configurato correttamente il controllo di integrità nellaconsola Google Cloud e che la convalida dell'intestazione JWT consenta il percorso del controllo di integrità. Per saperne di più, consulta Creare un'eccezione del controllo di integrità.
Prima di iniziare
Per proteggere l'app con le intestazioni firmate, avrai bisogno di:
- Un'applicazione a cui vuoi che gli utenti si connettano.
- Una libreria JWT di terze parti per la tua lingua che supporti
l'algoritmo
ES256.
Proteggere l'app con le intestazioni IAP
Per proteggere l'app con il JWT IAP, verifica l'intestazione, il payload e la firma del JWT. Il JWT si trova nell'intestazione della richiesta HTTP x-goog-iap-jwt-assertion. Se un utente malintenzionato bypassa IAP, può falsificare le intestazioni dell'identità non firmate di IAP, x-goog-authenticated-user-{email,id}. Il JWT IAP fornisce un'alternativa più sicura.
Le intestazioni firmate forniscono una sicurezza secondaria nel caso in cui qualcuno bypassi IAP. Quando IAP è abilitato, rimuove le intestazioni x-goog-* fornite dal client quando la richiesta passa attraverso l'infrastruttura di pubblicazione di IAP.
Verificare l'intestazione JWT
Verifica che l'intestazione del JWT sia conforme ai seguenti vincoli:
| Rivendicazioni dell'intestazione JWT | ||
|---|---|---|
alg |
Algoritmo | ES256 |
kid |
ID chiave |
Deve corrispondere a una delle chiavi pubbliche elencate nel
file di chiavi IAP, disponibile in due formati diversi:
https://www.gstatic.com/iap/verify/public_key
e
https://www.gstatic.com/iap/verify/public_key-jwk
|
Assicurati che il JWT sia stato firmato con la chiave privata corrispondente alla rivendicazione kid del token. Innanzitutto, recupera la chiave pubblica da uno dei due seguenti:
https://www.gstatic.com/iap/verify/public_key. Questo URL contiene un dizionario JSON che mappa le rivendicazionikidai valori della chiave pubblica.https://www.gstatic.com/iap/verify/public_key-jwk. Questo URL contiene le chiavi pubbliche IAP in JWKJWK.
Dopo aver ottenuto la chiave pubblica, utilizza una libreria JWT per verificare la firma.
IAP ruota periodicamente le chiavi pubbliche. Per assicurarti di poter sempre verificare i JWT, consulta Automatizzare la memorizzazione nella cache delle chiavi pubbliche.
Verificare il payload JWT
Verifica che il payload del JWT sia conforme ai seguenti vincoli:
| Rivendicazioni del payload JWT | ||
|---|---|---|
exp |
Scadenza | Deve essere nel futuro. L'ora è misurata in secondi trascorsi dall'epoca di Unix. Concedi 30 secondi per la distorsione. La durata massima di un token è di 10 minuti + 2 * distorsione. |
iat |
Ora di emissione | Deve essere nel passato. L'ora è misurata in secondi trascorsi dall'epoca di Unix. Concedi 30 secondi per la distorsione. |
aud |
Pubblico |
Deve essere una stringa con i seguenti valori:
|
iss |
Emittente |
Deve essere https://cloud.google.com/iap.
|
hd |
Dominio dell'account |
Se un account appartiene a un dominio ospitato, viene fornita la
hd rivendicazione per distinguere il
dominio a cui è associato l'account.
|
google |
Rivendicazione Google |
Se alla richiesta si applicano uno o più livelli di accesso
i relativi nomi vengono memorizzati nell'oggetto JSON della google
rivendicazione, sotto la chiave access_levels, come array
di stringhe.
Quando specifichi una policy per i dispositivi e l'organizzazione ha accesso ai dati sul dispositivo, anche |
Puoi ottenere i valori per la stringa aud menzionata sopra accedendo alla
Google Cloud console oppure utilizzando lo strumento a riga di comando gcloud.
Per ottenere i valori della stringa aud dalla Google Cloud console, vai alle
impostazioni del proxy Identity-Aware Proxy
per il tuo progetto, fai clic su Altro accanto alla risorsa del bilanciatore del carico e poi
seleziona Pubblico JWT con intestazione firmata. La finestra di dialogo JWT con intestazione firmata visualizzata mostra la rivendicazione aud per la risorsa selezionata.
Se vuoi utilizzare lo strumento a riga di comando gcloud CLI
gcloud per ottenere i valori della stringa aud, devi conoscere
l'ID progetto. Puoi trovare l'ID progetto nella
Google Cloud console
scheda Informazioni sul progetto, quindi esegui i comandi specificati per ogni valore.
Numero progetto
Per ottenere il numero del progetto utilizzando lo strumento a riga di comando gcloud, esegui il comando seguente:
gcloud projects describe PROJECT_ID
Il comando restituisce un output simile al seguente:
createTime: '2016-10-13T16:44:28.170Z' lifecycleState: ACTIVE name: project_name parent: id: '433637338589' type: organization projectId: PROJECT_ID projectNumber: 'PROJECT_NUMBER'
ID servizio
Per ottenere l'ID servizio utilizzando lo strumento a riga di comando gcloud, esegui il comando seguente:
gcloud compute backend-services describe SERVICE_NAME --project=PROJECT_ID --global
Il comando restituisce un output simile al seguente:
affinityCookieTtlSec: 0 backends: - balancingMode: UTILIZATION capacityScaler: 1.0 group: https://www.googleapis.com/compute/v1/projects/project_name/regions/us-central1/instanceGroups/my-group connectionDraining: drainingTimeoutSec: 0 creationTimestamp: '2017-04-03T14:01:35.687-07:00' description: '' enableCDN: false fingerprint: zaOnO4k56Cw= healthChecks: - https://www.googleapis.com/compute/v1/projects/project_name/global/httpsHealthChecks/my-hc id: 'SERVICE_ID' kind: compute#backendService loadBalancingScheme: EXTERNAL name: my-service port: 8443 portName: https protocol: HTTPS selfLink: https://www.googleapis.com/compute/v1/projects/project_name/global/backendServices/my-service sessionAffinity: NONE timeoutSec: 3610
Recuperare l'identità dell'utente
Se tutte le verifiche precedenti hanno esito positivo, recupera l'identità dell'utente. Il payload del token ID contiene le seguenti informazioni sull'utente:
| Identità utente del payload del token ID | ||
|---|---|---|
sub |
Oggetto |
L'identificatore univoco e stabile dell'utente. Utilizza questo valore anziché l'
x-goog-authenticated-user-id intestazione.
|
email |
Email dell'utente | Indirizzo email dell'utente.
|
Di seguito è riportato un codice campione per proteggere un'app con le intestazioni IAP firmate:
C#
Go
Java
Node.js
PHP
Python
Ruby
Testare il codice di convalida
Se visiti l'app utilizzando i
secure_token_test parametri di query,
IAP includerà un JWT non valido. Utilizza questa opzione per assicurarti che la logica di convalida JWT gestisca tutti i vari casi di errore e per vedere come si comporta l'app quando riceve un JWT non valido.
Creare un'eccezione del controllo di integrità
Come accennato in precedenza, i controlli di integrità di Compute Engine e GKE non utilizzano le intestazioni JWT e IAP non gestisce i controlli di integrità. Dovrai configurare il tuo controllo di integrità e l'app per consentire l' accesso al controllo di integrità.
Configurare il controllo di integrità
Se non hai ancora impostato un percorso per il controllo di integrità, utilizza la Google Cloud console per impostare un percorso non sensibile per il controllo di integrità. Assicurati che questo percorso non sia condiviso da altre risorse.
- Vai alla pagina Google Cloud console
Controlli di integrità.
Vai alla pagina Controlli di integrità - Fai clic sul controllo di integrità che utilizzi per l'app, quindi fai clic su Modifica.
- In Percorso della richiesta aggiungi un nome di percorso non sensibile. Questo specifica il
percorso dell'URL utilizzato quando vengono inviate le richieste di controllo di integrità. Google Cloud
Se omessa, la richiesta di controllo di integrità viene inviata a
/. - Fai clic su Salva.
Configurare la convalida JWT
Nel codice che chiama la routine di convalida JWT, aggiungi una condizione per pubblicare uno stato HTTP 200 per il percorso della richiesta di controllo di integrità. Ad esempio:
if HttpRequest.path_info = '/HEALTH_CHECK_REQUEST_PATH' return HttpResponse(status=200) else VALIDATION_FUNCTION
Automatizzare la memorizzazione nella cache delle chiavi pubbliche
IAP ruota periodicamente le chiavi pubbliche. Per assicurarti di poter sempre verificare il JWT IAP, ti consigliamo di memorizzare le chiavi nella cache per evitare di recuperarle dall'URL pubblico per ogni richiesta e di automatizzare il processo di aggiornamento della chiave memorizzata nella cache. Questo approccio è particolarmente utile per le applicazioni che vengono eseguite in un ambiente con restrizioni di rete, come un perimetro dei Controlli di servizio VPC.
Un perimetro dei Controlli di servizio VPC può impedire l'accesso diretto all'URL pubblico per le chiavi. Memorizzando le chiavi nella cache in un bucket Cloud Storage, le applicazioni possono recuperarle da una località all'interno del perimetro dei Controlli di servizio VPC.
La seguente configurazione Terraform esegue il deployment di una funzione in Cloud Run che recupera le ultime chiavi pubbliche IAP da https://www.gstatic.com/iap/verify/public_key-jwk e le archivia in un bucket Cloud Storage. Un job Cloud Scheduler attiva questa funzione ogni 12 ore per mantenere aggiornate le chiavi.
Questa configurazione include quanto segue:
- API necessarie Google Cloud abilitate per utilizzare Cloud Run e archiviare e memorizzare nella cache le chiavi
- Un bucket Cloud Storage per archiviare le chiavi pubbliche IAP recuperate
- Un bucket Cloud Storage per eseguire lo staging del codice sorgente delle funzioni Cloud Run
- Service account per le funzioni Cloud Run e Cloud Scheduler con le autorizzazioni IAM appropriate
- Una funzione Python per recuperare e archiviare le chiavi
- Un job Cloud Scheduler per attivare la funzione ogni 12 ore
Struttura delle directory
├── function_source/ │ ├── main.py │ └── requirements.txt ├── main.tf ├── outputs.tf ├── variables.tf └── terraform.tfvars
function_source/main.py
import functions_framework import requests from google.cloud import storage import os # Environment variables to be set in the function configuration BUCKET_NAME = os.environ.get("BUCKET_NAME") OBJECT_NAME = os.environ.get("OBJECT_NAME", "iap_public_keys.jwk") IAP_KEYS_URL = "https://www.gstatic.com/iap/verify/public_key-jwk" @functions_framework.http def update_iap_keys(request): """Fetches IAP public keys from the public URL and stores them in a Cloud Storage bucket.""" if not BUCKET_NAME: print("Error: BUCKET_NAME environment variable not set.") return "BUCKET_NAME environment variable not set.", 500 try: # Fetch the keys response = requests.get(IAP_KEYS_URL) response.raise_for_status() # Raise an exception for bad status codes keys_content = response.text print(f"Successfully fetched keys from {IAP_KEYS_URL}") # Store in Cloud Storage storage_client = storage.Client() bucket = storage_client.bucket(BUCKET_NAME) blob = bucket.blob(OBJECT_NAME) blob.upload_from_string(keys_content, content_type='application/json') print(f"Successfully wrote IAP keys to gs://{BUCKET_NAME}/{OBJECT_NAME}") return f"Successfully updated {OBJECT_NAME} in bucket {BUCKET_NAME}", 200 except requests.exceptions.RequestException as e: print(f"Error fetching keys from {IAP_KEYS_URL}: {e}") return f"Error fetching keys: {e}", 500 except Exception as e: print(f"Error interacting with Cloud Storage: {e}") return f"Error interacting with Cloud Storage: {e}", 500
Sostituisci quanto segue:
-
BUCKET_NAME: il nome del tuo bucket Cloud Storage -
OBJECT_NAME: il nome dell'oggetto in cui archiviare le chiavi
function_source/requirements.txt
functions-framework==3.* requests google-cloud-storage
variables.tf
variable "project_id" { description = "The Google Cloud project ID." type = string default = PROJECT_ID } variable "region" { description = "The Google Cloud region." type = string default = "REGION" } variable "iap_keys_bucket_name" { description = "The name of the Cloud Storage bucket to store IAP keys." type = string default = BUCKET_NAME" } variable "function_source_bucket_name" { description = "The name of the Cloud Storage bucket to store the function source code." type = string default = "BUCKET_NAME_FUNCTION" }
Sostituisci quanto segue:
-
PROJECT_ID: il tuo Google Cloud ID progetto -
REGION: la regione in cui eseguire il deployment delle risorse, ad esempious-central1 -
BUCKET_NAME: il nome del bucket Cloud Storage che archivia le chiavi IAP -
BUCKET_NAME_FUNCTION: il nome del bucket Cloud Storage che archivia il codice sorgente delle funzioni Cloud Run
main.tf
terraform { required_providers { google = { source = "hashicorp/google" version = ">= 4.50.0" } google-beta = { source = "hashicorp/google-beta" version = ">= 4.50.0" } } } provider "google" { project = var.project_id region = var.region } provider "google-beta" { project = var.project_id region = var.region } # Enable necessary APIs resource "google_project_service" "services" { for_each = toset([ "storage.googleapis.com", "cloudfunctions.googleapis.com", "run.googleapis.com", # Cloud Functions v2 uses Cloud Run "cloudscheduler.googleapis.com", "iamcredentials.googleapis.com", "cloudbuild.googleapis.com" # Needed for Cloud Functions deployment ]) service = each.key disable_on_destroy = false } # Cloud Storage Bucket to store the IAP public keys resource "google_storage_bucket" "iap_keys_bucket" { name = var.iap_keys_bucket_name location = var.region uniform_bucket_level_access = true versioning { enabled = true } lifecycle { prevent_destroy = false # Set to true in production to prevent accidental deletion } } # Cloud Storage Bucket to store the Cloud Function source code resource "google_storage_bucket" "function_source_bucket" { name = var.function_source_bucket_name location = var.region uniform_bucket_level_access = true } # Archive the function source code data "archive_file" "function_source_zip" { type = "zip" source_dir = "${path.module}/function_source" output_path = "${path.module}/function_source.zip" } # Upload the zipped source code to the source bucket resource "google_storage_bucket_object" "function_source_object" { name = "function_source.zip" bucket = google_storage_bucket.function_source_bucket.name source = data.archive_file.function_source_zip.output_path } # Service Account for the Cloud Function resource "google_service_account" "iap_key_updater_sa" { account_id = "iap-key-updater" display_name = "IAP Key Updater Function SA" } # Grant the function's SA permission to write to the IAP keys bucket resource "google_storage_bucket_iam_member" "keys_bucket_writer" { bucket = google_storage_bucket.iap_keys_bucket.name role = "roles/storage.objectAdmin" member = "serviceAccount:${google_service_account.iap_key_updater_sa.email}" } # Cloud Function (v2) resource "google_cloudfunctions2_function" "update_iap_keys_func" { provider = google-beta # CFv2 often has newer features in google-beta name = "update-iap-keys-function" location = var.region build_config { runtime = "python312" entry_point = "update_iap_keys" source { storage_source { bucket = google_storage_bucket.function_source_bucket.name object = google_storage_bucket_object.function_source_object.name } } } service_config { max_instance_count = 1 available_memory = "256M" timeout_seconds = 60 ingress_settings = "ALLOW_ALL" service_account_email = google_service_account.iap_key_updater_sa.email environment_variables = { BUCKET_NAME = google_storage_bucket.iap_keys_bucket.name OBJECT_NAME = "iap_public_keys.jwk" } } depends_on = [ google_project_service.services, google_storage_bucket_iam_member.keys_bucket_writer ] } # Service Account for the Cloud Scheduler job resource "google_service_account" "iap_key_scheduler_sa" { account_id = "iap-key-scheduler" display_name = "IAP Key Update Scheduler SA" } # Grant the Scheduler SA permission to invoke the Cloud Function resource "google_cloudfunctions2_function_iam_member" "invoker" { provider = google-beta project = google_cloudfunctions2_function.update_iap_keys_func.project location = google_cloudfunctions2_function.update_iap_keys_func.location cloud_function = google_cloudfunctions2_function.update_iap_keys_func.name role = "roles/cloudfunctions.invoker" member = "serviceAccount:${google_service_account.iap_key_scheduler_sa.email}" } # Cloud Scheduler Job resource "google_cloud_scheduler_job" "iap_key_update_schedule" { name = "iap-key-update-schedule" description = "Fetches IAP public keys and stores them in Cloud Storage every 12 hours" schedule = "0 */12 * * *" # Every 12 hours time_zone = "Etc/UTC" region = var.region http_target { uri = google_cloudfunctions2_function.update_iap_keys_func.service_config[0].uri http_method = "POST" oidc_token { service_account_email = google_service_account.iap_key_scheduler_sa.email } } depends_on = [ google_cloudfunctions2_function_iam_member.invoker, google_project_service.services ] }
outputs.tf
output "iap_keys_bucket_url" { description = "The Cloud Storage bucket URL where IAP public keys are stored." value = "gs://${google_storage_bucket.iap_keys_bucket.name}" } output "cloud_function_url" { description = "The URL of the Cloud Function endpoint that triggers key updates." value = google_cloudfunctions2_function.update_iap_keys_func.service_config[0].uri }
terraform.tfvars
Crea un file terraform.tfvars per specificare l'ID progetto e personalizzare i nomi dei bucket, se necessario:
project_id = "your-gcp-project-id" # Optional: Customize bucket names # iap_keys_bucket_name = "custom-iap-keys-bucket" # function_source_bucket_name = "custom-func-src-bucket"
Eseguire il deployment con Terraform
- Salva i file nella struttura delle directory descritta in precedenza.
- Vai alla directory nel terminale e inizializza Terraform:
terraform init - Pianifica le modifiche:
terraform plan - Applica le modifiche:
terraform apply
In questo modo viene eseguito il deployment dell'infrastruttura. Il job Cloud Scheduler attiva la
funzione ogni 12 ore, recuperando le chiavi IAP e archiviandole
in gs://BUCKET_NAME/iap_public_keys.jwk per impostazione predefinita. Le tue applicazioni possono ora recuperare le chiavi da questo bucket.
Liberare spazio dalle risorse
Per rimuovere le risorse create da Terraform, esegui i seguenti comandi:
gsutil rm -a gs://BUCKET_NAME/** terraform destroy -auto-approve
Sostituisci BUCKET_NAME con il bucket Cloud Storage per le tue chiavi.
JWT per identità esterne
Se utilizzi IAP con identità esterne, IAP emetterà comunque un JWT firmato su ogni richiesta autenticata, proprio come fa con le identità Google. Tuttavia, ci sono alcune differenze.
Informazioni sul provider
Quando utilizzi identità esterne, il payload JWT conterrà una rivendicazione denominata gcip. Questa rivendicazione contiene informazioni sull'utente, come l'indirizzo email, l'URL della foto e tutti gli attributi aggiuntivi specifici del provider.
Di seguito è riportato un esempio di JWT per un utente che ha eseguito l'accesso con Facebook:
"gcip": '{
"auth_time": 1553219869,
"email": "facebook_user@gmail.com",
"email_verified": false,
"firebase": {
"identities": {
"email": [
"facebook_user@gmail.com"
],
"facebook.com": [
"1234567890"
]
},
"sign_in_provider": "facebook.com",
},
"name": "Facebook User",
"picture: "https://graph.facebook.com/1234567890/picture",
"sub": "gZG0yELPypZElTmAT9I55prjHg63"
}',
I campi email e sub
Se un utente è stato autenticato da Identity Platform, i campi email e sub del JWT avranno come prefisso l'emittente del token Identity Platform e l'ID tenant utilizzato (se presente). Ad esempio:
"email": "securetoken.google.com/PROJECT-ID/TENANT-ID:demo_user@gmail.com", "sub": "securetoken.google.com/PROJECT-ID/TENANT-ID:gZG0yELPypZElTmAT9I55prjHg63"
Controllare l'accesso con sign_in_attributes
IAM non supporta le identità esterne, ma puoi utilizzare le rivendicazioni incorporate nel campo sign_in_attributes per controllare l'accesso. Ad esempio, considera un utente che ha eseguito l'accesso utilizzando un provider SAML:
{
"aud": "/projects/project_number/apps/my_project_id",
"gcip": '{
"auth_time": 1553219869,
"email": "demo_user@gmail.com",
"email_verified": true,
"firebase": {
"identities": {
"email": [
"demo_user@gmail.com"
],
"saml.myProvider": [
"demo_user@gmail.com"
]
},
"sign_in_attributes": {
"firstname": "John",
"group": "test group",
"role": "admin",
"lastname": "Doe"
},
"sign_in_provider": "saml.myProvider",
"tenant": "my_tenant_id"
},
"sub": "gZG0yELPypZElTmAT9I55prjHg63"
}',
"email": "securetoken.google.com/my_project_id/my_tenant_id:demo_user@gmail.com",
"exp": 1553220470,
"iat": 1553219870,
"iss": "https://cloud.google.com/iap",
"sub": "securetoken.google.com/my_project_id/my_tenant_id:gZG0yELPypZElTmAT9I55prjHg63"
}
Puoi aggiungere alla tua applicazione una logica simile al codice riportato di seguito per limitare l'accesso agli utenti con un ruolo valido:
const gcipClaims = JSON.parse(decodedIapJwtClaims.gcip);
if (gcipClaims &&
gcipClaims.firebase &&
gcipClaims.firebase.sign_in_attributes &&
gcipClaims.firebase.sign_in_attribute.role === 'admin') {
// Allow access to admin restricted resource.
} else {
// Block access.
}
Puoi accedere ad altri attributi utente dai provider SAML e OIDC di Identity Platform utilizzando la rivendicazione nidificata gcipClaims.gcip.firebase.sign_in_attributes.
Limitazioni delle dimensioni delle rivendicazioni IdP
Dopo che un utente ha eseguito l'accesso con Identity Platform, gli attributi utente aggiuntivi verranno propagati al payload del token ID senza stato di Identity Platform, che verrà passato in modo sicuro a IAP. IAP emetterà quindi il proprio cookie opaco senza stato, che contiene anche le stesse rivendicazioni. IAP genererà l'intestazione JWT firmata in base ai contenuti del cookie.
Di conseguenza, se viene avviata una sessione con molte rivendicazioni, potrebbe superare la dimensione massima consentita dei cookie, che in genere è di circa 4 kB nella maggior parte dei browser. In questo caso, l'operazione di accesso avrà esito negativo.
Assicurati che solo le rivendicazioni necessarie vengano propagate negli attributi SAML o OIDC dell'IdP. Un'altra opzione è utilizzare le funzioni di blocco per filtrare le rivendicazioni non richieste per il controllo di autorizzazione.
const gcipCloudFunctions = require('gcip-cloud-functions');
const authFunctions = new gcipCloudFunctions.Auth().functions();
// This function runs before any sign-in operation.
exports.beforeSignIn = authFunctions.beforeSignInHandler((user, context) => {
if (context.credential &&
context.credential.providerId === 'saml.my-provider') {
// Get the original claims.
const claims = context.credential.claims;
// Define this function to filter out the unnecessary claims.
claims.groups = keepNeededClaims(claims.groups);
// Return only the needed claims. The claims will be propagated to the token
// payload.
return {
sessionClaims: claims,
};
}
});