使用 Kube-Mgmt 将 OPA 集成到 Kubernetes 集群中

使用 Kube-Mgmt 将 OPA 集成到 Kubernetes 集群中

作者:阳明 2022-03-24 07:44:41

云计算

云原生 OPA 将策略决策与策略执行分离,当应用需要做出策略决策时,它会查询 OPA 并提供结构化数据(例如 JSON)作为输入,OPA 接受任意结构化数据作为输入。

Open Policy Agent 简称 OPA,是一种开源的通用策略代理引擎,是 CNCF 毕业的项目。OPA 提供了一种高级声明式语言 Rego,简化了策略规则的定义,以减轻程序中策略的决策负担。在微服务、Kubernetes、CI/CD、API 网关等场景中均可以使用 OPA 来定义策略。

我们这里主要讲解在 Kubernetes 中如何集成 OPA,在 Kubernetes 中 OPA 是通过 Admission Controllers 来实现安全策略的。事实上使用 Pod 安全策略(要废弃了)来执行我们的安全策略并没有什么问题,然而,根据定义,PSP 只能应用于 pods。它们不能处理其他 Kubernetes 资源,如 Ingresses、Deployments、Services 等,OPA 的强大之处在于它可以应用于任何 Kubernetes 资源。OPA 作为一个准入控制器部署到 Kubernetes,它拦截发送到 APIServer 的 API 调用,并验证和/或修改它们。你可以有一个统一的 OPA 策略,适用于系统的不同组件,而不仅仅是 pods,例如,有一种策略,强制用户在其服务中使用公司的域,并确保用户只从公司的镜像仓库中拉取镜像。

概述

OPA 将策略决策与策略执行分离,当应用需要做出策略决策时,它会查询 OPA 并提供结构化数据(例如 JSON)作为输入,OPA 接受任意结构化数据作为输入。

OPA 通过评估查询输入策略和数据来生成策略决策,你可以在你的策略中描述几乎任何的不变因素,例如:

  • 哪些用户可以访问哪些资源。
  • 哪些子网的出口流量被允许。
  • 工作负载必须部署到哪些集群。
  • 二进制文件可以从哪里下载。
  • 容器可以用哪些操作系统的能力来执行。
  • 系统在一天中的哪些时间可以被访问。

策略决定不限于简单的是/否或允许/拒绝,与查询输入一样,你的策略可以生成任意结构化数据作为输出。让我们看一个例子。OPA 的策略是用一种叫做 Rego 的高级声明性语言来声明的,Rego 是专门为表达复杂的分层数据结构的策略而设计的。

在 Kubernetes 中,准入控制器在创建、更新和删除操作期间对对象实施策略。准入控制是 Kubernetes 中策略执行的基础。通过将 OPA 部署为准入控制器,可以:

  • 要求在所有资源上使用特定标签。
  • 要求容器镜像来自企业镜像仓库。
  • 要求所有 Pod 指定资源请求和限制。
  • 防止创建冲突的 Ingress 对象。
  • ……

Kubernetes APIServer 配置为在创建、更新或删除对象时查询 OPA 以获取准入控制策略。APIServer 将 webhook 请求中的整个对象发送给 OPA,OPA 使用准入审查作为输入来评估它已加载的策略。这个其实和我们自己去实现一个准入控制器是类似的,只是不需要我们去编写代码,只需要编写策略规则,OPA 就可以根据我们的规则去对输入的对象进行验证。

部署

接下来我们介绍下如何在 Kubernetes 集群中集成 OPA,由于 Kubernetes 中是通过准入控制器来集成 OPA 的,所以我们必须在集群中启用 ValidatingAdmissionWebhook 这个准入控制器。

首先创建一个名为 opa 的命名空间,可以让 OPA 从该命名空间中的 ConfigMap 去加载策略:

kubectlcreatenamespaceopa

并将上下文更改为 opa 命名空间:

kubectlconfigcurrent-context
kubernetes-admin@kubernetes
contentkubectlconfigset-contextkubernetes-admin@kubernetes--namespace=opa
Context"kubernetes-admin@kubernetes"modified.
kubectlgetpods
Noresourcesfoundinopanamespace.

为了保护 APIServer 和 OPA 之间的通信,我们需要配置 TLS 证书。

创建证书颁发机构和密钥:

