この文書は自動機械翻訳技術を使用して翻訳されています。 正確な翻訳を提供するように努めておりますが、翻訳された内容の完全性、正確性、信頼性については一切保証いたしません。 相違がある場合は、元の英語版 英語 が優先され、正式なテキストとなります。

これは未公開の文書です Admission Controller 1.34-dev.

検証ロジックの作成

検証ロジックは`validate.go`ファイルに配置されます。

検証ロジックは次のようにする必要があります:

  • 受信した`payload`オブジェクトから関連情報を抽出します。

  • 入力とポリシー設定に基づいて応答を返します。

受信したペイロードはJSONオブジェクトであり、この文書で説明されています。データは2つの方法で取得できます:

  1. JSONデータをGo型にアンマーシャルします。

  2. JSONクエリを実行します(https://stedolan.github.io/jq/[jq]に似たもの)。

このドキュメントのこのセクションは、Go型を使用した最初のアプローチに焦点を当てています。 2番目のアプローチの説明は、後のクエリを使用した検証セクションにあります。

Kubernetesオブジェクトに依存することは、jqのような検索を行う代わりに、より大きなWebAssemblyモジュールの生成につながります。 Kubernetesオブジェクトを使用するポリシーは約1.5 MBであり、`gjson`を使用するポリシーは約300 KBです。

サイズ以外にも、Kubernetesオブジェクトを使用するポリシーは初回実行時にはるかに多くの時間を要します。 その後の呼び出しは速く、SUSE Security Admission ControllerがWasmtimeのキャッシュ機能を使用するためです。 最初の実行には約20秒かかる場合があり、以降の実行は1〜2秒です。 したがって、Admission Controllerポリシーサーバーは起動時間が遅いですが、その後のポリシー評価時間は通常Kubernetesオブジェクトの使用によって影響を受けません。

`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`セクションがあります:

  1. JSONペイロードをアンマーシャルして`kubewarden_protocol.ValidationRequest`を作成します。

  2. `settings.go`ファイルで以前に定義した関数を使用して`Settings`オブジェクトを作成します。

  3. `ValidationRequest`の一部であるPodの生のJSON表現にアクセスします。

  4. Podオブジェクトをアンマーシャルします。

  5. 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。 ここでは、次のコードを使用して異なるテストケースを繰り返します:

  1. `testCase`から提供されたデータを使用して`BasicSettings`オブジェクトを作成します。

  2. `testCase`で定義されたラベルを割り当てて`Pod`オブジェクトを作成します。

  3. payload`オブジェクトを作成します。Admission Controller SDKのヘルパー関数を使用してこれを行います:`kubewarden_testing.BuildValidationRequest。 この関数は、リクエストに関するオブジェクト、Pod、および設定を説明するオブジェクト、`BasicSettings`インスタンスを入力として受け取ります。

  4. 最後に、コードはあなたの`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`のテストケースの1つは合格していません:

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にはこのラベルがないため、拒否する必要があります。 しかし、これは発生していないので、次のセクションで修正できます。

ユニットテストの出力に`NATIVE: |{"level":"debug","message":"validating pod object","name":"test-pod","namespace":"default"}`のような行が表示される理由を疑問に思っているかもしれません。

ポリシー内の`logger`ステートメントはこの出力を生成します。 これは、コードがWebAssemblyコンテキストの外で実行されるときのみ発生します。 ポリシーがAdmission Controllerで評価されるときにはこれが発生せず、そのコンテキストでは`logger`ステートメントがOpenTelemetryイベントを発生させます。

壊れたユニットテストを修正してください

発見した壊れたテストを修正するには、`validate`の`validate.go`で検証関数に変更を加える必要があります。

現在、あなたの検証ロジックのコアは以下の行です:

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

ご覧の通り、今回はすべてのテストが合格しました。 これで次のステップであるエンドツーエンドテストの作成に進むことができます。