Pular para o conteúdo principal

Open Policy Agent (OPA)

logo

É uma extensão que podemos instalar dentro do Kubernetes que nos permite criar políticas personalizadas.

Como vimos anteriormente uma request primeiro precisa se autenticar para saber quem é, depois passar pelo processo de autorização do RBAC para saber se o quem fez a request tem ou não permissão de fazer o que foi solicitado e depois vamos para o admission control que é onde o OPA irá funcionar.

O Open Policy Agent (OPA) é um mecanismo de controle de políticas de código aberto que permite definir, gerenciar e aplicar políticas de forma centralizada em diversos sistemas, incluindo Kubernetes. No contexto do Kubernetes, o OPA é geralmente usado como uma solução de Policy-as-Code para definir regras que controlam o comportamento de recursos dentro do cluster, como Pods, Deployments, ConfigMaps, etc.

Como o OPA funciona no Kubernetes

  1. O OPA pode ser implementado como um Admission Controller dinâmico no Kubernetes. Isso significa que ele intercepta todas as requisições de API que chegam ao Kubernetes, avaliando-as de acordo com políticas definidas antes que os recursos sejam realmente criados ou modificados no cluster.

  2. O OPA recebe a solicitação da API, avalia se ela cumpre com as políticas (por exemplo, restrições de segurança, padrões de nomenclatura, limites de recursos), e então permite ou bloqueia a operação, retornando uma resposta ao Kubernetes.

  3. As políticas no OPA são escritas em uma linguagem declarativa chamada Rego. Com Rego, você pode definir expressões lógicas para determinar se uma determinada operação é permitida ou negada.

  4. Funciona com JSON/YAML. O OPA não sabe o que é pods, deployments ou qualquer outro recurso do kubernetes, somente trabalha com base no JSON ou YAML.

Exemplos de políticas incluem:

  • Bloquear a criação de Pods que não têm uma solicitação de CPU/memória definida.

  • Restringir o uso de imagens de contêiner que não são de um registro privado aprovado.

  • Garantir que todas as configurações de rede estejam seguindo regras específicas de segurança.

OPA Gatekeeper vs Kyverno

O OPA Gatekeeper usa o OPA mas traz a facilidade de uso com o Kubernetes. Na verdade o OPA Gatekeeper instala custom resource definitions no cluster para que possamos criar as políticas do OPA.

OPA Gatekeeper e Kyverno são duas ferramentas populares de Policy-as-Code para Kubernetes, usadas para definir, aplicar e auditar políticas dentro de clusters Kubernetes. Embora ambos tenham o objetivo de gerenciar políticas de segurança e conformidade, eles diferem em sua abordagem, linguagem de definição de políticas, facilidade de uso e casos de uso específicos.

  • Linguagem das Políticas:

    • OPA Gatekeeper: Usa a linguagem Rego para definir políticas. Rego é uma linguagem declarativa, expressiva e poderosa, mas tem uma curva de aprendizado, especialmente para quem não tem familiaridade com programação lógica.
    • Kyverno: Usa YAML, que é o mesmo formato utilizado para definir recursos no Kubernetes. Como a maioria dos operadores de Kubernetes já está familiarizada com YAML, a criação de políticas em Kyverno é geralmente mais intuitiva e rápida.
  • Abordagem de Políticas:

    • OPA Gatekeeper: É um mecanismo de policy genérico, não específico para Kubernetes, que pode ser estendido para outros sistemas e contextos além do Kubernetes. Suas políticas são definidas com Rego e ele pode ser usado em qualquer lugar que suporte o OPA.

    • Kyverno: Foi projetado especificamente para Kubernetes. Ele foca diretamente nas necessidades dos operadores de Kubernetes, fornecendo uma sintaxe YAML amigável e recursos específicos de Kubernetes, como cópia de configurações, mutações e geração de recursos.

  • Recursos e Funcionalidades:

    • OPA Gatekeeper:

      • Verificação de Políticas (Validation): Verifica solicitações contra políticas antes de aplicá-las.
      • Aplicação de Políticas (Enforcement): Rejeita ou permite requisições de acordo com as regras definidas.
      • Auditoria: Suporta auditoria de políticas, permitindo identificar quais recursos existentes violam as políticas.
    • Kyverno:

      • Verificação de Políticas (Validation): Similar ao Gatekeeper, valida as requisições com base nas regras definidas.
      • Mutações (Mutation): Permite modificar recursos automaticamente (ex., adicionar rótulos, anotações, definir padrões).
      • Geração de Recursos (Generation): Pode gerar automaticamente recursos do Kubernetes, como ConfigMaps ou Secrets, em resposta a eventos.
      • Auditoria: Suporta auditoria e geração de relatórios de conformidade de políticas.
  • Facilidade de Uso e Integração:

    • OPA Gatekeeper: Requer o aprendizado da linguagem Rego e pode ser mais complexo de configurar e usar para novos usuários. É mais flexível e genérico, mas essa flexibilidade vem com maior complexidade.

    • Kyverno: Focado em Kubernetes, é mais fácil para equipes já acostumadas com YAML e Kubernetes. A integração com o Kubernetes é direta, e o desenvolvimento de políticas pode ser mais rápido e fácil devido à familiaridade com o formato YAML.

  • Casos de Uso:

    • OPA Gatekeeper: Ideal para organizações que desejam uma solução de políticas mais genérica, que possa ser aplicada a múltiplos sistemas além do Kubernetes. Utilizado quando há a necessidade de lógica complexa e personalizada, onde a expressividade da linguagem Rego é uma vantagem.

    • Kyverno: Ideal para equipes que querem focar diretamente em Kubernetes e precisam de uma solução simples e fácil de implementar. Excelente para cenários onde há necessidade de mutação ou geração automática de recursos, além de validação.

