关于kubernetes:K8S-Pod-新安全策略-Pod-Security-Admission-介绍-K8S-Internals-系列第一期

2次阅读

共计 13597 个字符,预计需要花费 34 分钟才能阅读完成。

K8S Internals 系列:第一期

容器编排之争在 Kubernetes 一统天下场面造成后,K8S 成为了云原生时代的新一代操作系统。K8S 让所有变得简略了,但本身逐步变得越来越简单。【K8S Internals 系列专栏】围绕 K8S 生态的诸多方面,将由博云容器云研发团队定期分享无关调度、平安、网络、性能、存储、利用场景等热点话题。心愿大家在享受 K8S 带来的高效便当的同时,又能够如庖丁解牛般领略其内核运行机制的魅力。

1. Pod Security Policy 简介

因为 Pod Security Admission 指标是代替 Pod Security Policy,所以介绍它之前有必要先介绍一下 Pod Security Policy,Pod Security Policy 定义了一组 Pod 运行时必须遵循的条件及相干字段的默认值,Pod 必须满足这些条件能力被胜利创立,Pod Security Policy 对象 Spec 蕴含以下字段也即是 Pod Security Policy 可能管制的方面:

管制的角度 字段名称
运行特权容器 privileged
应用宿主名字空间 hostPID,hostIPC
应用宿主的网络和端口 hostNetwork, hostPorts
管制卷类型的应用 volumes
应用宿主文件系统 allowedHostPaths
容许应用特定的 FlexVolume 驱动 allowedFlexVolumes
调配领有 Pod 卷的 FSGroup 账号 fsGroup
以只读形式拜访根文件系统 readOnlyRootFilesystem
设置容器的用户和组 ID runAsUser, runAsGroup, supplementalGroups
限度 root 账号特权级晋升 allowPrivilegeEscalation, defaultAllowPrivilegeEscalation
Linux 性能(Capabilities) defaultAddCapabilities, requiredDropCapabilities, allowedCapabilities
设置容器的 SELinux 上下文 seLinux
指定容器能够挂载的 proc 类型 allowedProcMountTypes
指定容器应用的 AppArmor 模版 annotations
指定容器应用的 seccomp 模版 annotations
指定容器应用的 sysctl 模版 forbiddenSysctls,allowedUnsafeSysctls

其中 AppArmor 和 seccomp 须要通过给 PodSecurityPolicy 对象增加注解的形式设定:

seccomp.security.alpha.kubernetes.io/allowedProfileNames: 'docker/default'
seccomp.security.alpha.kubernetes.io/defaultProfileNames: 'docker/default'
apparmor.security.beta.kubernetes.io/allowedProfileNames: 'runtime/default' 
apparmor.security.beta.kubernetes.io/defaultProfileNames: 'runtime/default' 

Pod Security Policy 是集群级别的资源,咱们看一下它的应用流程:


PSP 应用流程

因为须要创立 ClusterRole/Role 和 ClusterRoleBinding/RoleBinding 绑定服务账号来应用 PSP, 这使得咱们不能很容易的看出到底应用了哪些 PSP, 更难看出 Pod 的创立被哪些平安规定限度。

2. 为什么呈现 Pod Security Admission

通过对 PodSecurityPolicy 应用,应该也会发现它的问题,例如没有 dry-run 和审计模式、不不便开启和敞开等,并且应用起来也不那么清晰。种种缺点造成的后果是 PodSecurityPolicy 在 Kubernetes v1.21 被标记为弃用,并且将在 v1.25 中被移除,在 kubernets v1.22 中则减少了新个性 Pod Security Admission。

3. Pod Security Admission 介绍

pod security admission 是 kubernetes 内置的一种准入控制器,在 kubernetes v1.23 版本中这一个性门是默认开启的,在 v1.22 中须要通过 kube-apiserver 参数 --feature-gates="...,PodSecurity=true" 开启。在低于 v1.22 的 kuberntes 版本中也能够自行装置 Pod Security Admission Webhook。

pod security admission 是通过执行内置的 Pod Security Standards 来限度集群中的 pod 的创立。

3.1 Pod Security Standards

为了宽泛的笼罩平安利用场景,Pod Security Standards 渐进式的定义了三种不同的 Pod 平安规范策略:

Profile 形容
Privileged 不受限制的策略,提供最大可能范畴的权限许可。此策略容许已知的特权晋升。
Baseline 限制性最弱的策略,禁止已知的策略晋升。容许应用默认的(规定起码)Pod 配置。
Restricted 限制性十分强的策略,遵循以后的爱护 Pod 的最佳实际。

具体内容参见 Pod Security Standards。

