Ce document a été traduit à l'aide d'une technologie de traduction automatique. Bien que nous nous efforcions de fournir des traductions exactes, nous ne fournissons aucune garantie quant à l'exhaustivité, l'exactitude ou la fiabilité du contenu traduit. En cas de divergence, la version originale anglaise prévaut et fait foi.

Il s'agit d'une documentation non publiée pour Admission Controller 1.34-dev.

Définir les paramètres de stratégie

Tout d’abord, vous devez définir la structure qui contient les paramètres de stratégie.

Vous le faites en modifiant le code dans le fichier settings.go (à partir de votre version locale du modèle de stratégie Go). Vous devez ajouter deux lignes supplémentaires à la section import, changer la structure Settings, et ajouter la structure RegularExpression.

Cela devrait correspondre au code suivant :

import (
    "encoding/json"
    "fmt"
    "regexp"

    mapset "github.com/deckarep/golang-set/v2"
    kubewarden "github.com/kubewarden/policy-sdk-go"
    kubewarden_protocol "github.com/kubewarden/policy-sdk-go/protocol"
)

type Settings struct {
    DeniedLabels      mapset.Set[string]            `+json:"denied_labels"+`
    ConstrainedLabels map[string]*RegularExpression `+json:"constrained_labels"+`
}

type RegularExpression struct {
    *regexp.Regexp
}

DeniedNames n’est plus requis dans la structure Settings définie dans settings.go.

Comme DeniedNames n’est plus défini, vous devez également supprimer la fonction IsNameDefined dans settings.go. Vous devez également supprimer la fonction qui y fait référence dans settings_test.go, TestIsNameDenied.

Vous utilisez le paquet regexp pour gérer les objets d’expressions régulières et le type mapset.Set pour stocker la liste des étiquettes refusées.

Puisque regexp.Regexp ne gère pas la désérialisation, vous devez définir des fonctions personnalisées pour gérer la sérialisation et la désérialisation des expressions régulières :

// UnmarshalText satisfies the encoding.TextMarshaler interface,
// also used by json.Unmarshal.
func (r *RegularExpression) UnmarshalText(text []byte) error {
    nativeRegExp, err := regexp.Compile(string(text))
    if err != nil {
        return err
    }
    r.Regexp = nativeRegExp
    return nil
}

// MarshalText satisfies the encoding.TextMarshaler interface,
// also used by json.Marshal.
func (r *RegularExpression) MarshalText() ([]byte, error) {
    if r.Regexp != nil {
        return []byte(r.Regexp.String()), nil
    }

    return nil, nil
}

De plus, vous avez besoin de la méthode UnmarshalJSON pour gérer la désérialisation de la structure Settings :

func (s *Settings) UnmarshalJSON(data []byte) error {
    rawSettings := struct {
        DeniedLabels      []string          `+json:"denied_labels"+`
        ConstrainedLabels map[string]*RegularExpression `+json:"constrained_labels"+`
    }{}

    err := json.Unmarshal(data, &rawSettings)
    if err != nil {
        return err
    }

    s.DeniedLabels = mapset.NewThreadUnsafeSet[string](rawSettings.DeniedLabels...)
    s.ConstrainedLabels = rawSettings.ConstrainedLabels

    return nil
}

Création d’instances de Settings

Une stratégie Admission Controller expose deux fonctions différentes qui reçoivent les paramètres de stratégie en entrée :

  • validate : Utilisez cette fonction lorsque l’objet Kubernetes nécessite une validation par la stratégie. Les paramètres font partie d’un objet ValidationRequest.

  • validate_settings : Appelez cette fonction lorsque la politique est chargée pour la première fois par Admission Controller. La fonction reçoit les paramètres de stratégie en entrée et vérifie leur validité.

Vous devez créer une fonction d’assistance qui crée un objet Settings à partir de la charge utile JSON :

