Files
capsule/pkg/ruleengine/audience.go
T
Oliver BählerandGitHub f1b760a87a feat(rules): improve metadata enforcement and add ingress rules (#2050)
* feat: implement namespace metadata enforcement

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

* feat: add ingress enforcment

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

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
2026-07-22 22:24:54 +02:00

102 lines
2.9 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package ruleengine
import (
"fmt"
"slices"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/api/rbac"
"github.com/projectcapsule/capsule/pkg/api/rules"
"github.com/projectcapsule/capsule/pkg/runtime/configuration"
"github.com/projectcapsule/capsule/pkg/users"
)
func FilterNamespaceRulesByAudience(
cfg configuration.Configuration,
tnt *capsulev1beta2.Tenant,
req admission.Request,
bodies []*rules.NamespaceRuleBodyNamespace,
) ([]*rules.NamespaceRuleBodyNamespace, error) {
out := make([]*rules.NamespaceRuleBodyNamespace, 0, len(bodies))
for _, body := range bodies {
if body == nil || len(body.Audience) == 0 {
out = append(out, body)
continue
}
matched, err := matchesAudience(cfg, tnt, req, body.Audience)
if err != nil {
return nil, err
}
if matched {
out = append(out, body)
}
}
return out, nil
}
func matchesAudience(cfg configuration.Configuration, tnt *capsulev1beta2.Tenant, req admission.Request, audience []rules.Audience) (bool, error) {
for _, subject := range audience {
switch subject.Kind {
case rules.AudienceKindUser:
if req.UserInfo.Username == subject.Name {
return true, nil
}
case rules.AudienceKindGroup:
if slices.Contains(req.UserInfo.Groups, subject.Name) {
return true, nil
}
case rules.AudienceKindServiceAccount:
if (rbac.UserListSpec{{Kind: rbac.ServiceAccountOwner, Name: subject.Name}}).IsPresent(req.UserInfo.Username, req.UserInfo.Groups) {
return true, nil
}
case rules.AudienceKindCustom:
if cfg == nil {
return false, fmt.Errorf("configuration is required for custom audience %q", subject.Name)
}
matched, err := matchesCustomAudience(cfg, tnt, req, rules.CustomAudience(subject.Name))
if err != nil {
return false, err
}
if matched {
return true, nil
}
default:
return false, fmt.Errorf("unsupported audience kind %q", subject.Kind)
}
}
return false, nil
}
func matchesCustomAudience(cfg configuration.Configuration, tnt *capsulev1beta2.Tenant, req admission.Request, custom rules.CustomAudience) (bool, error) {
switch custom {
case rules.CustomAudienceCapsuleUser:
return cfg.Users().IsPresent(req.UserInfo.Username, req.UserInfo.Groups), nil
case rules.CustomAudienceAdministrator:
return cfg.Administrators().IsPresent(req.UserInfo.Username, req.UserInfo.Groups), nil
case rules.CustomAudienceTenantOwner:
if tnt == nil {
return false, nil
}
return tnt.Spec.Owners.IsOwner(req.UserInfo.Username, req.UserInfo.Groups) ||
tnt.Status.Owners.IsOwner(req.UserInfo.Username, req.UserInfo.Groups), nil
case rules.CustomAudienceController:
return users.IsControllerServiceAccount(req.UserInfo.Username), nil
default:
return false, fmt.Errorf("unsupported custom audience %q", custom)
}
}