Deployment sicuro in Cloud Run

1. Panoramica

Modificherai i passaggi predefiniti per il deployment di un servizio in Cloud Run per migliorare la sicurezza e poi vedrai come accedere all'app di cui è stato eseguito il deployment in modo sicuro. L'app è un "servizio di registrazione dei partner" dell'applicazione Cymbal Eats, utilizzata dalle aziende che collaborano con Cymbal Eats per elaborare gli ordini di cibo.

Cosa imparerai a fare

Apportando alcune piccole modifiche ai passaggi predefiniti minimi per il deployment di un'app in Cloud Run, puoi aumentarne notevolmente la sicurezza. Dovrai prendere un'app esistente e le istruzioni di implementazione e modificare i passaggi di implementazione per migliorare la sicurezza dell'app di cui è stato eseguito il deployment.

Vedrai quindi come autorizzare l'accesso all'app e inviare richieste autorizzate.

Non si tratta di un'analisi esaustiva della sicurezza del deployment delle applicazioni, ma di un'analisi delle modifiche che puoi apportare a tutti i tuoi deployment di app futuri per migliorarne la sicurezza con pochissimo sforzo.

2. Configurazione e requisiti

Configurazione dell'ambiente a tuo ritmo

  1. Accedi alla console Google Cloud e crea un nuovo progetto o riutilizzane uno esistente. Se non hai ancora un account Gmail o Google Workspace, devi crearne uno.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • Il nome del progetto è il nome visualizzato per i partecipanti al progetto. Si tratta di una stringa di caratteri non utilizzata dalle API di Google. Puoi aggiornarlo in qualsiasi momento.
  • L'ID progetto è univoco per tutti i progetti Google Cloud ed è immutabile (non può essere modificato dopo essere stato impostato). La console Cloud genera automaticamente una stringa univoca; in genere non è importante quale sia. Nella maggior parte dei codelab, dovrai fare riferimento all'ID progetto (in genere identificato come PROJECT_ID). Se l'ID generato non ti piace, puoi generarne un altro casuale. In alternativa, puoi provare il tuo e vedere se è disponibile. Non può essere modificato dopo questo passaggio e rimarrà invariato per tutta la durata del progetto.
  • Per tua informazione, esiste un terzo valore, un Numero progetto, utilizzato da alcune API. Scopri di più su tutti e tre questi valori nella documentazione.
  1. Successivamente, dovrai abilitare la fatturazione nella console Cloud per utilizzare le API/risorse Cloud. L'esecuzione di questo codelab non dovrebbe costare molto, se non del tutto. Per arrestare le risorse in modo da non generare costi oltre questo tutorial, puoi eliminare le risorse che hai creato o l'intero progetto. I nuovi utenti di Google Cloud possono partecipare al programma Prova senza costi di 300$.

Attiva Cloud Shell

  1. Nella console Cloud, fai clic su Attiva Cloud Shell 853e55310c205094.png.

55efc1aaa7a4d3ad.png

Se non hai mai avviato Cloud Shell, viene visualizzata una schermata intermedia (sotto la piega) che descrive di cosa si tratta. In questo caso, fai clic su Continua (e non la vedrai mai più). Ecco come appare la schermata una tantum:

9c92662c6a846a5c.png

Dovrebbero bastare pochi istanti per eseguire il provisioning e connettersi a Cloud Shell.

9f0e51b578fecce5.png

Questa macchina virtuale contiene tutti gli strumenti di sviluppo di cui hai bisogno. Offre una home directory permanente da 5 GB e viene eseguita in Google Cloud, migliorando notevolmente le prestazioni e l'autenticazione della rete. Gran parte, se non tutto, del lavoro in questo codelab può essere svolto semplicemente con un browser o con il tuo Chromebook.

Una volta eseguita la connessione a Cloud Shell, dovresti vedere che il tuo account è già autenticato e il progetto è già impostato sul tuo ID progetto.

  1. Esegui questo comando in Cloud Shell per verificare che l'account sia autenticato:
gcloud auth list

Output comando

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. Esegui il seguente comando in Cloud Shell per verificare che il comando gcloud conosca il tuo progetto:
gcloud config list project

Output comando

[core]
project = <PROJECT_ID>

In caso contrario, puoi impostarlo con questo comando:

gcloud config set project <PROJECT_ID>

Output comando

Updated property [core/project].

