Integrazione di GitHub CodeQL e Checkov per la Sicurezza Completa: Un Approccio Pratico con GitHub Actions

Nell’era dello sviluppo software rapido e della DevOps, la sicurezza non può essere un ripensamento, bensì un elemento fondamentale integrato nel ciclo di vita dello sviluppo software (SDLC). L’automazione dei controlli di sicurezza è cruciale per identificare le vulnerabilità nelle prime fasi del processo di sviluppo, riducendo i rischi e i costi d’intervento in produzione.
Gli strumenti chiave che vedremo nel corso di questo articolo sono:
- GitHub CodeQL: Motore di analisi semantica per identificare vulnerabilità nel codice.
- Checkov: Strumento di analisi statica per Infrastructure as Code (IaC).
- GitHub Actions: Automazione del workflow di sicurezza nel tuo repository GitHub.
- GitHub Security: Piattaforma integrata per la gestione della sicurezza del codice.
Questo articolo esplora l’integrazione di questi strumenti, utilizzando un progetto di esempio (codeql-checkov-quickstart) arricchito con vulnerabilità realistiche, tra cui SQL Injection e configurazioni infrastrutturali insicure, per dimostrare come identificarle e correggerle efficacemente.
Workflow di Integrazione
Per orchestrare l’integrazione di CodeQL e Checkov, impiegheremo un workflow di GitHub Actions progettato per attivarsi a ogni push di codice o pull request. Questo workflow automatizzato lancerà in sequenza due scansioni di sicurezza.
Scansione CodeQL Approfondita: eseguirà un’analisi semantica del codice sorgente, puntando a vulnerabilità di sicurezza, errori di qualità del codice e potenziali backdoor.
Scansione Checkov Estesa: scansionerà i file IaC (Dockerfile, Kubernetes, Terraform), ricercando configurazioni errate, violazioni delle best practices di sicurezza e potenziali punti deboli nell’infrastruttura.
Il file .github/workflows/security-scan.yml è il workflow di GitHub Actions che integra CodeQL e Checkov. Questo workflow è progettato per essere eseguito su un repository GitHub che contiene un’applicazione Python e file di configurazione IaC. Il flow del workflow è mostrato nella figura seguente.
Diagramma 1 - Flusso del workflow di GitHub Actions per la sicurezza.
Invito a visionare .github/workflows/security-scan.yml per una maggiore comprensione del workflow; inoltre, troverete sul file yaml, dove necessario, i commenti che spiegano ogni sezione.
Un esempio pratico
Un progetto dimostrativo con vulnerabilità comuni in Python, Docker, Kubernetes e Terraform mostra come questi strumenti identificano e aiutano a correggere le debolezze di sicurezza. Il progetto di esempio è disponibile su GitHub codeql-checkov-quickstart è in particolare il branch the-red-code/code-with-vulnerability-1
contiene il codice con le vulnerabilità.
Test delle Vulnerabilità
Prima di correggere le vulnerabilità, è possibile testarle per verificarne l’effettiva presenza e il loro impatto. Vedremo come testare la vulnerabilità di SQL Injection nell’applicazione Python e la vulnerabilità di esecuzione come utente root
nel container Docker.
Correzione delle Vulnerabilità e GitHub Security
Quali sono gli strumenti e le funzionalità di GitHub che ci aiutano a correggere le vulnerabilità e a garantire la sicurezza del codice e dell’infrastruttura?
- GitHub Security fornisce un’interfaccia centralizzata per la gestione degli avvisi di sicurezza, inclusi quelli generati da CodeQL e Checkov.
- Gli sviluppatori possono visualizzare i dettagli delle vulnerabilità, assegnare priorità, monitorare lo stato di correzione e chiudere gli avvisi una volta risolti.
- GitHub Security offre anche funzionalità di automazione, come l’apertura automatica di issue per le vulnerabilità rilevate e l’integrazione con strumenti di terze parti per la gestione delle vulnerabilità.
- CodeQL fornisce esempi concreti di vulnerabilità e la loro correzione, offrendo al lettore una guida pratica all’uso degli strumenti.
- Integrazione con GitHub Copilot: GitHub Copilot può assistere nella correzione delle vulnerabilità suggerendo patch di codice e fornendo spiegazioni dettagliate.
A seguire una serie di figure mostrano come GitHub Security visualizza le vulnerabilità rilevate da CodeQL e Checkov, e come gli sviluppatori possono interagire con gli avvisi per correggere le vulnerabilità.
GitHub mostrerà un security alert nella sezione “Security” del repository, dettagliando la vulnerabilità, il file e la riga di codice interessata, e fornendo raccomandazioni per la correzione.
Figura 1 - Code Scanning: GitHub CodeQL individua una vulnerabilità di SQL Injection in app.py
.
Figura 2 - Code Scanning: Dettagli della vulnerabilità di SQL Injection individuata da CodeQL.
Checkov, che scansionerà i file Kubernetes, Terraform e Dockerfile, e riporterà diverse vulnerabilità e best practices non rispettate. I risultati saranno anche disponibili nel log di GitHub Actions, evidenziando i problemi individuati e fornendo suggerimenti per la correzione.
Figura 3 - Code Scanning: Checkov individua vulnerabilità e best practices non rispettate.
Esempio pratico: Vulnerabilità SQL Injection in Python
Il codice a seguire mostra un esempio di vulnerabilità di SQL Injection dell’applicazione Python di esempio. Prima di correggere le vulnerabilità, possiamo testarle per verificarne l’effettiva presenza e il loro impatto.
# ...
@app.route('/utente')
def get_utente():
utente_id = request.args.get('id')
db_connection = sqlite3.connect('database.db')
cursor = db_connection.cursor()
# Vulnerabilità SQL Injection (String formatting)
query = "SELECT nome, email FROM utenti WHERE id = {}".format(utente_id)
cursor.execute(query)
utente = cursor.fetchone()
db_connection.close()
if utente:
return f"Nome: {utente[0]}, Email: {utente[1]}"
else:
return "Utente non trovato", 404
# ...
Python 1 - Vulnerabilità di SQL Injection nell’applicazione Python.
Avviamo l’applicazione Python con il comando python app.py
per testare l’endpoint /utente
con un parametro id
malevolo.
Per la verifica della vulnerabilità di SQL Injection, possiamo utilizzare un valore come '1 OR 1=1 ORDER BY 1'
per id
e accertare che l’applicazione restituisca dati non autorizzati.
http "http://127.0.0.1:5000/utente?id=1 OR 1=1 ORDER BY 1"
Console 1 - Test della vulnerabilità di SQL Injection nell’applicazione Python.
Qualora l’applicazione restituisca dati non autorizzati o il comportamento sia diverso da quello atteso, la vulnerabilità di SQL Injection è confermata.
HTTP/1.1 200 OK
Server: Werkzeug/3.1.3 Python/3.12.6
Nome: Anna Neri, Email: anna.neri@example.com
Console 2 - Risposta dell’applicazione Python alla vulnerabilità di SQL Injection.
Come possiamo vedere dalla Console 2, l’applicazione Python restituisce i dati dell’utente Anna Neri
(il cui id
è diverso da 1
) nonostante il parametro id
contenga un valore malevolo (1 OR 1=1 ORDER BY 1
), confermando la presenza della vulnerabilità di SQL Injection.
Possiamo provare qualcosa di pià sofisticato, come '1 UNION SELECT nome, password_chiaro FROM utenti --'
per verificare se l’applicazione espone dati non autorizzati.
http "http://127.0.0.1:5000/utente?id=1 UNION SELECT nome, password_chiaro FROM utenti --"
Console 3 - Test avanzato della vulnerabilità di SQL Injection nell’applicazione Python.
Qualora l’applicazione restituisca dati sensibili, come password in chiaro, la presenza di una vulnerabilità SQL Injection è confermata.
HTTP/1.1 200 OK
Server: Werkzeug/3.1.3 Python/3.12.6
Nome: Anna Neri, Email: passwordAnna
Console 4 - Risposta dell’applicazione Python alla vulnerabilità avanzata di SQL Injection.
Come possiamo vedere dalla Console 4, l’applicazione Python restituisce i dati dell’utente Anna Neri
e la password in chiaro passwordAnna
nonostante il parametro id
contenga un valore malevolo (1 UNION SELECT nome, password_chiaro FROM utenti --
), confermando la presenza della vulnerabilità di SQL Injection avanzata.
Correzione della Vulnerabilità di SQL Injection
Per correggere la vulnerabilità di SQL Injection nella funzione get_utente
in app.py
, dobbiamo sostituire la string formatting con le parametrized queries per prevenire l’iniezione di SQL. Ecco come possiamo modificare il codice per correggere la vulnerabilità (SQL Injection → Parametrized Queries).
#...
# Correzione della vulnerabilità di SQL Inject utilizzando
# le parametrized queries.
query = "SELECT nome, email FROM utenti WHERE id = ?"
cursor.execute(query, (utente_id,)) # Passa l'input come parametro
utente = cursor.fetchone()
#...
Python 2 - Correzione vulnerabilità di SQL Inject sul file app.py
Rammentate che la correzione della vulnerabilità di SQL Injection è solo un esempio e che la correzione delle vulnerabilità di sicurezza deve essere valutata caso per caso, in base alle esigenze dell’applicazione e del sistema.
Esempio pratico: Vulnerabilità di Docker
Il file Dockerfile
che definisce l’immagine Docker per l’applicazione Python, soffre di alcune vulnerabilità comuni.
- Nessun utente specifico: il container viene eseguito come
root
all’interno del container. - Porta 5000 esposta: anche se l’applicazione potrebbe non aver bisogno di essere esposta direttamente all’esterno in produzione, lasciarla aperta nel Dockerfile può essere un rischio se non gestito correttamente.
- Copia di tutti i file: il comando
COPY . .
copia tutti i file del repository all’interno del container, inclusi file sensibili o non necessari per l’applicazione. Questo potrebbe esporre informazioni sensibili o aumentare le dimensioni dell’immagine Docker inutilmente.
Il file completo Dockerfile
è mostrato a seguire.
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY ../../draft .
EXPOSE 5000
CMD ["python", "app.py"]
Docker 1 - Dockerfile dell’applicazione Python che contiene vulnerabilità.
Per testare la vulnerabilità di esecuzione come utente root
nel container, possiamo eseguire il comando podman build
e podman run
per verificare se il container viene eseguito come utente root
. A seguire sono indicati i passaggi per testare la vulnerabilità.
Forse sarete più avvezzi a Docker che potete comunque utilizzare al posto di
podman
se preferite. Vi invito alla lettura dell’articolo Cos’è Podman: scopriamo l’alternativa a Docker.
# Build dell'immagine container dell'applicazione Python usando
# il Dockerfile vulnerabile.
podman build -f docker/Dockerfile -t docker.io/amusarra/codeql-checkov-quickstart $(pwd)
Console 5 - Build dell’immagine Docker vulnerabile.
# Esecuzione del comando `id` all'interno del container dell'applicazione Python
# per verificare l'utente con cui viene eseguito il container.
podman run --rm -it docker.io/amusarra/codeql-checkov-quickstart:latest id
Console 6 - Verifica dell’utente con cui viene eseguito il container Docker.
L’output del comando id
all’interno del container ci mostrerà l’utente corrente e il gruppo di appartenenza. Qualora l’utente sia root
, la vulnerabilità di esecuzione come utente root
è confermata.
uid=0(root) gid=0(root) groups=0(root)
Console 7 - Esecuzione del container come utente root
.
Correzione della Vulnerabilità di Docker
Per migliorare la sicurezza del Dockerfile, possiamo apportare alcune modifiche per mitigare le vulnerabilità individuate.
- Esecuzione come utente non-root: aggiungiamo un utente non-root al Dockerfile e cambiamo l’utente di esecuzione per ridurre i privilegi del container.
- Rimuovere
EXPOSE 5000
: se la porta 5000 non deve essere esposta direttamente, rimuoverla dal Dockerfile (la porta verrà comunque gestita dal sistema di orchestrazione come Kubernetes se necessario).
Il file Dockerfile
corretto è mostrato di seguito.
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
# Create a non-root group
RUN groupadd -r appuser
# Create a non-root user and add it to the 'appuser' group
RUN useradd -r -g appuser appuser
# Change ownership of /app to the non-root user
RUN chown -R appuser:appuser /app
# Switch to the non-root user to run the application
USER appuser
CMD ["python", "app.py"]
Dockerfile 2 - Dockerfile corretto per l’applicazione Python.
Successivamente alla correzione del Dockerfile, possiamo creare una nuova immagine e testare l’output del comando id
all’interno del container e verificare che l’utente sia appuser
e non root
.
# Creazione nuova immagine container dell'applicazione Python
# usando il nuovo Dockerfile (vedi Dockerfile 2)
podman build -f docker/Dockerfile -t docker.io/amusarra/codeql-checkov-quickstart:1.0.0 $(pwd)
Console 8 - Build dell’immagine Docker corretta.
# Esecuzione del comando id all'interno del container dell'applicazione Python
# per verificare l'utente con cui viene eseguito il container.
podman run --rm -it docker.io/amusarra/codeql-checkov-quickstart:1.0.0 id
Console 9 - Verifica dell’utente con cui viene eseguito il comando all’interno del container.
uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)
Console 10 - Verifica del comando id
all’interno del container come utente appuser
e non root
.
Come possiamo vedere dalla Console 8, l’utente corrente all’interno del container è appuser
e non root
, confermando che la vulnerabilità di esecuzione come utente root
è stata corretta.
Esempio pratico: Vulnerabilità di Kubernetes
Prendendo in considerazione il file deployment.yaml
che definisce il deployment di Kubernetes per l’applicazione Python, possiamo identificare alcune vulnerabilità comuni.
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-app
spec:
replicas: 1
selector:
matchLabels:
app: python-app
template:
metadata:
labels:
app: python-app
spec:
containers:
- name: python-app
image: docker.io/amusarra/codeql-checkov-quickstart:latest # Immagine Docker da buildare
ports:
- containerPort: 5000
securityContext:
privileged: true # Container in modalità privilegiata!
resources: # Limiti di risorse MANCANTI! VULNERABILITA'
{ } # Nessun limite specificato!
Kubernetes 1 - File deployment.yaml
con vulnerabilità.
Mancanza di limiti di risorse (
resources: {}
): la sezioneresources
è presente ma vuota. Questo significa che non sono stati definiti limiti di risorse (CPU e memoria) per il container. In un ambiente di produzione, questo è un rischio significativo.- Denial of Service (DoS) locale: un container senza limiti di risorse può consumare tutte le risorse disponibili sul nodo Kubernetes su cui è in esecuzione, causando il blocco di altri container o dell’intero nodo.
- Vulnerabilità a monte: se l’applicazione ha una vulnerabilità che può essere sfruttata per causare un consumo eccessivo di risorse (es. un attacco DoS applicativo, una regular expression Denial of Service - ReDoS, etc.), l’assenza di limiti di risorse aggrava l’impatto della vulnerabilità, permettendo all’attaccante di saturare le risorse del nodo.
- Instabilità e imprevedibilità: l’assenza di limiti rende l’allocazione delle risorse imprevedibile e può portare a comportamenti inattesi dell’applicazione e dell’intero cluster.
privileged: true
: il container è configurato per essere eseguito in modalità privilegiata. Questo concede al container accesso a tutte le capabilities del kernel host, rappresentando un grave rischio di sicurezza.image: docker.io/amusarra/codeql-checkov-quickstart:latest
: utilizzo del taglatest
per l’immagine Docker. Questo rende difficile tracciare la versione dell’immagine in produzione e può portare a comportamenti inattesi se l’immaginelatest
viene aggiornata.
Sull’ultimo punto vi rimando alla lettura dell’articolo Perché il tag “latest” non va utilizzato.
Correzione delle Vulnerabilità di Kubernetes
Per migliorare la sicurezza del file deployment.yaml
per Kubernetes, possiamo apportare alcune modifiche per mitigare le vulnerabilità individuate.
- Utilizzare tag specifici per le immagini Docker: sostituire
image: docker.io/amusarra/codeql-checkov-quickstart
con un tag specifico dell’immagine Docker per tracciare le versioni. - Rimuovere
privileged: true
: rimuoviamo il campoprivileged: true
dal filedeployment.yaml
per eseguire il container in modalità non privilegiata. - Assegnare limiti di risorse: aggiungiamo limiti di risorse (CPU e memoria) al container per garantire che non possa consumare più risorse di quelle assegnate.
Il file deployment.yaml
corretto è mostrato di seguito.
apiVersion: apps/v1
kind: Deployment
metadata:
name: python-app
spec:
replicas: 1
selector:
matchLabels:
app: python-app
template:
metadata:
labels:
app: python-app
spec:
containers:
- name: python-app
image: docker.io/amusarra/codeql-checkov-quickstart:1.0.0 # Tag immagine specifico
ports:
- containerPort: 5000
securityContext: # Security context NON privilegiato
{} # Vuoto, significa che non è privilegiato
resources: # Limiti di risorse DEFINITI! (CORRETTO)
requests: # Richieste minime di risorse
cpu: 100m # 100 millicore CPU (0.1 core)
memory: 64Mi # 128 MiB di memoria
limits: # Limiti massimi di risorse
cpu: 500m # 500 millicore CPU (0.5 core)
memory: 128Mi # 128 MiB di memoria
Deployment 2 - File deployment.yaml
corretto per il deployment dell’applicazione Python in un cluster Kubernetes.
La sezione resources
ora contiene le chiavi requests
e limits
:
requests
: definisce la quantità minima di risorse (CPU e memoria) che il container richiede per essere schedulato su un nodo Kubernetes. Il scheduler di Kubernetes userà queste richieste per decidere su quale nodo schedulare il container.limits
: definisce la quantità massima di risorse (CPU e memoria) che il container può utilizzare. Kubernetes farà in modo che il container non superi questi limiti. Se il container tenta di superare i limiti di memoria, potrebbe essere throttled (rallentato) o killed (terminato), a seconda della configurazione del cluster e del tipo di limite (CPU o memoria).
In questo esempio, abbiamo definito richieste di 100 millicore CPU e 64MiB di memoria, e limiti di 500 millicore CPU e 128MiB di memoria. Questi valori sono solo di esempio e dovrebbero essere adattati in base alle reali esigenze di risorse dell’applicazione. È importante monitorare l’utilizzo delle risorse dell’applicazione in ambiente di test e produzione per definire limiti e richieste appropriati.
Lascio a voi la correzione del file main.tf per Terraform per la creazione del bucket S3.
Dopo aver corretto i file ed effettuato un nuovo push o pull request, il workflow security-scan.yml
verrà eseguito nuovamente. Questa volta dovremmo vedere un minor numero di vulnerabilità e nessuna vulnerabilità corretta sul deployment Kubernetes.
Conclusioni
L’integrazione di GitHub CodeQL e Checkov tramite GitHub Actions, potenziata da GitHub Security, rappresenta un approccio completo e automatizzato per la sicurezza del software. Combinando l’analisi semantica del codice, la scansione delle configurazioni infrastrutturali e una piattaforma centralizzata per la gestione della sicurezza, è possibile ottenere una copertura di sicurezza più ampia e identificare le vulnerabilità in diverse fasi del processo di sviluppo.