É possível utilizar os dois ao mesmo tempo aproveitando o que cada um tem de melhor, mas precisaria ser algo muito bem executado.

  • Performance: Cada ferramenta adiciona um pouco de sobrecarga ao processamento de solicitações no cluster. Se você usar ambos, é importante monitorar o impacto no desempenho e ajustar as políticas conforme necessário.

  • Manutenção: Ter duas ferramentas de políticas pode aumentar a complexidade de manutenção e operação, pois você precisará monitorar e gerenciar políticas em dois sistemas diferentes.

  • Conflito de Políticas: Há potencial para conflitos se as políticas definidas nas duas ferramentas se sobrepuserem ou contradizerem. Portanto, é importante coordenar as políticas entre ambas para evitar resultados indesejados.

Instalação do OPA Gatekeeper

Os pré requisitos para que o OPA Gatekeeper funcione são plugins de admission ValidatingAdmissionWebhook e MutatingAdmissionWebhook estarem carregados. Usando o kubeadm para criar o cluster eles são ativados por padrão. Veja na documentação. A grande maioria dos clusters oferecidos pelas nuvens também possuem esses plugins já ativados.

Plugins default:

  • CertificateApproval
  • CertificateSigning
  • CertificateSubjectRestriction
  • DefaultIngressClass
  • DefaultStorageClass
  • DefaultTolerationSeconds
  • LimitRanger
  • MutatingAdmissionWebhook
  • NamespaceLifecycle
  • PersistentVolumeClaimResize
  • PodSecurity
  • Priority
  • ResourceQuota
  • RuntimeClass
  • ServiceAccount
  • StorageObjectInUseProtection
  • TaintNodesByCondition
  • ValidatingAdmissionPolicy
  • ValidatingAdmissionWebhook
# Aqui só tem os plugins extras.
root@cks-master:~# k get pod -n kube-system kube-apiserver-cks-master -o yaml | grep admission
- --enable-admission-plugins=NodeRestriction

# Apesar de não estar mostrando acima, eles foram carregados.
root@cks-master:~# kubectl logs -n kube-system kube-apiserver-kind-control-plane| grep ValidatingAdmissionWebhook
I0918 19:16:46.978587 1 plugins.go:160] Loaded 13 validating admission controller(s) successfully in the following order: LimitRanger,ServiceAccount,PodSecurity,Priority,PersistentVolumeClaimResize,RuntimeClass,CertificateApproval,CertificateSigning,ClusterTrustBundleAttest,CertificateSubjectRestriction,ValidatingAdmissionPolicy,ValidatingAdmissionWebhook,ResourceQuota.