Configurazione dell'ambiente

Per questo lab eseguirai i comandi nella riga di comando di Cloud Shell. In genere puoi copiare i comandi e incollarli così come sono, anche se in alcuni casi dovrai modificare i valori segnaposto con quelli corretti.

  1. Imposta una variabile di ambiente sull'ID progetto per utilizzarla nei comandi successivi:
export PROJECT_ID=$(gcloud config get-value project)
export REGION=us-central1
export SERVICE_NAME=partner-registration-service
  1. Abilita l'API di servizio Cloud Run che eseguirà la tua app, l'API Firestore che fornirà lo spazio di archiviazione dei dati NoSQL, l'API Cloud Build che verrà utilizzata dal comando di deployment e Artifact Registry che verrà utilizzato per contenere il contenitore dell'applicazione al momento della compilazione:
gcloud services enable \
  run.googleapis.com \
  firestore.googleapis.com \
  cloudbuild.googleapis.com \
  artifactregistry.googleapis.com
  1. Inizializza il database Firestore in modalità Native. Questo comando utilizza l'API App Engine, quindi deve essere attivato per primo.

Il comando deve specificare una regione per App Engine, che non verrà utilizzata, ma che deve essere creata per motivi storici, e una regione per il database. Utilizzeremo us-central per App Engine e nam5 per il database. nam5 è la località multiregionale degli Stati Uniti. Le località multiregionali massimizzano la disponibilità e la durabilità del database.

gcloud services enable appengine.googleapis.com

gcloud app create --region=us-central
gcloud firestore databases create --region=nam5
  1. Clona il repository dell'app di esempio e vai alla directory
git clone https://github.com/GoogleCloudPlatform/cymbal-eats.git

cd cymbal-eats/partner-registration-service

3. Esamina il file README

Apri l'editor e controlla i file che compongono l'app. Visualizza README.md, che descrive i passaggi necessari per il deployment di questa app. Alcuni di questi passaggi potrebbero richiedere decisioni di sicurezza implicite o esplicite. Modificherai alcune di queste scelte per migliorare la sicurezza dell'app di cui è stato eseguito il deployment, come descritto di seguito:

Passaggio 3: esegui npm install

È importante conoscere la provenienza e l'integrità di qualsiasi software di terze parti utilizzato in un'app. La gestione della sicurezza della catena di fornitura del software è pertinente per la creazione di qualsiasi software, non solo delle app di cui è stato eseguito il deployment in Cloud Run. Questo lab si concentra sul deployment, quindi non tratta questa area, ma ti consigliamo di esaminare l'argomento separatamente.

Passaggi 4 e 5: modifica ed esecuzione deploy.sh

Questi passaggi eseguono il deployment dell'app in Cloud Run lasciando la maggior parte delle opzioni sui valori predefiniti. Modificherai questo passaggio per rendere il deployment più sicuro in due modi fondamentali:

  1. Non consentire l'accesso non autenticato. Può essere comodo consentire questa operazione per provare qualcosa durante l'esplorazione, ma si tratta di un servizio web destinato all'utilizzo da parte di partner commerciali e deve sempre autenticare i propri utenti.
  2. Specifica che l'applicazione deve utilizzare un account di servizio dedicato personalizzato con solo i privilegi necessari, anziché uno predefinito che probabilmente avrà più accesso alle API e alle risorse del necessario. Questo è noto come principio del privilegio minimo ed è un concetto fondamentale della sicurezza delle applicazioni.

Passaggi da 6 a 11: effettua richieste web di esempio per verificare il comportamento corretto

Poiché il deployment dell'applicazione ora richiede l'autenticazione, queste richieste ora devono includere una prova dell'identità del richiedente. Anziché modificare questi file, invierai le richieste direttamente dalla riga di comando.

4. Esegui il deployment del servizio in sicurezza

Sono state identificate due modifiche necessarie nello script deploy.sh: non consentire l'accesso non autenticato e l'utilizzo di un account di servizio dedicato con privilegi minimi.

Dovrai prima creare un nuovo account di servizio, poi modificare lo script deploy.sh in modo da fare riferimento a questo account e a non consentire l'accesso non autenticato, quindi eseguire il deployment del servizio eseguendo lo script modificato prima di poter eseguire lo script deploy.sh modificato.

Crea un account di servizio e concedi l'accesso necessario a Firestore/Datastore

