Pulling an unknown image from the Internet (or anywhere other than a repository of trusted images) carries risks — such as malware. But there are other good reasons to maintain a single trusted source, such as enabling supportability in the enterprise. By ensuring that images only come from trusted mirror repositories, you can closely control the image inventory, reduce the risk of software entropy and contagion, and improve the overall security of the cluster. In addition, there are times when you need to check the tag of an image, such as disallowing Latest image.

Today we try to implement OPA with a “policy as code” implementation.

Before we even start, one might ask: Why add OPA when one Admission Webhook is all that is required?

Yes, but then the policy and logic will be coupled together, and the code will need to be modified and redistributed when the policy needs to be adjusted. OPA, on the other hand, is used for decoupling and is more like an execution engine for policies.

What is the OPA

Open Policy Agent (OPA, pronounced “OH-PA”) is an Open source, common Policy engine that can unify the entire stack of Policy execution. OPA provides a high-level declarative language (REGO) that lets you specify policies as code and simple APIs to offload policy decisions from your software. You can use OPA to enforce policies in microservices, Kubernetes, CI/CD pipes, API gateways, and more.

Rego is a high-level declarative language built specifically for OPA. More information about OPA can be found on the Open Policy Agent website.

Now let’s get down to business.

Start the cluster

Start the minikube

minikube start

Create the namespace to deploy the OPA

Create and switch to namespace opa (use Kubens for namespace switching, see more tools here)

kubectl create namespace opa
kubens opa

Deploy OPA on Kubernetes

Communication between Kubernetes and OPA must be secured using TLS. Configure TLS to create certificate/secret key pairs for Certificate Authority CA and OPA using OpenSSL.

openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -days 100000 -out ca.crt -subj "/CN=admission_ca"

Create TLS secret keys and certificates for OPA:

cat >server.conf <<EOF
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt = no
[req_distinguished_name]
CN = opa.opa.svc
[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = opa.opa.svc
EOF

Pay attention to
CN
alt_namesMust match the OPA Service created later.

openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr -config server.conf
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions v3_req -extfile server.conf

Create a Secret for OPA that holds TLS credentials:

kubectl create secret tls opa-server --cert=server.crt --key=server.key

Deploy the OPA as the Admission Controller.

Admission to the controller. Yaml:

# kube-mgmt synchronizes resource information to OPA for use in policy with kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: opa-viewer roleRef: kind: ClusterRole name: view apiGroup: rbac.authorization.k8s.io subjects: - kind: Group name: system:serviceaccounts:opa apiGroup: . Rbac authorization. K8s. IO # for OPA/kube - MGMT define roles to update strategy status in configmaps kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: opa name: configmap-modifier rules: - apiGroups: [""] resources: ["configmaps"] -- # Configure kind: ROLEBinding apiVersion: ["update"] -- # Configure kind: ROLEBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: namespace: opa name: opa-configmap-modifier roleRef: kind: Role name: configmap-modifier apiGroup: rbac.authorization.k8s.io subjects: - kind: Group name: system:serviceaccounts:opa apiGroup: rbac.authorization.k8s.io --- kind: Service apiVersion: v1 metadata: name: opa namespace: opa spec: selector: app: opa ports: - name: https protocol: TCP port: 443 targetPort: 8443 --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: opa namespace: opa name: opa spec: replicas: 1 selector: matchLabels: app: opa template: metadata: labels: app: opa name: opa spec: containers: # WARNING: OPA is NOT running with an authorization policy configured. This # means that clients can read and write policies in OPA. If you are # deploying OPA in an insecure environment, be sure to configure # authentication and authorization on the daemon. See the Security page for # details: https://www.openpolicyagent.org/docs/security.html. - name: opa image: openpolicyagent/opa: 0.30.1 - rootless args: - "run" - "--server" - "--tls-cert-file=/certs/tls.crt" - "--tls-private-key-file=/certs/tls.key" - "- addr = 0.0.0.0:8443" - "-- addr = http://127.0.0.1:8181" - "-- the log - format = json - pretty" - "-- set = decision_logs. Console = true"  volumeMounts: - readOnly: true mountPath: /certs name: opa-server readinessProbe: httpGet: path: /health?plugins&bundle scheme: HTTPS port: 8443 initialDelaySeconds: 3 periodSeconds: 5 livenessProbe: httpGet: path: /health scheme: HTTPS port: 8443 initialDelaySeconds: 3 periodSeconds: 5 - name: kube-mgmt image: OpenpolicyAgent/kubeMGMT :0.11 args: - "-- REPLICATE = V1 /pods" volumes: -Name: opA-server secret: secretName: opa-server --- kind: ConfigMap apiVersion: v1 metadata: name: opa-default-system-main namespace: opa data: main: | package system import data.kubernetes.validating.images main = { "apiVersion": "admission.k8s.io/v1beta1", "kind": "AdmissionReview", "response": response, } default uid = "" uid = input.request.uid response = { "allowed": false, "uid": uid, "status": { "reason": reason, }, } { reason = concat(", ", images.deny) reason ! = "" } else = {"allowed": true, "uid": uid}
kubectl apply -f admission-controller.yaml

Next, generate the MANIFEST that will be used to register the OPA as an access controller.

cat > webhook-configuration.yaml <<EOF
kind: ValidatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1beta1
metadata:
  name: opa-validating-webhook
webhooks:
  - name: validating-webhook.openpolicyagent.org
    rules:
      - operations: ["CREATE", "UPDATE"]
        apiGroups: ["*"]
        apiVersions: ["*"]
        resources: ["pods"]
    clientConfig:
      caBundle: $(cat ca.crt | base64 | tr -d '\n')
      service:
        namespace: opa
        name: opa
EOF

The generated configuration file contains the Base64 encoding of the CA certificate so that a TLS connection can be established between the Kubernetes API server and the OPA.

kubectl apply -f webhook-configuration.yaml

Check the OPA log:

kubectl logs -l app=opa -c opa -f

Define the policy and load it into the OPA through Kubernetes

Here we define checks for container mirroring:

  • Is it from a trusted repository
  • Is a Latest Tag mirror used

image-policy.rego

package kubernetes.validating.images deny[msg] { some i input.request.kind.kind == "Pod" image := input.request.object.spec.containers[i].image endswith(image, ":latest") msg := sprintf("Image '%v' used latest image", [image]) } { some i input.request.kind.kind == "Pod" image := input.request.object.spec.containers[i].image not StartsWith (image, "192.168.64.1:5000") MSG := sprintf(" image '%v' comes from untrusted registry", [image])}
kubectl create configmap image-policy --from-file=image-policy.rego

If check configmap annotation openpolicyagent.org/policy-status values for ‘{” status “:” ok “}’. Otherwise, the problem is handled based on the error message.

Note: 192.168.64.1:5000 is a private repository that my local container runs.

Version: '3.6' Services: Registry: Image: Registry :2.7.1 Container_Name: Registry Restart: Always Environment: Registry_http_addr: 0.0.0.0:5000 Registry_storage: filesystem Registry_storage_delete_enabled: 'true' REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY: /var/lib/registry ports: - '5000:5000' volumes: - '/Users/addo/Downloads/tmp/registry:/var/lib/registry'

test

pod-bad-repo.yaml

apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: web-server name: web-server spec: containers: -image: nginx:1.21.1 name: web-server resources: {} dnsPolicy: ClusterFirst restartPolicy: Always status: {}
Kubectl apply-f pod-bad-repo. YAML Error from server (Image 'nginx:1.21.1' comes from untrusted registry): error when creating "pod-bad-repo.yaml": admission webhook "validating-webhook.openpolicyagent.org" denied the request: Image 'nginx:1.21.1' comes from untrusted registry

pod-bad-tag.yaml

apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: web-server name: web-server spec: containers: - image: 192.168.64.1:5000 / nginx: latest name: web - server resources: {} dnsPolicy: ClusterFirst restartPolicy: Always status: {}
Kubectl apply -f pod - bad - tag. Yaml Error from the server (Image '192.168.64.1:5000 / nginx: latest informs the latest Image) : error when creating "pod-bad-tag.yaml": admission webhook "validating-webhook.openpolicyagent.org" denied the request: Image '192.168.64.1:5000 / nginx: latest informs the latest Image

pod-ok.yaml

apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: web-server name: web-server spec: containers: - image: 192.168.64.1:5000 / nginx: 1.21.1 name: web - server resources: {} dnsPolicy: ClusterFirst restartPolicy: Always status: {}
kubectl apply -f pod-ok.yaml
pod/web-server created

conclusion

Policy is code, which expresses policy by the implementation of code. By decoupling the policy from the execution engine, it makes the policy more flexible.

We’ll explore more OPA scenarios later.

The article is uniformly published in the public cloud native refers to the north