
Plugin Jenkins che fornisce flussi di lavoro di approvazione degli script e sandboxing Groovy per garantire l'esecuzione sicura degli script, con controlli di autorizzazione consapevoli degli ACL e gestione delle whitelist per amministratori.
(adattato dalle informazioni sul Plugin Template nella guida CloudBees Plugins)
Vari plugin di Jenkins richiedono che gli utenti definiscano script personalizzati, più comunemente in linguaggio Groovy, per personalizzare il comportamento di Jenkins. Se tutti coloro che scrivono questi script sono amministratori di Jenkins—in particolare se hanno il permesso Overall/RunScripts, utilizzato ad esempio dal collegamento Script Console—possono scrivere qualsiasi script desiderino. Questi script possono fare riferimento diretto agli oggetti interni di Jenkins utilizzando la stessa API offerta ai plugin. Tali utenti devono essere completamente fidati, poiché possono fare qualsiasi cosa su Jenkins (anche modificare le impostazioni di sicurezza o eseguire comandi shell sul server).
Tuttavia, se alcuni autori di script sono "utenti normali" con solo permessi più limitati, come Job/Configure, non è appropriato permettere loro di eseguire script arbitrari. Per supportare tale divisione dei ruoli, il plugin libreria Script Security può essere integrato in vari plugin di funzionalità. Supporta due sistemi correlati: approvazione degli script e sandboxing Groovy.
Il primo sistema di sicurezza, e più semplice, è quello di permettere l'esecuzione di qualsiasi tipo di script, ma solo con l'approvazione di un amministratore. Esiste un elenco mantenuto globalmente di script approvati che si ritiene non eseguano azioni malevole.
Quando un amministratore salva un qualche tipo di configurazione (ad esempio, un job), gli script modificati dall'amministratore vengono automaticamente approvati e sono pronti per l'esecuzione senza ulteriori interventi. Per gli script inviati da utenti con privilegi inferiori verranno visualizzati avvisi appropriati che indicano la necessità di approvazione. Gli amministratori possono approvare tali script utilizzando la pagina di configurazione di Approvazione Script o modificando lo script e salvandolo. Nelle versioni precedenti del Plugin di Sicurezza degli Script, gli amministratori potevano approvare automaticamente gli script inviati da utenti non privilegiati salvandoli senza apportare modifiche, ma questa funzionalità è stata disabilitata per prevenire attacchi di ingegneria sociale. ("Salvare" di solito significa dall'interfaccia web, ma potrebbe anche significare caricare una nuova configurazione XML tramite REST o CLI.)
Quando un non amministratore salva una configurazione modello, viene effettuato un controllo per verificare se eventuali script contenuti sono stati modificati rispetto a un testo approvato. (Più precisamente, se il contenuto richiesto è mai stato approvato in precedenza.) Se non è stato approvato, una richiesta di approvazione per questo script viene aggiunta a una coda. (Un avviso viene anche visualizzato nell' interfaccia di configurazione quando il testo corrente di uno script non è attualmente approvato.)
Un amministratore può ora andare in Gestisci Jenkins » Approvazione script in-process dove verrà mostrato un elenco di script in attesa di approvazione. Supponendo che non venga richiesto nulla di pericoloso, basta fare clic su Approva per permettere l'esecuzione dello script d'ora in poi.
Se si tenta di eseguire uno script non approvato, fallirà semplicemente, tipicamente con un messaggio che spiega che è in attesa di approvazione. È possibile riprovare una volta che lo script è stato approvato. I dettagli di questo comportamento possono variare a seconda del plugin di funzionalità che integra questa libreria.
Aspettare che un amministratore approvi ogni modifica a uno script, per quanto apparentemente banale, potrebbe essere inaccettabile in un team distribuito su fusi orari diversi o durante scadenze strette. Come opzione alternativa, il sistema Script Security permette l'esecuzione di script Groovy senza approvazione purché si limitino a operazioni considerate intrinsecamente sicure. Questo ambiente di esecuzione limitato è chiamato sandbox. (Attualmente non sono disponibili implementazioni sandbox per altri linguaggi, quindi tutti questi script devono essere approvati se configurati da non amministratori.)
Per passare a questa modalità, basta spuntare la casella Usa Groovy Sandbox sotto il campo di inserimento dello script Groovy. Gli script in sandbox possono essere eseguiti immediatamente da chiunque. (Anche gli amministratori, anche se lo script è soggetto alle stesse restrizioni indipendentemente da chi lo ha scritto.) Quando lo script viene eseguito, ogni chiamata a metodo, costruzione di oggetto e accesso a campo viene controllata rispetto a una whitelist di operazioni approvate. Se viene tentata un'operazione non approvata, lo script viene terminato e la corrispondente funzionalità di Jenkins non può ancora essere utilizzata.
Il plugin Script Security viene fornito con una piccola whitelist predefinita, e i plugin che lo integrano possono aggiungere operazioni a tale elenco (tipicamente metodi specifici di quel plugin).
Ma non sei limitato alla whitelist predefinita: ogni volta che uno script fallisce prima di eseguire un'operazione non ancora inserita nella whitelist, tale operazione viene automaticamente aggiunta a un'altra coda di approvazione. Un amministratore può andare nella stessa pagina descritta sopra per l'approvazione di interi script e vedere un elenco di approvazioni di operazioni in sospeso. Se si fa clic su Approva accanto alla firma di un'operazione, viene immediatamente aggiunta alla whitelist e resa disponibile per gli script in sandbox.
La maggior parte delle firme sono della forma method class.Name methodName arg1Type arg2Type…,
indicando una chiamata a metodo Java con una specifica classe "ricevente" (this), nome del metodo e
lista di tipi di argomenti (o parametri). (Verrà offerta per l'approvazione la firma più generale
di una chiamata a metodo tentata, anche quando l'oggetto reale su cui doveva essere chiamato era
di un tipo più specifico che sovrascrive quel metodo.) Potresti anche vedere staticMethod per
metodi statici (di classe), new per costruttori e field per accessi a campi (get o set).
Gli amministratori in ambienti sensibili alla sicurezza dovrebbero considerare attentamente quali
operazioni inserire nella whitelist. Le operazioni che cambiano lo stato di oggetti persistenti (come
i job Jenkins) dovrebbero generalmente essere negate. La maggior parte dei metodi getSomething sono innocui.
Tieni presente tuttavia che anche alcuni metodi "getter" sono progettati per controllare
permessi specifici (utilizzando un ACL: lista di controllo accessi), mentre gli script sono spesso eseguiti
da uno pseudo-utente di sistema a cui sono concessi tutti i permessi. Quindi, ad esempio
method hudson.model.AbstractItem getParent (che ottiene la cartella o la radice Jenkins contenente
un job) è di per sé innocuo, ma la possibile chiamata successiva method hudson.model.ItemGroup getItems (che elenca i job per nome all'interno di una cartella) controlla
Job/Read. Questa seconda chiamata sarebbe pericolosa da inserire incondizionatamente nella whitelist,
poiché significherebbe che un utente a cui è concesso Job/Create in una cartella potrebbe
leggere almeno alcune informazioni da qualsiasi job in quella cartella, anche quelli che
dovrebbero essere nascosti secondo una strategia di autorizzazione basata su progetto; basterebbe
creare un job nella cartella che includa uno script Groovy come questo (i dettagli varierebbero secondo
il plugin che integra):
println("I sniffed ${thisjob.getParent().getItems()}!");
Durante l'esecuzione, l'output dello script visualizzerebbe almeno i nomi di progetti
presumibilmente segreti. Un amministratore potrebbe invece fare clic su Approva supponendo il controllo dei permessi
per getItems; questo permetterà la chiamata quando eseguita come utente reale (se il plugin
che integra lo fa mai), mentre la vieta quando eseguita come utente di sistema (che è più
tipico). In questo caso, getItems è effettivamente implementato per restituire solo quei job che
l'utente corrente ha accesso, quindi se eseguito nel primo caso (come utente specifico), la
descrizione mostrerà solo quei job che potrebbero comunque vedere. Questo pulsante più avanzato viene
mostrato solo per chiamate a metodi (e costruttori) e dovrebbe essere usato solo dove si sa
che Jenkins sta effettuando un controllo dei permessi.
Integrazione di esempio completa
Per una tipica integrazione Groovy, in cui offri all'utente l'opzione di utilizzare l'approvazione
degli script o la sandbox, modifica il campo script di tipo String della tua classe describable in un
campo SecureGroovyScript. Nel tuo costruttore, prima di memorizzare il valore, chiama
configuringWithKeyItem (se può esserci un solo script per elemento di primo livello) o
configuringWithNonKeyItem (se potrebbero essercene diversi). Il modulo di configurazione dovrebbe
usare <f:property field="…"/> per raccogliere la configurazione dello script e della sandbox. Quando
vuoi eseguire lo script, chiama semplicemente evaluate.
(Per compatibilità con dati vecchi, scegli un nome di campo diverso e depreca l'originale.
Quindi puoi definire un metodo readResolve che imposta il nuovo campo su un SecureGroovyScript
con sandbox disattivata, chiama configuring(ApprovalContext.create()) su di esso per notificare al
sistema che uno script non approvato è stato caricato, e deseleziona il vecchio campo.)
Da utilizzare se hai bisogno di più controllo di quanto offerto da SecureGroovyScript:
Introduci un campo sandbox booleano nella tua configurazione.
Quando non impostato, devi chiamare ScriptApproval.configuring nel @DataBoundConstructor.
Usa ApprovalContext.withCurrentUser, e anche withItemAsKey dove applicabile (quando
c'è un solo script per job); altrimenti almeno withItem dove applicabile, e/o
withKey quando puoi identificare univocamente questo utilizzo dal contesto
(StaplerRequest.findAncestorObject è utile qui). Questo fa sapere al sistema che uno
(possibilmente) nuovo script è stato configurato da una persona particolare. Avrai anche bisogno di
un readResolve che chiami configuring per notificare al sistema quando un oggetto configurabile
con script è stato caricato dal disco (e quindi il configuratore è sconosciuto). Chiama
ScriptApproval.using quando lo script viene eseguito, e cattura UnapprovedUsageException se
necessario. Il descrittore dovrebbe usare la validazione del form sul campo script e chiamare
ScriptApproval.checking (generalmente il tuo descrittore dovrebbe già fare almeno un
controllo sintattico su questo campo).
Quando il campo sandbox è impostato, devi semplicemente configurare la shell Groovy con
GroovySandbox.createSecureCompilerConfiguration e poi chiamare GroovySandbox.run;
preparati a catturare RejectedAccessException e chiamare ScriptApproval.accessRejected.
Per preapprovare alcune chiamate a metodi particolari, annotale semplicemente con @Whitelisted se
nel tuo plugin; altrimenti puoi registrare (con @Extension) un ProxyWhitelist che delega a
StaticWhitelist.from e carica un file di testo che elenca i metodi nella whitelist.
Quando costruisci un GroovyShell per valutare uno script, o chiami
SecureGroovyScript.evaluate, devi passare un ClassLoader che rappresenti il classpath
effettivo per lo script. Potresti usare il loader del core di Jenkins, o del tuo plugin, o
Jenkins.getInstance().getPluginManager().uberClassLoader.
Qualunque cosa tu scelga, non permettere a un utente non privilegiato di aggiungere voci arbitrarie
al classpath creando un URLClassLoader! Ciò renderebbe banale aggirare tutta la sicurezza
quando si utilizza la sandbox. (Un utente deve semplicemente fare archiviare in questo o in un altro
job un JAR contenente qualche classe con un metodo statico contrassegnato @Whitelisted e
facendo ciò che vuole, quindi chiamare il metodo dal proprio script.) Nessun attacco è stato ancora
dimostrato quando si utilizza l'approvazione dell'intero script—un URLClassLoader con normale
delega parent-first non permetterebbe un banale mascheramento di API dall'aspetto innocuo da
versioni compromesse—ma è probabile che un uso intelligente di
META-INF/services/org.codehaus.groovy.transform.ASTTransformation o simile possa causare il
comportamento inaspettato e non autorizzato di uno script altrimenti sicuro.
JENKINS-22834 suggerisce un'alternativa
standard sicura.
Quando scrivi test per plugin che utilizzano il Plugin di Sicurezza degli Script potresti incontrare alcuni errori nei tuoi test.
Se i tuoi test chiamano, direttamente o indirettamente, il metodo ScriptApproval.get(),
allora i tuoi test unitari devono usare JenkinsRule in modo che Jenkins.getInstance() non
restituisca null. È probabile che test che funzionavano ora inizino a fallire se non stai usando
la sandbox. Ciò accade perché vengono messi in coda per l'approvazione. Nel caso in cui tu
abbia bisogno di eseguire script indipendentemente dalle approvazioni,
ScriptApproval.get().preapprove(script, GroovyLanguage.get()) garantirà che tutti gli script
configurati siano approvati. In alternativa, puoi far eseguire ai tuoi test script usando la
sandbox. In questo caso potresti dover mettere in whitelist i metodi utilizzati dai tuoi test —
sia in generale per utenti reali, o usando un @TestExtension per avere una whitelist solo
per i test.
Vedi il changelog