写点什么

使用 Open Policy Agent 实现可信镜像仓库检查

用户头像
张晓辉
关注
发布于: 3 小时前
使用 Open Policy Agent 实现可信镜像仓库检查

从互联网(或可信镜像仓库库以外的任何地方)拉取未知镜像会带来风险——例如恶意软件。但是还有其他很好的理由来维护单一的可信来源,例如在企业中实现可支持性。通过确保镜像仅来自受信任的镜像仓库,可以密切控制镜像库存,降低软件熵和蔓延的风险,并提高集群的整体安全性。除此以外,有时还会需要检查镜像的 tag,比如禁止使用 latest 镜像。


这今天我们尝试用“策略即代码”的实现 OPA 来实现功能。


还没开始之前可能有人会问:明明可以实现个 Admission Webhook 就行,为什么还要加上 OPA?


确实可以,但是这样策略和逻辑都会耦合在一起,当策略需要调整的时候需要修改代码重新发布。而 OPA 就是用来做解耦的,其更像是一个策略的执行引擎。

什么是 OPA

Open Policy Agent(以下简称 OPA,发音 “oh-pa”)一个开源的通用策略引擎,可以统一整个堆栈的策略执行。OPA 提供了一种高级声明性语言(Rego),可让你将策略指定为代码和简单的 API,以从你的软件中卸载策略决策。你可以使用 OPA 在微服务、Kubernetes、CI/CD 管道、API 网关等中实施策略。


Rego 是一种高级的声明性语言,是专门为 OPA 建立的。更多 OPA 的介绍可以看 Open Policy Agent 官网,不想看英文直接看这里



现在进入正题。

启动集群

启动 minikube


minikube start
复制代码

创建用于部署 OPA 的命名空间

创建并切换到命名空间 opa (命名空间的切换使用 kubens,更多工具介绍见这里


kubectl create namespace opakubens opa
复制代码

在 Kubernetes 上部署 OPA

Kubernetes 和 OPA 间的通信必须使用 TLS 进行保护。配置 TLS,使用 openssl 创建证书颁发机构(certificate authority CA)和 OPA 的证书/秘钥对。


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


为 OPA 创建 TLS 秘钥和证书:


cat >server.conf <<EOF[req]req_extensions = v3_reqdistinguished_name = req_distinguished_nameprompt = no[req_distinguished_name]CN = opa.opa.svc[ v3_req ]basicConstraints = CA:FALSEkeyUsage = nonRepudiation, digitalSignature, keyEnciphermentextendedKeyUsage = clientAuth, serverAuthsubjectAltName = @alt_names[alt_names]DNS.1 = opa.opa.svcEOF
复制代码


注意 CNalt_names 必须与后面创建 OPA service 的匹配。


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


为 OPA 创建保存 TLS 凭证的 Secret:


kubectl create secret tls opa-server --cert=server.crt --key=server.key
复制代码


将 OPA 部署为准入控制器(admission controller)。


admission-controller.yaml


# 授权 OPA/kube-mgmt 对资源的只读权限# kube-mgmt 会同步资源信息给 OPA,以便在策略中使用kind: ClusterRoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:  name: opa-viewerroleRef:  kind: ClusterRole  name: view  apiGroup: rbac.authorization.k8s.iosubjects:- kind: Group  name: system:serviceaccounts:opa  apiGroup: rbac.authorization.k8s.io---# 为 OPA/kube-mgmt 定义角色来在 configmaps 中更新策略状态kind: RoleapiVersion: rbac.authorization.k8s.io/v1metadata:  namespace: opa  name: configmap-modifierrules:- apiGroups: [""]  resources: ["configmaps"]  verbs: ["update", "patch"]---# 为 OPA/kube-mgmt 授予角色kind: RoleBindingapiVersion: rbac.authorization.k8s.io/v1metadata:  namespace: opa  name: opa-configmap-modifierroleRef:  kind: Role  name: configmap-modifier  apiGroup: rbac.authorization.k8s.iosubjects:- kind: Group  name: system:serviceaccounts:opa  apiGroup: rbac.authorization.k8s.io---kind: ServiceapiVersion: v1metadata:  name: opa  namespace: opaspec:  selector:    app: opa  ports:  - name: https    protocol: TCP    port: 443    targetPort: 8443---apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: opa  namespace: opa  name: opaspec:  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"            - "--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/kube-mgmt:0.11          args:            - "--replicate=v1/pods"      volumes:        - name: opa-server          secret:            secretName: opa-server---kind: ConfigMapapiVersion: v1metadata:  name: opa-default-system-main  namespace: opadata:  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
复制代码


接下来,生成将用于将 OPA 注册为准入控制器的 manifest。


cat > webhook-configuration.yaml <<EOFkind: ValidatingWebhookConfigurationapiVersion: admissionregistration.k8s.io/v1beta1metadata:  name: opa-validating-webhookwebhooks:  - 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: opaEOF
复制代码


生成的配置文件包含 CA 证书的 base64 编码,以便可以在 Kubernetes API 服务器和 OPA 之间建立 TLS 连接。


kubectl apply -f webhook-configuration.yaml
复制代码


查看 OPA 日志:


kubectl logs -l app=opa -c opa -f
复制代码

定义策略并通过 Kubernetes 将其加载到 OPA

这里我们定义了对容器镜像的检查:


  • 是否来自受信任的仓库

  • 是否使用了 latest tag 的镜像


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
复制代码


检查 configmap 的 annotation openpolicyagent.org/policy-status 值是否 为 '{"status":"ok"}'。否则,就要根据报错信息处理问题。


注:192.168.64.1:5000 是笔者本地容器运行的一个私有仓库。


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'
复制代码

测试

pod-bad-repo.yaml

apiVersion: v1kind: Podmetadata:  creationTimestamp: null  labels:    run: web-server  name: web-serverspec:  containers:  - image: nginx:1.21.1    name: web-server    resources: {}  dnsPolicy: ClusterFirst  restartPolicy: Alwaysstatus: {}
复制代码


kubectl apply -f pod-bad-repo.yamlError 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: v1kind: Podmetadata:  creationTimestamp: null  labels:    run: web-server  name: web-serverspec:  containers:  - image: 192.168.64.1:5000/nginx:latest    name: web-server    resources: {}  dnsPolicy: ClusterFirst  restartPolicy: Alwaysstatus: {}
复制代码


kubectl apply -f pod-bad-tag.yamlError from server (Image '192.168.64.1:5000/nginx:latest' used 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' used latest image
复制代码

pod-ok.yaml

apiVersion: v1kind: Podmetadata:  creationTimestamp: null  labels:    run: web-server  name: web-serverspec:  containers:  - image: 192.168.64.1:5000/nginx:1.21.1    name: web-server    resources: {}  dnsPolicy: ClusterFirst  restartPolicy: Alwaysstatus: {}
复制代码


kubectl apply -f pod-ok.yamlpod/web-server created
复制代码

总结

策略即代码,以代码的实现表达策略;在通过策略与执行引擎的解耦分离,让策略更加的灵活。


后面我们再探索 OPA 的更多场景。

发布于: 3 小时前阅读数: 3
用户头像

张晓辉

关注

大胆尝试,小心求证 2018.04.09 加入

胡说八道

评论

发布
暂无评论
使用 Open Policy Agent 实现可信镜像仓库检查