|
本文档采用自动化机器翻译技术翻译。 尽管我们力求提供准确的译文,但不对翻译内容的完整性、准确性或可靠性作出任何保证。 若出现任何内容不一致情况,请以原始 英文 版本为准,且原始英文版本为权威文本。 |
|
这是尚未发布的文档。 Admission Controller 1.34-dev. |
编写验证逻辑
验证逻辑应位于 validate.go 文件中。
您的验证逻辑需要:
-
从传入的
payload对象中提取相关信息。 -
根据输入和策略设置返回响应。
传入的有效负载是一个 JSON 对象,描述在本文件中,您可以通过两种方式从中获取数据:
-
将 JSON 数据解组为 Go 类型。
-
执行 JSON 查询(类似于
jq)。
本节文档重点介绍第一种方法,使用 Go 类型。 第二种方法的描述在后面的 使用查询的验证 部分中。
|
依赖 Kubernetes 对象,而不是进行类似 jq 的搜索,会导致生成更大的 WebAssembly 模块。
使用 Kubernetes 对象的策略大约为 1.5 MB,而使用 除了大小,使用 Kubernetes 对象的策略在第一次执行时需要更多时间。
后续调用很快,因为 SUSE Security Admission Controller 使用了 Wasmtime 的缓存功能。
第一次执行可能需要大约 20 秒 |
validate 函数
由脚手架模板提供的策略在 validate.go 中已经包含 validate 函数。
您需要对其进行一些更改以完成本教程。
完成时,函数应如下所示:
func validate(payload []byte) ([]byte, error) {
// NOTE 1
// Create a ValidationRequest instance from the incoming payload
validationRequest := kubewarden_protocol.ValidationRequest{}
err := json.Unmarshal(payload, &validationRequest)
if err != nil {
return kubewarden.RejectRequest(
kubewarden.Message(err.Error()),
kubewarden.Code(400))
}
// NOTE 2
// Create a Settings instance from the ValidationRequest object
settings, err := NewSettingsFromValidationReq(&validationRequest)
if err != nil {
return kubewarden.RejectRequest(
kubewarden.Message(err.Error()),
kubewarden.Code(400))
}
// NOTE 3
// Access the **raw** JSON that describes the object
podJSON := validationRequest.Request.Object
// NOTE 4
// Try to create a Pod instance using the RAW JSON we got from the
// ValidationRequest.
pod := &corev1.Pod{}
if err := json.Unmarshal([]byte(podJSON), pod); err != nil {
return kubewarden.RejectRequest(
kubewarden.Message(
fmt.Sprintf("Cannot decode Pod object: %s", err.Error())),
kubewarden.Code(400))
}
logger.DebugWithFields("validating pod object", func(e onelog.Entry) {
e.String("name", pod.Metadata.Name)
e.String("namespace", pod.Metadata.Namespace)
})
// NOTE 5
for label, value := range pod.Metadata.Labels {
if err := validateLabel(label, value, &settings); err != nil {
return kubewarden.RejectRequest(
kubewarden.Message(err.Error()),
kubewarden.NoCode)
}
}
return kubewarden.AcceptRequest()
}
代码包含 NOTE 个部分:
-
通过反序列化 JSON 有效负载来创建
kubewarden_protocol.ValidationRequest。 -
通过使用您在
settings.go文件中之前定义的函数来创建Settings对象。 -
访问属于
ValidationRequest的 Pod 的原始 JSON 表示。 -
反序列化 Pod 对象。
-
遍历 Pod 的标签。 您使用一个名为
validateLabel的新函数来识别违反策略的标签。
您还需要在 validate.go 文件中定义 validateLabel 函数:
func validateLabel(label, value string, settings *Settings) error {
if settings.DeniedLabels.Contains(label) {
return fmt.Errorf("Label %s is on the deny list", label)
}
regExp, found := settings.ConstrainedLabels[label]
if found {
// This is a constrained label
if !regExp.Match([]byte(value)) {
return fmt.Errorf("The value of %s doesn't pass user-defined constraint", label)
}
}
return nil
}
测试验证代码
现在您可以编写单元测试以检查验证代码的行为。
在 validate_test.go 文件中找到测试。
您应该替换脚手架文件的内容以匹配以下内容:
validate_test.go
package main
import (
"regexp"
"testing"
"encoding/json"
mapset "github.com/deckarep/golang-set/v2"
corev1 "github.com/kubewarden/k8s-objects/api/core/v1"
metav1 "github.com/kubewarden/k8s-objects/apimachinery/pkg/apis/meta/v1"
kubewarden_protocol "github.com/kubewarden/policy-sdk-go/protocol"
kubewarden_testing "github.com/kubewarden/policy-sdk-go/testing"
)
func TestValidateLabel(t *testing.T) {
// NOTE 1
cases := []struct {
podLabels map[string]string
deniedLabels mapset.Set[string]
constrainedLabels map[string]*RegularExpression
expectedIsValid bool
}{
{
// ➀
// Pod has no labels -> should be accepted
podLabels: map[string]string{},
deniedLabels: mapset.NewThreadUnsafeSet[string]("owner"),
constrainedLabels: map[string]*RegularExpression{},
expectedIsValid: true,
},
{
// ➁
// Pod has labels, none is denied -> should be accepted
podLabels: map[string]string{
"hello": "world",
},
deniedLabels: mapset.NewThreadUnsafeSet[string]("owner"),
constrainedLabels: map[string]*RegularExpression{},
expectedIsValid: true,
},
{
// ➂
// Pod has labels, one is denied -> should be rejected
podLabels: map[string]string{
"hello": "world",
},
deniedLabels: mapset.NewThreadUnsafeSet[string]("hello"),
constrainedLabels: map[string]*RegularExpression{},
expectedIsValid: false,
},
{
// ➃
// Pod has labels, one has constraint that is respected -> should be accepted
podLabels: map[string]string{
"cc-center": "team-123",
},
deniedLabels: mapset.NewThreadUnsafeSet[string]("hello"),
constrainedLabels: map[string]*RegularExpression{
"cc-center": {
Regexp: regexp.MustCompile(`+team-\d++`),
},
},
expectedIsValid: true,
},
{
// ➄
// Pod has labels, one has constraint that are not respected -> should be rejected
podLabels: map[string]string{
"cc-center": "team-kubewarden",
},
deniedLabels: mapset.NewThreadUnsafeSet[string]("hello"),
constrainedLabels: map[string]*RegularExpression{
"cc-center": {
Regexp: regexp.MustCompile(`+team-\d++`),
},
},
expectedIsValid: false,
},
{
// ➅
// Settings have a constraint, pod doesn't have this label -> should be rejected
podLabels: map[string]string{
"owner": "team-kubewarden",
},
deniedLabels: mapset.NewThreadUnsafeSet[string]("hello"),
constrainedLabels: map[string]*RegularExpression{
"cc-center": {
Regexp: regexp.MustCompile(`+team-\d++`),
},
},
expectedIsValid: false,
},
}
// NOTE 2
for _, testCase := range cases {
settings := Settings{
DeniedLabels: testCase.deniedLabels,
ConstrainedLabels: testCase.constrainedLabels,
}
pod := corev1.Pod{
Metadata: &metav1.ObjectMeta{
Name: "test-pod",
Namespace: "default",
Labels: testCase.podLabels,
},
}
payload, err := kubewarden_testing.BuildValidationRequest(&pod, &settings)
if err != nil {
t.Errorf("Unexpected error: %+v", err)
}
responsePayload, err := validate(payload)
if err != nil {
t.Errorf("Unexpected error: %+v", err)
}
var response kubewarden_protocol.ValidationResponse
if err := json.Unmarshal(responsePayload, &response); err != nil {
t.Errorf("Unexpected error: %+v", err)
}
if testCase.expectedIsValid && !response.Accepted {
t.Errorf("Unexpected rejection: msg %s - code %d with pod labels: %v, denied labels: %v, constrained labels: %v",
*response.Message, *response.Code, testCase.podLabels, testCase.deniedLabels, testCase.constrainedLabels)
}
if !testCase.expectedIsValid && response.Accepted {
t.Errorf("Unexpected acceptance with pod labels: %v, denied labels: %v, constrained labels: %v",
testCase.podLabels, testCase.deniedLabels, testCase.constrainedLabels)
}
}
}
该测试使用 "测试用例驱动" 方法。
您首先定义一个 struct,它保存测试用例所需的数据,见 NOTE 1:
struct {
podLabels map[string]string
deniedLabels mapset.Set[string]
constrainedLabels map[string]*RegularExpression
expectedIsValid bool
}
然后您声明几个测试用例。 它们在上面的可折叠代码块中以 ➀ 到 ➅ 的起始行标记。
例如,您应该将没有标签的 Pod 视为有效。 您可以使用以下输入值进行测试:
{
podLabels: map[string]string{},
deniedLabels: mapset.NewThreadUnsafeSet[string]("owner"),
constrainedLabels: map[string]*RegularExpression{},
expectedIsValid: true,
}
测试以这种方式定义新场景,直到 NOTE 2。
在这里,您可以使用以下代码遍历不同的测试用例:
-
通过使用
testCase提供的数据创建一个BasicSettings对象。 -
创建一个
Pod对象,并将testCase中定义的标签分配给它。 -
创建一个
payload对象。使用 Admission Controller SDK 的辅助函数:kubewarden_testing.BuildValidationRequest来完成此操作。 此函数以请求相关的对象、Pod和描述设置的对象(BasicSettings实例)作为输入。 -
最后,代码调用您的
validate函数并对结果进行检查。
现在,您可以运行所有单元测试,包括在 settings_test.go 中定义的测试,方法是:
make test
这将产生以下输出:
来自 make test 的输出
make test
go test -v
=== RUN TestParsingSettingsWithNoValueProvided
--- PASS: TestParsingSettingsWithNoValueProvided (0.00s)
=== RUN TestIsNameDenied
--- PASS: TestIsNameDenied (0.00s)
=== RUN TestParseValidSettings
--- PASS: TestParseValidSettings (0.00s)
=== RUN TestParseSettingsWithInvalidRegexp
--- PASS: TestParseSettingsWithInvalidRegexp (0.00s)
=== RUN TestDetectValidSettings
--- PASS: TestDetectValidSettings (0.00s)
=== RUN TestDetectNotValidSettingsDueToBrokenRegexp
--- PASS: TestDetectNotValidSettingsDueToBrokenRegexp (0.00s)
=== RUN TestDetectNotValidSettingsDueToConflictingLabels
--- PASS: TestDetectNotValidSettingsDueToConflictingLabels (0.00s)
=== RUN TestValidateLabel
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
validate_test.go:126: Unexpected acceptance with pod labels: map[owner:team-kubewarden], denied labels: Set{hello}, constrained labels: map[cc-center:team-\d+]
--- FAIL: TestValidateLabel (0.00s)
FAIL
exit status 1
FAIL github.com/kubewarden/go-policy-template 0.003s
make: *** [Makefile:29: test] Error 1
正如您所看到的,所有 Settings 测试都通过了,但有一个 TestValidateLabel 的测试用例没有通过:
validate_test.go:126: Unexpected acceptance with pod labels: map[owner:team-kubewarden], denied labels: Set{hello}, constrained labels: map[cc-center:team-\d+]
在这种情况下,您的策略设置表示 Pods 必须有一个标签,键为 cc-center,满足 team-\d+ 正则表达式。
被测试的 Pod 没有这个标签,因此您应该拒绝它。
然而,这并没有发生,因此您可以在下一部分中修复此问题。
|
您可能会想知道为什么单元测试的输出中会出现像 策略中的 |
修复损坏的单元测试
要修复您发现的损坏测试,您需要修改位于 validate.go 中的 validate 验证函数。
目前,您验证逻辑的核心是以下几行:
for label, value := range pod.Metadata.Labels {
if err := validateLabel(label, value, &settings); err != nil {
return kubewarden.RejectRequest(
kubewarden.Message(err.Error()),
kubewarden.NoCode)
}
}
在这里,您遍历每个标签以检查它是否未被拒绝,并且它是否没有违反用户指定的约束。
然而,您并没有确保 Pod 拥有 Settings.ConstrainedLabels 中指定的所有标签。
在 for 循环后添加新代码:
for requiredLabel := range settings.ConstrainedLabels {
_, found := pod.Metadata.Labels[requiredLabel]
if !found {
return kubewarden.RejectRequest(
kubewarden.Message(fmt.Sprintf(
"Constrained label %s not found inside of Pod",
requiredLabel),
),
kubewarden.NoCode)
}
}
再次运行单元测试:
make test
这将输出:
最终 make test 的输出
make test
go test -v
=== RUN TestParsingSettingsWithNoValueProvided
--- PASS: TestParsingSettingsWithNoValueProvided (0.00s)
=== RUN TestIsNameDenied
--- PASS: TestIsNameDenied (0.00s)
=== RUN TestParseValidSettings
--- PASS: TestParseValidSettings (0.00s)
=== RUN TestParseSettingsWithInvalidRegexp
--- PASS: TestParseSettingsWithInvalidRegexp (0.00s)
=== RUN TestDetectValidSettings
--- PASS: TestDetectValidSettings (0.00s)
=== RUN TestDetectNotValidSettingsDueToBrokenRegexp
--- PASS: TestDetectNotValidSettingsDueToBrokenRegexp (0.00s)
=== RUN TestDetectNotValidSettingsDueToConflictingLabels
--- PASS: TestDetectNotValidSettingsDueToConflictingLabels (0.00s)
=== RUN TestValidateLabel
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}
|
--- PASS: TestValidateLabel (0.00s)
PASS
ok github.com/kubewarden/go-policy-template 0.003s
如您所见,这次所有测试都通过了。 您现在可以进入下一步,编写端到端测试。