Skip to content

feat: Add NodePort namespace range policy#713

Closed
elhamafzalizadeh wants to merge 5 commits intoopen-policy-agent:masterfrom
elhamafzalizadeh:nodeport-namespace-port-range
Closed

feat: Add NodePort namespace range policy#713
elhamafzalizadeh wants to merge 5 commits intoopen-policy-agent:masterfrom
elhamafzalizadeh:nodeport-namespace-port-range

Conversation

@elhamafzalizadeh
Copy link
Copy Markdown

Gatekeeper Policy: K8sNodePortNamespaceRange

This is a Gatekeeper constraint that restricts the NodePort ranges based on team namespaces. Its purpose is to prevent accidental or uncoordinated NodePort assignments in the cluster.

Policy Details

  • apiVersion: constraints.gatekeeper.sh/v1beta1
  • kind: K8sNodePortNamespaceRange
  • metadata.name: enforce-nodeport-namespace-range
  • enforcementAction: deny (any Service violating this policy will be denied)

Copy link
Copy Markdown
Contributor

@JaydipGabani JaydipGabani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elhamafzalizadeh you will need to restructure and add the policy here - https://github.com/open-policy-agent/gatekeeper-library/tree/master/src/general/allowedreposv2, while keeping the examples and suits in the library/general. Please follow the structure similar to other policies.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new Gatekeeper policy that restricts NodePort assignments based on namespace patterns. The policy enforces that services in specific namespaces can only use NodePort values within configured ranges, preventing port conflicts across team boundaries.

  • Implements a ConstraintTemplate with Rego logic to validate NodePort ranges against namespace patterns
  • Provides test suite with allowed and disallowed examples demonstrating the policy behavior
  • Integrates the new policy into the library's kustomization structure

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
library/general/kustomization.yaml Adds reference to the new enforce-nodeport-range policy
library/general/enforce-nodeport-range/template.yaml Defines the K8sNodePortNamespaceRange ConstraintTemplate with Rego logic for validating NodePort values against namespace-specific ranges
library/general/enforce-nodeport-range/suite.yaml Configures test suite with allowed and disallowed test cases
library/general/enforce-nodeport-range/samples/enforce-nodeport-range/constraint.yaml Provides example constraint configuration with team-a and team-b namespace patterns and port ranges
library/general/enforce-nodeport-range/samples/enforce-nodeport-range/example_allowed.yaml Sample Service in team-a-service namespace with port 30007 that should be allowed
library/general/enforce-nodeport-range/samples/enforce-nodeport-range/example_disallowed.yaml Sample Service in team-b-service namespace with port 30007 that should be rejected
library/general/enforce-nodeport-range/kustomization.yaml Kustomization file that references the template

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +4
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8snodeportnamespacerange
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ConstraintTemplate is missing standard annotations that are consistently used across all other templates in this codebase. Based on the established convention (see library/general/block-nodeport-services/template.yaml:5-9, library/general/externalip/template.yaml:5-11), the metadata should include:

  • metadata.gatekeeper.sh/title: A human-readable title for the policy
  • metadata.gatekeeper.sh/version: A version number (e.g., 1.0.0)
  • description: A description of what the policy does and optionally a link to relevant Kubernetes documentation

These annotations are important for documentation and for tools that consume Gatekeeper policies.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +22
ranges:
type: object
additionalProperties:
type: object
properties:
namespacePattern:
type: string
portRange:
type: string
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The schema properties lack description fields. Following the convention established in similar policies (e.g., library/general/externalip/template.yaml:22-26), each property in the openAPIV3Schema should include a description field explaining its purpose. This improves documentation and helps users understand how to configure the constraint correctly.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +44
some key
rule := input.parameters.ranges[key]

glob.match(rule.namespacePattern, [], namespace)
not port_in_range(port, rule.portRange)