3.2 Pod Security Standards 施行办法

在 kubernetes 集群中开启了 pod security admission 个性门之后,就能够通过给 namespace 设置 label 的形式来施行 Pod Security Standards。其中有三种设定模式可选用:

Mode Description
enforce 违反平安规范策略的 Pod 将被回绝。
audit 违反平安规范策略触发向审计日志中记录的事件增加审计正文,但其余行为被容许。
warn 违反平安规范策略将触发面向用户的正告,但其余行为被容许。

label 设置模板解释:

# 设定模式及平安规范策略等级
# MODE 必须是 `enforce`, `audit` 或 `warn` 其中之一。# LEVEL 必须是 `privileged`, `baseline` 或 `restricted` 其中之一
pod-security.kubernetes.io/<MODE>: <LEVEL>

# 此选项是非必填的,用来锁定应用哪个版本的的平安规范
# MODE 必须是 `enforce`, `audit` 或 `warn` 其中之一。# VERSION 必须是一个无效的 kubernetes minor version(例如 v1.23),或者 `latest`
pod-security.kubernetes.io/<MODE>-version: <VERSION>

一个 namesapce 能够设定任意种模式或者不同的模式设定不同的平安规范策略。

通过准入控制器配置文件,能够为 pod security admission 设置默认配置:

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
  configuration:
    apiVersion: pod-security.admission.config.k8s.io/v1beta1
    kind: PodSecurityConfiguration
    # Defaults applied when a mode label is not set.
    #
    # Level label values must be one of:
    # - "privileged" (default)
    # - "baseline"
    # - "restricted"
    #
    # Version label values must be one of:
    # - "latest" (default) 
    # - specific version like "v1.23"
    defaults:
      enforce: "privileged"
      enforce-version: "latest"
      audit: "privileged"
      audit-version: "latest"
      warn: "privileged"
      warn-version: "latest"
    exemptions:
      # Array of authenticated usernames to exempt.
      usernames: []
      # Array of runtime class names to exempt.
      runtimeClassNames: []
      # Array of namespaces to exempt.
      namespaces: []

pod security admission 能够从 username,runtimeClassName,namespace 三个维度对 pod 进行平安规范查看的豁免。

3.3 Pod Security Standards 施行演示

  • 环境: kubernetes v1.23

运行时的容器面临很多攻打危险,例如容器逃逸,从容器发动资源耗尽型攻打。

3.3.1 Baseline 策略

Baseline 策略指标是利用于常见的容器化利用,禁止已知的特权晋升,在官网的介绍中此策略针对的是利用运维人员和非关键性利用开发人员,在该策略中包含:

必须禁止共享宿主命名空间、禁止容器特权、限度 Linux 能力、禁止 hostPath 卷、限度宿主机端口、设定 AppArmor、SElinux、Seccomp、Sysctls 等。

上面演示设定 Baseline 策略。

违反 Baseline 策略存在的危险:

  • 特权容器能够看到宿主机设施
  • 挂载 procfs 后能够看到宿主机过程,突破过程隔离
  • 能够突破网络隔离
  • 挂载运行时 socket 后能够不受限制的与运行时通信

等等以上危险都可能导致容器逃逸。

  1. 创立名为 my-baseline-namespace 的 namespace,并设定 enforce 和 warn 两种模式都对应 Baseline 等级的 Pod 平安规范策略:
apiVersion: v1
kind: Namespace
metadata:
  name: my-baseline-namespace
  labels:
    pod-security.kubernetes.io/enforce: baseline  
    pod-security.kubernetes.io/enforce-version: v1.23
    pod-security.kubernetes.io/warn: baseline
    pod-security.kubernetes.io/warn-version: v1.23
  1. 创立 pod

    • 创立一个违反 baseline 策略的 pod
    apiVersion: v1
    kind: Pod
    metadata:
      name: hostnamespaces2
      namespace: my-baseline-namespace
    spec:
      containers:
      - image: bitnami/prometheus:2.33.5
        name: prometheus
        securityContext:
          allowPrivilegeEscalation: true
          privileged: true
          capabilities:
            drop:
            - ALL
      hostPID: true
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
    • 执行 apply 命令,显示不能设置 hostPID=true,securityContext.privileged=true,Pod 创立被回绝,特权容器的运行,并且开启 hostPID,容器过程没有与宿主机过程隔离,容易造成 Pod 容器逃逸:
    [root@localhost podSecurityStandard]# kubectl apply -f fail-hostnamespaces2.yaml
    Error from server (Forbidden): error when creating "fail-hostnamespaces2.yaml": pods "hostnamespaces2" is forbidden: violates PodSecurity "baseline:v1.23": host namespaces (hostPID=true), privileged (container "prometheus" must not set securityContext.privileged=true)
    • 创立不违反 baseline 策略的 pod,设定 Pod 的 hostPID=false,securityContext.privileged=false
    apiVersion: v1
    kind: Pod
    metadata:
      name: hostnamespaces2
      namespace: my-baseline-namespace
    spec:
      containers:
      - image: bitnami/prometheus:2.33.5
        name: prometheus
        securityContext:
          allowPrivilegeEscalation: false
          privileged: false
          capabilities:
            drop:
            - ALL
      hostPID: false
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
    • 执行 apply 命令,pod 被容许创立:
    [root@localhost podSecurityStandard]# kubectl apply -f pass-hostnamespaces2.yaml
    pod/hostnamespaces2 created

