Skip to main content

Implementando policy as code (PaC) com OPA e Rego

Escrito por
blog feature snyk iac magenta

19 de janeiro de 2022

0 minutos de leitura

O Cambridge Dictionary define política como: "um conjunto de ideias ou um plano sobre o que fazer em situações específicas, aprovado oficialmente por um grupo de pessoas, uma organização empresarial, um governo ou um partido político." No contexto do desenvolvimento de software, sua organização pode ter regras sobre como uma política é criada, configurada, implantada e usada. Veja alguns exemplos de políticas de software:

Políticas de aplicações

  • O acesso a determinados contextos de serviços web é restrito a usuários internos.

  • Recursos regionais só serão exibidos em solicitações de usuários com endereços IP da mesma localização geográfica.

  • Transações acima de determinados valores só são permitidas para usuários em cargos gerenciais.

Políticas de build e implantação

  • Todas as bibliotecas de aplicações de código aberto devem usar uma das licenças aprovadas.

  • Todas as imagens de contêiner devem ser obtidas de registros específicos.

  • Nenhum Pod do Kubernetes deve ser executado em modo privilegiado.

  • O token padrão de uma ServiceAccount não deve ser montado automaticamente em um contêiner.

Policy as code (PaC)é o processo de definir políticas em código usando uma linguagem declarativa de alto nível, o que permite automatizar a aplicação e gerenciar as políticas de forma centralizada. Isso reduz a necessidade de duplicar lógica entre os serviços da sua aplicação, cada vez mais distribuídos e que podem usar diversos tipos de linguagens e plataformas.

Neste artigo, vamos conhecer o Open Policy Agent (OPA) e sua linguagem de regras Rego, que podem ajudar a simplificar seus esforços de PaC tanto nas suas próprias aplicações quanto na aplicação de políticas a cargas de trabalho em contêineres no Kubernetes.

Definição uniforme de políticas para serviços distintos

A aplicação de políticas em aplicações costuma ser implementada diretamente no código. Nos sistemas distribuídos atuais, a mesma lógica pode ser duplicada. Isso pode se agravar quando esses serviços distintos são desenvolvidos por equipes diferentes e/ou com tecnologias diferentes.

É essencial ter definições de políticas padronizadas e ferramentas de aplicação que possam ser usadas por todas as equipes da sua organização, independentemente das linguagens em que foram escritas ou dos frameworks que utilizam. Definições uniformes de políticas permitem uma implementação simples e flexível, além de facilitar a governança de conformidade para todos os envolvidos.

Aplicação proativa ou reativa de políticas de build e implantação

As regras de políticas de build e implantação costumam ser aplicadas por meio de uma combinação de revisões manuais de código, análise estática (de preferência automatizada no pipeline de CI/CD) ou simplesmente da ameaça de medidas disciplinares caso alguém viole a política. Essas práticas são reativas: elas nos alertam sobre violações de políticas durante algum tipo de auditoria. Isso pode funcionar, mas muitas vezes cria uma relação adversarial entre quem define as políticas e os desenvolvedores, que podem nem saber que essas regras existem.

Implementar políticas e aplicá-las no fluxo de trabalho dos desenvolvedores, seja como parte dos processos de build ou como uma etapa de aprovação para a implantação, permite interromper violações assim que surgem, em vez de depender de auditorias tardias ou revisões manuais.

Aplicação uniforme de políticas em ambientes distintos

Depois de unificar suas regras de políticas em uma única plataforma compartilhada, você pode aplicá-las uniformemente em todos os ambientes. Por exemplo, se o cluster Kubernetes de sandbox de desenvolvimento permite implantar qualquer coisa, mas o cluster de produção tem várias restrições, os desenvolvedores talvez só percebam que implementaram algo que viola as regras mais adiante no SDLC. Porém, se todos os clusters compartilharem o mesmo conjunto de regras, será muito menos provável que essas violações cheguem ao controle de versão, pois teriam sido detectadas durante os testes unitários iniciais.

Open Policy Agent (OPA)

O Open Policy Agent — geralmente chamado pelas iniciais OPA e pronunciado “ô-pá” — é um projeto de código aberto graduado pela CNCF que implementa um mecanismo de políticas de uso geral. Ele é amplamente utilizado tanto para políticas de aplicações — especialmente em arquiteturas distribuídas de microsserviços — quanto como controlador de admissão da API do Kubernetes, muitas vezes integrado por meio do projeto OPA Gatekeeper.

As aplicações podem delegar a validação de políticas ao OPA, eliminando assim a necessidade de acoplar essa lógica diretamente ao código. Na prática, um processo do OPA costuma ser iniciado junto com o serviço da aplicação e disponibiliza uma API REST para a comunicação com ele. Em uma implantação Kubernetes, isso geralmente é feito por meio de um contêiner sidecar no mesmo Pod que o contêiner do serviço, oferecendo uma conexão “localhost” de latência extremamente baixa. Mas o OPA também pode ser executado como um serviço externo. Há outras formas de incluir o OPA na aplicação; no momento em que este artigo foi escrito, elas incluem uma API Go, WebAssembly e um SDK para incorporar o OPA a uma aplicação Go.