opensslgenrsa-outca.key2048
opensslreq-x509-new-nodes-keyca.key-days100000-outca.crt-subj"/CN=admission_ca"

为 OPA 生成密钥和证书:

cat>server.conf<<EOF
[req]
req_extensions=v3_req
distinguished_name=req_distinguished_name
[req_distinguished_name]
[ v3_req ]
basicConstraints=CA:FALSE
keyUsage=nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage=clientAuth, serverAuth
subjectAltName=@alt_names
[ alt_names ]
DNS.1=opa.opa.svc
EOF
opensslgenrsa-outserver.key2048
opensslreq-new-keyserver.key-outserver.csr-subj"/CN=opa.opa.svc"-configserver.conf
opensslx509-req-inserver.csr-CAca.crt-CAkeyca.key-CAcreateserial-outserver.crt-days100000-extensionsv3_req-extfileserver.conf

创建一个 Kubernetes TLS Secret 来存储我们的 OPA 凭证:

kubectlcreatesecrettlsopa-server--cert=server.crt--key=server.key

证书准备好后就可以部署准入控制器了,对应的资源清单文件如下所示:

# opa-admission-controller.yaml
# GrantOPA/kube-mgmtread-onlyaccesstoresources. Thisletskube-mgmt
# replicateresourcesintoOPAsotheycanbeusedinpolicies.
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
---
# DefineroleforOPA/kube-mgmttoupdateconfigmapswithpolicystatus.
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: opa
name: configmap-modifier
rules:
-apiGroups: [""]
resources: ["configmaps"]
verbs: ["update", "patch"]
---
# GrantOPA/kube-mgmtroledefinedabove.
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
port: 443
targetPort: 443
---
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
---
# DefineroleforOPA/kube-mgmttoupdateconfigmapswithpolicystatus.
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: opa
name: configmap-modifier
rules:
-apiGroups: [""]
resources: ["configmaps"]
verbs: ["update", "patch"]
---
# GrantOPA/kube-mgmtroledefinedabove.
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: 443
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: opa
namespace: opa
spec:
selector:
matchLabels:
app: opa
template:
metadata:
labels:
app: opa
spec:
containers:
-name: opa
image: openpolicyagent/opa:latest
args:
-"run"
-"--server"
-"--tls-cert-file=/certs/tls.crt"
-"--tls-private-key-file=/certs/tls.key"
-"--addr=0.0.0.0:443"
-"--addr=http://127.0.0.1:8181"
-"--log-level=debug"
-"--log-format=json-pretty"
volumeMounts:
-readOnly: true
mountPath: /certs
name: opa-server
readinessProbe:
httpGet:
path: /health
scheme: HTTPS
port: 443
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
scheme: HTTPS
port: 443
initialDelaySeconds: 10
periodSeconds: 15
-name: kube-mgmt
image: openpolicyagent/kube-mgmt:4.0.0
args:
---replicate-cluster=v1/namespaces
---replicate=networking.k8s.io/v1/ingresses
---opa-url=http://127.0.0.1:8181/v1
---enable-data=true
---enable-policies=true
---policies=opa
---require-policy-label=true
volumes:
-name: opa-server
secret:
secretName: opa-server
---
kind: ConfigMap
apiVersion: v1
metadata:
name: opa-default-system-main
namespace: opa
labels:
openpolicyagent.org/policy: rego
data:
main: |
packagesystem
importdata.kubernetes.admission
main= {
"apiVersion": "admission.k8s.io/v1",
"kind": "AdmissionReview",
"response": response,
}
defaultuid=""
uid=input.request.uid
response= {
"allowed": false,
"uid": uid,
"status": {
"message": reason,
},
} {
reason=concat(", ", admission.deny)
reason!=""
}
else= {"allowed": true, "uid": uid}

上面的资源清单中我们添加了一个 kube-mgmt 的 Sidecar 容器,该容器可以将 ConfigMap 对象中的策略动态加载到 OPA 中,kube-mgmt 容器还可以将任何其他 Kubernetes 对象作为 JSON 数据加载到 OPA 中。

另外需要注意的是 Service 的名称(opa)必须与我们证书配置的 CN 匹配,否则 TLS 通信会失败。在 kube-mgmt 容器中还指定了以下命令行参数:

  • –replicate-cluster=v1/namespaces。
  • –replicate=networking.k8s.io/v1/ingresses。
  • –enable-policies=true。
  • –policies=opa。
  • –require-policy-label=true。

前两个参数允许 sidecar 容器复制命名空间、Ingress 对象,并将它们加载到 OPA 引擎中,enable-policies=true 表示会通过 Configmap 加载 OPA 策略,下面的 –policies=opa 表示从 opa 命名空间中的 Configmap 来加载策略,如果还配置了 –require-policy-label=true 参数,则需要 Configmap 中带有 openpolicyagent.org/policy=rego 这个标签才会被自动加载。

现在直接应用上面的资源清单即可:

kubectlapply-fopa-admission-controller.yaml
kubectlgetpods
NAMEREADYSTATUSRESTARTSAGE
opa-6cd68f74f-s9zcv2/2Running05m28s

为了让准入控制器工作,我们还需要一个准入 webhook 来接收准入 HTTP 回调并执行它们,创建如下所示的 webhook 配置文件:

cat>webhook-configuration.yaml<<EOF
kind: ValidatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1
metadata:
name: opa-validating-webhook
webhooks:
-name: validating-webhook.openpolicyagent.org
admissionReviewVersions: ["v1", "v1beta1"]
namespaceSelector:
matchExpressions:
-key: openpolicyagent.org/webhook
operator: NotIn
values:
-ignore
failurePolicy: Ignore
rules:
-apiGroups:
-'*'
apiVersions:
-'*'
operations:
-'*'
resources:
-'*'
sideEffects: None
clientConfig:
caBundle: $(catca.crt|base64|tr-d'\n')
service:
namespace: opa
name: opa
EOF

上面的 webhook 中配置了以下属性:

  • 不会监听来自带有 openpolicyagent.org/webhook=ignore 标签的命名空间的操作。
  • 会监听所有资源的操作。
  • 它使用我们之前创建的 CA 证书,以便能够与 OPA 通信。

现在,在使用配置之前,我们标记 kube-system 和 opa 命名空间,使它们不在 webhook 范围内:

kubectllabelnskube-systemopenpolicyagent.org/webhook=ignore
kubectllabelnsopaopenpolicyagent.org/webhook=ignore

然后应用上面的配置对象将 OPA 注册为准入控制器:

kubectlapply-fwebhook-configuration.yaml
kubectlgetpods
NAMEREADYSTATUSRESTARTSAGE
opa-6cd68f74f-s9zcv2/2Running072m
kubectlgetvalidatingwebhookconfiguration
NAMEWEBHOOKSAGE
opa-validating-webhook12m14s

策略示例

OPA 使用 Rego 语言来描述策略,这里我们使用官方文档中提到的示例来进行说明,创建一个限制 Ingress 可以使用的主机名策略,只允许匹配指定正则表达式的主机名。

创建如下所示名为 ingress-allowlist.rego 的策略文件:

packagekubernetes.admission
operations= {"CREATE", "UPDATE"}
deny[msg] {
input.request.kind.kind=="Ingress"
operations[input.request.operation]
host :=input.request.object.spec.rules[_].host
notfqdn_matches_any(host, valid_ingress_hosts)
msg :=sprintf("invalid ingress host %q", [host])
}
valid_ingress_hosts= {host|
allowlist :=namespaces[input.request.namespace].metadata.annotations["ingress-allowlist"]
hosts :=split(allowlist, ",")
host :=hosts[_]
}
fqdn_matches_any(str, patterns) {
fqdn_matches(str, patterns[_])
}
fqdn_matches(str, pattern) {
pattern_parts :=split(pattern, ".")
pattern_parts[0] =="*"
str_parts :=split(str, ".")
n_pattern_parts :=count(pattern_parts)
n_str_parts :=count(str_parts)
n_pattern_parts==n_str_parts
suffix :=trim(pattern, "*.")
endswith(str, suffix)
}
fqdn_matches(str, pattern) {
notcontains(pattern, "*")
str==pattern
}

如果你是 Rego 新手,上面的代码看上去可能有点陌生,但 Rego 让定义策略变得非常容易,我们来分析下这个策略是如何使用白名单中的 Ingress 命名空间强制执行的:

第1行:package 的使用方式与在其他语言中的使用方式是一样的。

第5行:我们定义一个包含两项操作的数据集:CREATE 和 UPDATE。

