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.
72
89%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Follow these steps IN ORDER. Each step depends on the previous.
File: pkg/apis/configuration/v1/types.go
type MyPolicy struct { ... })*MyPolicy pointer field to PolicySpec*bool/*int = optional/nullable. Plain bool/int = required or zero-defaultfalse must be non-pointer value typesRun make update-codegen to update zz_generated.deepcopy.go.
Run make update-crds to regenerate config/crd/bases/, deploy/crds.yaml, and chart CRDs.
File: pkg/apis/configuration/validation/policy.go
validate<MyPolicy>(spec *v1.MyPolicy, fieldPath *field.Path) field.ErrorListvalidatePolicySpec() with field count increment and feature gate checkpolicy_test.go with valid and invalid casesFile: internal/configs/version2/http.go
type MyPolicyConfig struct { ... })*MyPolicyConfig or fields to Server, Location, or bothVirtualServerConfigFile: internal/configs/policy.go
policiesCfgadd<MyPolicy>Config() method following the pattern belowswitch in generatePolicies()policy_test.goFile: internal/configs/virtualserver.go
GenerateVirtualServerConfig(), extract from policiesCfg and assign to version2 fieldsaddPoliciesCfgToLocation() for location-level assignmentFile: internal/configs/ingress.go
generateNginxCfg(), extract from policiesCfg and assign to version1 fieldsgenerateNginxCfgForMergeableIngresses()internal/configs/version2/nginx.virtualserver.tmpl and internal/configs/version2/nginx-plus.virtualserver.tmplinternal/configs/version1/nginx.ingress.tmpl and internal/configs/version1/nginx-plus.ingress.tmpl{{- if }} / {{- with }} guards around directive blocksinternal/configs/version2/template_helper.go and/or internal/configs/version1/template_helper.go, matching the template version you are updatingserver{}server{}, location-level inside each location{}Files: internal/configs/version2/templates_test.go (VS/VSR/TS), internal/configs/version1/template_test.go (Ingress)
make test-update-snaps.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.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.
charts/nginx-ingress/values.yaml -- add value with ## doccharts/nginx-ingress/values.schema.json -- add schema entrycharts/nginx-ingress/templates/_helpers.tpl -- add CLI arg or ConfigMap keycharts/tests/testdata/ -- add test values filecharts/tests/helmunit_test.go -- add test caseFile: internal/k8s/
syncPolicy(), ensure the new type is handled for VS/VSR/IngressDirectory: tests/suite/
tests/data/<feature>/test_<feature>_policies_vs.py, _vsr.py, _ingress.py@pytest.mark.policies and @pytest.mark.policies_<feature> markerspyproject.toml -- pytest runs with --strict-markersmake update-codegen after changing types.go -- the build will fail with missing DeepCopy methodscontainsDangerousChars() validationmake update-crds also refreshes deploy/crds*.yaml and docs/crd/; charts/nginx-ingress/crds is a symlink to config/crd/bases/make telemetry-schema -- CI fails on any diff in internal/telemetrypoliciesCfg duplicate check must warn and return, not error (exception: addCORSConfig has no duplicate check -- it overwrites, since CORS is additive via headers)Every add*Config() method in internal/configs/policy.go follows this pattern:
func (p *policiesCfg) addMyPolicyConfig(spec *conf_v1.MyPolicy, key, namespace string,
secretRefs map[string]*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
secretRef := secretRefs[secretKey]
if secretRef.Error != nil {
res.isError = true
res.addWarningf("secret %s has error: %v", secretKey, secretRef.Error)
return res
}
if secretRef.Type != secrets.SecretTypeExpected {
res.isError = true
res.addWarningf("secret %s has wrong type", secretKey)
return res
}
// 3. Build template struct and assign
p.MyPolicy = &version2.MyPolicyConfig{
Field1: spec.Field1,
Field2: spec.Field2,
Secret: secretRef.Path,
}
return res
}{{- with $s.MyPolicy }}
my_directive {{ .Value }};
{{- if .OptionalField }}
my_optional_directive {{ .OptionalField }};
{{- end }}
{{- end }}95d3987
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.