gcloud iam service-accounts create partner-sa

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:partner-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role=roles/datastore.user

Modifica deploy.sh

Modifica il file deploy.sh per non consentire l'accesso non autenticato(–no-allow-unauthenticated) e per specificare il nuovo account di servizio(–service-account) per l'app di cui è stato eseguito il deployment. Correggi GOOGLE_PROJECT_ID in modo che corrisponda all'ID del tuo progetto.

Dovrai eliminare le prime due righe e modificare altre tre righe come mostrato di seguito.

gcloud run deploy $SERVICE_NAME \
  --source . \
  --platform managed \
  --region ${REGION} \
  --no-allow-unauthenticated \
  --project=$PROJECT_ID \
  --service-account=partner-sa@${PROJECT_ID}.iam.gserviceaccount.com

Esegui il deployment del servizio

Dalla riga di comando, esegui lo script deploy.sh:

./deploy.sh

Al termine del deployment, l'ultima riga dell'output del comando mostrerà l'URL del servizio della nuova app. Salva l'URL in una variabile di ambiente:

export SERVICE_URL=<URL from last line of command output>

Ora prova a recuperare un ordine dall'app utilizzando lo strumento curl:

curl -i -X GET $SERVICE_URL/partners

Il flag -i per il comando curl indica di includere le intestazioni di risposta nell'output. La prima riga dell'output dovrebbe essere:

HTTP/2 403

L'app è stata dispiattata con l'opzione per disattivare le richieste non autenticate. Questo comando curl non contiene informazioni di autenticazione, pertanto viene rifiutato da Cloud Run. L'applicazione di cui è stato eseguito il deployment effettivo non esegue né riceve dati da questa richiesta.

5. Inviare richieste autenticate

L'app di cui è stato eseguito il deployment viene invocata inviando richieste web, che ora devono essere autenticate per consentire a Cloud Run di accettarle. Le richieste web vengono autenticate includendo un'intestazione Authorization del modulo:

Authorization: Bearer identity-token

L'identity-token è una stringa codificata firmata in modo crittografico a breve termine emessa da un provider di autenticazione attendibile. In questo caso, è necessario un token di identità valido emesso da Google e non scaduto.

Invia una richiesta con il tuo account utente

Lo strumento Google Cloud CLI può fornire un token per l'utente autenticato predefinito. Esegui questo comando per ottenere un token di identità per il tuo account e salvarlo nella variabile di ambiente ID_TOKEN:

export ID_TOKEN=$(gcloud auth print-identity-token)

Per impostazione predefinita, i token di identità emessi da Google sono validi per un'ora. Esegui il seguente comando curl per effettuare la richiesta che è stata rifiutata in precedenza perché non era autorizzata. Questo comando includerà l'intestazione necessaria:

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $ID_TOKEN"

L'output del comando deve iniziare con HTTP/2 200, a indicare che la richiesta è accettabile e verrà soddisfatta. Se aspetti un'ora e riprovi a effettuare questa richiesta, non andrà a buon fine perché il token sarà scaduto. Il corpo della risposta si trova alla fine dell'output, dopo una riga vuota:

{"status":"success","data":[]}

Non ci sono ancora partner.

Registra i partner utilizzando i dati JSON di esempio nella directory con due comandi curl:

curl -X POST \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "@example-partner.json" \
  $SERVICE_URL/partner

e

curl -X POST \
  -H "Authorization: Bearer $ID_TOKEN" \
  -H "Content-Type: application/json" \
  -d "@example-partner2.json" \
  $SERVICE_URL/partner

Ripeti la richiesta GET precedente per visualizzare tutti i partner registrati:

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $ID_TOKEN"

Dovresti vedere i dati JSON con molti più contenuti, che forniscono informazioni sui due partner registrati.

Effettuare una richiesta da un account non autorizzato

La richiesta autenticata effettuata nell'ultimo passaggio è andata a buon fine non solo perché è stata autenticata, ma anche perché l'utente autenticato (il tuo account) era autorizzato. In altre parole, l'account aveva l'autorizzazione a richiamare l'app. Non tutti gli account autenticati saranno autorizzati a farlo.

L'account predefinito utilizzato nella richiesta precedente è stato autorizzato perché è l'account che ha creato il progetto contenente l'app e per impostazione predefinita ha l'autorizzazione a richiamare qualsiasi applicazione Cloud Run nell'account. Questa autorizzazione può essere revocata, se necessario, il che è auspicabile in un'applicazione di produzione. Invece di farlo ora, crea un nuovo account di servizio senza privilegi o ruoli assegnati e utilizzalo per provare ad accedere all'app di cui è stato eseguito il deployment.

  1. Crea un account di servizio chiamato tester.
gcloud iam service-accounts create tester
  1. Riceverai un token di identità per questo nuovo account nello stesso modo in cui ne hai ricevuto uno per il tuo account predefinito in precedenza. Tuttavia, è necessario che il tuo account predefinito disponga dell'autorizzazione per simulare l'identità degli account di servizio. Concedi questa autorizzazione al tuo account.
export USER_EMAIL=$(gcloud config list account --format "value(core.account)")

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="user:$USER_EMAIL" \
  --role=roles/iam.serviceAccountTokenCreator
  1. Esegui il comando seguente per salvare un token di identità per questo nuovo account nella variabile di ambiente TEST_IDENTITY. Se il comando mostra un messaggio di errore, attendi un minuto o due e riprova.
export TEST_TOKEN=$( \
  gcloud auth print-identity-token \
    --impersonate-service-account \
    "tester@$PROJECT_ID.iam.gserviceaccount.com" \
)
  1. Effettua la richiesta web autenticata come prima, ma utilizzando questo token di identità:
curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

L'output del comando inizierà di nuovo con HTTP/2 403 perché la richiesta, sebbene autenticata, non è autorizzata. Il nuovo account di servizio non ha l'autorizzazione per richiamare questa app.

Autorizzazione di un account

Un account utente o di servizio deve disporre del ruolo Invoker di Cloud Run in un servizio Cloud Run per poter inviare richieste. Assegna questo ruolo all'account di servizio del tester con il comando:

export REGION=us-central1
gcloud run services add-iam-policy-binding ${SERVICE_NAME} \
  --member="serviceAccount:tester@$PROJECT_ID.iam.gserviceaccount.com" \
  --role=roles/run.invoker \
  --region=${REGION}

Dopo aver aspettato uno o due minuti per l'aggiornamento del nuovo ruolo, ripeti la richiesta autenticata. Salva un nuovo TEST_TOKEN se è trascorsa almeno un'ora dal primo salvataggio.

curl -i -X GET $SERVICE_URL/partners \
  -H "Authorization: Bearer $TEST_TOKEN"

L'output del comando ora inizia con HTTP/1.1 200 OK e l'ultima riga contiene la risposta JSON. Questa richiesta è stata accettata da Cloud Run ed elaborata dall'app.

6. Autenticazione dei programmi e autenticazione degli utenti

Le richieste autenticate che hai effettuato finora hanno utilizzato lo strumento a riga di comando curl. Avremmo potuto usare anche altri strumenti e linguaggi di programmazione. Tuttavia, le richieste Cloud Run autenticate non possono essere effettuate utilizzando un browser web con pagine web semplici. Se un utente fa clic su un link o su un pulsante per inviare un modulo in una pagina web, il browser non aggiungerà l'intestazione Authorization richiesta da Cloud Run per le richieste autenticate.

Il meccanismo di autenticazione integrato di Cloud Run è destinato all'utilizzo da parte dei programmi, non degli utenti finali.

Nota:

Cloud Run può ospitare applicazioni web rivolte agli utenti, ma questi tipi di applicazioni devono impostare Cloud Run per consentire richieste non autenticate dai browser web degli utenti. Se le applicazioni richiedono l'autenticazione utente, l'applicazione deve gestirla anziché chiedere a Cloud Run di farlo. L'applicazione può farlo nello stesso modo in cui lo fanno le applicazioni web al di fuori di Cloud Run. La procedura non rientra nell'ambito di questo codelab.

Potresti aver notato che finora le risposte alle richieste di esempio sono state oggetti JSON, non pagine web. Questo perché questo servizio di registrazione dei partner è destinato all'utilizzo da parte dei programmi e JSON è un formato pratico per loro. Successivamente, scriverai ed eseguirai un programma per utilizzare questi dati.

Richieste autenticate da un programma Python