第7行:这是策略的核心部分,以 deny 开头,然后是策略正文。如果正文中的语句组合评估为真,则违反策略,便会阻止操作,并将消息返回给用户,说明操作被阻止的原因。

第8行:指定输入对象,发送到 OPA 的任何 JSON 消息都是从输入对象的根部开始的,我们遍历 JSON 对象,直到找到有问题的资源,并且它必须是 Ingress 才能应用该策略。

第9行:我们需要应用策略来创建或更新资源,在 Rego 中,我们可以通过使用 operations[input.requset.operations] 来实现,方括号内的代码会提取请求中指定的操作,如果它与第5行的操作集中定义的元素相匹配,则该语句为真。

第10行:为了提取 Ingress 对象的 host 信息,我们需要迭代 JSON 对象的 rules 数组,同样 Rego 提供了 _ 字符来循环浏览数组,并将所有元素返回到 host 变量中。

第11行:现在我们有了 host 变量,我们需要确保它不是列入白名单的主机,要记住,只有在评估为 true 时才会违反该策略,为了检查主机是否有效,我们使用第21行中定义的 fqdn_matches_any 函数。

第12行:定义应返回给用户的消息,说明无法创建 Ingress 对象的原因。

第15-19行:这部分从 Ingress 命名空间的 annotations 中提取列入白名单的主机名,主机名添加在逗号分隔的列表中,使用 split 内置函数用于将其转换为列表。最后,_ 用于遍历所有提取的主机列表,将结果通过 | 管道传送给 host 变量(这与 Python 中的列表推导非常类似)。

第21行:该函数只接受一个字符串,并在一个 patterns 列表中搜索它,这是第二个参数。实际上是调用的下方的 fqdn_matches 函数来实现的。在 Rego 中,可以定义具有多个相同名称的函数,只要它们都产生相同的输出,当调用多次定义的函数时,将调用该函数的所有实例。

第25-33行:第一个 fqdn_matches 函数的定义。

  • 首先它通过点 . 将 pattern 进行拆分,比如 *.example.com 会分割成 *、example 和 com。
  • 接下来确保 pattern 的第一个标记是星号,同样对输入的字符串按照 . 进行拆分。
  • 删除 pattern 中的 *.。
  • 最后评估输入字符串是否以后缀结尾,比如如果允许的模式字符串是 *.mydomain.com,被评估的字符串是 www.example.com,则违反了该策略,因为该字符串不是 mydomain.com 的一部分。

第35-38行:第二个验证函数,该函数用于验证不使用通配符的模式,例如,当模式写为 mycompany.mydomain.com 的时候。

  • 首先,需要确保提供的模式不包含通配符,否则,该语句将评估为 false 并且函数将不会继续。
  • 如果模式指的是特定的域名,那么我们只需要确保 fqdn 与该模式匹配。换句话说,如果模式是mycompany.mydomain.com,那么主机的 fqdn 也必须是 mycompany.mydomain.com。

我们之所以有两个具有相同名称的函数,是因为 Rego 语言的一个限制,它会阻止函数产生一个以上的输出结果,所以,要想在同一时间用不同的逻辑进行多个验证,必须使用多个同名的函数。

在生产环境中,在将 Rego 代码应用到集群之前一定要进行全方位测试,比如可以添加单元测试,同时也可以使用 Rego Playground 来对代码进行验证。

要将该策略应用于集群,我们需要将上面的 Rego 文件以 Configmap 的形式应用到 opa 命名空间中:

kubectlcreateconfigmapingress-allowlist--from-file=ingress-allowlist.rego
kubectllabelconfigmapingress-allowlistopenpolicyagent.org/policy=rego

由于我们开启了 –require-policy-label 参数,所以还需要带上对应的标签。创建完成后最好检查下我们的策略是否被 OPA 获取了,并且没有语法错误,可以通过检查 ConfigMap 的状态来判断:

kubectlgetcmingress-allowlist-ojson|jq'.metadata.annotations'
{
"openpolicyagent.org/policy-status": "{\"status\":\"ok\"}"
}

接下来,让我们创建两个命名空间,一个用于 QA 环境,另一个用于生产环境。要注意它们都包含 ingress-allowlist 注解,其中包含 Ingress 主机名应该匹配的模式。

# qa-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
annotations:
ingress-allowlist: "*.qa.qikqiak.com,*.internal.qikqiak.com"
name: qa
---
# production-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
annotations:
ingress-allowlist: "*.qikqiak.com"
name: production

