基于开源OPA实现Kubernetes资源安全合规基线检查 | 运维进阶

网友投稿 1201 2022-09-26

本站部分文章、图片属于网络上可搜索到的公开信息,均用于学习和交流用途,不能代表睿象云的观点、立场或意见。我们接受网民的监督,如发现任何违法内容或侵犯了您的权益,请第一时间联系小编邮箱jiasou666@gmail.com 处理。

基于开源OPA实现Kubernetes资源安全合规基线检查 | 运维进阶

1 Kubernetes资源治理问题

近年来越来越多的企业选择使用容器用于软件构建和发布,随着Docker Swarm的逐渐没落,毫无疑问Kubernetes已经成为事实上的容器编排工具标准。

目前使用Kubernetes已经不是什么大的难题,基本上几个命令几个yaml文件就能轻易地使应用运行在Kubernetes平台上。

而对于Kubernetes平台运维人员来说关键的挑战在于如何用好Kubernetes以及如何高效治理Kubernetes的资源。因为往往企业应用部署和平台运维是独立负责的,极力推广的DevOps理念大大提升了开发人员的自主性,但是开发人员往往只关注于应用如何部署运行,而很少关注一些跟应用没有直接关系的诸如安全、可靠性、资源配比等因素。

比如:

开发人员可能错误配置liveness以及readiness,甚至根本不配置。开发人员可能不会想到去合理配置limits以及requests资源,或者错误随意配置limits以及requests资源,设置不合理的比例。开发人员可能过度滥用容器的capacities权限,容器内部OS用户随意使用root,volume的fsuser、fsgroup设置的uid、gid不合理等。未设置企业要求的一些元数据标签labels或者注解annotations。Pod使用未受信任的镜像仓库以及大量使用latest标签的镜像。

而解决如上问题,最有效的方法就是设置准入门槛,不符合规范的请求直接拒绝,而不是先污染后治理。

Kubernetes一开始设计就支持通过准入控制器(Admission Controllers)方案设置准入门槛,官方也提供了许多现成的准入控制器供用户选择使用。

但是不同的企业可能安全准则、应用最佳实践标准并不一样,现有的准入控制器可能无法完全满足企业的所有需求,重新自研开发控制器则涉及开发成本高,并且后期也不易于维护扩展。

因此,我认为最有效的办法是能通过统一标准的规则引擎实现合规基线检测和修正,针对不同的规范要求,只需要编写对应规则动态注入,无需大量开发控制器插件,无需重启Kubernetes平台。

本文接下来将介绍基于开源OPA实现Kubernetes资源合规基线检查方案。

2 关于Kubernetes准入控制

虽然本文主要介绍如何使用OPA执行Kubernetes资源合规基线检查,但其原理离不开Kubernetes的准入控制,因此本文首先简单介绍下Kubernetes自带的准入控制机制。

众所周知,我们每次请求Kubernetes API时都会经过API Server的三大核心关口,层层把关:

第一个关口是认证,判定请求用户是谁以及其身份的真实性。关于认证的介绍可以参考我之前的几篇文章浅聊Kubernetes的各种认证策略以及适用场景以及Kubernetes通过Dex集成企业外部认证系统。第二个关口是授权,判定这个用户是否具有该操作的权限。目前用的比较多的是默认的RBAC插件,即通过role、clusterrole、rolebingding以及clusterrolebingding进行权限访问控制。第三个关口是准入控制。准入控制是对请求的进一步控制,比如检查请求的资源数量是否超出了配额(Quota)限制,请求的Pod是否符合容器安全要求等。

每个关口都有可能存在多个保安插件,Kubernetes支持配置多个认证以及授权引擎从而支持多认证多授权模式。

同理,Kubernetes可以配置多个准入控制器,只有所有准入控制器都判定通过时,请求方可放行。

另外准入控制器还支持对请求的资源进行修改,比如AlwaysPullImages准入控制器会强制修改Pod的imagePullPolicy为Always。

Kubernetes的准入控制与认证授权的设计理念相同,都是通过动态扩展插件的形式实现,每个插件依次串接。官方提供了许多现成的非常实用的准入控制器,比如:

LimitRanger: 对请求的CPU、Memory等资源进行范围限制,阻止请求超出设置范围的CPU数量或者内存大小。ResourceQuota: 对Namespace的CPU、内存、Pod数量等资源进行配额限制,如果请求超出Namespace配额,则拒绝服务。PodSecurity: 替换即将被废弃的PodSecurityPolicy,在v1.23中升为Beta 版,对Pod进行安全限制,比如Capabilities、Privileged等。ValidatingAdmissionWebhook: 与认证授权一样,准入控制也支持外部Webhook模式,api server会把请求体转发给外部Webhook,由外部的Webhook进行评审判定,返回判定结果。

更多的准入控制器可以参考官方文档Using Admission Controllers[1]。当然用户也可以根据自己的需求实现自定义准入控制器。

3 Open Policy Agent简介

3.1 Open Policy Agent

比如最典型的基于角色的权限控制(RBAC)就可以抽象成策略模型:定义各个角色具有哪些权限,通过规则判断用户隶属于哪个角色,从而判定该用户具备哪些权限,比如:

如果用户属于角色admin,则允许GET、PUT、POST、DELETE等所有操作。如果用户属于角色readonly,则只允许GET操作。如果用户不属于任何角色,则不允许任何操作。

还有一个典型的场景便是安全合规性检测,比如我们定义规则Kubernetes的所有Pod不允许使用latest标签镜像,通过这个规则可以查找出所有包含latest标签的违规Pod。我们还可以把这个规则设置为准入门槛,每次创建Pod前,都会通过我们的规则去做评审(Review),如果包含latest镜像则直接拒绝服务。

做策略引擎的,除了OPA,还有之前介绍的Cloud Custodian[3](c7n),相对OPA,c7n主要面向公有云服务的合规检测,不同的公有云厂商需要绑定不同的provider,目前实现比较完善的provider是AWS。关于c7n的介绍可以参考我之前的一篇文章云资源安全合规基线自动化检查与配置。

而OPA的设计理念则是软件服务本体与策略、策略与决策完全松耦合,换句话说,通过标准统一的策略决策,与检测体完全独立,只要检测对象能够转化为标准输入数据源即可。

3.2 典型场景

官方提供了几个典型的应用场景:

3.3 Rego语法快速入门

使用OPA,最核心的工作便是编写规则,而OPA使用的是Rego语言进行规则定义,因此不妨先简单介绍下Rego。

关于Rego的详细语法可以参考官方文档policy-language[13],这里只简单介绍一些最简单的语法。

Rego最基本的规则rule模板格式如下:

x = y {    expr_1    expr_2    ...}

如上所有表达式expr均为AND关系,即当且仅当所有的expr判定结果为true时,x会被置为y。

比如下面这条Rule,当x > y且x > z时,x_is_greatest为true:

x_is_greatest = true {    x > y    x > z}

后面变量赋值= true为默认值,可以省略,因此如下写法的效果是完全一样的:

x_is_greatest {    x > y    x > z}

这有点类似Python的语法:

x_is_greatest = True if x > y and x > z

default allow = falseallow { input.user == "admin"}allow { input.user == "Jim" input.method == "GET"}

如上定义了两个相同的Rule allow,当满足任意两个条件之一时,allow为true:

user为admin;user为Jim,且method为GET。

因此有如下判定结果:

allow with input as {"user": "admin", "method": "POST"} # trueallow with input as {"user": "Jim", "method": "GET"} # trueallow with input as {"user": "Jim", "method": "POST"} # false

除了OR,Rego也不像大多数编程语言一样具有丰富的分支选择语法(if-else),但是可以通过相同的方式实现if-else逻辑,比如实现f(x) 函数:

f(x) = A, 如果 x >= 90;f(x) = B, 如果 80 <= x < 90;f(x) = C,如果 70 <= x < 80;f(x) = D, 如果 x < 70

f(x) = "A" { x >= 90 }f(x) = "B" { x >= 80; x < 90 }f(x) = "C" { x >= 70; x < 80 }f(x) = "D" { x < 70 }

前面介绍的语法还是比较容易理解的,Rego与大多数编程语言在使用方面最大的差别在于集合的操作以及迭代遍历,初次使用会感觉特别奇怪。

