作者:CODING - 王炜

1. 背景

如果对 Kubernetes 集群安全特别关注,那么我们可能想要实现这些需求:

  • 如何实现 Kubernetes 集群的两步验证,除了集群凭据,还需要提供一次性的 Token 校验?
  • 如何验证部署的镜像是否安全合规,使得仅允许部署公司内部镜像仓库的 Docker 镜像?
  • 如何实现对每一个 Deployment 动态注入 sidecar ,满足特定安全或业务需求?
  • 如何实现集群级的 imagePullSecrets ,当创建新的命名空间的时候,自动将 imagePullSecrets 注入到新的命名空间?

本文以实现 Kubernetes 两步验证为例,利用 Kubernetes Admission 动态准入控制,同时借助 Serverless 实现一个两步验证的 Demo,使读者对动态准入控制Serverless有较深入的了解。

1.2 实现效果

Token 两步验证失败,不允许部署

Token 两步验证成功,允许部署

2. 什么是 Admission

Admission 是在用户执行 kubectl 通过认证之后,在将资源持久化到 ETCD 之前的步骤,Kubernetes 为了将这部分逻辑解耦,通过调用 Webhook 的方式来实现用户自定义业务逻辑的补充。而以上过程,都是在用户执行 kuberctl 并等待 API Server 同步返回结果的生命周期内。

上图标注的 ① 和 ② 是 Admission 介入的工作流程,我们会发现有这些特点:

  1. Admission 工作在集群认证通过之后
  2. Admission 一共有两种:MutatingValidating
  3. 这两种具体的实现方式都是以 Webhook 实现的
  4. Admission 的操作对象可以是当前部署的用户、Yaml 内容等

2.2 Admission Mutating

Mutating 的字面理解是“变异”的意思,真正的含义是,在资源持久化到 ETCD 之前,Mutating 控制器可以修改所部署的资源文件,比如给特定的 POD 动态增加 Labels,动态注入 sidecar 等。 细心的读者会发现,Admission Mutating 在很多产品都被用到,比如 Istio 里面就是使用它来动态的给每一个容器注入 sidecar Envoy 容器来实现流量的劫持和管理。

2.3 Admission Validating

Validating 比较好理解,也就是“验证”,它在 Mutating 之后,我们可以将自定义的验证逻辑放在这个阶段实现。本文我们就是利用它来实现一个简单的两步验证机制。

3. 什么是 Admission Webhook

Admission Webhook 其实就是 Mutating ControllersValidating Controllers 的具体实现方式,也就是说,我们需要给 Kubernetes 集群提供一个外部 Webhook Endpoint,API Server 执行到对应流程时,会调用我们预定义的 Webhook 来实现我们预定义的业务逻辑,通过返回规定的数据结构,来实现对 Yaml 文件的变更或者验证。

4. 动手实践

4.1 集群条件

根据官方文档,先决条件有以下几点:

  • Kubernetes 集群版本至少为 v1.16
  • 启用了 MutatingAdmissionWebhook 和 ValidatingAdmissionWebhook 控制器

如果不确定,可以通过以下命令查询:

kubectl get pods kube-apiserver -n kube-system -o yaml | grep MutatingAdmissionWebhook,ValidatingAdmissionWebhook

如果你使用的是托管集群,那么请使用以下命令查询:

kubectl api-versions | grep admission

如果出现 admissionregistration.k8s.io/v1beta1 说明集群支持,进行下一步。

4.2 其他条件

  • 开通 CODING DevOps
  • 克隆代码仓库 admission-webhook-example.git 并推送到自己的 CODING Git 仓库
  • 准备一个 腾讯云账户

4.3 部署腾讯 Serverless 服务

  1. 登陆 CODING,并在配置 Serverless 身份授权,记录凭据 ID(类似:b68948cb-2ad9-4b67-8a49-ad7ba910ed92),稍后使用

  2. 克隆代码仓库 admission-webhook-example

    git clone https://e.coding.net/wangweicoding/admission-webhook-example.git
  3. 修改根目录下的文件

  • 根目录下的 Jenkinsfile,将上一步获取的凭据 ID 替换光标处的凭据 ID
  • 修改 serverless/.env 的 VPC_IDSUBNET_ID,这两项可以在腾讯云控制台“私有网络”找到;如果没有私有网络和子网,则可以自己新建一个,注意地域选择“广州”
  • 修改完成后,将代码推送到你自己的 CODING Git 代码仓库
  1. 使用“空白模板”创建构建计划,选择“使用代码仓库的 Jenkinsfile”

  2. 运行构建计划,部署 Serverless 服务 运行完成后,点击“输出 Endpoint”阶段,查看输出的 URL (类似:https://service-faeax9cy-1301578102.gz.apigw.tencentcs.com/release/index), 此 URL 即为 Serverless 服务对外提供服务的 URL 。记录供下一个阶段使用

至此,腾讯云 Serverless 服务已部署完成。

4.4 Kubernetes 集群部署 Validating Webhook

因为 Admission Webhook 只允许 https 协议并且需要提供证书信息,所以需要我们提前生成,代码仓库已经提供脚本,运行即可配置集群证书。

$ ./deployment/webhook-create-signed-cert.shcreating certs in tmpdir /var/folders/mt/965plkfs62v6wqx2839qthz40000gq/T/tmp.i1imELSt
Generating RSA private key, 2048 bit long modulus (2 primes)
...................+++++
....+++++
e is 65537 (0x010001)
certificatesigningrequest.certificates.k8s.io/admission-webhook-example-svc.default created
NAME                                    AGE   REQUESTOR   CONDITION
admission-webhook-example-svc.default   1s    admin       Pending
certificatesigningrequest.certificates.k8s.io/admission-webhook-example-svc.default approved
secret/admission-webhook-example-certs configured
(base)

修改 deployment/deployment.yaml 文件,将 serverlessURL 替换为上一个阶段记录下的 Endpoint(类似:https://service-faeax9cy-1301578102.gz.apigw.tencentcs.com/release/index)

证书创建成功后,部署 Deployment 和 Services

$ kubectl create -f deployment/deployment.yaml
deployment.apps "admission-webhook-example-deployment" created$ kubectl create -f deployment/service.yaml
service "admission-webhook-example-svc" created

至此我们用来接收 Validating 请求的服务已经部署完成,最后配置 ValidatingWebhookConfiguration,运行以下命令:

cat ./deployment/validatingwebhook.yaml | ./deployment/webhook-patch-ca-bundle.sh > ./deployment/validatingwebhook-ca-bundle.yaml

执行完成后,可以看到 validatingwebhook-ca-bundle.yamlcaBundle 字段已经被替换。

脚本运行依赖于 jq (Shell 读取 JSON 工具),如果你还没有安装,请移步:https://www.ibm.com/developerworks/cn/linux/1612_chengg_jq/index.html

Mac 系统可以直接使用:brew install jq 进行安装。

接下来,我们为 default 命名空间打标签,因为我们的 ValidatingWebhookConfiguration 使用了 namespaceSelector 只对包含特定 labels 的命名空间做两步验证。

$ kubectl label namespace default admission-webhook-example=enabled
namespace "default" labeled

最后,创建 ValidatingWebhookConfiguration

$ kubectl create -f deployment/validatingwebhook-ca-bundle.yaml
validatingwebhookconfiguration.admissionregistration.k8s.io "validation-webhook-example-cfg" created

这样,一旦在 default 命名空间创建资源,我们部署的服务(Deployment) 将会拦截请求,并进行二次校验。

4.5 尝试两步验证

至此,我们已经成功部署了两步验证的 Demo,整体架构图现在变成了:

现在,我们可以尝试部署

$ kubectl apply -f deployment/sleep.yaml
Error from server (Token 错误,不允许部署): error when creating "deployment/sleep.yaml": admission webhook "required-labels.coding.net" denied the request: Token 错误,不允许部署

由于我们在创建 Serverless 服务的时候,预先向数据库配置了四组 token,分别是:1111、2222、3333、4444,所以我们可以修改 sleep.yaml ,将注解metadata.annotations.token 修改为 1111,再次尝试部署

$ kubectl apply -f deployment/sleep.yaml
deployment.apps/sleep created

部署成功,如果重复使用此 token,是无法验证通过的。至此,基于 Serverless 的两步验证已经完成。

5. 源码分析

5.1 我们部署的 Deployment 做了什么

当执行 kubectl apply 之后, API Server 将请求转发到我们部署的 POD ,核心代码在项目根目录下,主要是 main.gowebhook.go

main.go 主要是启动了一个 HTTP 服务,并从命令行读取了我们创建的证书以及 Serverless Endpoint

// main.goflag.IntVar(&parameters.port, "port", 443, "Webhook server port.")
flag.StringVar(&parameters.certFile, "tlsCertFile", "/etc/webhook/certs/cert.pem", "File containing the x509 Certificate for HTTPS.")
flag.StringVar(&parameters.keyFile, "tlsKeyFile", "/etc/webhook/certs/key.pem", "File containing the x509 private key to --tlsCertFile.")
flag.StringVar(&parameters.serverlessURL, "serverlessURL", "https://example.com", "serverless endpoint URL.")

webhook.go 主要是转发 API Server 发送的请求,我们将 validate 重新改写,将所有请求转发到 Serverless Endpoint。

// webhook.goglog.Infof("parameters.serverlessURL is %v", whsvr.parameters.serverlessURL)
res, _ := Post(whsvr.parameters.serverlessURL, req)
// 初始化请求变量结构
jsonData := make(map[string]string)
// 调用json包的解析,解析请求body
_ = json.NewDecoder(res.Body).Decode(&jsonData)
glog.Infof("res is %v", jsonData)
allowed := false
reason := &metav1.Status{Reason: "Token 错误,不允许部署",
}
if jsonData["allow"] == "true" {allowed = true
}
return &v1beta1.AdmissionResponse{Allowed: allowed,Result:  reason,
}

POD 将请求转发到我们的 Serverless 函数之后,由它来做业务逻辑判断是否允许准入。随后,POD 将 Serverless 的结果重新格式化之后返回给 API Server。

5.2 Serverless 做了什么?

我们部署的 Serverless 服务,主要包含了四个部分:

  • API Gateway
  • 云函数
  • Postgresql
  • VPC

我们使用 CODING DevOps 在腾讯云部署了以上几个 Serverless 服务,Jenkinsfile 核心代码:

stage('部署 Serverless 服务') {steps {withCredentials([string(credentialsId:"b68948cb-2ad9-4b67-8a49-ad7ba910ed92", variable:'tencent_serverless')]) {sh 'echo "${tencent_serverless}" > .tmp'sh '''SecretId=$(cat .tmp | jq -r .SecretId)SecretKey=$(cat .tmp | jq -r .SecretKey)token=$(cat .tmp | jq -r .token)AppId=$(cat .tmp | jq -r .AppId)echo "TENCENT_SECRET_ID=${SecretId}" >> ./serverless/.envecho "TENCENT_SECRET_KEY=${SecretKey}" >> ./serverless/.envecho "TENCENT_APP_ID=${AppId}" >> ./serverless/.envecho "TENCENT_TOKEN=${token}" >> ./serverless/.env'''sh 'cd serverless && cat .env'sh 'cd serverless && npm run bootstrap && sls deploy --all | tee log.log'sh 'rm ./serverless/.env'}echo '部署完成'}
}
stage('输出 Endpoint') {steps {sh 'cd serverless && cat log.log | grep apigw.tencentcs.com'}
}

这里主要是使用临时凭据,以及使用 Serverless SDK 对预定义的 serverless.yml 进行部署。

API Gateway 负责对外提供外网访问

# ./serverless/api/serverless.yml API Gateway 部署文件events:- apigw:name: k8sAdmissionparameters:protocols:- http- httpsserviceName:description: Based on Tencent Cloud Serverless, it provides dynamic access control for K8Senvironment: releaseendpoints:- path: /indexmethod: POST

Postgresql 负责存储预定义的 tokens

# ./serverless/db/serverless.yml  数据库部署文件org: k8sAdmission
app: k8sAdmission-db
stage: devcomponent: postgresql
name: fullstackDBinputs:region: ${env:REGION}zone: ${env:ZONE}dBInstanceName: ${name}vpcConfig:vpcId: ${output:${stage}:${app}:serverlessVpc.vpcId}subnetId: ${output:${stage}:${app}:serverlessVpc.subnetId}extranetAccess: false

VPC 实现将云函数和 Postgresql 网络互通

# ./serverless/vpc/serverless.yml VPC部署文件org: k8sAdmission
app: k8sAdmission-db
stage: devcomponent: vpc # (required) name of the component. In that case, it's vpc.
name: serverlessVpc # (required) name of your vpc component instance.inputs:region: ${env:REGION}zone: ${env:ZONE}vpcName: serverlesssubnetName: serverless

云函数负责准入逻辑判断,可以看到 handler: api_service.main_handler,也就是说云函数的入口函数是 main_handler,当有外部请求过来时,将会执行 main_handler 函数

# ./serverless/api/serverless.yml 云函数部署文件org: k8sAdmission
component: scf # (必填) 引用 component 的名称,当前用到的是 tencent-scf 组件
name: k8s # (必填) 该组件创建的实例名称
app: k8sAdmission-db # (可选) 该 SCF 应用名称
stage: dev # (可选) 用于区分环境信息,默认值是 devinputs:src: ./name: ${name}description: 基于腾讯云 Serverless 的 K8S 动态准入控制handler: api_service.main_handler  # 入口函数runtime: Python3.6 # 云函数的运行时环境。除 Nodejs10.15 外,可选值为:Python2.7、Python3.6、Nodejs6.10、Nodejs8.9、PHP5、PHP7、Golang1、Java8。region: ${env:REGION}vpcConfig:vpcId: ${output:${stage}:${app}:serverlessVpc.vpcId}subnetId: ${output:${stage}:${app}:serverlessVpc.subnetId}timeout: 10environment:variables:PG_CONNECT_STRING: ${output:${stage}:${app}:fullstackDB.private.connectionString}PG_DN_NAME: ${output:${stage}:${app}:fullstackDB.private.dbname}events:- apigw:name: k8sAdmissionparameters:protocols:- http- httpsserviceName:description: Based on Tencent Cloud Serverless, it provides dynamic access control for K8Senvironment: releaseendpoints:- path: /indexmethod: POST

云函数关键代码

我们将在首次触发(请求)时创建 TOKENS 表,并将 4 组预定义的 tokens 插入到表内。并检查我们在执行 kubectl apply yaml 文件 annotations(注解) 内携带的 tokens 是否合法,并将 token 和 Postgresql 数据库存储的 token 进行比对。

# ./serverless/api/api_service.py 云函数业务逻辑def main_handler(event,content):logger.info('start main_handler')logger.info('got event{}'.format(event))logger.info('got content{}'.format(content))# 连接数据库print('Start Serverlsess DB SDK function')conn = psycopg2.connect(DB_HOST)print("Opened database successfully")cur = conn.cursor()cur.execute('''CREATE TABLE IF NOT EXISTS TOKENS(ID INT PRIMARY KEY     NOT NULL,tokens TEXT    NOT NULL);''')conn.commit()cur.execute("select * from TOKENS")myresult = cur.fetchall()for row in myresult:print("ID = " + str(row[0]))print("tokens = " + row[1])if not bool(cur.rowcount):print("insert default tokens")cur.execute("INSERT INTO TOKENS (ID,tokens) \VALUES (1, '1111')")cur.execute("INSERT INTO TOKENS (ID,tokens) \VALUES (2, '2222')")cur.execute("INSERT INTO TOKENS (ID,tokens) \VALUES (3, '3333')")cur.execute("INSERT INTO TOKENS (ID,tokens) \VALUES (4, '4444')")conn.commit()json_dict = json.loads(event["body"])if json_dict["object"]["metadata"]["annotations"]["token"] == "":return {"errorCode":0,"errorMsg":"","allow":"false"}cur.execute("SELECT * FROM TOKENS where tokens=%s",[json_dict["object"]["metadata"]["annotations"]["token"]])myresult = cur.fetchall()allow = "false"if len(myresult) > 0:allow = "true"query_id = myresult[0][0]cur.execute("DELETE FROM TOKENS where ID=%s",[query_id])conn.commit()conn.close()return {"errorCode":0,"errorMsg":json_dict["object"]["metadata"]["annotations"]["token"],"allow":allow}

如果 token 在数据库内存在,则从数据库删除本次使用的 token,并返回 JSON 给我们在集群内部署的POD

{"errorCode":0,"errorMsg":"tokens","allow":"true"}

POD 根据 Serverless 返回的结果重新组装信息,返回如下 JSON 给 Kubernetes API Server

{"UID":"b24ab5f7-8b6b-4ea2-83ff-6f9834a9937e","Allowed":false,"Result":{"ListMeta":{"SelfLink":"","ResourceVersion":"","Continue":""},"Status":"","Message":"","Reason":"Token 错误,不允许部署","Details":"","Code":0},"Patch":"","PatchType":""
}

其中,Allowed 字段为本次 kubectl apply 是否准入关键,Reason 信息将作为结果展示。

这里可能有同学会问,为啥要通过我们部署的 POD 再调用 Serverless 服务?让 API Server 直接请求 Serverless Endpoint 不行吗?答案是不行的,因为 API Server 请求的 webhook URL 要求双向 TLS 验证,我们需要创建 Kubernetes CA 签名的 TLS 证书,确保 Webhook 和 Api Server 之间通信的安全,所以我们采用这种方式来实现。

6. 结束语

至此,我们实现了简单的 Kubernetes 两步验证。如果想实现更多的逻辑,比如判断 image 合规性、对于来源于非公司内部仓库的镜像拒绝部署,都可以在 Serverless 云函数内实现。

在生产实践中,如本例的 token,属于动态的 yaml 制品类型部署,我们可以结合 CODING 持续部署来为制品文件提供动态的参数绑定。

如果想要实现对 Deployment 动态注入 sidecar,可以利用 Mutating Webhook 监听部署的 Deployment,将需要注入的 sidecar 动态 Patch 注入。

如果想要实现集群级的 imagePullSecrets ,一个可行的思路是利用 Mutating Webhook 监听创建 namespaces 行为,自动将已存在的 imagePullSecrets Patch 到新的 namespaces 内。

实现 Mutating Webhook ,请留意项目根目录的 webhook.go 文件的 mutate 函数,原理与 Validating Webhook 类似,不同点在于其主要通过 Patch 来实现。

Kubernetes admission 通过 Webhook 的方式解耦了 kubectl 的过程,使得我们自己的业务逻辑能够动态加入到用户执行 kubectl 到返回结果的过程当中,本文的两步验证只是一个简单的 Demo,想要更加深入了解,可以浏览“参考资料”的链接。

7. 参考资料

  • In-depth introduction to Kubernetes admission webhooks
  • 可扩展 Admission 进入 Beta 阶段
  • 动态准入控制
  • Tencent Serverless
  • 本项目 Fork 的源码

    本文由博客群发一文多发等运营工具平台 OpenWrite 发布

Kubernetes 两步验证 - 使用 Serverless 实现动态准入控制相关推荐

  1. Google账户两步验证的工作原理【转】

    最近在考虑一些用户登录验证的问题,现阶段在涉及到一些交易时,基本上都使用的是短信验证码验证,但有朋友说,有些时候短信验证码会出现延时,不及时.于是就看了一下Google Authenticator(c ...

  2. 两步验证杀手锏:Java 接入 Google 身份验证器实战

    转载自   两步验证杀手锏:Java 接入 Google 身份验证器实战 什么是两步验证? 大家应该对两步验证都熟悉吧?如苹果有自带的两步验证策略,防止用户账号密码被盗而锁定手机进行敲诈,这种例子屡见 ...

  3. 苹果再遭诉讼!因两步验证太过耗时...

    限于iPhone XS系列新品的售价昂贵,创新度不足,苹果手机正在遭遇着前所未有的低迷状况,然而,即便硬件不给力,一般意义上来看,苹果在iOS系统上的优势还是存在的.但仍然有消费者不买账. 近期,名为 ...

  4. 三型两步验证登不上_苹果手机开启了双重认证,未信任的设备之前登入了,开启了双重认证未...

    展开全部 首先需要明确的一个概念:「62616964757a686964616fe58685e5aeb931333365643661双重认证」不是「两步验证」. 「双重认证」与「两步验证」的区别 尽管 ...

  5. 两步验证 非双重认证

    Two-factor authentication must be turned on for your Apple ID. After you turn it on, signing into yo ...

  6. apple id两步验证服务器,如何开启 Apple ID 两步验证

    本期教学点: 开启 Apple ID 两步验证,减小 Apple ID 被盗风险,大大提高安全性. 首先打开 Safari 浏览器,在地址栏输入 appleid.apple.com. 点击右边&quo ...

  7. Gmail设置两步验证密码后,foxmail收不到gmail邮件

    最近直接访问gmail.com可以登录gmail邮箱,但是用outlook或者foxmail登录不上,一直提示密码错误.原因很简单,就是你设置了google的两步验证密码.从这上面着手. 你需要使用应 ...

  8. Apple ID 两步验证 诡异中招 怀疑苹果服务器问题

    账户密码突然不对了, 两步验证密钥无效, 苹果官方不给解决 设备在这段时间没有任何变更 id开启了两步验证 发生问题时是store上下app提示输入密码错误 上apple官网登录账户,密码错误 点忘记 ...

  9. 印象笔记开启两步验证,绑定阿里身份宝

    1.登录网页版-印象笔记,进入个人账户页面,找到基本设置,开启两步验证,可选择短信方式或者谷歌验证器. 2.由于考虑到谷歌验证器可能需要翻墙,这里可使用阿里身份宝(http://otp.aliyun. ...

  10. 两步验证Authy时间同步问题

    Authy是我常用的软件之一,通常用于Google的两步验证,或者是其他基于Google两步验证的原理的衍生程序.比如Namesilo.印象笔记等均有使用. 先说说什么是两步验证. 两步验证 两步验证 ...

最新文章

  1. SAP PM 入门系列14 – PM模块与其它模块的集成
  2. 2021年春季学期-信号与系统-第五次作业参考答案-第九小题
  3. 设置函数环境——setfenv
  4. oracle电梯案例,Oracle技术嘉年华的一个案例,redo的那些事,连载一
  5. 马斯克公开特斯拉Model 3成本 价值这个数...
  6. java 裁剪 pdf_java – 使用iTextPDF修剪页面的空白
  7. SQL Server Profiler概述
  8. FLAT:又快又好的中文命名实体识别模型
  9. php教程 TTP中GET与POST的区别
  10. consul服务下线通知_服务注册中心如何实现秒级服务上下线通知 | SOFARegistry 解析...
  11. 软件评测师考试通过啦
  12. android app隐藏图标不见了,Android 10如何隐藏应用图标
  13. GII全球创新指数(2011-2018年)
  14. 使用计算机要遵循哪些规则,中国大学MOOC:\\\在计算机网络的定义中,把众多计算机有机连接起来要遵循规定的约定和规则,称之为( )。\\\;...
  15. SpringBoot(三)配置文件
  16. 心理学 | (1)焦虑症和恐惧症--一种认知的观点
  17. QtCreator生成标准多行函数或者类详细注释
  18. 隐马尔科夫模型模型:原理、实现
  19. 一篇文章带你了解和学会VCN安卓快速开发
  20. c# 在word文件指定位置 插入图片

热门文章

  1. 【数学建模算法汇总】
  2. SpringBoot 实现SQL脚本自动执行
  3. iphone长截图哪个软件好_iPhone上最好的长截图工具!
  4. 基于Simulink的机械式飞行操纵系统建模与仿真
  5. Linux面试必备20个常用命令
  6. JMeter录制脚本和参数化
  7. 小程序技术可以提升桌面应用安全等级?
  8. 《Python语言程序设计基础》:第2章:Python程序实例解析:程序练习题
  9. python下载文件的11种方式_python 下载文件的多种方法汇总
  10. linux grep -v多个关键字,Linux grep 命令详解