直接应用上面的两个资源清单文件即可:

kubectlapply-fqa-namespace.yaml-fproduction-namespace.yaml

接下来让我们创建一个被策略允许的 Ingress 对象:

kubectlapply-f-<<EOT
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-ok
namespace: production
spec:
ingressClassName: nginx
rules:
-host: prod.qikqiak.com
http:
paths:
-backend:
service:
name: nginx
port:
number: 80
path: /
pathType: Prefix
EOT

正常上面的资源对象可以创建:

kubectlgeting-nproduction
NAMECLASSHOSTSADDRESSPORTSAGE
ingress-oknginxprod.qikqiak.com8017s

接着我们创建一个不符合策略的 Ingress 对象:

kubectlapply-f-<<EOT
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-bad
namespace: qa
spec:
ingressClassName: nginx
rules:
-host: opa.k8s.local
http:
paths:
-backend:
service:
name: nginx
port:
number: 80
path: /
pathType: Prefix
EOT
Errorfromserver: errorwhencreating"test.yaml": admissionwebhook"validating-webhook.openpolicyagent.org"deniedtherequest: invalidingresshost"opa.k8s.local"

从输出中可以看出,APIServer 拒绝创建 Ingress 对象,因为上面的对象违反了我们的 OPA 策略规则。

到这里我们就完成了理由 OPA 在 Kubernetes 集群中实施准入控制策略,而无需修改或重新编译任何 Kubernetes 组件。此外,还可以通过 OPA 的 Bundle 功能策略,可以定期从远程服务器下载以满足不断变化的操作要求,关于 OPA 的更多高级功能请看后续文章介绍。

文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/257299.html<

(0)
运维的头像运维
上一篇2025-05-01 17:14
下一篇 2025-05-01 17:16

相关推荐

  • 个人主题怎么制作?

    制作个人主题是一个将个人风格、兴趣或专业领域转化为视觉化或结构化内容的过程,无论是用于个人博客、作品集、社交媒体账号还是品牌形象,核心都是围绕“个人特色”展开,以下从定位、内容规划、视觉设计、技术实现四个维度,详细拆解制作个人主题的完整流程,明确主题定位:找到个人特色的核心主题定位是所有工作的起点,需要先回答……

    2025-11-20
    0
  • 社群营销管理关键是什么?

    社群营销的核心在于通过建立有温度、有价值、有归属感的社群,实现用户留存、转化和品牌传播,其管理需贯穿“目标定位-内容运营-用户互动-数据驱动-风险控制”全流程,以下从五个维度展开详细说明:明确社群定位与目标社群管理的首要任务是精准定位,需明确社群的核心价值(如行业交流、产品使用指导、兴趣分享等)、目标用户画像……

    2025-11-20
    0
  • 香港公司网站备案需要什么材料?

    香港公司进行网站备案是一个涉及多部门协调、流程相对严谨的过程,尤其需兼顾中国内地与香港两地的监管要求,由于香港公司注册地与中国内地不同,其网站若主要服务内地用户或使用内地服务器,需根据服务器位置、网站内容性质等,选择对应的备案路径(如工信部ICP备案或公安备案),以下从备案主体资格、流程步骤、材料准备、注意事项……

    2025-11-20
    0
  • 如何企业上云推广

    企业上云已成为数字化转型的核心战略,但推广过程中需结合行业特性、企业痛点与市场需求,构建系统性、多维度的推广体系,以下从市场定位、策略设计、执行落地及效果优化四个维度,详细拆解企业上云推广的实践路径,精准定位:明确目标企业与核心价值企业上云并非“一刀切”的方案,需先锁定目标客户群体,提炼差异化价值主张,客户分层……

    2025-11-20
    0
  • PS设计搜索框的实用技巧有哪些?

    在PS中设计一个美观且功能性的搜索框需要结合创意构思、视觉设计和用户体验考量,以下从设计思路、制作步骤、细节优化及交互预览等方面详细说明,帮助打造符合需求的搜索框,设计前的规划明确使用场景:根据网站或APP的整体风格确定搜索框的调性,例如极简风适合细线条和纯色,科技感适合渐变和发光效果,电商类则可能需要突出搜索……

    2025-11-20
    0

发表回复

您的邮箱地址不会被公开。必填项已用 * 标注