比如arr[i],如果i为已定义的变量,则与大多数编程语言语义相同,都是取数组对应索引的值。

但是当i未定义时,则表示遍历数组的索引和值,类似Python的enumerate()函数,在后面的迭代过程中可以引用索引值i以及对应数组的值。

> arr := ["a", "b", "a", "c"]> arr[i]+---+--------+| i | arr[i] |+---+--------+| 0 | "a"    || 1 | "b"    || 2 | "a"    || 3 | "c"    |+---+--------+> i := 2> arr[i]"a"

特别地,当变量名为_时,则表示忽略索引值:

> arr := ["a", "b", "a", "c"]> arr[_]+--------+| arr[_] |+--------+| "a"    || "b"    || "a"    || "c"    |+--------+

因此在Rego中遍历数组的方法如下:

containers := ["int32bit.com/nginx:v1.7", "dockerhub.com/mysql:5.7"]container := containers[_] not startswith(container, "int32bit.com/") ...

注:如上空格缩进仅为方便理解,实际编写时不需要缩进。

语义类似Python的如下写法:

containers = ["int32bit.com/nginx:v1.7", "dockerhub.com/mysql:5.7"]for container in containers:    if not container.startswith("int32bit.com/"):       ...

当然新版本的Rego也支持了in语法,如上代码可等效为:

containers := ["int32bit.com/nginx:v1.7", "dockerhub.com/mysql:5.7"]some container in containers  not startswith(container, "int32bit.com/")  ...

根据这种语法进一步引申,判断元素是否包含在列表中,看起来也会比较奇怪:

> arr := ["a", "b", "c", "b"]> arr[i] == "c" # 如果i变量未定义,则会遍历数组,返回所有值为c的索引列表+---+| i |+---+| 2 |+---+> i := 1> arr[i] == "c" # i已定义为数字1,则判断指定索引1中的值是否为cfalse

引入了集合后,派生出一种新的规则rule语法,样例如下:

containers := ["int32bit.com/nginx:v1.7", "dockerhub.com/mysql:5.7"]deny_images[c] {    container := containers[_]    not startswith(container, "int32bit.com/")      c := container.image}

如上的deny_images是一个列表,语义为遍历containers列表,找出不是以int32bit.com/为前缀的元素,加到deny_images中,等效Python语法:

containers = ["int32bit.com/nginx:v1.7", "dockerhub.com/mysql:5.7"]deny_images = []for container in containers:    if not container.startswith("int32bit.com/"):        c = container        deny_images.append(c)

为什么会有如上奇怪的语法?个人感觉Rego的语法虽然理解起来可能比较绕,但是针对集合操作与查询写起来会比Python更简练些。

与Python一样,Rego也支持集合推导(Comprehension),比如:

arr := ["a", "b", "c", "a", "c", "d"]s := {i | i = arr[_]}

等价于Python:

arr = ["a", "b", "c", "a", "c", "d"]s = {i for i in arr}

除了以上的语法,Rego集成了非常丰富的内置函数,可以参考Rego built-in-functions[14],关于Rego的更多详细语法可以参考官方文档policy-language[15]。

4 Kubernetes集成OPA准入控制器

前面介绍了Kubernetes的准入控制以及通用策略规则引擎工具OPA。显然,OPA是非常适合用于实现Kubernetes资源的安全合规基线配置检查的。

针对这个场景,OPA官方已提供了现成的集成方案OPA Gatekeeper[16],该方案通过Webhook准入控制器方式集成。

可以认为OPA Gatekeeper是一个代理Web服务,Kubernetes API Server通过Webhook准入控制器将请求体转发给OPA Gatekeeper评审,请求Body样例如下:

{    "kind": "AdmissionReview",    "request": {        "kind": {            "kind": "Pod",            "version": "v1"        },        "object": {            "metadata": {                "name": "myapp"            },            "spec": {                "containers": [                    {                        "image": "nginx",                        "name": "nginx-frontend"                    },                    {                        "image": "mysql",                        "name": "mysql-backend"                    }                ]            }        }    }}

OPA Gatekeeper基于Repo规则对请求的Body进行判定,并返回给API Server。

