feat: upstream enterprise preview (#1841)

feat: upstream enterprise preview

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
Co-authored-by: CorentinPtrl <pitrel.corentin@gmail.com>
This commit is contained in:
Oliver Bähler
2026-05-28 00:58:58 +02:00
committed by GitHub
co-authored by CorentinPtrl
parent 7a65ab7afc
commit cc4fb45d70
462 changed files with 48456 additions and 10529 deletions
+101
View File
@@ -0,0 +1,101 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package users
import (
authenticationv1 "k8s.io/api/authentication/v1"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"github.com/projectcapsule/capsule/pkg/runtime/configuration"
)
type AdmissionUserType string
const (
AdmissionUserUnknown AdmissionUserType = "Unknown"
AdmissionUserAdmin AdmissionUserType = "Admin"
AdmissionUserCapsule AdmissionUserType = "Capsule"
)
type AdmissionUser struct {
Type AdmissionUserType
Username string
Groups []string
ServiceAccount *AdmissionServiceAccount
}
type AdmissionServiceAccount struct {
Namespace string
Name string
}
func NewAdmissionUser(userType AdmissionUserType, info authenticationv1.UserInfo) AdmissionUser {
return AdmissionUser{
Type: userType,
Username: info.Username,
Groups: info.Groups,
ServiceAccount: ToServiceAccount(info.Username),
}
}
func (u AdmissionUser) IsAdmin() bool {
return u.Type == AdmissionUserAdmin
}
func (u AdmissionUser) IsCapsule() bool {
return u.Type == AdmissionUserCapsule
}
func (u AdmissionUser) IsUnknown() bool {
return u.Type == AdmissionUserUnknown
}
func (u AdmissionUser) UserInfo() authenticationv1.UserInfo {
return authenticationv1.UserInfo{
Username: u.Username,
Groups: u.Groups,
}
}
func (u AdmissionUser) IsControllerServiceAccount() bool {
if u.ServiceAccount == nil {
return false
}
name, namespace := configuration.ControllerServiceAccount()
if namespace == "" || name == "" {
return false
}
return u.ServiceAccount.Namespace == namespace && u.ServiceAccount.Name == name
}
func ToServiceAccount(username string) *AdmissionServiceAccount {
namespace, name, err := serviceaccount.SplitUsername(username)
if err != nil {
return nil
}
return &AdmissionServiceAccount{
Namespace: namespace,
Name: name,
}
}
func ServiceAccountUsername(namespace, name string) string {
return serviceaccount.MakeUsername(namespace, name)
}
func ServiceAccountGroups(namespace string) []string {
return GetServiceAccountGroups(namespace)
}
func ServiceAccountUserInfo(namespace, name string) authenticationv1.UserInfo {
return authenticationv1.UserInfo{
Username: ServiceAccountUsername(namespace, name),
Groups: ServiceAccountGroups(namespace),
}
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package users
func HasIgnoredGroup(userGroups []string, ignoredGroups []string) bool {
if len(userGroups) == 0 || len(ignoredGroups) == 0 {
return false
}
ignored := make(map[string]struct{}, len(ignoredGroups))
for _, group := range ignoredGroups {
ignored[group] = struct{}{}
}
for _, group := range userGroups {
if _, ok := ignored[group]; ok {
return true
}
}
return false
}
+19 -2
View File
@@ -4,11 +4,28 @@
package users
import (
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"github.com/projectcapsule/capsule/pkg/api"
"github.com/projectcapsule/capsule/pkg/api/rbac"
"github.com/projectcapsule/capsule/pkg/runtime/configuration"
)
func IsAdminUser(req admission.Request, administrators api.UserListSpec) bool {
func IsAdminUser(req admission.Request, administrators rbac.UserListSpec) bool {
if IsControllerServiceAccount(req.UserInfo.Username) {
return true
}
return administrators.IsPresent(req.UserInfo.Username, req.UserInfo.Groups)
}
func IsControllerServiceAccount(username string) bool {
namespace, name, err := serviceaccount.SplitUsername(username)
if err != nil {
return false
}
controllerName, controllerNamespace := configuration.ControllerServiceAccount()
return namespace == controllerNamespace && name == controllerName
}
+11 -7
View File
@@ -5,14 +5,13 @@ package users
import (
"context"
"os"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"sigs.k8s.io/controller-runtime/pkg/client"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
"github.com/projectcapsule/capsule/pkg/api"
"github.com/projectcapsule/capsule/pkg/api/rbac"
"github.com/projectcapsule/capsule/pkg/runtime/configuration"
)
@@ -31,14 +30,21 @@ func IsCapsuleUser(
return false
}
capsuleUsers := cfg.GetUsersByStatus()
//nolint:nestif
if sets.NewString(groups...).Has("system:serviceaccounts") {
namespace, name, err := serviceaccount.SplitUsername(user)
if err == nil {
if namespace == os.Getenv("NAMESPACE") && name == os.Getenv("SERVICE_ACCOUNT") {
if configuration.IsControllerServiceAccount(name, namespace) {
return false
}
serviceaccounts := capsuleUsers.GetByKinds([]rbac.OwnerKind{rbac.ServiceAccountOwner})
if len(serviceaccounts) > 0 && sets.New[string](serviceaccounts...).Has(user) {
return true
}
var tl capsulev1beta2.TenantList
if err := c.List(ctx, &tl, client.MatchingFields{".status.namespaces": namespace}); err != nil {
return false
@@ -50,10 +56,8 @@ func IsCapsuleUser(
}
}
capsuleUsers := cfg.GetUsersByStatus()
//nolint:modernize
for _, group := range capsuleUsers.GetByKinds([]api.OwnerKind{api.GroupOwner}) {
for _, group := range capsuleUsers.GetByKinds([]rbac.OwnerKind{rbac.GroupOwner}) {
if groupList.Find(group) {
if len(cfg.IgnoreUserWithGroups()) > 0 {
for _, ignoreGroup := range cfg.IgnoreUserWithGroups() {
@@ -67,7 +71,7 @@ func IsCapsuleUser(
}
}
users := capsuleUsers.GetByKinds([]api.OwnerKind{api.UserOwner})
users := capsuleUsers.GetByKinds([]rbac.OwnerKind{rbac.UserOwner})
if len(users) > 0 && sets.New[string](users...).Has(user) {
return true
}
+8 -7
View File
@@ -19,7 +19,7 @@ import (
func IsTenantOwner(
ctx context.Context,
c client.Client,
c client.Reader,
cfg configuration.Configuration,
tnt *capsulev1beta2.Tenant,
userInfo authenticationv1.UserInfo,
@@ -32,18 +32,19 @@ func IsTenantOwner(
}
func IsTenantOwnerByStatus(
ctx context.Context,
c client.Client,
cfg configuration.Configuration,
tnt *capsulev1beta2.Tenant,
userInfo authenticationv1.UserInfo,
user AdmissionUser,
) bool {
return tnt.Status.Owners.IsOwner(userInfo.Username, userInfo.Groups)
if user.IsAdmin() {
return true
}
return tnt.Status.Owners.IsOwner(user.Username, user.Groups)
}
func IsCommonOwner(
ctx context.Context,
c client.Client,
c client.Reader,
cfg configuration.Configuration,
tnt *capsulev1beta2.Tenant,
userInfo authenticationv1.UserInfo,
+38 -1
View File
@@ -5,10 +5,14 @@ package users
import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
@@ -20,7 +24,7 @@ import (
// if a serviceaccount is in a tenant namespace they will return the tenant.
func ResolveServiceAccountActor(
ctx context.Context,
c client.Client,
c client.Reader,
ns *corev1.Namespace,
username string,
cfg configuration.Configuration,
@@ -54,3 +58,36 @@ func ResolveServiceAccountActor(
return tnt, err
}
// GetServiceAccountFullName return the full qualified name for the serviceaccount.
func GetServiceAccountFullName(ref meta.NamespacedRFC1123ObjectReferenceWithNamespace) string {
return serviceaccount.ServiceAccountUsernamePrefix + string(ref.Namespace) + ":" + string(ref.Name)
}
// GetServiceAccountGroups returns all groups associated with a ServiceAccount.
func GetServiceAccountGroups(namespace string) []string {
return []string{
fmt.Sprintf("%s%s", serviceaccount.ServiceAccountGroupPrefix, namespace),
serviceaccount.AllServiceAccountsGroup,
user.AllAuthenticated,
}
}
// ImpersonatedKubernetesClientForServiceAccount returns a controller-runtime client.Client that impersonates a given ServiceAccount.
func ImpersonatedKubernetesClientForServiceAccount(
base *rest.Config,
scheme *runtime.Scheme,
reference meta.NamespacedRFC1123ObjectReferenceWithNamespace,
) (client.Client, error) {
imp := rest.CopyConfig(base)
imp.Impersonate = rest.ImpersonationConfig{
UserName: GetServiceAccountFullName(reference),
}
k8sClient, err := client.New(imp, client.Options{Scheme: scheme})
if err != nil {
return nil, fmt.Errorf("failed to create impersonated client: %w", err)
}
return k8sClient, nil
}
+4 -2
View File
@@ -1,12 +1,14 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package users
package users_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/projectcapsule/capsule/pkg/users"
)
func TestIsInCapsuleGroups(t *testing.T) {
@@ -22,5 +24,5 @@ func TestIsInCapsuleGroups(t *testing.T) {
capsuleGroup := "kubernetes-abilitytologin"
assert.True(t, NewUserGroupList(groups).Find(capsuleGroup), nil)
assert.True(t, users.NewUserGroupList(groups).Find(capsuleGroup), nil)
}