La VPN è attiva, la dashboard interna si apre nel browser e lo script Python lancia comunque un errore di proxy. In quel quadro non c'è niente di sbagliato nel tunnel: l'interprete legge un indirizzo proxy dal proprio ambiente e vi invia la richiesta. Questo runbook legge l'ambiente che il processo vede davvero, applica l'esclusione più stretta e solo dopo considera una politica di sessione esplicita. Ogni passo termina con un comando il cui output decide se continui.

Leggi l'ambiente che il processo vede davvero

Le impostazioni proxy del desktop, gli export della shell e le righe Environment= di un'unità systemd sono tre fonti diverse, e Requests legge solo l'ultima che eredita. Esegui la diagnostica qui sotto nella stessa shell, container o unità che esegue lo script che fallisce, non nel tuo terminale interattivo. Una variabile esportata nella shell di login è invisibile a un processo in container, e quel divario spiega molte segnalazioni di un proxy che compare dal nulla. I due comandi stampano gli stessi dati a livelli di dettaglio diversi.

env | grep -i -E 'proxy|requests_ca_bundle|curl_ca_bundle'
python3 -c "import os;[print(k,'=',v) for k,v in sorted(os.environ.items()) if 'proxy' in k.lower()]"

Vengono lette entrambe le grafie di ogni variabile, e le voci duplicate vengono applicate nell'ordine in cui il processo le ha ricevute, quindi un HTTP_PROXY obsoleto può sovrascrivere il valore appena esportato. Anche le variabili del bundle CA contano qui, perché cambiano ciò in cui verify si risolve senza comparire da nessuna parte nel codice. Se l'output è vuoto, l'ambiente non è il tuo problema e puoi smettere di leggere questa sezione. Se elenca un proxy che non riconosci, trova il file o l'unità che lo imposta prima di cambiare qualcosa.

Chiedi a Requests cosa farebbe con un URL specifico. Questa chiamata mostra i valori proxy, verify e cert uniti per quella richiesta senza inviare un pacchetto.

python3 -c "import requests;s=requests.Session();print(s.merge_environment_settings('https://api.corp.example/status',{},None,None,None))"

Una voce proxies non vuota per l'host privato è la tua risposta, e il valore verify stampato ti dice in quale bundle CA la richiesta avrebbe fiducia. Se entrambi sembrano corretti, il guasto è più avanti nello stack e dovresti saltare alla sezione di verifica. Non indovinare dal codice: l'unione avviene per richiesta, per questo una sessione può comportarsi diversamente per due host.

Escludi l'host privato prima di disattivare qualsiasi cosa

Quando il proxy è davvero necessario per altre destinazioni, la correzione stretta è un'esclusione per questo host. Aggiungi alla lista esistente invece di sostituirla, perché cancellare NO_PROXY rompe ogni altra destinazione interna che vi faceva affidamento. I comandi qui sotto aggiungono la voce e poi chiedono a Requests se l'esclusione si applica.

export NO_PROXY="${NO_PROXY:+$NO_PROXY,}api.corp.example,.corp.example"
python3 -c "import requests;print(requests.utils.should_bypass_proxies('https://api.corp.example/status',None))"

La corrispondenza avviene per suffisso dell'host, quindi .corp.example copre l'intero dominio mentre api.corp.example copre un solo nome. Un asterisco nudo disattiva il proxy per ogni host e quasi mai è ciò che vuole un job di produzione. Che una voce CIDR venga rispettata dipende dalle versioni di Requests e urllib3 nell'immagine, quindi prova la voce esatta invece di presumere che funzioni. Controlla il risultato di should_bypass_proxies per l'URL reale, non per un nome host digitato a memoria.

Tieni separati i due livelli nella testa. Aggirare il proxy HTTP non aggira la VPN, perché la decisione sul proxy avviene nella libreria e quella sul tunnel nella tabella di routing del kernel.

Disattiva trust_env solo come politica di sessione deliberata

Se l'intero job deve ignorare la configurazione ambientale, costruisci la sessione in modo esplicito e registra la decisione nel codice. È una politica, non un aggiramento, e appartiene a un diff che un revisore può vedere. L'esempio qui sotto mantiene un timeout, un percorso CA esplicito e un controllo di stato.

import requests

with requests.Session() as session:
    session.trust_env = False
    response = session.get(
        "https://api.corp.example/status",
        timeout=(5, 20),
        verify="/etc/ssl/certs/corp-ca.pem",
    )
    response.raise_for_status()

Disattivare trust_env cambia tre comportamenti insieme, e solo uno riguarda i proxy. Requests smette di leggere le credenziali .netrc, e le variabili REQUESTS_CA_BUNDLE e CURL_CA_BUNDLE smettono di influire su verify, che quindi ricade sull'archivio di certificati incluso. Se la tua organizzazione termina il TLS con una CA propria, questo ripiego genera un errore di verifica che sembra non avere relazione con la modifica appena fatta. È il passaggio esplicito di verify che tiene separati i due effetti.

Quello che non dovresti fare è impostare verify=False per mettere a tacere l'errore risultante. Così si scambia una configurazione proxy errata con una connessione non verificata, e si nasconde il problema di certificato che l'esclusione doveva rivelare.

Verifica il livello che fallisce e mantieni un rollback

Tre guasti sembrano uguali in un log e non hanno nulla in comune: un proxy che non raggiunge la destinazione genera ProxyError o restituisce 502, un problema di fiducia genera SSLError, e un rifiuto dell'applicazione restituisce 401 o 403. Riproduci la richiesta fuori da Requests per vedere quale hai. I comandi qui sotto rimuovono il proxy, verificano la catena di certificati e poi ripetono la chiamata tramite la libreria.

curl -sv --noproxy '*' https://api.corp.example/status -o /dev/null
openssl s_client -connect api.corp.example:443 -servername api.corp.example </dev/null
python3 -c "import requests;r=requests.get('https://api.corp.example/status',timeout=10);print(r.status_code)"

Se la chiamata curl riesce e quella Python fallisce, l'ambiente è ancora la differenza. Se entrambe falliscono allo stadio TLS, il proxy non è mai stato il problema e lo è la catena di certificati. Se entrambe restituiscono 401, la richiesta ha raggiunto l'applicazione e il lavoro rimasto è l'autenticazione. Annotare quale delle tre hai osservato fa risparmiare un'ora alla persona successiva.

Il rollback è una riga in entrambe le direzioni: rimuovi l'esclusione o riporta trust_env indietro. Salva il valore precedente di NO_PROXY prima di aggiungervi qualcosa, e preferisci uno script wrapper o una voce Environment= a livello di unità a un export che vive solo nella cronologia del tuo terminale. Entrambe le modifiche sono visibili nell'ambiente del processo, quindi un riavvio di solito ti riporta al vecchio comportamento.

Chiudi confermando quattro cose: le impostazioni unite per l'URL privato non mostrano alcun proxy, il certificato si valida contro la CA prevista, la richiesta restituisce lo stato che ti aspetti, e la modifica è committata in un punto riproducibile. Uno script verde con un ambiente inspiegato è una correzione che si rompe di nuovo sulla macchina successiva. I comandi di questo runbook sono quelli da incollare nel ticket.

Articoli correlati