By Jiahui Feng (Google) |
我代表 Kubernetes 项目组成员,很高兴地宣布 ValidatingAdmissionPolicy 已经作为 Kubernetes 1.30 发布的一部分正式发布。
如果你还不了解这个全新的声明式验证准入 Webhook 的替代方案,
请参阅有关这个新特性的上一篇博文。
如果你已经对 ValidatingAdmissionPolicy 有所了解并且想要尝试一下,那么现在是最好的时机。
让我们替换一个简单的 Webhook,体验一下 ValidatingAdmissionPolicy。
准入 Webhook 示例
首先,让我们看一个简单 Webhook 的示例。以下是一个强制将
runAsNonRoot
、readOnlyRootFilesystem
、allowPrivilegeEscalation
和 privileged
设置为最低权限值的 Webhook 代码片段。
func verifyDeployment(deploy *appsv1.Deployment) error {
var errs []error
for i, c := range deploy.Spec.Template.Spec.Containers {
if c.Name == "" {
return fmt.Errorf("container %d has no name", i)
}
if c.SecurityContext == nil {
errs = append(errs, fmt.Errorf("container %q does not have SecurityContext", c.Name))
}
if c.SecurityContext.RunAsNonRoot == nil || !*c.SecurityContext.RunAsNonRoot {
errs = append(errs, fmt.Errorf("container %q must set RunAsNonRoot to true in its SecurityContext", c.Name))
}
if c.SecurityContext.ReadOnlyRootFilesystem == nil || !*c.SecurityContext.ReadOnlyRootFilesystem {
errs = append(errs, fmt.Errorf("container %q must set ReadOnlyRootFilesystem to true in its SecurityContext", c.Name))
}
if c.SecurityContext.AllowPrivilegeEscalation != nil && *c.SecurityContext.AllowPrivilegeEscalation {
errs = append(errs, fmt.Errorf("container %q must NOT set AllowPrivilegeEscalation to true in its SecurityContext", c.Name))
}
if c.SecurityContext.Privileged != nil && *c.SecurityContext.Privileged {
errs = append(errs, fmt.Errorf("container %q must NOT set Privileged to true in its SecurityContext", c.Name))
}
}
return errors.NewAggregate(errs)
}
查阅什么是准入 Webhook?,
或者查看这个 Webhook 的完整代码以便更好地理解下述演示。
策略
现在,让我们尝试使用 ValidatingAdmissionPolicy 来忠实地重新创建验证。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "pod-security.policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: object.spec.template.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.runAsNonRoot) && c.securityContext.runAsNonRoot)
message: 'all containers must set runAsNonRoot to true'
- expression: object.spec.template.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.readOnlyRootFilesystem) && c.securityContext.readOnlyRootFilesystem)
message: 'all containers must set readOnlyRootFilesystem to true'
- expression: object.spec.template.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.allowPrivilegeEscalation) || !c.securityContext.allowPrivilegeEscalation)
message: 'all containers must NOT set allowPrivilegeEscalation to true'
- expression: object.spec.template.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.Privileged) || !c.securityContext.Privileged)
message: 'all containers must NOT set privileged to true'
使用 kubectl
创建策略。很好,到目前为止没有任何问题。那我们获取此策略对象并查看其状态。
kubectl get -oyaml validatingadmissionpolicies/pod-security.policy.example.com
status:
typeChecking:
expressionWarnings:
- fieldRef: spec.validations[3].expression
warning: |
apps/v1, Kind=Deployment: ERROR: :1:76: undefined field 'Privileged'
| object.spec.template.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.Privileged) || !c.securityContext.Privileged)
| ...........................................................................^
ERROR: :1:128: undefined field 'Privileged'
| object.spec.template.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.Privileged) || !c.securityContext.Privileged)
| ...............................................................................................................................^
系统根据所匹配的类别 apps/v1.Deployment
对策略执行了检查。
查看 fieldRef
后,发现问题出现在第 3 个表达式上(索引从 0 开始)。
有问题的表达式访问了一个未定义的 Privileged
字段。
噢,看起来是一个复制粘贴错误。字段名应该是小写的。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "pod-security.policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: object.spec.template.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.runAsNonRoot) && c.securityContext.runAsNonRoot)
message: 'all containers must set runAsNonRoot to true'
- expression: object.spec.template.spec.containers.all(c, has(c.securityContext) && has(c.securityContext.readOnlyRootFilesystem) && c.securityContext.readOnlyRootFilesystem)
message: 'all containers must set readOnlyRootFilesystem to true'
- expression: object.spec.template.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.allowPrivilegeEscalation) || !c.securityContext.allowPrivilegeEscalation)
message: 'all containers must NOT set allowPrivilegeEscalation to true'
- expression: object.spec.template.spec.containers.all(c, !has(c.securityContext) || !has(c.securityContext.privileged) || !c.securityContext.privileged)
message: 'all containers must NOT set privileged to true'
再次检查状态,你应该看到所有警告都已被清除。
接下来,我们创建一个命名空间进行测试。
kubectl create namespace policy-test
接下来,我将策略绑定到命名空间。但此时我将动作设置为 Warn
,
这样此策略将打印出警告而不是拒绝请求。
这对于在开发和自动化测试期间收集所有表达式的结果非常有用。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "pod-security.policy-binding.example.com"
spec:
policyName: "pod-security.policy.example.com"
validationActions: ["Warn"]
matchResources:
namespaceSelector:
matchLabels:
"kubernetes.io/metadata.name": "policy-test"
测试策略的执行过程。
kubectl create -n policy-test -f-