https://open-policy-agent.github.io/gatekeeper/website/docs/install

Podemos fazer a instalação usando o helm ou aplicando o conjunto de manifestos disponível.

# Versão de lançamento
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.17.0/deploy/gatekeeper.yaml


# Ou usando o helm
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace

NAME STATUS AGE
default Active 14d
gatekeeper-system Active 2m12s
ingress-nginx Active 11d
kube-node-lease Active 14d
kube-public Active 14d
kube-system Active 14d
kubernetes-dashboard Active 11d

root@cks-master:~# k get -n gatekeeper-system pod,svc,deploy
NAME READY STATUS RESTARTS AGE
pod/gatekeeper-audit-5cf8bcb8f7-pq9tp 1/1 Running 0 3m7s
pod/gatekeeper-controller-manager-5dbdb9b595-2kprv 1/1 Running 0 3m7s
pod/gatekeeper-controller-manager-5dbdb9b595-846m8 1/1 Running 0 3m7s
pod/gatekeeper-controller-manager-5dbdb9b595-vch4t 1/1 Running 0 3m7s

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/gatekeeper-webhook-service ClusterIP 10.103.64.162 <none> 443/TCP 3m7s

NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/gatekeeper-audit 1/1 1 1 3m7s
deployment.apps/gatekeeper-controller-manager 3/3 3 3 3m7s

# Custom resources do gatekeeper em nível de namespace
root@cks-master:~# k api-resources --namespaced | grep gatekeeper
configs config.gatekeeper.sh/v1alpha1 true Config
constraintpodstatuses status.gatekeeper.sh/v1beta1 true ConstraintPodStatus
constrainttemplatepodstatuses status.gatekeeper.sh/v1beta1 true ConstraintTemplatePodStatus
expansiontemplatepodstatuses status.gatekeeper.sh/v1beta1 true ExpansionTemplatePodStatus
mutatorpodstatuses status.gatekeeper.sh/v1beta1 true MutatorPodStatus

# Custom resources do gatekeeper em nível de cluster
root@cks-master:~# k api-resources --namespaced=false | grep gatekeeper
expansiontemplate expansion.gatekeeper.sh/v1beta1 false ExpansionTemplate
providers externaldata.gatekeeper.sh/v1beta1 false Provider
assign mutations.gatekeeper.sh/v1 false Assign
assignimage mutations.gatekeeper.sh/v1alpha1 false AssignImage
assignmetadata mutations.gatekeeper.sh/v1 false AssignMetadata
modifyset mutations.gatekeeper.sh/v1 false ModifySet
syncsets syncset.gatekeeper.sh/v1alpha1 false SyncSet
constrainttemplates templates.gatekeeper.sh/v1 false ConstraintTemplate

É necessário entender um pouco sobre Dynamic Admission Control. Admission webhook é como um admission controller. Se criamos um webhook customizado como o OPA Gatekeerp cria para nós toda vez que uma request existir ela passará por estes webhook.

Kubernetes oferece dois possíveis jeitos para fisgar isso para dentro do sistema.

  • Validating Admission Webhook que só serve como validação do request feito e somente pode ser aprovado ou negado.
  • Mutating admission Webhook que dá a possibilidade de alterar a request como por exemplo adicionar labels, anotations, número de réplicas mínimas para um deployment, etc.

Vamos criar uma regra inicial DENY ALL para tudo e ver o que acontece.

Precisamos criar um template que define constraints (restrições). As constraints dependem de um template.

Primeiro criamos o template, neste template criaremos o custom resource definition que nos dará kind que será usado para criar uma constraint, bem como os parâmetros que serão definidos na constraint. Se criarmos tentarmos criar diretamente a constraint teremos um erro, pois o crd para esse kind não existe. Vamos testar.

Definindo uma constraint.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAlwaysDeny # De onde vem isso? Por enquanto estamos inventando
metadata:
name: pod-always-deny
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
message: "ACCESS DENIED!"