msg := sprintf(
"NodePort %v is not allowed for namespace %v. Allowed range: %v",
[port, namespace, rule.portRange]
)
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current Rego logic has a potential issue when a namespace matches multiple patterns. The violation rule iterates through all configured ranges and generates a violation for each rule where the namespace matches the pattern but the port is outside the range. This means if a namespace matches multiple patterns, a NodePort must be valid for ALL matching patterns to avoid violations, effectively requiring the port to be in the intersection of all matching ranges.

For example, with patterns "team-a-" (range 30000-30100) and "team-" (range 30000-30200), a service in "team-a-service" with port 30150 would violate the first rule even though it satisfies the second.

The more typical expectation would be to allow a port if it's valid for ANY of the matching patterns. Consider restructuring the logic to check if at least one matching rule allows the port, rather than requiring all matching rules to allow it.

Suggested change
some key
rule := input.parameters.ranges[key]
glob.match(rule.namespacePattern, [], namespace)
not port_in_range(port, rule.portRange)
msg := sprintf(
"NodePort %v is not allowed for namespace %v. Allowed range: %v",
[port, namespace, rule.portRange]
)
namespace_has_matching_rule(namespace)
not port_allowed_for_some_matching_rule(port, namespace)
msg := sprintf(
"NodePort %v is not allowed for namespace %v.",
[port, namespace]
)
}
namespace_has_matching_rule(namespace) {
some key
rule := input.parameters.ranges[key]
glob.match(rule.namespacePattern, [], namespace)
}
port_allowed_for_some_matching_rule(port, namespace) {
some key
rule := input.parameters.ranges[key]
glob.match(rule.namespacePattern, [], namespace)
port_in_range(port, rule.portRange)

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +54
port_in_range(port, range) {
cleaned := trim(range, "[]")
parts := split(cleaned, ":")
start := to_number(parts[0])
end := to_number(parts[1])
port >= start
port <= end
}
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The port_in_range function does not validate the portRange format before processing. If a portRange is misconfigured (e.g., using a delimiter other than ":", or malformed numbers), the to_number conversion will fail, causing the function to not match and effectively reject all services. While this might alert administrators to the misconfiguration, it would be more user-friendly to add explicit validation or at least document the expected format "[start:end]" in the parameter schema description.

Copilot uses AI. Check for mistakes.
Comment on lines +29 to +30
violation[{"msg": msg}] {
input.review.object.spec.type == "NodePort"
Copy link

Copilot AI Jan 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the convention established in similar Service policies (e.g., library/general/block-nodeport-services/template.yaml:23), it's recommended to add a check for input.review.kind.kind == "Service" before accessing Service-specific fields. While the constraint match configuration already filters to Services, adding this check is a defensive programming practice that makes the policy more explicit and consistent with the codebase.

Copilot uses AI. Check for mistakes.
@elhamafzalizadeh
Copy link
Copy Markdown
Author

Thanks for the clarification.
I misunderstood the expected layout.
I’ve restructured the enforce-nodeport-range policy to match the layout of
existing policies (e.g. allowedreposv2), by moving the template and rego
files under src/general/enforce-nodeport-range and keeping examples and
test suites under library/general.

@JaydipGabani
Copy link
Copy Markdown
Contributor

@elhamafzalizadeh can you fix CI?

@elhamafzalizadeh
Copy link
Copy Markdown
Author

Thanks for the feedback.
The CI issues are now fixed and the latest changes are pushed.
I’d appreciate another review.

@elhamafzalizadeh
Copy link
Copy Markdown
Author

Hi, just checking in if there's anything else needed for this PR and if we could run the CI again. Thanks!

@JaydipGabani
Copy link
Copy Markdown
Contributor

@elhamafzalizadeh you need to fix DCO, please sign your commit to fix it.

@JaydipGabani
Copy link
Copy Markdown
Contributor

@elhamafzalizadeh please resolve the CI error and address copilot feedback as well.

@stale
Copy link
Copy Markdown

stale Bot commented Apr 3, 2026

This issue/PR has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.

@stale stale Bot added the stale label Apr 3, 2026
@stale stale Bot closed this Apr 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants