• 【无标题】


    原理

    准入webhook工作流程图

    在这里插入图片描述

    准入控制器阶段

    在这里插入图片描述

    步骤

    需求

    ​ 给k8s集群增加镜像校验能力,不允许使用带:latest标签的镜像创建资源,允许使用携带具体标签的镜像来创建资源,比如:nginx:1.16

    先决条件

    • 1)确保启用ImagePolicyWebhook控制器。 这里 是一组推荐的 admission 控制器,通常可以启用。

    检查ImagePolicyWebhook是否启用,若能查询到,则启用了:

    kubectl exec kube-apiserver-k8s-master -n kube-system – kube-apiserver -h | grep “enable-admission-plugins”

    检查MutatingAdmissionWebhook是否启用:(默认启用)

    kubectl exec kube-apiserver-k8s-master -n kube-system – kube-apiserver -h | grep -i “MutatingAdmissionWebhook”

    检查ValidatingAdmissionWebhook是否启用:(默认启用)

    kubectl exec kube-apiserver-k8s-master -n kube-system – kube-apiserver -h | grep -i “ValidatingAdmissionWebhook”

    • 2)确保启用了admissionregistration.k8s.io/v1版本的API

    从k8s v1.16开始,admissionregistration.k8s.io/v1 API就是默认开启状态,若要显示开启,参考文档

    如下命令能查到,说明启用

    kubectl api-versions | grep admissionregistration.k8s.io/v1

    编写admission webhook服务器

    自定义开发一个类似http服务器,用于接收apiserver的HTTPS的post请求,并从请求判断是否符合预定义的校验策略,然后响应校验结果。

    请求类似:

    {
      "apiVersion":"imagepolicy.k8s.io/v1alpha1",
      "kind":"ImageReview",
      "spec":{
        "containers":[
          {
            "image":"myrepo/myimage:latest"
          }
        ],
        "namespace":"mynamespace"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    响应类似:

    {
        "apiVersion": "imagepolicy.k8s.io/v1alpha1",
        "kind": "ImageReview",
        "status": {
            "allowed": false,
            "reason": "检查镜像失败!镜像标签不允许使用latest!"
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    本文档中实际从dockerhub中拉取的现成的镜像lizhenliang/image-policy-webhook,也可以使用如下源码和Dockerfile自行编译镜像。

    使用Python源码和Dockerfile可以构建一个类似镜像 lizhenliang/image-policy-webhook,如下

    #Dockerfile
    FROM python
    RUN useradd python
    RUN mkdir /data/www -p
    COPY . /data/www
    RUN chown -R python /data
    RUN pip install flask -i https://mirrors.aliyun.com/pypi/simple/
    WORKDIR /data/www
    USER python
    CMD python main.py
    
    
    #main.py源码
    from flask import Flask,request
    import json
    
    app = Flask(__name__)
    
    @app.route('/image_policy',methods=["POST"])
    def image_policy():
        post_data = request.get_data().decode()
        #print("POST数据: %s" %post_data)
        data = json.loads(post_data)
        for c in data['spec']['containers']:
            if ":" not in c['image'] or ":latest" in c['image']:  # 如果镜像里不带冒号或者带:latest说明是镜像使用latest标签
                allowed, reason = False, "检查镜像失败!镜像标签不允许使用latest!"
                break
            else:
                allowed, reason = True, "检查镜像通过."
        print("检查结果: %s" %reason)
        result = {"apiVersion": "imagepolicy.k8s.io/v1alpha1","kind": "ImageReview",
                  "status": {"allowed": allowed,"reason": reason}}
    
        return json.dumps(result,ensure_ascii=False)
    
    if __name__ == "__main__":
        app.run(host="0.0.0.0",port=8080,ssl_context=('/data/www/webhook.pem','/data/www/webhook-key.pem'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    自签https证书

    几个用处:

    1)webhook服务器需要提供https访问;

    2)apiserver访问webhook需要通过https访问,所以需要提供证书来进行身份认证。

    举例,亦可以用其他认证方式,甚至关闭认证(生产禁止)

    1)ca.crt & ca.key: 用于签发证书的ca

    2)webhook.pem & webhook-key.pem:起webhook https服务时用的证书

    3)apiserver-client.pem & apiserver-client-key.pem: apiserver访问webhook服务时,携带的的证书

    cfssl_gen_certs.sh如下: (ps:需要先安装cfssl,cfssljson

    #!/bin/bash
    
    # 保存证书的目录
    : ${1?'missing key directory'}
    key_dir="$1"
    chmod 0700 "$key_dir"
    cd "$key_dir"
    
    cat > ca-config.json <<EOF
    {
        "signing": {
            "default": {
                "expiry": "87600h"
            },
            "profiles": {
                "kubernetes": {
                    "expiry": "87600h",
                    "usages": [
                        "signing",
                        "key encipherment",
                        "server auth",
                        "client auth"
                    ]
                }
            }
        }
    }
    EOF
    
    #"CN": "kubernetes",生成自签证书时,需要当做-profile的值传入
    cat > ca-csr.json <<EOF
    {
        "CN": "kubernetes",   
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [
            {
                "C": "CN",
                "L": "Beijing",
                "ST": "Beijing"
            }
        ]
    }
    EOF
    # 生成ca.pem、ca-key.pem、ca.csr
    /usr/local/bin/cfssl gencert -initca ca-csr.json | /usr/local/bin/cfssljson -bare ca -
    
    ###########################################################################################################
    # 注意“host"为空时,创建的证书不能用于website。
    # 10.4.0.16  为容器IP,用webhook服务的实际容器IP替换
    cat > webhook-csr.json <<EOF
    {
        "CN": "webhook",
        "hosts": [
            "zpsimon.demo.com"
        ],
    
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [
            {
                "C": "CN",
                "L": "Beijing",
                "ST": "Beijing"
            }
        ]
    }
    EOF
    
    # 生成 webhook-csr.pem、webhook-csr-key.pem、webhook-csr.csr
    /usr/local/bin/cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes webhook-csr.json | /usr/local/bin/cfssljson -bare webhook
    
    ###########################################################################################################
    
    cat > apiserver-client-csr.json <<EOF
    {
        "CN": "apiserver",
        "hosts": [],
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [
            {
                "C": "CN",
                "L": "Beijing",
                "ST": "Beijing"
            }
        ]
    }
    EOF
    
    # 生成apiserver-client.pem、apiserver-client-key.pem、apiserver-client.csr
    /usr/local/bin/cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes apiserver-client-csr.json | /usr/local/bin/cfssljson -bare apiserver-client
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    将apiserver-client.pem、apiserver-client-key.pem、webhook.pem传输到k8s的master节点

    scp apiserver-client.pem     root@k8s-master:/zp/cks/webhook/
    scp apiserver-client-key.pem root@k8s-master:/zp/cks/webhook/
    scp webhook.pem              root@k8s-master:/zp/cks/webhook/
    
    • 1
    • 2
    • 3

    部署准入webhook服务

    直接从现有的dockerhub拉取镜像lizhenliang/image-policy-webhook

    运行容器的宿主机:10.90.207.20

    cd /zp/webhook/cfssl_cert
    
    nerdctl run -d -u root --name=image-policy-webhook \
    -v $PWD/webhook.pem:/data/www/webhook.pem \
    -v $PWD/webhook-key.pem:/data/www/webhook-key.pem \
    -e PYTHONUNBUFFERED=1 \
    -p 8080:8080 \
    lizhenliang/image-policy-webhook
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    看到如下输出,说明webhook服务已经正常启动
    在这里插入图片描述

    先用curl命令验证一下:

    echo "" >> /etc/hosts
    curl --noproxy "*" --cacert ca.pem --location 'https://zpsimon.demo.com:8080/image_policy' \
    --header 'Content-Type: application/json' \
    --data '{
      "apiVersion":"imagepolicy.k8s.io/v1alpha1",
      "kind":"ImageReview",
      "spec":{
        "containers":[
          {
            "image":"myrepo/myimage:latest"
          }
        ],
        "namespace":"mynamespace"
      }
    }' -vvv
    ########output#######
    {"apiVersion": "imagepolicy.k8s.io/v1alpha1", "kind": "ImageReview", "status": {"allowed": false, "reason": "检查镜像失败!镜像标签不允许使用latest!"}}
    
    注意:
    # 1.  10.4.0.4为容器的IP,nerdctl inspect命令可以查看
    # 2. --data 格式需要满足一定的格式
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    如上,结果如预期,将带有":latest"tag的镜像拦截

    配置kube-apiserver访问webhook

    编写保存访问webhook服务器凭据和地址配置文件(形如kubeconfig文件)

    用于apiserver访问webhook时需要一些必要信息(如webhook用到的证书,apiserver提供的证书(用于向webhook展示身份有效性)、webhook服务器的地址等)

    connect_webhook.yaml:

    cd /zp/cks/webhook
    echo "10.90.207.20 zpsimon.demo.com" >> /etc/hosts    # 配置webhook服务地址的解析
    cat > connect_webhook.yaml<<-EOF
    apiVersion: v1
    kind: Config
    clusters:
    - cluster:
        certificate-authority: /zp/cks/webhook/webhook.pem   # webhook服务器启动用到的证书
        server: https://zpsimon.demo.com:8080/image_policy    # 远程webhook服务的接口地址,必须是https(官方要求)
      name: webhook
    contexts:
    - context:
        cluster: webhook
        user: apiserver
      name: webhook
    current-context: webhook
    # users 指的是 API 服务器的 Webhook 配置
    users:
    - name: apiserver
      user:
        client-certificate: /zp/cks/webhook/apiserver-client.pem # Webhook 准入控制器使用的证书
        client-key: /zp/cks/webhook/apiserver-client-key.pem     # 证书匹配的密钥
    EOF
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    配置admissionconfiguration文件

    启动 API 服务器时,通过 --admission-control-config-file参数指定刚刚创建的准入控制配置文件(即AdmissionConfiguration的位置)。

    cd /zp/cks/webhook
    cat > admission_configuration.yaml<<-EOF
    apiVersion: apiserver.config.k8s.io/v1
    kind: AdmissionConfiguration
    plugins:
    - name: ImagePolicyWebhook
      configuration:
        imagePolicy:
          kubeConfigFile: /zp/cks/webhook/connect_webhook.yaml   #即上一步骤的 connect_webhook.yaml位置
          allowTTL: 50   #控制批准请求的缓存时间,单位:秒,即同一个请求在该时间内不会再次请求webhook
          denyTTL: 50  #控制拒绝请求的缓存时间,单位:秒,即同一个请求在该时间内不会再次请求webhook
          retryBackoff: 500   #控制重试间隔,单位:毫秒
          defaultAllow: true  #如果后端webhook失效,则默认允许 
    EOF
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    调整kube-apiserver.yaml的配置

    在k8s master节点修改/etc/kubernetes/manifests/kube-apiserver.yaml

    #修改1,加上ImagePolicyWebhook
        - --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
    
    #添加1,通过 --admission-control-config-file 参数指定准入控制配置文件的位置
        - --admission-control-config-file=/zp/cks/webhook/admission_configuration.yaml
    
    #添加2,kube-apiserver需要加到宿主机上的/zp/cks/webhook目录下apiserver-client.pem,apiserver-key.pem
        - mountPath: /zp/cks/webhook
          name: image-policy
          readOnly: true
    
    #添加3
      volumes:
      - hostPath:
          path: /zp/cks/webhook
          type: DirectoryOrCreate
        name: image-policy
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    保存退出,kubelet会自动重启kube-apiserver,若没重启,可systemctl restart kubelet来重启kubelet

    特别注意

    要重点检查下,一旦出错,kube-apiserver会起不来,集群就无法操作了,可用通过crictl ps -a 查看Exited的容器,并使用crictl logs <容器ID> 来查看报错日志

    测试验证

    通过样例

    [root@k8s-master webhook]# k run pod1 --image=nginx:1.16
    pod/pod1 created
    [root@k8s-master webhook]# k get po 
    NAME        READY   STATUS    RESTARTS         AGE
    busybox01   1/1     Running   53 (6h54m ago)   26d
    pod1        1/1     Running   0                4s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    webhook服务的日志也有显示:

    10.90.118.39 - - [13/Nov/2023 04:05:12] "POST /image_policy?timeout=30s HTTP/1.1" 200 -
    检查结果: 检查镜像通过.
    
    • 1
    • 2

    不通过样例

    [root@k8s-master webhook]# k run pod1 --image=nginx:latest
    Error from server (Forbidden): pods "pod1" is forbidden: image policy webhook backend denied one or more images: 检查镜像失败!镜像标签不允许使用latest!
    
    • 1
    • 2

    webhook服务的日志也有显示:

    10.90.118.39 - - [13/Nov/2023 04:05:55] "POST /image_policy?timeout=30s HTTP/1.1" 200 -
    检查结果: 检查镜像失败!镜像标签不允许使用latest!
    
    • 1
    • 2

    参考文档:

    • https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/admission-controllers/#imagepolicywebhook
    • https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/
    • 样例1: https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/extensible-admission-controllers/#experimenting-with-admission-webhooks
    • 样例2:
      博客: https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/
      github代码: https://github.com/stackrox/admission-controller-webhook-demo#deploying-the-webhook-server
  • 相关阅读:
    手把手教你前后分离架构(三) 前端项目美化
    跨境电商独立站怎么做?好做吗?
    [GYCTF2020]Ezsqli 绕过or information_schema 无列名注入
    ThreadLocal for Golang
    CentOS部署集群版Presto
    计算机流水线在正常程序中的体现(效果可视)
    Java通过cellstyle属性设置Excel单元格常用样式全面总结
    R包Colorfindr识别图片颜色|用刀剑神域方式打开SCI科研配色
    熊市里的大机构压力倍增,灰度、Tether、微策略等巨鲸会不会成为"巨雷"?
    高等工程数学 —— 第四章 (2)线性方程组的迭代解法和极小化方法
  • 原文地址:https://blog.csdn.net/zpsimon/article/details/134399923