Un programma può inviare richieste autenticate di un'applicazione Cloud Run protetta tramite richieste web HTTP standard, ma includendo un'intestazione Authorization. L'unica nuova sfida per questi programmi è ottenere un token di identità valido e non scaduto da inserire nell'intestazione. Il token verrà convalidato da Cloud Run utilizzando Cloud Identity and Access Management (IAM) di Google Cloud, pertanto deve essere emesso e firmato da un'autorità riconosciuta da IAM. Esistono librerie client disponibili in molti linguaggi che i programmi possono utilizzare per richiedere l'emissione di un token di questo tipo. La libreria client utilizzata in questo esempio è quella di Python google.auth. Esistono diverse librerie Python per l'invio di richieste web in generale; questo esempio utilizza il popolare modulo requests.

Il primo passaggio consiste nell'installare le due librerie client:

pip install google-auth
pip install requests

Il codice Python per richiedere un token di identità per l'utente predefinito è:

credentials, _ = google.auth.default()
credentials.refresh(google.auth.transport.requests.Request())
identity_token = credentials.id_token

Se utilizzi una shell di comando come Cloud Shell o la shell del terminale standard sul tuo computer, l'utente predefinito è quello che si è autenticato all'interno della shell. In Cloud Shell, in genere si tratta dell'utente che ha eseguito l'accesso a Google. In altri casi, è l'utente autenticato con gcloud auth login o un altro comando gcloud. Se l'utente non ha mai eseguito l'accesso, non esisterà un utente predefinito e questo codice non andrà a buon fine.

In genere, per un programma che effettua richieste a un altro programma, non è consigliabile utilizzare l'identità di una persona, ma l'identità del programma che effettua la richiesta. Gli account di servizio servono a questo. Hai eseguito il deployment del servizio Cloud Run con un account di servizio dedicato che fornisce l'identità utilizzata per effettuare richieste API, ad esempio a Cloud Firestore. Quando un programma viene eseguito su una piattaforma Google Cloud, le librerie client utilizzeranno automaticamente l'account di servizio assegnato come identità predefinita, quindi lo stesso codice del programma funziona in entrambe le situazioni.

Il codice Python per effettuare una richiesta con un'intestazione Authorization aggiunta è:

auth_header = {"Authorization": "Bearer " + identity_token}
response = requests.get(url, headers=auth_header)

Il seguente programma Python completo invierà una richiesta autenticata al servizio Cloud Run per recuperare tutti i partner registrati e poi stampare i loro nomi e gli ID assegnati. Copia ed esegui il comando seguente per salvare questo codice nel file print_partners.py.

cat > ./print_partners.py << EOF
def print_partners():
    import google.auth
    import google.auth.transport.requests
    import requests

    credentials, _ = google.auth.default()
    credentials.refresh(google.auth.transport.requests.Request())
    identity_token = credentials.id_token

    auth_header = {"Authorization": "Bearer " + identity_token}
    response = requests.get("${SERVICE_URL}/partners", headers=auth_header)

    parsed_response = response.json()
    partners = parsed_response["data"]

    for partner in partners:
        print(f"{partner['partnerId']}: {partner['name']}")


print_partners()
EOF

Eseguirai questo programma con un comando shell. Dovrai prima autenticarti come utente predefinito, in modo che il programma possa utilizzare queste credenziali. Esegui il seguente comando gcloud auth:

gcloud auth application-default login

Segui le istruzioni per completare l'accesso. Quindi esegui il programma dalla riga di comando:

python print_partners.py

L'output sarà simile al seguente:

10102: Zippy food delivery
67292: Foodful

La richiesta del programma ha raggiunto il servizio Cloud Run perché è stata autenticata con la tua identità e tu sei il proprietario di questo progetto e quindi autorizzato a eseguirlo per impostazione predefinita. Sarebbe più comune che questo programma venga eseguito con l'identità di un account di servizio. Se viene eseguito sulla maggior parte dei prodotti Google Cloud, come Cloud Run o App Engine, l'identità predefinita sarà un account di servizio e verrà utilizzata al posto di un account personale.

7. Complimenti!

Complimenti, hai completato il codelab.

Passaggi successivi

Esplora altri codelab di Cymbal Eats:

Esegui la pulizia

Per evitare che al tuo account Google Cloud vengano addebitati costi relativi alle risorse utilizzate in questo tutorial, elimina il progetto che contiene le risorse oppure mantieni il progetto ed elimina le singole risorse.

Elimina il progetto

Il modo più semplice per eliminare la fatturazione è eliminare il progetto che hai creato per il tutorial.