root@cks-master:~# vim constraint.yaml

root@cks-master:~# k apply -f constraint.yaml
error: resource mapping not found for name: "pod-always-deny" namespace: "" from "constraint.yaml": no matches for kind "K8sAlwaysDeny" in version "constraints.gatekeeper.sh/v1beta1"
ensure CRDs are installed first

Um template cria o custom resource definition para as constraints. As constraints apenas nos dizem sobre qual recurso vamos atuar e algumas coisas a mais, mas a lógica de violacão fica no template.

Agora vamos criar um template que define uma constraint.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8salwaysdeny # Será usado o mesmo nome no Rego.
spec:
crd:
spec:
names:
kind: K8sAlwaysDeny # A constrant que estamos definindo neste template
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
message:
type: string # Observe que na constraint temos o parametro.message que é definido aqui.
# Este é o bloco da linguagem Rego usado pelo OPA. Aqui definimos as regras de violação.
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8salwaysdeny

violation[{"msg": msg}] {
1 > 0
msg := input.parameters.message
}

No bloco target usamos a linguagem Rego (Linguagem com propósito de criação de policy) e temos a referência ao próprio metadata.name do template.

Na sessão violation recebemos um parâmetro msg que mais tarde será exibida caso a condição seja atingida. A violação será lançada se todas as condições forem verdadeiras. No momento, temos apenas uma condição simples, 1 é maior que zero, o que significa que a condição é verdadeira, não há mais condições, o que significa que a violação será lançada e todos os pods serão negados e a mensagem será lançada.

Se não entendeu tudo bem, vamos criar e verificar.

root@cks-master:~# vim template.yaml

root@cks-master:~# cat template.yaml

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8salwaysdeny
spec:
crd:
spec:
names:
kind: K8sAlwaysDeny
validation:
openAPIV3Schema:
properties:
message:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8salwaysdeny

violation[{"msg": msg}] {
1 > 0
msg := input.parameters.message
}

root@cks-master:~# k apply -f template.yaml
constrainttemplate.templates.gatekeeper.sh/k8salwaysdeny created

root@cks-master:~# k get constrainttemplates
NAME AGE
k8salwaysdeny 22s

# Buscando pelo resource criado
root@cks-master:~# k get K8sAlwaysDeny
No resources found

# Criando o resource que não conseguimos criar anteriormente.
root@cks-master:~# k apply -f constraint.yaml
k8salwaysdeny.constraints.gatekeeper.sh/pod-always-deny created

root@cks-master:~# k get K8sAlwaysDeny
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
pod-always-deny deny

# Vamos tentar criar um pod agora.
root@cks-master:~# k run nginx --image nginx
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [pod-always-deny] ACCESS DENIED!

Somente novos pods serão analisados, pois a regra somente tem valor no momento que foi criada, mas não interfere no que já existe. Se um replicaset for escalado para cima e precisar criar novos pods para um deployment não irá conseguir ou qualquer outra forma que seja.

root@cks-master:~# k get pods
NAME READY STATUS RESTARTS AGE
app 2/2 Running 0 14h

root@cks-master:~# k get pod app -o yaml | kubectl replace -f - --force
pod "app" deleted
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [pod-always-deny] ACCESS DENIED!

root@cks-master:~# k get pods
No resources found in default namespace.

Sabendo disso podemos observar que no bloco status temos 21 ocorrências de violações que são pods que já existem no cluster teoricamente se fossem recriados não passariam na regra.

# Total de pods que temos no cluster
root@cks-master:~# k get pod -A --no-headers | wc -l
21

root@cks-master:~# k describe k8salwaysdeny pod-always-deny
Name: pod-always-deny
Namespace:
Labels: <none>
Annotations: <none>
API Version: constraints.gatekeeper.sh/v1beta1
Kind: K8sAlwaysDeny
Metadata:
Creation Timestamp: 2024-08-30T14:26:51Z
Generation: 1
Resource Version: 1402472
UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Spec:
Enforcement Action: deny
Match:
Kinds:
API Groups:

Kinds:
Pod
Parameters:
Message: ACCESS DENIED!
Status:
Audit Timestamp: 2024-08-30T14:41:11Z
By Pod:
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-audit-5cf8bcb8f7-pq9tp
Observed Generation: 1
Operations:
audit
mutation-status
status
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-2kprv
Observed Generation: 1
Operations:
mutation-webhook
webhook
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-846m8
Observed Generation: 1
Operations:
mutation-webhook
webhook
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-vch4t
Observed Generation: 1
Operations:
mutation-webhook
webhook
Total Violations: 21
Violations:
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kubernetes-dashboard-metrics-scraper-5485b64c47-8jsxc
Namespace: kubernetes-dashboard
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kubernetes-dashboard-kong-7696bb8c88-kw4s7
Namespace: kubernetes-dashboard
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kubernetes-dashboard-auth-784d848dcb-zg8wq
Namespace: kubernetes-dashboard
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kubernetes-dashboard-api-9567bc759-zfnk6
Namespace: kubernetes-dashboard
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-scheduler-cks-master
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-proxy-w2xzr
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-proxy-5mx5m
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-controller-manager-cks-master
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: kube-apiserver-cks-master
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: etcd-cks-master
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: coredns-7db6d8ff4d-hsmkr
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: coredns-7db6d8ff4d-7ktqv
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: canal-8nn2f
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: canal-665nh
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: calico-kube-controllers-75bdb5b75d-wfr98
Namespace: kube-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: ingress-nginx-controller-7d4db76476-xxqvt
Namespace: ingress-nginx
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: gatekeeper-controller-manager-5dbdb9b595-vch4t
Namespace: gatekeeper-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: gatekeeper-controller-manager-5dbdb9b595-846m8
Namespace: gatekeeper-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: gatekeeper-controller-manager-5dbdb9b595-2kprv
Namespace: gatekeeper-system
Version: v1
Enforcement Action: deny
Group:
Kind: Pod
Message: ACCESS DENIED!
Name: gatekeeper-audit-5cf8bcb8f7-pq9tp
Namespace: gatekeeper-system
Version: v1
Events: <none>

Podemos fazer essa análise para ir conferindo se teremos ou não problemas no futuro quando definimos essas policies em um cluster que já esteja operando.

Se mudarmos a condição 1 > 0 que no caso é true para 1 > 2 seria false então tudo seria permitido e a violação não aconteceria.

root@cks-master:~# vim template.yaml

root@cks-master:~# cat template.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8salwaysdeny
spec:
crd:
spec:
names:
kind: K8sAlwaysDeny
validation:
openAPIV3Schema:
properties:
message:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8salwaysdeny

violation[{"msg": msg}] {
1 > 2
msg := input.parameters.message
}

# Veja acima que mudamos a condição para ser sempre false
root@cks-master:~# k run nginx --image nginx
pod/nginx created

# Não temos nenhuma violação mais
root@cks-master:~# k describe k8salwaysdeny pod-always-deny
Name: pod-always-deny
Namespace:
Labels: <none>
Annotations: <none>
API Version: constraints.gatekeeper.sh/v1beta1
Kind: K8sAlwaysDeny
Metadata:
Creation Timestamp: 2024-08-30T14:26:51Z
Generation: 1
Resource Version: 1403247
UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Spec:
Enforcement Action: deny
Match:
Kinds:
API Groups:

Kinds:
Pod
Parameters:
Message: ACCESS DENIED!
Status:
Audit Timestamp: 2024-08-30T14:49:11Z
By Pod:
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-audit-5cf8bcb8f7-pq9tp
Observed Generation: 1
Operations:
audit
mutation-status
status
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-2kprv
Observed Generation: 1
Operations:
mutation-webhook
webhook
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-846m8
Observed Generation: 1
Operations:
mutation-webhook
webhook
Constraint UID: aaca7120-6206-47f0-9144-6c5dd17561cd
Enforced: true
Id: gatekeeper-controller-manager-5dbdb9b595-vch4t
Observed Generation: 1
Operations:
mutation-webhook
webhook
Total Violations: 0 # <<<<<
Events: <none>

Limpe as constraints para o próximo cenário.

