Files
Oliver BählerandGitHub 755cef54bf feat(rules): add service enforcement rules (#1982)
* fix(controller): decode old object for delete requests

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* fix: preserve ca-bundles injected from external providers

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(rules): add service enforcement rules

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(rules): add service enforcement rules

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(rules): add service enforcement rules

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(rules): add service enforcement rules

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat(rules): add service enforcement rules

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
2026-06-24 11:20:23 +02:00

135 lines
3.2 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package validation
import (
"fmt"
corev1 "k8s.io/api/core/v1"
apirules "github.com/projectcapsule/capsule/pkg/api/rules"
ruleengine "github.com/projectcapsule/capsule/pkg/ruleengine"
"github.com/projectcapsule/capsule/pkg/runtime/events"
)
func (h *serviceRules) validateNodePorts(
svc *corev1.Service,
enforceBodies []*apirules.NamespaceRuleEnforceBody,
) (*ruleengine.Evaluation, error) {
if svc == nil || !serviceTypeIsNodePort(svc) {
return nil, nil
}
if requiresNodePortRanges(enforceBodies) && len(nodePortValues(svc)) == 0 {
return &ruleengine.Evaluation{
Blocking: &ruleengine.Decision{
SetName: "nodePort",
EventReason: events.ReasonForbiddenNodePort,
Action: apirules.ActionTypeDeny,
Value: ruleengine.Value{
Value: string(svc.Spec.Type),
Path: "spec.type",
},
Message: "service requires explicit spec.ports[*].nodePort because nodePort ranges are enforced by namespace rule",
},
}, nil
}
values := nodePortValues(svc)
if len(values) == 0 {
return nil, nil
}
return evaluateServiceRules[apirules.ServiceNodePortRange](
svc,
enforceBodies,
serviceRuleSet[apirules.ServiceNodePortRange]{
Name: "nodePort",
EventReason: events.ReasonForbiddenNodePort,
Values: func(_ *corev1.Service) []ruleengine.Value {
return values
},
Rules: func(enforce *apirules.NamespaceRuleEnforceBody) []apirules.ServiceNodePortRange {
if enforce == nil || enforce.Services.NodePorts == nil {
return nil
}
return enforce.Services.NodePorts.Ports
},
Matches: func(r apirules.ServiceNodePortRange, value ruleengine.Value) (ruleengine.Match, error) {
if r.From > r.To {
return ruleengine.Match{}, fmt.Errorf(
"invalid nodePort range: from %d must be lower than or equal to %d",
r.From,
r.To,
)
}
port, err := portFromValue(value.Value)
if err != nil {
return ruleengine.Match{}, err
}
matched := port >= r.From && port <= r.To
match := ruleengine.Match{
Matched: matched,
MatchedValue: describeNodePortRange(r),
}
if matched {
match.Detail = fmt.Sprintf("nodePort %d is within allowed range %s", port, describeNodePortRange(r))
}
return match, nil
},
RuleDescription: describeNodePortRange,
AllowedDescription: "Allowed ranges",
},
)
}
func describeNodePortRange(r apirules.ServiceNodePortRange) string {
if r.From == r.To {
return fmt.Sprintf("%d", r.From)
}
return fmt.Sprintf("%d-%d", r.From, r.To)
}
func nodePortValues(svc *corev1.Service) []ruleengine.Value {
out := make([]ruleengine.Value, 0, len(svc.Spec.Ports))
for i := range svc.Spec.Ports {
port := svc.Spec.Ports[i]
if port.NodePort == 0 {
continue
}
out = append(out, ruleengine.Value{
Value: fmt.Sprintf("%d", port.NodePort),
Path: fmt.Sprintf("spec.ports[%d].nodePort", i),
})
}
return out
}
func requiresNodePortRanges(
enforceBodies []*apirules.NamespaceRuleEnforceBody,
) bool {
for _, enforce := range enforceBodies {
if enforce == nil ||
enforce.Services.NodePorts == nil {
continue
}
if len(enforce.Services.NodePorts.Ports) > 0 {
return true
}
}
return false
}