3.3.2 Restricted 策略

Restricted 策略指标是施行以后爱护 Pod 的最佳实际,在官网介绍中此策略次要针对运维人员和安全性很重要的利用开发人员,以及不太被信赖的用户。该策略蕴含所有的 baseline 策略的内容,额定减少:限度能够通过 PersistentVolumes 定义的非核心卷类型、禁止(通过 SetUID 或 SetGID 文件模式)取得特权晋升、必须要求容器以非 root 用户运行、Containers 不能够将 runAsUser 设置为 0、容器组必须弃用 ALL capabilities 并且只容许增加 NET_BIND_SERVICE 能力。

restricted 策略进一步的限度在容器内获取 root 权限,linux 内核性能。例如针对 kubernetes 网络的中间人攻打须要领有 Linux 零碎的 CAP_NET_RAW 权限来发送 ARP 包。

  1. 创立名为 my-restricted-namespace 的 namespace,并设定 enforce 和 warn 两种模式都对应 Restricted 等级的 Pod 平安规范策略:

    apiVersion: v1
    kind: Namespace
    metadata:
    name: my-restricted-namespace
    labels:
     pod-security.kubernetes.io/enforce: restricted 
     pod-security.kubernetes.io/enforce-version: v1.23
     pod-security.kubernetes.io/warn: restricted
     pod-security.kubernetes.io/warn-version: v1.23
  2. 创立 pod

    • 创立一个违反 Restricted 策略的 pod
    apiVersion: v1
    kind: Pod
    metadata:
      name: runasnonroot0
      namespace: my-restricted-namespace
    spec:
      containers:
      - image: bitnami/prometheus:2.33.5
        name: prometheus
        securityContext:
          allowPrivilegeEscalation: false
      securityContext:
        seccompProfile:
          type: RuntimeDefault
    • 执行 apply 命令,显示必须设置 securityContext.runAsNonRoot=true,securityContext.capabilities.drop=[“ALL”],Pod 创立被回绝,容器以 root 用户运行时容器获取权限过大,联合没有 Drop linux 内核能力有 kubernetes 网络中间人攻打的危险:
    [root@localhost podSecurityStandard]# kubectl apply -f fail-runasnonroot0.yaml
    Error from server (Forbidden): error when creating "fail-runasnonroot0.yaml": pods "runasnonroot0" is forbidden: violates PodSecurity "restricted:v1.23": unrestricted capabilities (container "prometheus" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "prometheus" must set securityContext.runAsNonRoot=true)
    • 创立不违反 Restricted 策略的 pod,设定 Pod 的 securityContext.runAsNonRoot=true,Drop 所有 linux 能力。
    apiVersion: v1
    kind: Pod
    metadata:
      name: runasnonroot0
      namespace: my-restricted-namespace
    spec:
      containers:
      - image: bitnami/prometheus:2.33.5
        name: prometheus
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
      securityContext:
        runAsNonRoot: true
        seccompProfile:
          type: RuntimeDefault
    • 执行 apply 命令,pod 被容许创立:
    [root@localhost podSecurityStandard]# kubectl apply -f pass-runasnonroot0.yaml
    pod/runasnonroot0 created

3.4 pod security admission 以后局限性

如果你的集群中曾经配置 PodSecurityPolicy,思考把它们迁徙到 pod security admission 是须要肯定的工作量的。

首先须要思考以后的 pod security admission 是否适宜你的集群,目前它旨在满足开箱即用的最常见的平安需要,与 PSP 相比它存在以下差别:

  • pod security admission 只是对 pod 进行平安规范的查看,不反对对 pod 进行批改,不能为 pod 设置默认的平安配置。
  • pod security admission 只反对官网定义的三种平安规范策略,不反对灵便的自定义平安规范策略。这使得不能齐全将 PSP 规定迁徙到 pod security admission,须要进行具体的平安规定考量。
  • pod security admission 不像 PSP 一样能够与具体的用户进行绑定,只反对豁免特定的用户或者 RuntimeClass 及 namespace。

4. pod security admission 源码剖析

kubernetes 准入控制器是在代码层面与 API server 逻辑解耦的插件,对象被创立、更新、或删除在 etcd 长久化之前能够对申请进行拦挡执行特定的逻辑。一个申请到 API server 经典的流程如下图所示:


Api Request 解决流程图

4.1 源码主体逻辑流程图


podsecurityAdmission 代码流程图

pod security admission 主体逻辑流程如图所示,准入控制器首先解析拦挡到的申请,依据解析到的资源类型进行不同的逻辑解决:

  • Namespace : 如果解析到的资源是 Namespace,准入控制器先依据该 namesapce 的 labels 解析出配置平安规范策略的等级、模式及锁定的 Pod 平安规范策略版本等信息。查看如果过不蕴含 Pod 平安规范策略信息则间接容许申请通过,如果蕴含 Pod 平安规范策略信息则判断是 create 新的 namespace, 还是 update 旧的 namespace, 如果是 create 则判断配置是否正确,如果是 update 则评估 namespace 中的 pod 是否合乎新设定的平安规范策略。
  • Pod: 如果解析到的资源是 Pod,准入控制器先获取该 Pod 所处的 namespace 设定的 Pod 平安规范策略信息,如果该 namespace 未设定 Pod 平安规范策略则容许申请通过,否则评估该 Pod 是否合乎平安规范策略。
  • others:准入控制器先获取该资源所处的 namespace 设定的 Pod 平安策略信息,如果该 namespace 未设定 Pod 安全策略则容许申请通过,否则进一步解析该资源判断该资源是否是诸如 PodTemplate,ReplicationController,ReplicaSet,Deployment,DaemonSet,StatefulSet,Job,CronJob 等蕴含 PodSpec 的资源,解析出 PodSpec 后评估该资源是否合乎 Pod 安全策略。

4.2 初始化 Pod security admission

像大多数 go 程序一样,Pod security admission 应用 github.com/spf13/cobra 创立了启动命令,在启动调用 runServer 初始化并启动 webhook 服务。入参 Options 中蕴含了 DefaultClientQPSLimit,DefaultClientQPSBurst,DefaultPort,DefaultInsecurePort 等默认配置。

// NewSchedulerCommand creates a *cobra.Command object with default parameters and registryOptions
func NewServerCommand() *cobra.Command {opts := options.NewOptions()

    cmdName := "podsecurity-webhook"
    if executable, err := os.Executable(); err == nil {cmdName = filepath.Base(executable)
    }
    cmd := &cobra.Command{
        Use: cmdName,
        Long: `The PodSecurity webhook is a standalone webhook server implementing the Pod
Security Standards.`,
        RunE: func(cmd *cobra.Command, _ []string) error {verflag.PrintAndExitIfRequested()
            // 初始化并且启动 webhook 服务
            return runServer(cmd.Context(), opts)
        },
        Args: cobra.NoArgs,
    }
    opts.AddFlags(cmd.Flags())
    verflag.AddFlags(cmd.Flags())

    return cmd
}

runserver 函数中加载了准入控制器的配置,初始化了 server, 最终启动 server。

func runServer(ctx context.Context, opts *options.Options) error {
    // 加载配置内容
    config, err := LoadConfig(opts)
    if err != nil {return err}
    // 依据配置内容初始化 server
    server, err := Setup(config)
    if err != nil {return err}
    
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    go func() {stopCh := apiserver.SetupSignalHandler()
        <-stopCh
        cancel()}()
    // 启动 server
    return server.Start(ctx)
}

上面截取了 Setup 函数局部次要代码片段,Setup 函数创立了 Admission 对象蕴含:

  • PodSecurityConfig: 准入控制器配置内容,包含默认的 Pod 平安规范策略等级及设定模式和锁定对应 kubernetes 版本,以及豁免的 Usernames、RuntimeClasses 和 Namespaces。
  • Evaluator: 创立的评估器,即定义了查看平安规范策略的具体方法。
  • Metrics: 用于收集 Prometheus 指标。
  • PodSpecExtractor:用解析申请对象中的 PodSpec。
  • PodLister: 用于获取指定 namespace 中的 Pods。
  • NamespaceGetter:用户获取拦挡到申请中的资源所处的 namespace。
