
(adaptado de la información sobre Template plugin en la guía de CloudBees Plugins)
Varios complementos de Jenkins requieren que los usuarios definan scripts personalizados, más comúnmente en el lenguaje Groovy, para personalizar el comportamiento de Jenkins. Si todos los que escriben estos scripts son administradores de Jenkins—específicamente si tienen el permiso Overall/RunScripts, usado por ejemplo por el enlace de Script Console—entonces pueden escribir los scripts que quieran. Estos scripts pueden referirse directamente a objetos internos de Jenkins usando la misma API que se ofrece a los complementos. Estos usuarios deben ser completamente confiables, ya que pueden hacer cualquier cosa en Jenkins (incluso cambiar su configuración de seguridad o ejecutar comandos de shell en el servidor).
Sin embargo, si algunos autores de scripts son "usuarios normales" con solo permisos más limitados, como Job/Configure, no es apropiado permitirles ejecutar scripts arbitrarios. Para soportar tal división de roles, el complemento de la librería Script Security puede integrarse en varios complementos de funcionalidades. Este soporta dos sistemas relacionados: aprobación de scripts, y sandboxing de Groovy.
El primer sistema de seguridad, y el más simple, es permitir que se ejecute cualquier tipo de script, pero solo con la aprobación de un administrador. Hay una lista mantenida globalmente de scripts aprobados que se consideran que no realizan ninguna acción maliciosa.
Cuando un administrador guarda algún tipo de configuración (por ejemplo, un trabajo), esos scripts que fueron editados por el administrador se aprueban automáticamente y están listos para ejecutarse sin más intervención. Para los scripts que fueron enviados por usuarios con menos privilegios, habrá advertencias apropiadas indicando que se requiere aprobación. Los administradores pueden aprobar esos scripts usando la página de configuración de Script Approval o editando el script y guardándolo. En versiones anteriores de Script Security Plugin, los administradores podían aprobar automáticamente scripts enviados por usuarios sin privilegios al guardarlos sin hacer ningún cambio, pero esta funcionalidad se deshabilitó para prevenir ataques basados en ingeniería social. ("Guardar" usualmente significa desde la interfaz web, pero también podría significar subir una nueva configuración XML vía REST o CLI.)
Cuando un no administrador guarda una configuración de plantilla, se verifica si cualquiera de los scripts contenidos ha sido editado respecto a un texto aprobado. (Más precisamente, si el contenido solicitado ha sido aprobado alguna vez antes.) Si no ha sido aprobado, se añade una solicitud de aprobación de este script a una cola. (También se muestra una advertencia en la interfaz de la pantalla de configuración cuando el texto actual de un script no está aprobado actualmente.)
Un administrador puede ahora ir a Manage Jenkins » In-process Script Approval donde se mostrará una lista de los scripts pendientes de aprobación. Suponiendo que no se esté solicitando nada de apariencia peligrosa, simplemente haga clic en Approve para permitir que el script se ejecute de aquí en adelante.
Si intenta ejecutar un script no aprobado, simplemente fallará, típicamente con un mensaje explicando que está pendiente de aprobación. Puede reintentar una vez que el script haya sido aprobado. Los detalles de este comportamiento pueden variar según el complemento de funcionalidad que integre esta librería.
Esperar a que un administrador apruebe cada cambio en un script, sin importar lo aparentemente trivial que sea, podría ser inaceptable en un equipo distribuido entre zonas horarias o durante plazos ajustados. Como opción alternativa, el sistema de Script Security permite que los scripts de Groovy se ejecuten sin aprobación siempre que se limiten a operaciones consideradas inherentemente seguras. Este entorno de ejecución limitado se llama sandbox. (Actualmente no hay implementaciones de sandbox disponibles para otros lenguajes, por lo que todos esos scripts deben ser aprobados si son configurados por no administradores.)
Para cambiar a este modo, simplemente marque la casilla Use Groovy Sandbox debajo del campo de entrada del script Groovy. Los scripts en sandbox pueden ejecutarse inmediatamente por cualquier persona. (Incluso los administradores, aunque el script está sujeto a las mismas restricciones sin importar quién lo haya escrito.) Cuando el script se ejecuta, cada llamada a método, construcción de objeto y acceso a campo se verifica contra una lista blanca de operaciones aprobadas. Si se intenta una operación no aprobada, el script se detiene y la característica correspondiente de Jenkins no puede usarse aún.
El complemento Script Security incluye una pequeña lista blanca predeterminada, y los complementos integradores pueden añadir operaciones a esa lista (típicamente métodos específicos de ese complemento).
Pero usted no está limitado a la lista blanca predeterminada: cada vez que un script falla antes de ejecutar una operación que aún no está en la lista blanca, esa operación se añade automáticamente a otra cola de aprobación. Un administrador puede ir a la misma página descrita anteriormente para la aprobación de scripts completos, y ver una lista de aprobaciones de operaciones pendientes. Si se hace clic en Approve junto a la firma de una operación, se añade inmediatamente a la lista blanca y queda disponible para scripts en sandbox.
La mayoría de las firmas tendrán la forma method class.Name methodName arg1Type arg2Type…,
indicando una llamada a un método Java con una clase "receptora" específica (this), nombre de método, y
lista de tipos de argumentos (o parámetros). (La firma más general de una llamada a método intentada
será ofrecida para aprobación, incluso cuando el objeto real sobre el que se iba a llamar era
de un tipo más específico que sobrescribe ese método.) También puede ver staticMethod para
métodos estáticos (de clase), new para constructores, y field para accesos a campos (obtener o
establecer).
Los administradores en entornos sensibles a la seguridad deben considerar cuidadosamente qué operaciones
incluir en la lista blanca. Las operaciones que cambian el estado de objetos persistidos (como
trabajos de Jenkins) generalmente deben denegarse. La mayoría de los métodos getSomething son inofensivos.
Tenga en cuenta, sin embargo, que incluso algunos métodos "getter" están diseñados para verificar permisos
específicos (usando una ACL: lista de control de acceso), mientras que los scripts a menudo son ejecutados por un
pseudo-usuario del sistema al que se le otorgan todos los permisos. Así, por ejemplo, method hudson.model.AbstractItem getParent (que obtiene la carpeta o la raíz de Jenkins que contiene
un trabajo) es inofensivo en sí mismo, pero la posible llamada posterior method hudson.model.ItemGroup getItems (que lista trabajos por nombre dentro de una carpeta) verifica
Job/Read. Esta segunda llamada sería peligrosa de incluir en la lista blanca incondicionalmente, ya que
significaría que un usuario al que se le ha concedido Job/Create en una carpeta podría leer al menos algo
de información de cualquier trabajo en esa carpeta, incluso aquellos que se supone deben estar ocultos
según una estrategia de autorización basada en proyectos; bastaría con crear un trabajo en
la carpeta que incluya un script Groovy como este (los detalles variarían según el
complemento integrador):
println("I sniffed ${thisjob.getParent().getItems()}!");
Cuando se ejecuta, la salida del script mostraría al menos los nombres de proyectos supuestamente
secretos. Un administrador puede, en cambio, hacer clic en Approve asumiendo la verificación de permisos para
getItems; esto permitirá la llamada cuando se ejecute como un usuario real (si el
complemento integrador alguna vez lo hace), mientras que la prohibirá cuando se ejecute como el usuario del sistema (lo cual es
más típico). En este caso, getItems está realmente implementado para devolver solo aquellos trabajos a los que
el usuario actual tiene acceso, así que si se ejecuta en el primer caso (como un usuario específico), la
descripción mostrará solo esos trabajos que podrían ver de todos modos. Este botón más avanzado se muestra
solo para llamadas a métodos (y constructores), y debe usarse solo donde sepa
que Jenkins está realizando una verificación de permisos.
Ejemplo completo de integración
Para una integración típica de Groovy, en la que ofrece al usuario la opción de usar ya sea la
aprobación de scripts o el sandbox, cambie el campo de script con valor String de su elemento describible
a un campo SecureGroovyScript. En su constructor, antes de almacenar el valor, llame a
configuringWithKeyItem (si solo podría haber un script de este tipo por elemento de nivel superior) o
configuringWithNonKeyItem (si podría haber varios). El formulario de configuración debe usar
<f:property field="…"/> para recoger el script y la configuración del sandbox. Cuando quiera
ejecutar el script, simplemente llame a evaluate.
(Para compatibilidad con datos antiguos, elija un nombre de campo diferente y marque el original como obsoleto.
Entonces puede definir un método readResolve que establezca el nuevo campo a un SecureGroovyScript
con el sandbox desactivado, llame a configuring(ApprovalContext.create()) sobre él para notificar al
sistema que se ha cargado un script no aprobado, y desactive el campo antiguo.)
Para usar si necesita más control del que ofrece SecureGroovyScript:
Introduzca un campo booleano sandbox en su configuración.
Cuando no esté establecido, necesita llamar a ScriptApproval.configuring en el @DataBoundConstructor.
Use ApprovalContext.withCurrentUser, y también withItemAsKey cuando corresponda (cuando
hay solo un script por trabajo); de lo contrario, al menos withItem cuando corresponda, y/o
withKey cuando pueda identificar de manera única este uso desde el contexto
(StaplerRequest.findAncestorObject es útil aquí). Esto permite que el sistema sepa que un
script (posiblemente) nuevo ha sido configurado por una persona en particular. También necesitará un
readResolve que llame a configuring para notificar al sistema cuando un configurable con script
ha sido cargado desde el disco (y por lo tanto el configurador es desconocido). Llame a
ScriptApproval.using cuando el script se ejecute, y capture UnapprovedUsageException si
es necesario. El descriptor debe usar validación de formulario en el campo de script y llamar a
ScriptApproval.checking (generalmente su descriptor ya debería estar haciendo al menos una
verificación de sintaxis en este campo).
Cuando el campo sandbox esté establecido, solo necesita configurar el shell de Groovy con
GroovySandbox.createSecureCompilerConfiguration y luego llamar a GroovySandbox.run; esté
preparado para capturar RejectedAccessException y llamar a ScriptApproval.accessRejected.
Para preaprobar algunas llamadas a métodos particulares, simplemente anótelas con @Whitelisted si están
en su complemento; de lo contrario, puede registrar (con @Extension) un ProxyWhitelist que delegue en
StaticWhitelist.from y cargue un archivo de texto que liste los métodos permitidos.
Al construir un GroovyShell para evaluar un script, o llamar a
ecureGroovyScript.evaluate, debe pasar un ClassLoader que represente el classpath efectivo
para el script. Podría usar el cargador del núcleo de Jenkins, o el de su complemento, o
Jenkins.getInstance().getPluginManager().uberClassLoader.
Sea cual sea el que elija, no permita que un usuario sin privilegios añada entradas arbitrarias al classpath
creando un URLClassLoader! Esto haría trivial eludir toda la seguridad al usar el
sandbox. (Un usuario solo necesitaría hacer que este u otro trabajo archive un JAR que contenga alguna
clase con un método estático marcado @Whitelisted y que haga lo que quiera, y luego llamar al
método desde su script.) Aún no se ha demostrado ningún ataque al usar la aprobación de scripts
completos—un URLClassLoader con delegación normal de padre-primero no permitiría un enmascaramiento trivial
de APIs de apariencia inocente por versiones comprometidas—pero es probable que algún uso
inteligente de META-INF/services/org.codehaus.groovy.transform.ASTTransformation o similar
pudiera causar que un script por lo demás seguro se comporte de manera inesperada y no autorizada.
JENKINS-22834 sugiere una
alternativa estándar segura.
Al escribir pruebas para complementos que usan Script Security Plugin, puede encontrar algunos errores en sus pruebas.
Si sus pruebas llaman, directa o indirectamente, al método ScriptApproval.get(), entonces sus
pruebas unitarias deben usar JenkinsRule para que Jenkins.getInstance() no devuelva null. Es
probable que las pruebas que estaban funcionando ahora empiecen a fallar si no está usando el sandbox.
Esto ocurre porque se están poniendo en cola para aprobación. En caso de que necesite ejecutar
scripts sin importar las aprobaciones, ScriptApproval.get().preapprove(script, GroovyLanguage.get()) asegurará que todos los scripts configurados estén aprobados. Alternativamente,
puede hacer que sus pruebas ejecuten scripts usando el sandbox. En este caso, puede necesitar
incluir en la lista blanca los métodos usados por sus pruebas—ya sea generalmente para usuarios reales, o usando un
@TestExtension para tener una lista blanca solo para pruebas.
Consulte el changelog