func NewSettingsFromValidationReq(validationReq *kubewarden_protocol.ValidationRequest) (Settings, error) {
    settings := Settings{}
    err := json.Unmarshal(validationReq.Settings, &settings)
    if err != nil {
        return Settings{}, err
    }

    return settings, nil
}

Tous les mapset.Set objets sont créés en utilisant le variant non sécurisé par les threads. Le code WebAssembly s’exécute dans un seul thread, donc il n’y a pas de problèmes de concurrence.

La norme WebAssembly ne couvre pas encore les threads. Voir la proposition officielle pour plus de détails.

Mise en œuvre de la validation Settings

Toutes les politiques Admission Controller doivent mettre en œuvre la validation des paramètres.

Vous le faites en ajoutant une méthode Valid aux instances Settings :

func (s *Settings) Valid() (bool, error) {
    constrainedLabels := mapset.NewThreadUnsafeSet[string]()

    for label := range s.ConstrainedLabels {
        constrainedLabels.Add(label)
    }

    constrainedAndDenied := constrainedLabels.Intersect(s.DeniedLabels)
    if constrainedAndDenied.Cardinality() != 0 {
        return false,
            fmt.Errorf("These labels cannot be constrained and denied at the same time: %v", constrainedAndDenied)
    }

    return true, nil
}

La méthode Valid garantit qu’aucun label "refusé" ne fait également partie de la carte "contraint". L’utilisation de la méthode Intersect fournie par mapset.Set simplifie la vérification.

L’invocation de la méthode Valid se fait sur un objet Setting déjà instancié. Cela signifie que la validation de l’expression régulière fournie par l’utilisateur a déjà eu lieu dans le désérialiseur Settings.

Enfin, vous avez besoin de la fonction validateSettings, fournie par l’échafaudage, pour changer afin qu’elle ressemble à ceci :

func validateSettings(payload []byte) ([]byte, error) {
    settings := Settings{}
    err := json.Unmarshal(payload, &settings)
    if err != nil {
        return kubewarden.RejectSettings(
            kubewarden.Message(fmt.Sprintf("Provided settings are not valid: %v", err)))
    }

    valid, err := settings.Valid()
    if valid {
        return kubewarden.AcceptSettings()
    }

    return kubewarden.RejectSettings(
        kubewarden.Message(fmt.Sprintf("Provided settings are not valid: %v", err)))
}

Vous pouvez voir que la fonction tire parti des fonctions utilitaires fournies par le SDK de Admission Controller.

Tester le code des paramètres

Il est important d’avoir une bonne couverture de tests du code que vous écrivez. Le code que vous utilisez, provenant de l’échafaudage, est livré avec une série de tests unitaires définis dans le fichier settings_test.go.

Vous devez changer le contenu de ce fichier pour refléter le nouveau comportement de la classe Settings.

Incluez les paquets Go que vous utilisez :

import (
    "testing"

    "encoding/json"

    kubewarden_protocol "github.com/kubewarden/policy-sdk-go/protocol"
)

Vous pouvez commencer par écrire un test unitaire qui garantit que vous pouvez assigner une instance Settings à partir d’un objet ValidationRequest :

func TestParseValidSettings(t *testing.T) {
    settingsJSON := []byte(`
        {
            "denied_labels": [ "foo", "bar" ],
            "constrained_labels": {
                    "cost-center": "cc-\\d+"
            }
        }`)

    settings := Settings{}
    err := json.Unmarshal(settingsJSON, &settings)
    if err != nil {
        t.Errorf("Unexpected error %+v", err)
    }

    expected_denied_labels := []string{"foo", "bar"}
    for _, exp := range expected_denied_labels {
        if !settings.DeniedLabels.Contains(exp) {
            t.Errorf("Missing value %s", exp)
        }
    }

    re, found := settings.ConstrainedLabels["cost-center"]
    if !found {
        t.Error("Didn't find the expected constrained label")
    }

    expected_regexp := `+cc-\d++`
    if re.String() != expected_regexp {
        t.Errorf("Expected regexp to be %v - got %v instead",
            expected_regexp, re.String())
    }
}

Ensuite, vous avez besoin d’un test qui vérifie qu’une instance Settings n’est pas générée lorsque l’utilisateur fournit une expression régulière incorrecte :

func TestParseSettingsWithInvalidRegexp(t *testing.T) {
    settingsJSON := []byte(`
        {
            "denied_labels": [ "foo", "bar" ],
            "constrained_labels": {
                    "cost-center": "cc-[a+"
            }
        }`)

    err := json.Unmarshal(settingsJSON, &Settings{})
    if err == nil {
        t.Errorf("Didn't get expected error")
    }
}

Vous pouvez maintenant définir un test qui vérifie le comportement du point d’entrée validate_settings.

Vous examinez l’objet SettingsValidationResponse retourné par votre fonction validateSettings :

func TestDetectValidSettings(t *testing.T) {
    settingsJSON := []byte(`
    {
        "denied_labels": [ "foo", "bar" ],
        "constrained_labels": {
            "cost-center": "cc-\\d+"
        }
    }`)

    responsePayload, err := validateSettings(settingsJSON)
    if err != nil {
        t.Errorf("Unexpected error %+v", err)
    }

    var response kubewarden_protocol.SettingsValidationResponse
    if err := json.Unmarshal(responsePayload, &response); err != nil {
        t.Errorf("Unexpected error: %+v", err)
    }

    if !response.Valid {
        t.Errorf("Expected settings to be valid: %s", *response.Message)
    }
}

Enfin, vous écrivez deux autres tests pour vérifier que la fonction validateSettings rejette les paramètres invalides avec les bons messages :

func TestDetectNotValidSettingsDueToBrokenRegexp(t *testing.T) {
    settingsJSON := []byte(`
    {
        "denied_labels": [ "foo", "bar" ],
        "constrained_labels": {
            "cost-center": "cc-[a+"
        }
    }
    `)

    responsePayload, err := validateSettings(settingsJSON)
    if err != nil {
        t.Errorf("Unexpected error %+v", err)
    }

    var response kubewarden_protocol.SettingsValidationResponse
    if err := json.Unmarshal(responsePayload, &response); err != nil {
        t.Errorf("Unexpected error: %+v", err)
    }

    if response.Valid {
        t.Error("Expected settings to not be valid")
    }

    if *response.Message != "Provided settings are not valid: error parsing regexp: missing closing ]: `+[a++`" {
        t.Errorf("Unexpected validation error message: %s", *response.Message)
    }
}

func TestDetectNotValidSettingsDueToConflictingLabels(t *testing.T) {
    settingsJSON := []byte(`
    {
        "denied_labels": [ "foo", "bar", "cost-center" ],
        "constrained_labels": {
            "cost-center": ".*"
        }
    }`)
    responsePayload, err := validateSettings(settingsJSON)
    if err != nil {
        t.Errorf("Unexpected error %+v", err)
    }

    var response kubewarden_protocol.SettingsValidationResponse
    if err := json.Unmarshal(responsePayload, &response); err != nil {
        t.Errorf("Unexpected error: %+v", err)
    }

    if response.Valid {
        t.Error("Expected settings to not be valid")
    }

    if *response.Message != "Provided settings are not valid: These labels cannot be constrained and denied at the same time: Set{cost-center}" {
        t.Errorf("Unexpected validation error message: %s", *response.Message)
    }
}

Vous pouvez maintenant exécuter les tests que vous avez définis jusqu’à présent en utilisant la commande suivante :

go test -v settings.go settings_test.go

Tous les tests réussiront avec la sortie suivante :

=== 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)
PASS
ok      command-line-arguments    0.002s

Vous pouvez maintenant implémenter le code de validation réel dans la section suivante.