Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9d887cd
fix(controller): decode old object for delete requests
oliverbaehler Dec 10, 2025
221e0e8
chore: modernize golang
oliverbaehler Dec 10, 2025
052660e
chore: modernize golang
oliverbaehler Dec 10, 2025
7c418d0
chore: modernize golang
oliverbaehler Dec 10, 2025
7db5048
Merge branch 'main' of github.com:projectcapsule/capsule
oliverbaehler Dec 11, 2025
98f9add
Merge branch 'main' of github.com:projectcapsule/capsule
oliverbaehler Dec 15, 2025
f448bdf
Merge branch 'main' of github.com:projectcapsule/capsule
oliverbaehler Dec 19, 2025
033e098
Merge branch 'main' of github.com:projectcapsule/capsule
oliverbaehler Dec 19, 2025
21cd932
Merge branch 'main' of github.com:projectcapsule/capsule
oliverbaehler Dec 25, 2025
1e28f1b
Merge branch 'main' of github.com:projectcapsule/capsule
oliverbaehler Jan 5, 2026
668c847
fix(config): remove usergroups default
oliverbaehler Jan 6, 2026
5b05b17
fix(config): remove usergroups default
oliverbaehler Jan 6, 2026
2327f9c
sec(ghsa-2ww6-hf35-mfjm): intercept namespace subresource
oliverbaehler Jan 22, 2026
1cc70ee
feat(api): add rulestatus api
oliverbaehler Jan 22, 2026
3c17c8f
chore: conflicts
oliverbaehler Jan 22, 2026
3203d4d
chore: conflicts
oliverbaehler Jan 22, 2026
b41cbdf
chore: conflicts
oliverbaehler Jan 22, 2026
b926322
chore: conflicts
oliverbaehler Jan 22, 2026
98c1732
chore: conflicts
oliverbaehler Jan 22, 2026
7747ade
chore: conflicts
oliverbaehler Jan 22, 2026
44e27bc
chore: conflicts
oliverbaehler Jan 22, 2026
d771a15
chore: conflicts
oliverbaehler Jan 22, 2026
17544df
chore: conflicts
oliverbaehler Jan 23, 2026
3cce446
chore: conflicts
oliverbaehler Jan 23, 2026
a671091
chore: conflicts
oliverbaehler Jan 23, 2026
b622023
chore: conflicts
oliverbaehler Jan 23, 2026
174d884
feat(api): add rulestatus api
oliverbaehler Jan 27, 2026
f41a3f3
feat(api): add rulestatus api
oliverbaehler Jan 27, 2026
548803e
feat(api): add rulestatus api
oliverbaehler Jan 27, 2026
50c04c2
feat(api): add rulestatus api
oliverbaehler Jan 27, 2026
820f6b5
feat(api): add rulestatus api
oliverbaehler Jan 27, 2026
45531a5
feat(api): add rulestatus api
oliverbaehler Jan 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ jobs:
fail-fast: false
matrix:
k8s-version:
- 'v1.30.0'
- 'v1.31.0'
- 'v1.32.0'
- 'v1.33.0'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/releaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- uses: creekorful/goreportcard-action@1f35ced8cdac2cba28c9a2f2288a16aacfd507f9 # v1.0
- uses: anchore/sbom-action/download-syft@0b82b0b1a22399a1c542d4d656f70cd903571b5c
- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5 # v3.10.1
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0
with:
Expand Down
3 changes: 2 additions & 1 deletion .nwa-config
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
nwa:
cmd: "update"
holder: "Project Capsule Authors"
year: "2020-2025"
year: "2020-2026"
spdxids: "Apache-2.0"
path:
- "pkg/**/*.go"
- "cmd/**/*.go"
- "api/**/*.go"
- "internal/**/*.go"
- "controllers/**/*.go"
- "main.go"
mute: false
Expand Down
19 changes: 10 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ helm-schema: helm-plugin-schema
helm-test: HELM_KIND_CONFIG ?= ""
helm-test: kind
@mkdir -p /tmp/results || true
@$(KIND) create cluster --wait=60s --name capsule-charts --image kindest/node:$(KUBERNETES_SUPPORTED_VERSION) --config $(HELM_KIND_CONFIG)
@$(KIND) create cluster --wait=60s --name capsule-charts --image kindest/node:$(KUBERNETES_SUPPORTED_VERSION) --config ./hack/kind-cluster.yaml
@make helm-test-exec
@$(KIND) delete cluster --name capsule-charts

Expand All @@ -104,7 +104,7 @@ helm-test-exec: ct helm-controller-version ko-build-all

# Setup development env
dev-build: kind
$(KIND) create cluster --wait=60s --name $(CLUSTER_NAME) --image kindest/node:$(KUBERNETES_SUPPORTED_VERSION)
$(KIND) create cluster --wait=60s --name $(CLUSTER_NAME) --image kindest/node:$(KUBERNETES_SUPPORTED_VERSION) --config ./hack/kind-cluster.yaml
$(MAKE) dev-install-deps

.PHONY: dev-destroy
Expand Down Expand Up @@ -220,12 +220,12 @@ dev-setup-capsule: dev-setup-fluxcd

dev-setup-capsule-example: dev-setup-fluxcd
@$(KUBECTL) kustomize --load-restrictor='LoadRestrictionsNone' hack/distro/capsule/example-setup | envsubst | kubectl apply -f -
@$(KUBECTL) create ns wind-test --as joe --as-group projectcapsule.dev
@$(KUBECTL) create ns wind-prod --as joe --as-group projectcapsule.dev
@$(KUBECTL) create ns green-test --as bob --as-group projectcapsule.dev
@$(KUBECTL) create ns green-prod --as bob --as-group projectcapsule.dev
@$(KUBECTL) create ns solar-test --as alice --as-group projectcapsule.dev
@$(KUBECTL) create ns solar-prod --as alice --as-group projectcapsule.dev
@$(KUBECTL) create ns wind-test --as joe --as-group projectcapsule.dev || true
@$(KUBECTL) create ns wind-prod --as joe --as-group projectcapsule.dev || true
@$(KUBECTL) create ns green-test --as bob --as-group projectcapsule.dev || true
@$(KUBECTL) create ns green-prod --as bob --as-group projectcapsule.dev || true
@$(KUBECTL) create ns solar-test --as alice --as-group projectcapsule.dev || true
@$(KUBECTL) create ns solar-prod --as alice --as-group projectcapsule.dev || true

wait-for-helmreleases:
@ echo "Waiting for all HelmReleases to have observedGeneration >= 0..."
Expand Down Expand Up @@ -316,7 +316,7 @@ e2e-build: kind
$(MAKE) e2e-install

.PHONY: e2e-install
e2e-install: ko-build-all
e2e-install: helm-controller-version ko-build-all
$(MAKE) e2e-load-image CLUSTER_NAME=$(CLUSTER_NAME) IMAGE=$(CAPSULE_IMG) VERSION=$(VERSION)
$(HELM) upgrade \
--dependency-update \
Expand All @@ -331,6 +331,7 @@ e2e-install: ko-build-all
--set 'manager.livenessProbe.failureThreshold=10' \
--set 'webhooks.hooks.nodes.enabled=true' \
--set "webhooks.exclusive=true"\
--set "manager.options.logLevel=debug"\
capsule \
./charts/capsule

Expand Down
2 changes: 1 addition & 1 deletion api/v1beta1/owner_list_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020-2025 Project Capsule Authors
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package v1beta1
Expand Down
3 changes: 1 addition & 2 deletions api/v1beta1/tenant_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ func (in *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error {
return nil
}

return ctrl.NewWebhookManagedBy(mgr).
For(in).
return ctrl.NewWebhookManagedBy(mgr, in).
Complete()
}
5 changes: 5 additions & 0 deletions api/v1beta2/capsuleconfiguration_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
package v1beta2

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/projectcapsule/capsule/pkg/api"
)

// CapsuleConfigurationStatus defines the Capsule configuration status.
type CapsuleConfigurationStatus struct {
// Last time all caches were invalided
LastCacheInvalidation metav1.Time `json:"lastCacheInvalidation,omitempty"`

// Users which are considered Capsule Users and are bound to the Capsule Tenant construct.
Users api.UserListSpec `json:"users,omitempty"`
}
45 changes: 45 additions & 0 deletions api/v1beta2/capsuleconfiguration_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package v1beta2

import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/projectcapsule/capsule/pkg/api"
Expand Down Expand Up @@ -53,6 +54,50 @@ type CapsuleConfigurationSpec struct {
// for interacting with namespaces. Because if that label is not defined, it's assumed that namespace interaction was not targeted towards a tenant and will therefor
// be ignored by capsule.
Administrators api.UserListSpec `json:"administrators,omitempty"`
// Configuration for dynamic Validating and Mutating Admission webhooks managed by Capsule.
Admission DynamicAdmission `json:"admission,omitempty"`
// Define Properties for managed ClusterRoles by Capsule
// +kubebuilder:default={}
RBAC *RBACConfiguration `json:"rbac"`
// Define the period of time upon a cache invalidation is executed for all caches.
// +kubebuilder:default="24h"
CacheInvalidation metav1.Duration `json:"cacheInvalidation"`
}

type RBACConfiguration struct {
// The ClusterRoles applied for Administrators
// +kubebuilder:default={capsule-namespace-deleter}
AdministrationClusterRoles []string `json:"administrationClusterRoles,omitempty"`
// The ClusterRoles applied for ServiceAccounts which had owner Promotion
// +kubebuilder:default={capsule-namespace-provisioner,capsule-namespace-deleter}
PromotionClusterRoles []string `json:"promotionClusterRoles,omitempty"`
// Name for the ClusterRole required to grant Namespace Deletion permissions.
// +kubebuilder:default=capsule-namespace-deleter
DeleterClusterRole string `json:"deleter,omitempty"`
// Name for the ClusterRole required to grant Namespace Provision permissions.
// +kubebuilder:default=capsule-namespace-provisioner
ProvisionerClusterRole string `json:"provisioner,omitempty"`
}

type DynamicAdmission struct {
// Configure dynamic Mutating Admission for Capsule
Mutating DynamicAdmissionConfig `json:"mutating,omitempty"`

// Configure dynamic Validating Admission for Capsule
Validating DynamicAdmissionConfig `json:"validating,omitempty"`
}

type DynamicAdmissionConfig struct {
// Name the Admission Webhook
Name api.Name `json:"name,omitempty"`
// Labels added to the Admission Webhook
// +optional
Labels map[string]string `json:"labels,omitempty"`
// Annotations added to the Admission Webhook
// +optional
Annotations map[string]string `json:"annotations,omitempty"`
// From the upstram struct
Client admissionregistrationv1.WebhookClientConfig `json:"client"`
}

type NodeMetadata struct {
Expand Down
33 changes: 33 additions & 0 deletions api/v1beta2/namespace_rule_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package v1beta2

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/projectcapsule/capsule/pkg/api"
)

// +kubebuilder:object:generate=true
type NamespaceRule struct {
// Enforce these properties via Rules
NamespaceRuleBody `json:",inline"`

// Select namespaces which are going to usese
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
}

// +kubebuilder:object:generate=true
type NamespaceRuleBody struct {
// Enforcement Rules applied
//+optional
Enforce NamespaceRuleEnforceBody `json:"enforce,omitzero"`
}

// +kubebuilder:object:generate=true
type NamespaceRuleEnforceBody struct {
// Define registries which are allowed to be used within this tenant
// The rules are aggregated, since you can use Regular Expressions the match registry endpoints
Registries []api.OCIRegistry `json:"registries,omitempty"`
}
2 changes: 1 addition & 1 deletion api/v1beta2/resourcepool_func_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020-2025 Project Capsule Authors
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package v1beta2
Expand Down
2 changes: 1 addition & 1 deletion api/v1beta2/resourcepoolclaim_func_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020-2025 Project Capsule Authors
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package v1beta2
Expand Down
44 changes: 44 additions & 0 deletions api/v1beta2/rule_status_type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package v1beta2

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +kubebuilder:object:root=true
// +kubebuilder:storageversion
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Age"
type RuleStatus struct {
metav1.TypeMeta `json:",inline"`

// +optional
metav1.ObjectMeta `json:"metadata,omitzero"`

// +optional
Status RuleStatusSpec `json:"status,omitzero"`
}