root@cks-master:~# k delete -f constraint.yaml
root@cks-master:~# k delete -f template.yaml
root@cks-master:~# rm -rf constraint.yaml template.yaml

Vamos criar uma policy em que todos os namespaces precisam ter a label cks. Vamos seguir os passos e explicando ao longo do caminho.

root@cks-master:~# vim template.yaml

# A idéia é criar um template geral que requere labels dos objetos e usar esse template para coisas diferentes.
root@cks-master:~# cat template.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels # O nome do kind que será criado
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
# labels é um tipo array de strings que precisará existir na constraint.
labels:
type: array
items: string

# Vamos analiser isso um pouco mais embaixo depois da explicação.
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels

violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}

# Aqui vamos criar duas constraints com o mesmo kind uma para namespaces e outra para pods que implementam a mesma coisa mas para objetos diferentes do kubernets.

root@cks-master:~# vim constraint-ns.yaml
root@cks-master:~# cat constraint-ns.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-cks
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["cks"]
root@cks-master:~#

root@cks-master:~# vim constraint-pod.yaml
root@cks-master:~# cat constraint-pod.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: pod-must-have-cks
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
labels: ["cks"]

Vamos analisar o template na parte de target.

  targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels

violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}

Novamente violation recebe os parâmetros, nesse caso msg e details onde msg será definida internamente em msg := sprintf("you must provide labels: %v", [missing]) e será missing_labels: missing onde missing será a quantidade de labels que faltam.

  • input: é a variável global no Rego que contém os dados que estão sendo avaliados.

  • input.review: Dentro do contexto do Gatekeeper é o caminho padrão para acessar a requisição sendo revisada, ou seja, objeto submetido ao admission controller.

  • input.review.object: É o recurso do kubernetes que esta sendo criado, modificado ou excluído. Pode ser um pod, pode ser um namespace, pode ser qualquer recurso sendo enviado a api do kubernetes.

  • input.review.object.metadata.labels: É o chave-valor que estamos buscando. No caso foi passado [label] para pegar a lista de labels.

  • No Rego label | está criando um conjunto a partir da entrada.

  • input.parameters esta buscando a lista na constraint. Agora olhando as variáveis em como pegamos os valores.

  • provided contém o conjunto (lista) de labels da request. Se for namespace será do objeto namespace, se for pod será label do pod.

  • required esta recebendo a lista de labels da constraint do mesmo tipo de objeto.

  • missing Estamos removendo todo o conjunto de labels da request das labels de required. Se conseguirmos remover tudo então foram definidas.

  • Por ultimos usamos uma função count para ver quantas labels ainda a condição e caso esteja faltando alguma label, ou seja, missing for maior que zero será negado e enviado a mensagem parametrizada dizendo as labels que devemos ter e quantas faltam.

Vamos criar e já conferir violações de uma forma mais simples.

root@cks-master:~# k create -f template.yaml
constrainttemplate.templates.gatekeeper.sh/k8srequiredlabels created
root@cks-master:~# k create -f constraint-ns.yaml
k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-cks created
root@cks-master:~# k create -f constraint-pod.yaml
k8srequiredlabels.constraints.gatekeeper.sh/pod-must-have-cks created

# Espera um pouquinho que TOTAL-VIOLATIONS irá aparecer.
root@cks-master:~# k get k8srequiredlabels
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
ns-must-have-cks deny 7
pod-must-have-cks deny 22

# As violações estão em todos os pods e em todos os namespace pois nenhum deles tem essa label definida.
root@cks-master:~# k get pod -A --no-headers | wc -l
22
root@cks-master:~# k get ns --no-headers | wc -l
7

# Se colocarmos uma label em um namespace será reduzido? Sim. Pode ser qualquer valor para a label, pois a regra espera que a label exista somente.
root@cks-master:~# k label namespaces default cks=true
namespace/default labeled

root@cks-master:~# k describe ns default
Name: default
Labels: cks=true
kubernetes.io/metadata.name=default
Annotations: <none>
Status: Active

No resource quota.

