
Plugin do Jenkins que fornece fluxos de trabalho de aprovação de scripts e sandboxing Groovy para garantir a execução segura de scripts, com verificações de permissão cientes de ACL e gerenciamento de lista de permissões para administradores.
(adaptado de informações sobre o Template plugin in CloudBees Plugins guide)
Vários plugins do Jenkins exigem que os usuários definam scripts personalizados, mais comumente na linguagem Groovy, para personalizar o comportamento do Jenkins. Se todos os que escrevem esses scripts forem administradores do Jenkins — especificamente se tiverem a permissão Overall/RunScripts, usada por exemplo pelo link Script Console — então eles poderão escrever quaisquer scripts que desejarem. Esses scripts podem referenciar diretamente objetos internos do Jenkins usando a mesma API oferecida aos plugins. Esses usuários devem ser completamente confiáveis, pois podem fazer qualquer coisa no Jenkins (até alterar suas configurações de segurança ou executar comandos de shell no servidor).
No entanto, se alguns autores de scripts forem "usuários comuns" com apenas permissões mais limitadas, como Job/Configure, não é apropriado permitir que eles executem scripts arbitrários. Para apoiar essa divisão de papéis, o plugin da biblioteca Script Security pode ser integrado a vários plugins de funcionalidades. Ele suporta dois sistemas relacionados: aprovação de scripts e sandbox do Groovy.
O primeiro e mais simples sistema de segurança é permitir que qualquer tipo de script seja executado, mas somente com a aprovação de um administrador. Existe uma lista mantida globalmente de scripts aprovados que são considerados como não realizando ações maliciosas.
Quando um administrador salva algum tipo de configuração (por exemplo, um job), os scripts que foram editados pelo administrador são aprovados automaticamente e estão prontos para serem executados sem nenhuma intervenção adicional. Para scripts que foram enviados por usuários com menos privilégios, haverá avisos apropriados indicando que a aprovação é necessária. Os administradores podem aprovar esses scripts usando a página de configuração Script Approval ou editando o script e salvando-o. Em versões anteriores do Script Security Plugin, os administradores podiam aprovar automaticamente scripts enviados por usuários sem privilégios, salvando-os sem fazer quaisquer alterações, mas essa funcionalidade foi desativada para prevenir ataques baseados em engenharia social. ("Salvar" geralmente significa pela interface web, mas também pode significar enviar uma nova configuração XML via REST ou CLI.)
Quando um não administrador salva uma configuração de template, é feita uma verificação para saber se algum dos scripts contidos foi editado em relação a um texto aprovado. (Mais precisamente, se o conteúdo solicitado já foi aprovado anteriormente.) Se não foi aprovado, uma solicitação de aprovação para este script é adicionada a uma fila. (Um aviso também é exibido na interface da tela de configuração quando o texto atual de um script não está atualmente aprovado.)
Um administrador pode agora ir para Manage Jenkins » In-process Script Approval onde uma lista de scripts pendentes de aprovação será exibida. Supondo que nada com aparência perigosa esteja sendo solicitado, basta clicar em Approve para permitir que o script seja executado daí em diante.
Se você tentar executar um script não aprovado, ele simplesmente falhará, tipicamente com uma mensagem explicando que está pendente de aprovação. Você pode tentar novamente depois que o script for aprovado. Os detalhes desse comportamento podem variar de acordo com o plugin de funcionalidade que integra essa biblioteca.
Esperar que um administrador aprove cada alteração em um script, por mais aparentemente trivial que seja, pode ser inaceitável em uma equipe distribuída por fusos horários ou durante prazos apertados. Como opção alternativa, o sistema Script Security permite que scripts Groovy sejam executados sem aprovação, desde que se limitem a operações consideradas inerentemente seguras. Esse ambiente de execução limitado é chamado de sandbox. (Atualmente não há implementações de sandbox disponíveis para outras linguagens, portanto todos esses scripts devem ser aprovados se configurados por não administradores.)
Para alternar para este modo, basta marcar a caixa Use Groovy Sandbox abaixo do campo de entrada do script Groovy. Scripts em sandbox podem ser executados imediatamente por qualquer pessoa. (Até mesmo administradores, embora o script esteja sujeito às mesmas restrições, independentemente de quem o escreveu.) Quando o script é executado, cada chamada de método, construção de objeto e acesso a campo é verificada contra uma lista de permissões de operações aprovadas. Se uma operação não aprovada for tentada, o script é interrompido e o recurso correspondente do Jenkins não pode ser usado ainda.
O plugin Script Security vem com uma pequena lista de permissões padrão, e os plugins integrados podem adicionar operações a essa lista (tipicamente métodos específicos desse plugin).
Mas você não está limitado à lista de permissões padrão: toda vez que um script falha antes de executar uma operação que ainda não está na lista de permissões, essa operação é adicionada automaticamente a outra fila de aprovação. Um administrador pode ir para a mesma página descrita acima para aprovação de scripts inteiros e ver uma lista de aprovações de operações pendentes. Se Approve for clicado ao lado da assinatura de uma operação, ela é imediatamente adicionada à lista de permissões e fica disponível para scripts em sandbox.
A maioria das assinaturas terá a forma method class.Name methodName arg1Type arg2Type…,
indicando uma chamada de método Java com uma classe "receptora" específica (this), nome do método e
lista de tipos de argumentos (ou parâmetros). (A assinatura mais geral de uma chamada de método tentada
será oferecida para aprovação, mesmo quando o objeto real sobre o qual ela seria chamada fosse
de um tipo mais específico que sobrescreve esse método.) Você também pode ver staticMethod para
métodos estáticos (de classe), new para construtores e field para acessos a campos (get ou
set).
Administradores em ambientes sensíveis à segurança devem considerar cuidadosamente quais operações
incluir na lista de permissões. Operações que alteram o estado de objetos persistidos (como
jobs do Jenkins) geralmente devem ser negadas. A maioria dos métodos getSomething é inofensiva.
Esteja ciente, no entanto, de que até mesmo alguns métodos "getter" são projetados para verificar permissões
específicas (usando uma ACL: lista de controle de acesso), enquanto scripts são frequentemente executados por um
pseudo-usuário do sistema ao qual todas as permissões são concedidas. Assim, por exemplo, method hudson.model.AbstractItem getParent (que obtém a pasta ou a raiz do Jenkins que contém
um job) é inofensivo por si só, mas a possível chamada de acompanhamento method hudson.model.ItemGroup getItems (que lista jobs por nome dentro de uma pasta) verifica
Job/Read. Essa segunda chamada seria perigosa de incluir na lista de permissões incondicionalmente, já que
significaria que um usuário com permissão Job/Create em uma pasta poderia ler pelo menos algumas
informações de qualquer job nessa pasta, até mesmo aqueles que deveriam estar ocultos
de acordo com uma estratégia de autorização baseada em projetos; bastaria criar um job na
pasta que incluísse um script Groovy como este (os detalhes variariam de acordo com o
plugin integrador):
println("I sniffed ${thisjob.getParent().getItems()}!");
Quando executado, a saída do script exibiria pelo menos os nomes de projetos supostamente
secretos. Um administrador pode, em vez disso, clicar em Approve assumindo a verificação de permissão para
getItems; isso permitirá a chamada quando executada como um usuário real (se o plugin integrador
algum dia o fizer), enquanto a proíbe quando executada como o usuário do sistema (o que é mais
típico). Nesse caso, getItems é na verdade implementado para retornar apenas os jobs aos quais
o usuário atual tem acesso, então se for executado no primeiro caso (como um usuário específico), a
descrição mostrará apenas aqueles jobs que ele já poderia ver de qualquer forma. Esse botão mais avançado é
exibido apenas para chamadas de método (e construtores) e deve ser usado somente quando você souber
que o Jenkins está fazendo uma verificação de permissão.
Exemplo completo de integração
Para uma integração Groovy típica, na qual você oferece ao usuário a opção de usar aprovação de scripts
ou o sandbox, altere o campo de script com valor String do seu describable para um campo
SecureGroovyScript. No seu construtor, antes de armazenar o valor, chame
configuringWithKeyItem (se só puder haver um script desse tipo por item de nível superior) ou
configuringWithNonKeyItem (se puder haver vários). O formulário de configuração deve usar
<f:property field="…"/> para capturar a configuração do script e do sandbox. Quando quiser
executar o script, basta chamar evaluate.
(Para compatibilidade com dados antigos, escolha um nome de campo diferente e deprecie o original.
Então você pode definir um método readResolve que define o novo campo como um SecureGroovyScript
com o sandbox desativado, chama configuring(ApprovalContext.create()) nele para notificar o
sistema de que um script não aprovado foi carregado e remove o valor do campo antigo.)
Para ser usado se você precisar de mais controle do que o SecureGroovyScript oferece:
Introduza um campo booleano sandbox na sua configuração.
Quando não estiver definido, você precisa chamar ScriptApproval.configuring no @DataBoundConstructor.
Use ApprovalContext.withCurrentUser e também withItemAsKey quando aplicável (quando
houver apenas um script por job); caso contrário, pelo menos withItem quando aplicável, e/ou
withKey quando você puder identificar exclusivamente este uso a partir do contexto
(StaplerRequest.findAncestorObject é útil aqui). Isso permite que o sistema saiba que um
script (possivelmente) novo foi configurado por uma pessoa específica. Você também precisará de um
readResolve que chame configuring para notificar o sistema quando um configurable com script
for carregado do disco (e, portanto, o configurador é desconhecido). Chame
ScriptApproval.using quando o script for executado e capture UnapprovedUsageException se
necessário. O descriptor deve usar validação de formulário no campo de script e chamar
ScriptApproval.checking (geralmente seu descriptor já deve estar fazendo pelo menos uma
verificação de sintaxe nesse campo).
Quando o campo sandbox estiver definido, você precisa apenas configurar o shell Groovy com
GroovySandbox.createSecureCompilerConfiguration e então chamar GroovySandbox.run; esteja
preparado para capturar RejectedAccessException e chamar ScriptApproval.accessRejected.
Para pré-aprovar algumas chamadas de método específicas, basta anotá-las com @Whitelisted se estiverem
no seu plugin; caso contrário, você pode registrar (com @Extension) um ProxyWhitelist que delegue a
StaticWhitelist.from e carregue um arquivo de texto listando os métodos permitidos.
Ao construir um GroovyShell para avaliar um script, ou chamar
ecureGroovyScript.evaluate, você deve passar um ClassLoader que represente o classpath
efetivo para o script. Você pode usar o carregador do núcleo do Jenkins, ou do seu plugin, ou
Jenkins.getInstance().getPluginManager().uberClassLoader.
Seja qual for sua escolha, não permita que um usuário sem privilégios adicione entradas de classpath arbitrárias
criando um URLClassLoader! Isso tornaria trivial contornar toda a segurança ao usar o
sandbox. (Um usuário precisaria apenas fazer este ou outro job arquivar um JAR contendo alguma
classe com um método estático marcado como @Whitelisted e fazendo o que quiser, e então chamar o
método a partir do seu script.) Nenhum ataque foi ainda demonstrado ao usar a aprovação de script
completo — um URLClassLoader com delegação normal pai-primeiro não permitiria mascaramento trivial
de APIs de aparência inocente por versões comprometidas — mas é provável que algum uso
inteligente de META-INF/services/org.codehaus.groovy.transform.ASTTransformation ou similar
pudesse fazer com que um script de outra forma seguro se comportasse de maneira inesperada e não autorizada.
JENKINS-22834 sugere uma
alternativa padrão segura.
Ao escrever testes para plugins que usam o Script Security Plugin, você pode encontrar alguns erros nos seus testes.
Se seus testes chamarem, direta ou indiretamente, o método ScriptApproval.get(), então seus
testes de unidade devem usar JenkinsRule para que Jenkins.getInstance() não retorne null. É
provável que testes que estavam funcionando agora comecem a falhar se você não estiver usando o sandbox.
Isso ocorre porque eles estão sendo colocados na fila para aprovação. Caso você precise executar
scripts independentemente de aprovações, ScriptApproval.get().preapprove(script, GroovyLanguage.get()) garantirá que todos os scripts configurados sejam aprovados. Alternativamente,
você pode fazer seus testes executarem scripts usando o sandbox. Nesse caso, você pode precisar
incluir métodos usados pelos seus testes na lista de permissões — seja geralmente para usuários reais, ou usando um
@TestExtension para ter uma lista de permissões apenas para testes.
Consulte o changelog