mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-25 16:07:24 +00:00
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:
co-authored by
CorentinPtrl
parent
7a65ab7afc
commit
cc4fb45d70
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user