// Setup creates an Admission object to handle the admission logic.
func Setup(c *Config) (*Server, error) {
    ...
    s.delegate = &admission.Admission{
        Configuration:    c.PodSecurityConfig,
        Evaluator:        evaluator,
        Metrics:          metrics,
        PodSpecExtractor: admission.DefaultPodSpecExtractor{},
        PodLister:        admission.PodListerFromClient(client),
        NamespaceGetter:  admission.NamespaceGetterFromListerAndClient(namespaceLister, client),
    }
   ...
    return s, nil
}

准入控制器服务启动之后注册了 HandleValidate 办法进行准入测验逻辑的解决, 在此办法中调用 Validate 办法进行具体 Pod 平安规范策略的测验。

// 解决 webhook 拦挡到的申请
func (s *Server) HandleValidate(w http.ResponseWriter, r *http.Request) {defer utilruntime.HandleCrash(func(_ interface{}) {
        // Assume the crash happened before the response was written.
        http.Error(w, "internal server error", http.StatusInternalServerError)
    })
     ...
    // 进行具体的测验操作
    response := s.delegate.Validate(ctx, attributes)
    response.UID = review.Request.UID // Response UID must match request UID
    review.Response = response
    writeResponse(w, review)
}

4.3 准入测验解决逻辑

Validate 办法依据获取申请蕴含的不同资源类型调用不同的测验办法进行具体的测验操作,以下三种解决方向最终都会调用 EvaluatePod 办法,对 Pod 进行平安规范策略评估。

// Validate admits an API request.
// The objects in admission attributes are expected to be external v1 objects that we care about.
// The returned response may be shared and must not be mutated.
func (a *Admission) Validate(ctx context.Context, attrs api.Attributes) *admissionv1.AdmissionResponse {
    var response *admissionv1.AdmissionResponse
    switch attrs.GetResource().GroupResource() {
    case namespacesResource:
        response = a.ValidateNamespace(ctx, attrs)
    case podsResource:
        response = a.ValidatePod(ctx, attrs)
    default:
        response = a.ValidatePodController(ctx, attrs)
    }
    return response
}

EvaluatePod 办法中对 namespace 设定平安规范策略和版本进行判断,从而选取不同的查看办法对 Pod 进行安全性测验。

func (r *checkRegistry) EvaluatePod(lv api.LevelVersion, podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) []CheckResult {
    // 如果设定的 Pod 平安规范策略等级是 Privileged(宽松的策略)间接返回
    if lv.Level == api.LevelPrivileged {return nil}
    // 如果注册的查看策略最大版本号低于 namespace 设定策略版本号,则应用注册的查看策略的最大版本号
    if r.maxVersion.Older(lv.Version) {lv.Version = r.maxVersion}

    var checks []CheckPodFn
    // 如果设定的 Pod 平安规范策略等级是 Baseline
    if lv.Level == api.LevelBaseline {checks = r.baselineChecks[lv.Version]
    } else {
        // includes non-overridden baseline checks
        // 其余走严格的 Pod 平安规范策略查看
        checks = r.restrictedChecks[lv.Version]
    }

    var results []CheckResult
    // 遍历查看办法,返回查看后果
    for _, check := range checks {results = append(results, check(podMetadata, podSpec))
    }
    return results
}

上面截取一个具体的测验办法来看一下是如何进行 pod 平安规范查看的,如下查看了 Pod 中的容器是否敞开了 allowPrivilegeEscalation,AllowPrivilegeEscalation 设置容器内的子过程是否能够晋升权限,通常在设置非 root 用户(MustRunAsNonRoot)时进行设置。

func allowPrivilegeEscalation_1_8(podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec) CheckResult {var badContainers []string
    visitContainers(podSpec, func(container *corev1.Container) {
        // 查看 pod 中容器平安上下文是否配置,AllowPrivilegeEscalation 是否配置,及 AllowPrivilegeEscalation 是否设置为 false.
        if container.SecurityContext == nil || container.SecurityContext.AllowPrivilegeEscalation == nil || *container.SecurityContext.AllowPrivilegeEscalation {badContainers = append(badContainers, container.Name)
        }
    })

    if len(badContainers) > 0 {
        // 存在违反 Pod 平安规范策略的内容,则返回具体后果信息
        return CheckResult{
            Allowed:         false,
            ForbiddenReason: "allowPrivilegeEscalation != false",
            ForbiddenDetail: fmt.Sprintf(
                "%s %s must set securityContext.allowPrivilegeEscalation=false",
                pluralize("container", "containers", len(badContainers)),
                joinQuote(badContainers),
            ),
        }
    }
    return CheckResult{Allowed: true}
}

总结

在 kubernetes v1.23 版本中 Pod Security Admission 曾经降级到 beta 版本,尽管目前性能不算弱小,但该个性将来可期。

正文完
 0