Skip to main content

nic-add-policy

Step-by-step checklist for adding a new Policy CRD type to NIC. Use when implementing a new policy like AccessControl, RateLimit, JWTAuth, ExternalAuth, BasicAuth, IngressMTLS, EgressMTLS, OIDC, WAF, APIKey, Cache, or CORS, or extending the policy system with a new policy type.

Informations de source

Dépôt
nginx/kubernetes-ingress
Dernière activité de la source
28 septembre 2026 à 10:05
Langue détectée de SKILL.md
anglais
Étoiles
5 086
Forks
2 055

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
nic-add-policy
description
Step-by-step checklist for adding a new Policy CRD type to NIC. Use when implementing a new policy like AccessControl, RateLimit, JWTAuth, ExternalAuth, BasicAuth, IngressMTLS, EgressMTLS, OIDC, WAF, APIKey, Cache, or CORS, or extending the policy system with a new policy type.
# Adding a New Policy Type Follow these steps IN ORDER. Each step depends on the previous. ## Step 1: Define the CRD type File: `pkg/apis/configuration/v1/types.go` - Add a new struct (e.g., `type MyPolicy struct { ... }`) - Add a `*MyPolicy` pointer field to `PolicySpec` - Use kubebuilder markers for validation - JSON tags: **kebab-case** for NGINX-proxy fields, **camelCase** for K8s fields - `*bool`/`*int` = optional/nullable. Plain `bool`/`int` = required or zero-default - Booleans defaulting to `false` must be non-pointer value types ## Step 2: Regenerate deep copy Run `make update-codegen` to update `zz_generated.deepcopy.go`. ## Step 3: Regenerate CRDs Run `make update-crds` to regenerate `config/crd/bases/`, `deploy/crds.yaml`, and chart CRDs. ## Step 4: Add validation File: `pkg/apis/configuration/validation/policy.go` - Add `validate<MyPolicy>(spec *v1.MyPolicy, fieldPath *field.Path) field.ErrorList` - Wire into `validatePolicySpec()` with field count increment and feature gate check - Add tests in `policy_test.go` with valid and invalid cases ## Step 5: Add template structs File: `internal/configs/version2/http.go` - Add struct (e.g., `type MyPolicyConfig struct { ... }`) - Add `*MyPolicyConfig` or fields to `Server`, `Location`, or both - If the policy needs HTTP-level directives (zones, maps), add fields to `VirtualServerConfig` ## Step 6: Add config generation File: `internal/configs/policy.go` - Add field(s) to `policiesCfg` - Add `add<MyPolicy>Config()` method following the pattern below - Wire into the `switch` in `generatePolicies()` - Add tests in `policy_test.go` ## Step 7: Wire into VirtualServer generation File: `internal/configs/virtualserver.go` - In `GenerateVirtualServerConfig()`, extract from `policiesCfg` and assign to `version2` fields - Use `addPoliciesCfgToLocation()` for location-level assignment ## Step 8: Wire into Ingress generation (if applicable) File: `internal/configs/ingress.go` - In `generateNginxCfg()`, extract from `policiesCfg` and assign to `version1` fields - Handle mergeable ingress in `generateNginxCfgForMergeableIngresses()` ## Step 9: Add NGINX template directives - Version 2: `internal/configs/version2/nginx.virtualserver.tmpl` and `internal/configs/version2/nginx-plus.virtualserver.tmpl` - Version 1: `internal/configs/version1/nginx.ingress.tmpl` and `internal/configs/version1/nginx-plus.ingress.tmpl` - Use `{{- if }}` / `{{- with }}` guards around directive blocks - Template helpers go in `internal/configs/version2/template_helper.go` and/or `internal/configs/version1/template_helper.go`, matching the template version you are updating - HTTP-level directives (zones, maps) go BEFORE `server{}` - Server-level inside `server{}`, location-level inside each `location{}` ## Step 10: Update snapshot tests Files: `internal/configs/version2/templates_test.go` (VS/VSR/TS), `internal/configs/version1/template_test.go` (Ingress) 1. Add the new policy fields to the fixture structs used by the snapshot tests -- a regeneration with no fixture change produces no diff and leaves the policy untested. 2. Run `make test-update-snaps`. 3. `git diff -- '**/__snapshots__/**'` and confirm your directives render in the golden files for every edition the policy supports. Plus-only policies (OIDC, WAF) must appear in the Plus golden files **only**; policies available to both editions must appear in both. 4. Run `make test` to confirm green, and commit the regenerated golden files with the template change. If you wired the policy into Ingress (Step 8), version1 snapshots must change too. ## Step 11: Update the Helm chart (if policy needs CLI flag or ConfigMap entry) - `charts/nginx-ingress/values.yaml` -- add value with `##` doc - `charts/nginx-ingress/values.schema.json` -- add schema entry - `charts/nginx-ingress/templates/_helpers.tpl` -- add CLI arg or ConfigMap key - `charts/tests/testdata/` -- add test values file - `charts/tests/helmunit_test.go` -- add test case ## Step 12: Add controller support File: `internal/k8s/` - In `syncPolicy()`, ensure the new type is handled for VS/VSR/Ingress - Check if it needs feature-gate guarding (isPlus, enableOIDC, etc.) If the policy references secrets: - Add every Secret field to `policySecretIndexFunc()` through `collectPolicySecretRefs()` or `collectWAFSecretRefs()`. - Resolve each reference during extended-resource construction with `secretStore.GetSecret(namespacedKey, role)`. - Store the result under `secrets.RefKey(namespacedKey, role)`. - Select the role from the reference site's semantics; never infer it from `Secret.type` or Secret data. - Ensure `syncPolicy()` fans out to every supported VS, VSR, and Ingress consumer. - Add index tests covering add, update, delete, cross-namespace references, and duplicate references. ## Step 13: Write integration tests Directory: `tests/suite/` - Create test data YAMLs in `tests/data/<feature>/` - Create `test_<feature>_policies_vs.py`, `_vsr.py`, `_ingress.py` - Use `@pytest.mark.policies` and `@pytest.mark.policies_<feature>` markers - Register the new marker in `pyproject.toml` -- pytest runs with `--strict-markers` --- ## Gotchas - **Never** skip `make update-codegen` after changing `types.go` -- the build will fail with missing DeepCopy methods - **Never** use raw user strings in NGINX config without `containsDangerousChars()` validation - Both OSS and Plus templates must be updated for policies available to both editions -- they are separate files, each with its own snapshot entries. Plus-only policies (OIDC, WAF) belong in the Plus templates only - A policy that reaches a template but has no snapshot fixture ships with zero rendered-output coverage - `make update-crds` also refreshes `deploy/crds*.yaml` and `docs/crd/`; `charts/nginx-ingress/crds` is a symlink to `config/crd/bases/` - If the policy adds telemetry counters, run `make telemetry-schema` -- CI fails on any diff in `internal/telemetry` - `policiesCfg` duplicate check must warn and return, not error (exception: `addCORSConfig` has no duplicate check -- it overwrites, since CORS is additive via headers) --- ## Policy add*Config() Pattern Every `add*Config()` method in `internal/configs/policy.go` follows this pattern: ```go func (p *policiesCfg) addMyPolicyConfig(spec *conf_v1.MyPolicy, key, namespace string, secretRefs map[secrets.SecretRefKey]*secrets.SecretReference) *validationResults { res := newValidationResults() // 1. Duplicate check if p.MyPolicy != nil { res.addWarningf("MyPolicy policy already configured, ignoring") return res } // 2. Secret resolution (if applicable) secretKey := namespace + "/" + spec.Secret refKey := secrets.RefKey(secretKey, secrets.RoleExpected) secretRef, ok := secretRefs[refKey] if !ok || secretRef == nil { res.isError = true res.addWarningf("secret %s could not be resolved", secretKey) return res } if secretRef.Error != nil { res.isError = true res.addWarningf("secret %s is invalid: %v", secretKey, secretRef.Error) return res } // 3. Build template struct and assign p.MyPolicy = &version2.MyPolicyConfig{ Field1: spec.Field1, Field2: spec.Field2, Secret: secretRef.Path, } return res } ``` ## NGINX Template Pattern ```nginx {{- with $s.MyPolicy }} my_directive {{ .Value }}; {{- if .OptionalField }} my_optional_directive {{ .OptionalField }}; {{- end }} {{- end }} ```
Voir sur GitHub