As políticas no OPA são escritas como um conjunto de regras usando uma linguagem chamada Rego.

Rego: a linguagem de regras de políticas do OPA

Rego é uma linguagem declarativa de alto nível, criada para implementar regras em documentos estruturados, como JSON. Um tutorial detalhado sobre como criar regras Rego está fora do escopo deste artigo, mas recomendo muito que você confira a documentação oficial e o excelente curso de aprendizagem da Styra, criadora do OPA e do Rego.

Noções básicas de Rego

O Rego trabalha com documentos de dados estruturados de várias maneiras, mas o padrão mais comum é o processo que o chama passar um documento, geralmente no formato JSON ou YAML, como entrada. Em seguida, as regras são aplicadas comparando elementos desse documento de entrada com expressões.

Por exemplo, imagine que temos uma política de validação de Pod do Kubernetes que retorna true desde que o valor image da entrada não termine com ":latest". Uma implementação simples dessa política em Rego poderia ser assim:

package kubernetes.validating.images

import future.keywords.in

default allow = false

allow {
    input.kind == "Pod"
some container in input.spec.containers
not endswith(container.image, ":latest")
}

Se o JSON a seguir for executado com essa política, será retornada a resposta { “allow”: false }, pois ":latest" não está no final da imagem.

{
    "kind": "Pod",
    "apiVersion": "v1",
    "metadata": {
        "name": "mypod",
        "labels": { "run": "mypod" }
    },
    "spec": {
        "containers": [
            {
                "name": "mypod",
                "image": "registry.mycorp.com/team-alpha/myapp:v1",
            }
        ]
    }
}

Entendendo o exemplo de Rego

Vamos analisar o código Rego do exemplo acima:

package kubernetes.validating.images

Aqui declaramos a qual package a regra pertence. Isso é semelhante à organização de pacotes em outras linguagens e serve para agrupar regras relacionadas no mesmo namespace.

import future.keywords.in

A palavra-chave import simplesmente instrui o parser do Rego a trazer a regra in do pacote future.keyworks para o escopo desta política, para que ela possa ser referenciada simplesmente como in.

default allow = false

"Isso define o valor default de allow como "false" caso nenhuma outra expressão de atribuição na política o defina de outra forma.

allow {
    input.kind == "Pod"
    some container in input.spec.containers
    not endswith(container.image, ":latest")
}

Aqui vemos a maior parte da lógica da regra. Quando executada, allow receberá o resultado da avaliação do conteúdo dentro das chaves { }. Todas as expressões dentro dessas chaves são executadas com um "AND" implícito, ou seja, todas precisam resultar em "true" para que allow seja definido como "true".

Você pode interpretar as três linhas assim: “Desde que o valor de input.kind seja "Pod" e haja pelo menos alguns contêineres em input.spec, retorne "true", a menos que o campo image de algum contêiner termine com ":latest".”

Rego Playground

O projeto OPA oferece um ambiente online de edição em Rego chamado The Rego Playground, onde você pode experimentar regras e executá-las com documentos de entrada à vontade. É uma ótima maneira de testar suas ideias de regras de forma interativa e compartilhar os experimentos com qualquer pessoa.

Interface do Rego Playground exibindo uma política de validação de imagem do Kubernetes, uma entrada JSON de Pod e uma saída indicando allow: true

O código Rego e o documento de entrada acima estão disponíveis neste exemplo do Rego Playground. Abra-o em outra aba, execute-o e experimente à vontade.

Criando políticas personalizadas com Snyk IaC e Rego

Snyk Infrastructure as Code (Snyk IaC) usa o OPA para fazer a análise de políticas, e você pode adicionar suas próprias políticas às análises com alguns comandos simples. Para começar, confira a documentação do Snyk IaC e nosso artigo Desenvolvimento de regras personalizadas de IaC com Snyk,.

Com uma conta gratuita da Snyk, você pode começar a identificar e corrigir configurações incorretas de IaC, incorporando perfeitamente aos seus fluxos de trabalho verificações de segurança, proteções de políticas e orientações de correção pensadas para desenvolvedores.

Proteja a infraestrutura desde a origem

A Snyk automatiza a segurança e a conformidade de IaC nos fluxos de trabalho e detecta recursos com configurações divergentes ou ausentes.

Recursos para saber mais

Agora que você já tem uma visão geral do OPA, do Rego e de algumas das formas como eles são usados, explore as muitas maneiras de aproveitá-los no seu software:

Obrigado por ler este artigo. Divirta-se automatizando a aplicação de políticas!