// +kubebuilder:object:root=true

// RuleStatusList contains a list of RuleStatus.
type RuleStatusList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitzero"`

Items []RuleStatus `json:"items"`
}

func init() {
SchemeBuilder.Register(&RuleStatus{}, &RuleStatusList{})
}

// RuleStatus contains the accumulated rules applying to namespace it's deployed in.
// +kubebuilder:object:generate=true
type RuleStatusSpec struct {
// Managed Enforcement properties per Namespace (aggregated from rules)
//+optional
Rule NamespaceRuleBody `json:"rule,omitzero"`
}
63 changes: 1 addition & 62 deletions api/v1beta2/tenant_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,78 +4,17 @@
package v1beta2

import (
"context"
"slices"
"sort"

corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/projectcapsule/capsule/pkg/api"
"github.com/projectcapsule/capsule/pkg/api/meta"
)

func (in *Tenant) CollectOwners(ctx context.Context, c client.Client, allowPromotion bool, admins api.UserListSpec) (api.OwnerStatusListSpec, error) {
owners := in.Spec.Owners.ToStatusOwners()

// Promoted ServiceAccounts
if allowPromotion && len(in.Status.Namespaces) > 0 {
saList := &corev1.ServiceAccountList{}
if err := c.List(ctx, saList,
client.MatchingLabels{
meta.OwnerPromotionLabel: meta.OwnerPromotionLabelTrigger,
},
); err != nil {
return nil, err
}

for _, sa := range saList.Items {
for _, ns := range in.Status.Namespaces {
if sa.GetNamespace() != ns {
continue
}

owners.Upsert(api.CoreOwnerSpec{
UserSpec: api.UserSpec{
Kind: api.ServiceAccountOwner,
Name: serviceaccount.ServiceAccountUsernamePrefix + sa.Namespace + ":" + sa.Name,
},
ClusterRoles: []string{
api.ProvisionerRoleName,
api.DeleterRoleName,
},
})
}
}
}

// Administrators
for _, a := range admins {
owners.Upsert(api.CoreOwnerSpec{
UserSpec: a,
ClusterRoles: []string{
api.DeleterRoleName,
},
})
}

// Dedicated Owner Objects
listed, err := in.Spec.Permissions.ListMatchingOwners(ctx, c, in.GetName())
if err != nil {
return nil, err
}

for _, o := range listed {
owners.Upsert(o.Spec.CoreOwnerSpec)
}

return owners, nil
}

func (in *Tenant) GetRoleBindings() []api.AdditionalRoleBindingsSpec {
roleBindings := make([]api.AdditionalRoleBindingsSpec, 0) //nolint:prealloc
roleBindings := make([]api.AdditionalRoleBindingsSpec, 0, len(in.Spec.AdditionalRoleBindings))

for _, owner := range in.Status.Owners {
roleBindings = append(roleBindings, owner.ToAdditionalRolebindings()...)
Expand Down
2 changes: 1 addition & 1 deletion api/v1beta2/tenant_func_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020-2025 Project Capsule Authors
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0

package v1beta2
Expand Down
8 changes: 8 additions & 0 deletions api/v1beta2/tenant_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ type TenantStatusNamespaceItem struct {
UID k8stypes.UID `json:"uid,omitempty"`
// Managed Metadata
Metadata *TenantStatusNamespaceMetadata `json:"metadata,omitempty"`
// Managed Metadata
//+optional
Enforce TenantStatusNamespaceEnforcement `json:"enforce,omitzero"`
}

type TenantStatusNamespaceEnforcement struct {
// Registries which are allowed within this namespace
Registries []api.OCIRegistry `json:"registry,omitempty"`
}

type TenantStatusNamespaceMetadata struct {
Expand Down
Loading
Loading