关于OPA Gatekeeper的安装配置可以参考官方文档OPA gatekeeper install[17],如果只需要默认配置,简单只需要一条apply命令即可:

kubectl apply -f \  https://raw.githubusercontent.com/open-policy-agent/gatekeeper/release-3.7/deploy/gatekeeper.yaml

如上会创建ValidatingWebhookConfiguration、gatekeeper-controller-manager以及相关CRD资源。

# kubectl get ValidatingWebhookConfigurationNAME                                          WEBHOOKS   AGEgatekeeper-validating-webhook-configuration   2          47h# kubectl get pod -n gatekeeper-systemNAME                                          READY STATUS    RESTARTS   AGEgatekeeper-audit-545fcc7c99-hgttl             1/1   Running   0          47hgatekeeper-controller-manager-744959ffc-28tnt 1/1   Running   0          47hgatekeeper-controller-manager-744959ffc-2jnkt 1/1   Running   0          47hgatekeeper-controller-manager-744959ffc-47zvz 1/1   Running   0          47h# kubectl api-resources | grep gatekeeperconfigs                       config.gatekeeper.sh/v1alpha1       true  Configk8srequiredlabels             constraints.gatekeeper.sh/v1beta1   false K8sRequiredLabelsproviders                     externaldata.gatekeeper.sh/v1alpha1 false Providerassign                        mutations.gatekeeper.sh/v1beta1     false Assignassignmetadata                mutations.gatekeeper.sh/v1beta1     false AssignMetadatamodifyset                     mutations.gatekeeper.sh/v1beta1     false ModifySetconstraintpodstatuses         status.gatekeeper.sh/v1beta1        true  ConstraintPodStatusconstrainttemplatepodstatuses status.gatekeeper.sh/v1beta1        true  ConstraintTemplatePodStatusmutatorpodstatuses            status.gatekeeper.sh/v1beta1        true  MutatorPodStatusconstrainttemplates           templates.gatekeeper.sh/v1          false ConstraintTemplate

5 利用OPA实现Kubernetes资源合规基线检查

5.1 规则模板定义

前面安装了OPA Gatekeeper,创建了相关CRD,其中constrainttemplates是最重要的CRD资源,用于定义规则模板。

以一个简单的场景为例,假设我们要求所有的资源必须包含指定的labels,则可定义模板如下:

kind: ConstraintTemplatemetadata:  name: k8srequiredlabelsspec:  crd:    spec:      names:        kind: K8sRequiredLabels      validation:        openAPIV3Schema:          properties:            labels:              type: array              items: string  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])        }

constrainttemplates模板定义包含两部分:

openAPIV3Schema定义输入参数,上面的例子我们定义了一个数组变量labels。targets定义Rego规则。

前面的Rego例子不难理解,首先取出资源Object的labels列表,然后取出要求必须配置的labels列表,最后二者取差集就是缺失的labels列表。

5.2 规则实例化及应用

配置了模板后,需要实例化constraint,引用前面的constrainttemplate并配置输入参数:

apiVersion: constraints.gatekeeper.sh/v1beta1kind: K8sRequiredLabelsmetadata:  name: namespace-labels-checkspec:  match:    kinds:    - apiGroups: [""]      kinds: ["Namespace"]  parameters:    labels: ["app_name", "org_name"]

constraint定义包含两个主要部分:

match:配置需要执行该策略的资源,支持基于namespace白名单、namespace黑名单、label、name等过滤。parameters:配置constrainttemplate中需要的输入参数。

创建如上资源后,我们尝试创建没有任何label的namespace:

# kubectl  create ns aaaError from server : you must provide labels: {"app_name", "org_name"}admission webhook "validation.gatekeeper.sh" denied the request

符合预期创建namespace失败,错误中提示没有配置app_name以及org_name标签。

5.3 查看存量违规资源

前面我们设置了K8sRequiredLabelsconstraint,当我们创建没有打app_name以及org_name标签的不合规的namespace时会直接拒绝请求。

但是往往在创建K8sRequiredLabelsconstraint之前已经有很多存量的namespaces了,如何查看哪些存量namespaces不合规呢?

