Skip to main content

Pregunta 24 | Extensión de Políticas OPA Gatekeeper

Usar contexto: kubectl config use-context infra-prod

Existe una política de Open Policy Agent + Gatekeeper existente para hacer cumplir que todos los Namespaces necesitan tener la etiqueta security-level configurada. Extiende la constraint de política y el template para que todos los Namespaces también necesiten configurar la etiqueta management-team. Cualquier creación de nuevo Namespace sin estas dos etiquetas debe ser prevenida.

Escribe los nombres de todos los Namespaces existentes que violan la política actualizada en /opt/course/p2/fix-namespaces.


Respuesta:

Echamos un vistazo a las constraints de OPA existentes, estas están implementadas usando CRDs por Gatekeeper:

➜ k get crd
NAME CREATED AT
blacklistimages.constraints.gatekeeper.sh 2020-09-14T19:29:31Z
configs.config.gatekeeper.sh 2020-09-14T19:29:04Z
constraintpodstatuses.status.gatekeeper.sh 2020-09-14T19:29:05Z
constrainttemplatepodstatuses.status.gatekeeper.sh 2020-09-14T19:29:05Z
constrainttemplates.templates.gatekeeper.sh 2020-09-14T19:29:05Z
requiredlabels.constraints.gatekeeper.sh 2020-09-14T19:29:31Z

Así que podemos hacer:

➜ k get constraint
NAME AGE
blacklistimages.constraints.gatekeeper.sh/pod-trusted-images 10m

NAME AGE
requiredlabels.constraints.gatekeeper.sh/namespace-mandatory-labels 10m

# Y verificar violaciones para namespace-mandatory-label, lo cual podemos hacer en el estado del recurso:
➜ k describe requiredlabels namespace-mandatory-labels
Name: namespace-mandatory-labels
Namespace:
Labels: <none>
Annotations: <none>
API Version: constraints.gatekeeper.sh/v1beta1
Kind: RequiredLabels
...
Status:
...
Total Violations: 1
Violations:
Enforcement Action: deny
Kind: Namespace
Message: you must provide labels: {"security-level"}
Name: sidecar-injector
Events:

# Vemos una violación para el Namespace "sidecar-injector". Obtengamos un resumen de todos los Namespaces:
➜ k get ns --show-labels
NAME STATUS AGE LABELS
default Active 21m management-team=green,security-level=high
gatekeeper-system Active 14m admission.gatekeeper.sh/ignore=no-self-managing,control-plane=controller-manager,gatekeeper.sh/system=yes,management-team=green,security-level=high
jeffs-playground Active 14m security-level=high
kube-node-lease Active 21m management-team=green,security-level=high
kube-public Active 21m management-team=red,security-level=low
kube-system Active 21m management-team=green,security-level=high
restricted Active 14m management-team=blue,security-level=medium
security Active 14m management-team=blue,security-level=medium
sidecar-injector Active 14m <none>

Cuando intentamos crear un Namespace sin la etiqueta requerida obtenemos un error de OPA:

➜ k create ns test
Error from server ([denied by namespace-mandatory-labels] you must provide labels: {"security-level"}): error when creating "ns.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by namespace-mandatory-labels] you must provide labels: {"security-level"}

A continuación editamos la constraint para añadir otra etiqueta requerida:

k edit requiredlabels namespace-mandatory-labels
# kubectl edit requiredlabels namespace-mandatory-labels
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RequiredLabels
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"constraints.gatekeeper.sh/v1beta1","kind":"RequiredLabels","metadata":{"annotations":{},"name":"namespace-mandatory-labels"},"spec":{"match":{"kinds":[{"apiGroups":[""],"kinds":["Namespace"]}]},"parameters":{"labels":["security-level"]}}}
creationTimestamp: "2020-09-14T19:29:53Z"
generation: 1
name: namespace-mandatory-labels
resourceVersion: "3081"
selfLink: /apis/constraints.gatekeeper.sh/v1beta1/requiredlabels/namespace-mandatory-labels
uid: 2a51a291-e07f-4bab-b33c-9b8c90e5125b
spec:
match:
kinds:
- apiGroups:
- ""
kinds:
- Namespace
parameters:
labels:
- security-level
- management-team # añadir

Como podemos ver, la constraint está usando kind: RequiredLabels como template, que es un CRD creado por Gatekeeper. Apliquemos el cambio y veamos qué sucede (dale a OPA un minuto para aplicar los cambios internamente):

➜ k describe requiredlabels namespace-mandatory-labels
...
Violations:
Enforcement Action: deny
Kind: Namespace
Message: you must provide labels: {"management-team"}
Name: jeffs-playground

Después de los cambios podemos ver que ahora otro Namespace jeffs-playground está en problemas. Porque ese solo especifica una etiqueta requerida. Pero ¿qué pasa con la violación anterior del Namespace sidecar-injector?

➜ k get ns --show-labels
NAME STATUS AGE LABELS
default Active 21m management-team=green,security-level=high
gatekeeper-system Active 17m admission.gatekeeper.sh/ignore=no-self-managing,control-plane=controller-manager,gatekeeper.sh/system=yes,management-team=green,security-level=high
jeffs-playground Active 17m security-level=high
kube-node-lease Active 21m management-team=green,security-level=high
kube-public Active 21m management-team=red,security-level=low
kube-system Active 21m management-team=green,security-level=high
restricted Active 17m management-team=blue,security-level=medium
security Active 17m management-team=blue,security-level=medium
sidecar-injector Active 17m <none>

El Namespace sidecar-injector también debería estar en problemas, pero ya no lo está. Esto no parece correcto, significa que aún podríamos crear Namespaces sin ninguna etiqueta simplemente usando k create ns test.

Así que verificamos el template:

➜ k get constrainttemplates
NAME AGE
blacklistimages 20m
requiredlabels 20m

➜ k edit constrainttemplates requiredlabels
# kubectl edit constrainttemplates requiredlabels
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
...
spec:
crd:
spec:
names:
kind: RequiredLabels
validation:
openAPIV3Schema:
properties:
labels:
items: string
type: array
targets:
- 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) == 1 # INCORRECTO
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}
target: admission.k8s.gatekeeper.sh

En el script rego necesitamos cambiar count(missing) == 1 a count(missing) > 0. Si no hacemos esto entonces la política solo se queja si hay una etiqueta faltante, pero puede haber múltiples faltantes.

Después de esperar un poco verificamos la constraint nuevamente:

➜ k describe requiredlabels namespace-mandatory-labels
...
Total Violations: 2
Violations:
Enforcement Action: deny
Kind: Namespace
Message: you must provide labels: {"management-team"}
Name: jeffs-playground
Enforcement Action: deny
Kind: Namespace
Message: you must provide labels: {"security-level", "management-team"}
Name: sidecar-injector
Events: <none>

Esto se ve mejor. Finalmente escribimos los nombres de los Namespaces con violaciones en la ubicación requerida:

# /opt/course/p2/fix-namespaces
sidecar-injector
jeffs-playground