Skip to main content

Rego 101: introdução ao Rego

Escrito por
feature cloud security

2 de novembro de 2023

0 minutos de leitura

Esta série de posts apresenta uma introdução acessível ao Rego, a linguagem de políticas criada pelos desenvolvedores do mecanismo Open Policy Agent (OPA). Se você está começando e quer aprender a escrever políticas como código em Rego, está no lugar certo.

Nesta série de três partes, vamos abordar:

  • Parte 1 (esta parte!): conceitos básicos do Rego e como começar a usar o OPA

  • Parte 2: sintaxe intermediária do Rego

  • Parte 3: tipos de valores e regras

O que são Rego e OPA?

Rego é uma linguagem declarativa de consulta criada pelos desenvolvedores do framework Open Policy Agent (OPA). A Cloud Native Computing Foundation (CNCF) aceitou o OPA como projeto hospedado em nível de incubação em abril de 2019, e o OPA concluiu a incubação em 2021.

Rego é usado para escrever políticas como código, aplicando práticas de programação, como controle de versão e design modular, à avaliação de recursos de nuvem e de infraestrutura como código (IaC).

OPA é o mecanismo que avalia as políticas como código escritas em Rego. Ele “desacopla a tomada de decisões sobre políticas da aplicação dessas políticas”, ou seja, determina se um recurso está em conformidade com a política, sem que você precise codificar essas verificações diretamente na sua aplicação.

Ao separar as políticas do seu software e delegar as verificações ao OPA, você ganha agilidade e flexibilidade no ciclo de desenvolvimento. É possível atualizar as políticas a qualquer momento, sem recompilar a aplicação nem reimplantar o serviço. Assim, você pode desenvolver em escala, ter mais visibilidade sobre sua postura de conformidade e aplicar políticas de forma programática. Adotar políticas como código também ajuda a deslocar o desenvolvimento de nuvem e IaC para a esquerda, ou seja, a introduzir essas práticas mais cedo no ciclo de vida.

Aqui na Snyk, as regras prontas para uso e personalizadas do Snyk IaC+ são escritas em Rego e, nos bastidores, a Snyk usa o mecanismo OPA para avaliar políticas Rego e retornar decisões. A Snyk já realizou mais de um bilhão de avaliações de regras de segurança usando o OPA!

Como o Rego funciona?

Em uma linguagem declarativa de consulta como o Rego, você descreve os dados que quer obter, e o programa procura uma correspondência em uma fonte de dados — chamada de entrada. Isso é diferente das linguagens imperativas tradicionais, nas quais você descreve as etapas necessárias para chegar a um resultado. Talvez você já conheça algumas linguagens declarativas de consulta — SQL provavelmente é a mais usada.

Com o Rego, você descreve as condições para uma política ser aprovada ou reprovada, e o OPA procura em um documento de entrada JSON (ou YAML) os dados que correspondem a essas condições.

A ideia parece ótima, mas como é uma regra na prática? Digamos que precisamos aplicar uma política corporativa segundo a qual somente Alice, administradora de rede, pode criar e excluir redes virtuais no ambiente de produção. Veja um exemplo de regra que poderíamos escrever:

allow := true {
  input.user == "alice"
}

Voltaremos a essa regra daqui a pouco para explicar o que ela faz e como funciona. Por enquanto, admire sua elegante simplicidade!

Entrada

O OPA pode processar qualquer documento JSON ou YAML como entrada. Você reparou que usamos input.user na regra de exemplo acima? input é tratado como um documento JSON especial, acessível globalmente. Isso significa que você pode fazer referência a ele em qualquer parte do arquivo de política Rego.

Veja um exemplo de documento de entrada para a regra da seção anterior:

{
  "user": "alice"
}

Vamos considerar que esse documento representa o usuário conectado no momento. No mundo real, o documento de entrada poderia ser um manifesto do Kubernetes ou o resultado de um plano do Terraform. Vamos mostrar um exemplo em um próximo post do blog.

Regras

Agora que você viu como é uma entrada, vamos explorar o conceito de regras. Na linguagem Rego, uma regra é uma atribuição condicional. Cada regra tem duas partes:

a head {
  and a body
}
  • O cabeçalho contém uma variável e um valor que pode ser atribuído a ela.

  • O corpo contém uma ou mais consultas que indicam ao OPA quais condições precisam ser atendidas para que o valor seja atribuído à variável.

Você pode ler uma regra assim:

THIS VARIABLE    :=	HAS THIS VALUE {
    IF THESE CONDITIONS ARE MET
}

Em resumo, uma regra consulta a entrada em busca de uma correspondência para uma condição e, se encontrar, atribui um valor a uma variável.

Veja novamente a regra de exemplo que usamos antes:

allow := true {
  input.user == "alice"
}

No exemplo acima, o cabeçalho (variável e valor) é allow := true, e o corpo (consulta) é input.user == "alice". Juntando o cabeçalho e o corpo, temos uma regra completa, que pode ser lida assim:

A variável allow recebe o valor true SE user for igual a "alice".

No Rego, := é o operador de atribuição, também conhecido como operador walrus. Ele simplesmente atribui um valor a uma variável, assim como o sinal de igual em outras linguagens.

Consultas

Vamos aprofundar o conceito de consultas. Como mencionamos, uma consulta representa uma condição a ser verificada — é, essencialmente, a primeira parte de uma instrução IF.

Por exemplo, esta é a consulta da regra apresentada na seção anterior:

input.user == "alice"

Esta linha instrui o OPA a consultar o documento input para descobrir SE o user é igual a "alice".

Há uma nuance aqui: o Rego é declarativo, então, tecnicamente, uma consulta apenas afirma: “É assim que as coisas são”, e o OPA encontra todos os valores na entrada que tornam essa afirmação verdadeira. Neste exemplo, a consulta diz: “a propriedade user está definida como alice”. Cabe ao OPA examinar a propriedade user e encontrar, se houver, todos os usuários na entrada que tornam essa afirmação verdadeira (neste caso, ele procura o usuário alice).

Referência à entrada em uma consulta

Ao criar uma consulta Rego, você usa a notação de ponto para chegar à propriedade desejada. Isso significa que cada nível aninhado do documento de entrada é separado por um ponto. Primeiro, comece com input; em seguida, adicione um ponto e o nome da propriedade no nível superior (neste caso, input.user).

Para fazer referência a uma propriedade aninhada, especifique todos os níveis pelos quais precisa passar até chegar a ela. Comece com input, um ponto e a propriedade de nível superior (input.your-property-here); depois, continue adicionando pontos e nomes de propriedades até chegar à propriedade aninhada que quer consultar. Se a propriedade desejada for um array, aguarde — vamos falar disso daqui a pouco.

Agora, imagine que o documento de entrada seja assim:

{
  "admins": {
    "user": "alice"
  }
}

Como user está aninhada em admins, que por sua vez está aninhada em input, a referência ficaria assim:

input.admins.user

Por outro lado, se a propriedade de entrada à qual você quer fazer referência for um array (lista), use o operador curinga — um sublinhado — para especificar a propriedade. Por exemplo, digamos que a entrada inclua um array user:

{
  "users": [ "alice", "bob", "carlotta" ]
}

Nesse caso, para descobrir se alice está no array users, você usaria esta sintaxe:

allow := true {
  input.users[_] == "alice"
}

Acima, o operador curinga instrui o OPA a iterar pelo array e verificar se algum elemento é igual a alice. Vamos explorar a iteração em um próximo post do blog.

Da mesma forma, digamos que você queira verificar se o valor da propriedade admin é true em algum dos elementos do array users abaixo (mesmo que apenas um elemento seja mostrado):

{
  "users": [
    {
      "name": "alice",
      "admin": true
    }
  ]
}

Você usaria uma sintaxe como esta:

allow := true {
  input.users[_].admin == true
}

É claro que condições IF não são muito úteis sem uma ação condicional correspondente. Por isso, é importante entender como a atribuição funciona no Rego — a segunda parte da instrução IF.

Avaliação de regras

A ação condicional em uma regra é atribuir uma variável. No nosso exemplo, consultamos a entrada para descobrir se a variável allow deve receber o valor true. Você pode ler assim:

allow := true {
  IF THIS CONDITION IS MET
}

O que são variáveis?

Uma variável é uma referência a um valor específico. Aqui, a variável x recebe o valor 1:

x := 1

Depois, você pode usar a variável no lugar do valor: para o Rego, dá no mesmo:

x := 1
y := 2
z := x + y

Voltando ao nosso exemplo, a variável é allow, e o valor atribuído a ela é true:

allow := true

Combinando com a condição (consulta) que vimos antes, temos a regra completa:

allow := true {
  input.user == "alice"
}

Recapitulando, você leria esta regra assim:

A regra allow recebe o valor true SE input.user for igual a "alice".

Na prática, esse tipo de regra é muito comum, por isso o Rego permite abreviá-la assim:

allow {
  input.user == "alice"
}

Avaliando a regra com o OPA

Temos uma regra e um documento de entrada. O próximo passo é usar o OPA para avaliar a entrada com base na regra. Vamos descobrir se a entrada está em conformidade com nossa política e se o usuário conectado tem permissão para criar e excluir redes virtuais no ambiente de produção.

Vamos nos concentrar em duas formas de interagir com o OPA:

  • Usar o Rego Playground

  • Usar a ferramenta de linha de comando do OPA

Usar o Rego Playground

A maneira mais fácil de começar a escrever regras é usar o Rego Playground do OPA. Essa ferramenta interativa permite escrever, testar e compartilhar regras e entradas.

Veja o básico:

  • Para editar uma regra, use o campo de texto da regra no lado esquerdo da página.

  • Para editar a entrada, use o campo Input, no canto superior direito da página. (Lembre-se de que ela precisa ser um JSON válido.)

  • Para avaliar uma regra, selecione o botão Evaluate acima da entrada.

  • Para ver os resultados da avaliação, confira o campo Output no canto inferior direito.

  • Para compartilhar uma regra, selecione o botão Publish no canto superior direito. O OPA gera uma URL que você pode compartilhar com qualquer pessoa para que ela teste ou modifique sua regra e entrada.

Experimente à vontade e não tenha medo de bagunçar! Você não vai quebrar nada — o compilador avisará se o Rego não for válido. Recarregue a página se quiser restaurar o estado original do playground (ou o estado publicado, se estiver vendo um playground publicado).

Compartilhamos um playground com a regra allow e o documento de entrada de exemplo, disponível neste URL: https://play.openpolicyagent.org/p/SH5ApmfodX 

Ou simplesmente abra um playground novo e cole a regra na caixa de texto à esquerda:

allow := true {
  input.user == "alice"
}

Depois, cole a entrada na caixa de texto no canto superior direito:

{
  "user": "alice"
}

Lembre-se: nossa política corporativa determina que somente Alice, administradora de rede, pode criar e excluir redes virtuais no ambiente de produção. O documento de entrada representa o usuário conectado no momento. Nossa regra verifica se o usuário no documento de entrada é igual a alice e, se for, allow recebe o valor true.

Clique no botão Evaluate. Você verá este resultado:

{
  "allow": true
}

Isso significa que allow realmente recebe o valor true. O OPA determinou que o documento de entrada está em conformidade com nossa política corporativa. Como o usuário é Alice, ele tem permissão para criar e excluir redes virtuais no ambiente de produção.

Usar a ferramenta de linha de comando do OPA

Outra forma de avaliar regras com o OPA é usar a ferramenta de linha de comando opa. Você encontra as instruções para instalar o opa na documentação do OPA. Depois de instalá-lo, você precisará de dois arquivos:

  • Um arquivo de política .rego com sua regra e uma declaração de pacote, como package rules.check_user, logo no início. Chamamos nosso arquivo de política de check_user.rego.

  • Um arquivo .json com a entrada; chamamos nosso arquivo de entrada de input.json.

Depois de ter esses dois itens, você pode usar o comando opa eval para avaliar sua política como código:

opa eval -i input.json -d check_user.rego "data.rules.check_user" --format pretty

Você pode dar aos arquivos o nome que quiser, é claro — basta garantir que o comando siga esta estrutura:

opa eval -i <input file> -d <rule file> "data.<package name>" --format pretty

Se allow for avaliado como true, como no nosso exemplo, você verá o mesmo resultado exibido no Rego Playground:

{
  "allow": true
}

Mais uma vez, o OPA indica que a entrada é consistente com — está em conformidade com — a política da nossa empresa. Isso significa que o usuário conectado no momento tem permissão para criar e excluir redes virtuais na conta de produção.

Testando uma entrada fora de conformidade

Como fica quando um documento de entrada não está em conformidade? No playground ou no arquivo .rego local, altere a entrada para o seguinte:

{
  "user": "bob"
}

Agora, ao avaliar a regra (clicando em Evaluate no Rego Playground ou executando o comando opa eval mencionado anteriormente), você verá o seguinte resultado:

{}

O que isso significa? Neste exemplo, o OPA retorna um resultado "undefined" (ou seja, um conjunto vazio) porque não encontra na entrada um valor que corresponda à condição input.user == "alice". O conjunto está vazio porque não há resultados. Portanto, allow não é avaliado como true, e o documento de entrada não está em conformidade com a política da empresa. Foi mal, Bob!

E agora?

Volte ao nosso blog para acompanhar o restante da série Rego para iniciantes. Nela, vamos explorar a sintaxe de regras Rego em nível intermediário, incluindo estruturas AND e OR, mensagens personalizadas, palavras-chave especiais e muito mais.

Enquanto isso, confira estes recursos úteis:

Se você tem interesse em usar Rego para escrever regras personalizadas para o Snyk IaC, confira nossa documentação. Além dos conjuntos de regras integrados da Snyk, com mapeamento de segurança e conformidade, as regras personalizadas do IaC+ permitem definir controles de segurança sob medida em todo o seu SDLC.

O IaC+ oferece uma visão unificada e controles para problemas de configuração, do código à nuvem, com uma interface de problemas, um conjunto de regras e um mecanismo de políticas que abrangem IDE, SCM, CLI, CI/CD, Terraform Cloud e ambientes de nuvem implantados, como AWS, Azure e Google Cloud. 

Segurança de IaC pensada para quem desenvolve

A Snyk protege sua infraestrutura como código do ciclo de vida do desenvolvimento de software até a execução na nuvem, com um mecanismo unificado de políticas como código para que todas as equipes possam desenvolver, implantar e operar com segurança.