OPA Gatekeeper提供了如下三种方式:

通过Prometheus Metrics,指标gatekeeper_violations提供了所有违规的资源列表,这种方式便于与监控告警平台集成。通过Audit Logs,Audit会定时检查不合规的资源并转化成JSON格式输出到stdout中,我们可直接通过查看gatekeeper-audit Pod的日志即可查看,这种方式便于与日志平台集成。通过Constraint Status,OPA Gatekeeper会把所有不合规的资源列表放到constraint实例的Status字段中。这种方式便于我们直接查看。

如下,我们可以查看所有不满足namespace-labels-check的违规namespaces列表:

# kubectl get constraint namespace-labels-check -o yamlapiVersion: constraints.gatekeeper.sh/v1beta1kind: K8sRequiredLabelsmetadata:  name: namespace-labels-checkspec:  match:    kinds:    - apiGroups:      - ""      kinds:      - Namespace  parameters:    labels:    - app_name    - org_namestatus:  totalViolations: 5  violations:  - enforcementAction: deny    kind: Namespace    message: 'you must provide labels: {"app_name", "org_name"}'    name: kube-node-lease  - enforcementAction: deny    kind: Namespace    message: 'you must provide labels: {"org_name"}'    name: nginx-app  - enforcementAction: deny    kind: Namespace    message: 'you must provide labels: {"app_name", "org_name"}'    name: kube-system  - enforcementAction: deny    kind: Namespace    message: 'you must provide labels: {"app_name", "org_name"}'    name: default  - enforcementAction: deny    kind: Namespace    message: 'you must provide labels: {"app_name", "org_name"}'    name: kube-public

5.4 关于间接创建违规资源处置

前面当我们直接创建不合规的namespace时会直接拒绝请求。但是有些资源我们不会直接创建,而是间接创建。比如我们一般不会直接创建Pod,而是往往通过Deployments或者DaemonSets间接创建Pod资源。

当我们的规则限制作用于Pod时,则无法直接拒绝创建违规的Deployments或者DaemonSets,但创建的Deployment会创建Pod失败。

比如我们创建如下的constraint实例,要求所有的Pod必须包含app_name以及org_name标签。

apiVersion: constraints.gatekeeper.sh/v1beta1kind: K8sRequiredLabelsmetadata:  name: pod-labels-checkspec:  match:    kinds:    - apiGroups: [""]      kinds: ["Pod"]  parameters:    labels: ["app_name", "org_name"]

我们创建nginx-app Deployment:

apiVersion: apps/v1kind: Deploymentmetadata:  labels:    app: nginx-app  name: nginx-appspec:  replicas: 3  selector:    matchLabels:      app: nginx-app  template:    metadata:      labels:        app: nginx-app    spec:      containers:      - image: nginx:latest        name: nginx

此时Deployment创建成功,但是Pod不会被创建出来:

# kubectl get deployments.apps NAME        READY   UP-TO-DATE   AVAILABLE   AGEnginx-app   0/3     0            0           3m49s

我们可以查看最近的Event:

# kubectl  get eventsLAST SEEN   TYPE      REASON         OBJECT                            MESSAGE2m31s       Warning   FailedCreate   replicaset/nginx-app-78b5f9b5fd   Error creating: admission "pod-label-check" denied the request: you must provide labels: {"app_name", "org_name"}

可见是由于pod-label-check阻断了Pod资源创建。当然我们也可以通过status获取阻断原因:

status:  conditions:  - lastTransitionTime: "2021-12-30T10:15:32Z"    lastUpdateTime: "2021-12-30T10:15:32Z"    message: 'admission webhook "validation.gatekeeper.sh" denied the request: [pod-labels-check]      you must provide labels: {"app_name", "org_name"}'    reason: FailedCreate    status: "True"    type: ReplicaFailure

5.5 违规处理

前面当我们创建不合规的资源时会直接拒绝请求,这是因为OPA的默认违规处理方式是deny,即拒绝请求。

除了deny阻断模式,OPA Gatekeeper还支持如下两种违规处理策略:

dryrun: 仿真模式,Gatekeeper只记录违规的资源列表,但不会阻断违规资源的创建。warn: 警告模式,和仿真模式一样不会阻断违规资源的创建,但是会返回告警信息。该模式要求Kubernetes的版本为v1.19+。

以warn为例,我们创建一个违规Pod:

# kubectl apply -f pod.yamlWarning: [prod-repo-is-openpolicyagent] container  has an invalid image repo "test.com", allowed repos are ["int32ibt.com"]pod/pause created

我们发现Pod创建成功,但是kubectl返回了Warning信息,提示Pod使用了违规的镜像仓库。

5.6 违规资源自动修正

Gatekeeper除了支持基于规则执行违规资源检测,还支持对违规资源进行自动修复。不过目前该功能还不是很完善,只支持静态赋值,不支持动态配置。

比如我们要求除kube-system namespace以外的所有Pod的imagePullPolicy必须为Always,则可以创建如下Assign实例:

apiVersion: mutations.gatekeeper.sh/v1beta1kind: Assignmetadata:  name: set-image-pull-policy-to-alwaysspec:  applyTo:  - groups: [""]    kinds: ["Pod"]    versions: ["v1"]  match:    scope: Namespaced    kinds:    - apiGroups: ["*"]      kinds: ["Pod"]    excludedNamespaces: ["kube-system"]  location: "spec.containers[name:*].imagePullPolicy"  parameters:    assign:      value: Always

另外一个场景是不允许用户设置Pod的securityContext为privileged,则可以通过自动设置securityContext.privileged为false实现。

apiVersion: mutations.gatekeeper.sh/v1beta1kind: Assignmetadata:  name: demo-privilegedspec:  applyTo:  - groups: [""]    kinds: ["Pod"]    versions: ["v1"]  match:    scope: Namespaced    kinds:    - apiGroups: ["*"]      kinds: ["Pod"]    namespaces: ["bar"]  location: "spec.containers[name:foo].securityContext.privileged"  parameters:    assign:      value: false

5.7 开源的模板仓库

在Github中官方维护了一些很有用的现成模板gatekeeper-library[18],内容还是很全的,涵盖安全、配置基线等最佳实践规则。

比如:

6 总结

本文首先简单介绍了Kubernetes的资源治理问题以及准入控制器,然后介绍了OPA以及Rego,最后介绍如何将OPA集成到Kubernetes的准入控制器中实现Kubernetes资源的安全合规自动检测。

参考资料

[1] Using Admission Controllers: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers[2] Open Policy Agent(OPA): https://openpolicyagent.org/docs/latest/[3] Cloud Custodian: https://cloudcustodian.io/[4] Docker权限访问控制: https://openpolicyagent.org/docs/latest/docker-authorization/[5] HTTP服务RBAC控制: https://openpolicyagent.org/docs/latest/http-api-authorization/[6] Terraform TF模板文件合规基线检查: https://openpolicyagent.org/docs/latest/terraform/[7] CloudIaC: http://idcos.com/products/cloudiac[8] 结合Linux PAM实现更细粒度的SSH以及sudo的权限控制: https://openpolicyagent.org/docs/latest/ssh-and-sudo-authorization/[9] Kafka: https://openpolicyagent.org/docs/latest/kafka-authorization/[10] Envoy: https://openpolicyagent.org/docs/latest/envoy-introduction/[11] Kubernetes的资源合规基线检查: https://openpolicyagent.org/docs/latest/kubernetes-introduction/[12] Datalog: https://en.wikipedia.org/wiki/Datalog[13] policy-language: https://openpolicyagent.org/docs/latest/policy-language[14] Rego built-in-functions: https://openpolicyagent.org/docs/latest/policy-reference/#built-in-functions[15] policy-language: https://openpolicyagent.org/docs/latest/policy-language[16] OPA Gatekeeper: https://open-policy-agent.github.io/gatekeeper[17] OPA gatekeeper install: https://open-policy-agent.github.io/gatekeeper/website/docs/install[18] gatekeeper-library: https://github.com/open-policy-agent/gatekeeper-library/tree/master/library

上一篇:AWS 峰会福利 | 为移动互联网时代的应用保驾护航(awsl什么意思)
下一篇:Django 安全策略的 7 条总结!(django框架)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~