No LimitRange resource.
root@cks-master:~# k get k8srequiredlabels
ns-must-have-cks pod-must-have-cks
root@cks-master:~# k get k8srequiredlabels ns-must-have-cks
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
ns-must-have-cks deny

# Vamos forçar para que o ns seja obrigado a ter duas labels, cks e team.\
root@cks-master:~# vim constraint-ns.yaml
root@cks-master:~# cat constraint-ns.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-cks
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["cks","team"]

root@cks-master:~# k replace -f constraint-ns.yaml
k8srequiredlabels.constraints.gatekeeper.sh/ns-must-have-cks replaced

root@cks-master:~# k create namespace test
AError from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-cks] you must provide labels: {"cks", "team"}

# Agora vamos criar um namespace com essas labels.
root@cks-master:~# k create ns test -oyaml --dry-run=client > ns.yaml
root@cks-master:~# vim ns.yaml
root@cks-master:~# cat ns.yaml
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: test
labels:
cks: isnothard
team: devops
spec: {}
status: {}
root@cks-master:~# k apply -f ns.yaml
namespace/test created

# Testando pro pod
root@cks-master:~# k run pod --image=nginx
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [pod-must-have-cks] you must provide labels: {"cks"}

root@cks-master:~# k run pod --image=nginx --labels cks=amazing
pod/pod created

Delete os templates, constraints e vamos fazer outro para averiguar se um pod tem o mínimo de replica count.

root@cks-master:~# k delete -f constraint-ns.yaml
k8srequiredlabels.constraints.gatekeeper.sh "ns-must-have-cks" deleted
root@cks-master:~# k delete -f constraint-pod.yaml
k8srequiredlabels.constraints.gatekeeper.sh "pod-must-have-cks" deleted
root@cks-master:~# k delete -f template.yaml
constrainttemplate.templates.gatekeeper.sh "k8srequiredlabels" deleted
root@cks-master:~# rm -rf template.yaml constraint-*

Aqui os arquivos que vamos precisar.

root@cks-master:~# vim template.yaml
root@cks-master:~# cat template.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8sminreplicacount
spec:
crd:
spec:
names:
kind: K8sMinReplicaCount
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
min:
type: integer
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sminreplicacount

violation[{"msg": msg, "details": {"missing_replicas": missing}}] {
provided := input.review.object.spec.replicas
required := input.parameters.min
missing := required - provided
missing > 0
msg := sprintf("you must provide %v more replicas", [missing])
}

root@cks-master:~# vim constraint-rc.yaml
root@cks-master:~# cat constraint-rc.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sMinReplicaCount
metadata:
name: deployment-must-have-min-replicas
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
min: 2
root@cks-master:~#

É praticamente a mesma coisa a única diferença é que agora ao invés de buscar no metadata buscamos em input.review.object.spec.replicas. Como não é uma lista podemos apenas conferir esse valor com o valor da constraint. Não precisamos usar a função count, pois já temos o número que precisamos.

Se a quantidade de réplicas for 3, 3-2=1 que é maior que zero. Se for 2 ficaria 2-2=0. Apesar de parecer que 0 > 0 não satisfaria na verdade em Rego satisfaz pois nesse caso conseguiu atender o requisito

root@cks-master:~# k apply -f template.yaml
constrainttemplate.templates.gatekeeper.sh/k8sminreplicacount created
root@cks-master:~# k apply -f constraint-rc.yaml
k8sminreplicacount.constraints.gatekeeper.sh/deployment-must-have-min-replicas created

root@cks-master:~# k get k8sminreplicacount deployment-must-have-min-replicas
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
deployment-must-have-min-replicas deny 8

root@cks-master:~# k create deployment nginx --image=nginx
error: failed to create deployment: admission webhook "validation.gatekeeper.sh" denied the request: [deployment-must-have-min-replicas] you must provide 1 more replicas
root@cks-master:~# k create deployment nginx --image=nginx --replicas 2
deployment.apps/nginx created

Vale mensionar este github com alguns templates e policies que vale a pena como estudo. É um bom ponto de partida.

Rego Playground

Para criar policies esse playground pode ser de grande ajuda incluindo alguns exemplos para kubernetes.