Skip to content

Commit

Permalink
Add RBAC rules for CBSL (#13174)
Browse files Browse the repository at this point in the history
* Add RBAC rules for CBSL

* refactor generate fuctions and add some test cases

* review comment

* fix linter
  • Loading branch information
moelsayed committed Mar 13, 2024
1 parent e2b023d commit 40165e5
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 19 deletions.
5 changes: 5 additions & 0 deletions pkg/apis/kubermatic/v1/cluster_backup.go
Expand Up @@ -22,6 +22,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
// ClusterBackupStorageLocationKind represents "Kind" defined in Kubernetes.
ClusterBackupStorageLocationKind = "ClusterBackupStorageLocation"
)

// +kubebuilder:object:generate=true
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
Expand Down
92 changes: 73 additions & 19 deletions pkg/controller/master-controller-manager/rbac/mapper.go
Expand Up @@ -643,17 +643,17 @@ func generateVerbsForResource(groupName, resourceKind string) ([]string, error)
}

func generateVerbsForNamespacedResource(groupName, resourceKind, namespace string) ([]string, error) {
// special case - only the owners of a project and project managers can create secrets in "saSecretsNamespaceName" namespace
// special case - only the owners of a project and project managers can create secrets in "kubermatic" namespace
//
if namespace == saSecretsNamespaceName || strings.HasPrefix(namespace, resources.KubeOneNamespacePrefix) {
switch {
case strings.HasPrefix(groupName, OwnerGroupNamePrefix) && resourceKind == secretV1Kind:
return []string{"create"}, nil
case strings.HasPrefix(groupName, ProjectManagerGroupNamePrefix) && resourceKind == secretV1Kind:
return []string{"create"}, nil
case resourceKind == secretV1Kind:
return nil, nil
}
if !isAcceptedNamespace(namespace) {
return nil, fmt.Errorf("unable to generate verbs, unsupported namespace %q given", namespace)
}
switch resourceKind {
case secretV1Kind:
return generateVerbsForNamespacedSecretKind(groupName)

case kubermaticv1.ClusterBackupStorageLocationKind:
return generateVerbsForNamespacedCBSLKind(groupName)
}

// unknown group passed
Expand All @@ -665,21 +665,75 @@ func generateVerbsForNamespacedResource(groupName, resourceKind, namespace strin
func generateVerbsForNamedResourceInNamespace(groupName, resourceKind, namespace string) ([]string, error) {
// special case - only the owners of a project can manipulate secrets in "ssaSecretsNamespaceName" namespace
//
if namespace == saSecretsNamespaceName || strings.HasPrefix(namespace, resources.KubeOneNamespacePrefix) {
switch {
case strings.HasPrefix(groupName, OwnerGroupNamePrefix) && resourceKind == secretV1Kind:
return []string{"get", "update", "delete"}, nil
case strings.HasPrefix(groupName, ProjectManagerGroupNamePrefix) && resourceKind == secretV1Kind:
return []string{"get", "update", "delete"}, nil
case resourceKind == secretV1Kind:
return nil, nil
}
if !isAcceptedNamespace(namespace) {
return nil, fmt.Errorf("unable to generate verbs, unsupported namespace %q given", namespace)
}
switch resourceKind {
case secretV1Kind:
return generateVerbsForNamedSecretKindInNamespace(groupName)

case kubermaticv1.ClusterBackupStorageLocationKind:
return generateVerbsForNamedCBSLKindInNamespace(groupName)
}

// unknown group passed
return nil, fmt.Errorf("unable to generate verbs for group %q, kind %q and namespace %q", groupName, resourceKind, namespace)
}

func isAcceptedNamespace(namespace string) bool {
return namespace == resources.KubermaticNamespace || strings.HasPrefix(namespace, resources.KubeOneNamespacePrefix)
}

func generateVerbsForNamespacedSecretKind(groupName string) ([]string, error) {
switch {
case strings.HasPrefix(groupName, OwnerGroupNamePrefix):
return []string{"create"}, nil
case strings.HasPrefix(groupName, ProjectManagerGroupNamePrefix):
return []string{"create"}, nil
default:
return nil, nil
}
}

func generateVerbsForNamespacedCBSLKind(groupName string) ([]string, error) {
switch {
case strings.HasPrefix(groupName, OwnerGroupNamePrefix):
return []string{"create"}, nil
case strings.HasPrefix(groupName, EditorGroupNamePrefix):
return []string{"create"}, nil
case strings.HasPrefix(groupName, ProjectManagerGroupNamePrefix):
return []string{"create"}, nil
default:
return nil, nil
}
}

func generateVerbsForNamedSecretKindInNamespace(groupName string) ([]string, error) {
switch {
case strings.HasPrefix(groupName, OwnerGroupNamePrefix):
return []string{"get", "update", "delete"}, nil
case strings.HasPrefix(groupName, ProjectManagerGroupNamePrefix):
return []string{"get", "update", "delete"}, nil
default:
return nil, nil
}
}

func generateVerbsForNamedCBSLKindInNamespace(groupName string) ([]string, error) {
switch {
case strings.HasPrefix(groupName, OwnerGroupNamePrefix):
return []string{"get", "list", "create", "patch", "update", "delete"}, nil
case strings.HasPrefix(groupName, ProjectManagerGroupNamePrefix):
return []string{"get", "list", "create", "patch", "update", "delete"}, nil
case strings.HasPrefix(groupName, EditorGroupNamePrefix):
return []string{"get", "list", "create", "patch", "update", "delete"}, nil
case strings.HasPrefix(groupName, ViewerGroupNamePrefix):
return []string{"get", "list"}, nil
default:
return nil, nil
}
}

func generateVerbsForClusterNamespaceResource(cluster *kubermaticv1.Cluster, groupName, kind string) ([]string, error) {
if strings.HasPrefix(groupName, ViewerGroupNamePrefix) &&
(kind == kubermaticv1.AddonKindName || kind == kubermaticv1.ConstraintKind || kind == kubermaticv1.RuleGroupKindName ||
Expand Down
183 changes: 183 additions & 0 deletions pkg/controller/master-controller-manager/rbac/mapper_test.go
Expand Up @@ -17,8 +17,13 @@ limitations under the License.
package rbac

import (
"fmt"
"reflect"
"testing"

kubermaticv1 "k8c.io/kubermatic/v2/pkg/apis/kubermatic/v1"
"k8c.io/kubermatic/v2/pkg/resources"

"k8s.io/apimachinery/pkg/api/equality"
)

Expand Down Expand Up @@ -210,3 +215,181 @@ func TestGenerateVerbsForResources(t *testing.T) {
})
}
}

func TestIsAcceptedNamespace(t *testing.T) {
tests := []struct {
namespace string
expected bool
}{
{resources.KubermaticNamespace, true},
{fmt.Sprintf("%s_namespace", resources.KubeOneNamespacePrefix), true},
{"different_namespace", false},
{"kubermatic_suffix", false}, // Test for suffix condition
{"", false}, // Test for empty string
}

for _, tc := range tests {
actual := isAcceptedNamespace(tc.namespace)
if actual != tc.expected {
t.Errorf("check(%q) = %v, expected %v", tc.namespace, actual, tc.expected)
}
}
}

func TestGenerateVerbsForNamespacedResource(t *testing.T) {
tests := []struct {
name string
groupName string
resourceKind string
namespace string
expectedVerbs []string
wantErr bool
}{
// Test successful creation of secrets in project namespaces
{
name: "Owner in project namespace (secret)",
groupName: OwnerGroupNamePrefix + "-2wsx3edc",
resourceKind: secretV1Kind,
namespace: resources.KubeOneNamespacePrefix + "my-project",
expectedVerbs: []string{"create"},
wantErr: false,
},
{
name: "Project manager in project namespace (secret)",
groupName: ProjectManagerGroupNamePrefix + "-2wsx3edc",
resourceKind: secretV1Kind,
namespace: resources.KubeOneNamespacePrefix + "your-project",
expectedVerbs: []string{"create"},
wantErr: false,
},
{
name: "Non-owner in project namespace (secret)",
groupName: "other-group",
resourceKind: secretV1Kind,
namespace: resources.KubeOneNamespacePrefix + "other-project",
expectedVerbs: nil,
wantErr: false,
},
// Test successful creation of CBSL in kubermatic namespace
{
name: "Owner in kubermatic namespace (CBSL)",
groupName: OwnerGroupNamePrefix + "-2wsx3edc",
resourceKind: kubermaticv1.ClusterBackupStorageLocationKind,
namespace: resources.KubermaticNamespace,
expectedVerbs: []string{"create"},
wantErr: false,
},
{
name: "Editor in kubermatic namespace (CBSL)",
groupName: EditorGroupNamePrefix + "-2wsx3edc",
resourceKind: kubermaticv1.ClusterBackupStorageLocationKind,
namespace: resources.KubermaticNamespace,
expectedVerbs: []string{"create"},
wantErr: false,
},
{
name: "Project manager in kubermatic namespace (CBSL)",
groupName: ProjectManagerGroupNamePrefix + "-2wsx3edc",
resourceKind: kubermaticv1.ClusterBackupStorageLocationKind,
namespace: resources.KubermaticNamespace,
expectedVerbs: []string{"create"},
wantErr: false,
},
// Test denied creation of CBSL in non-kubermatic namespace
{
name: "Owner in non-kubermatic namespace (CBSL)",
groupName: OwnerGroupNamePrefix + "-2wsx3edc",
resourceKind: kubermaticv1.ClusterBackupStorageLocationKind,
namespace: "other-namespace",
expectedVerbs: nil,
wantErr: true,
},
// Test unknown group
{
name: "Unknown group",
groupName: "unknown-group",
resourceKind: "unknown-kind",
namespace: resources.KubermaticNamespace,
expectedVerbs: nil,
wantErr: true,
},
}

for _, tc := range tests {
actualVerbs, actualError := generateVerbsForNamespacedResource(tc.groupName, tc.resourceKind, tc.namespace)
if !reflect.DeepEqual(actualVerbs, tc.expectedVerbs) || (tc.wantErr && actualError == nil) || (!tc.wantErr && actualError != nil) {
t.Errorf("Test: %s - generateVerbsForNamespacedResource(%q, %q, %q) = %v, %v; expected %v, wantErr: %v", tc.name, tc.groupName, tc.resourceKind, tc.namespace, actualVerbs, actualError, tc.expectedVerbs, tc.wantErr)
}
}
}

func TestGenerateVerbsForNamedResourceInNamespace(t *testing.T) {
tests := []struct {
name string
groupName string
resourceKind string
namespace string
expectedVerbs []string
wantErr bool
}{
// Namespace and kind acceptance cases
{
name: "Owner can get, update, and delete secrets in saSecretsNamespaceName",
groupName: OwnerGroupNamePrefix + "-group",
resourceKind: secretV1Kind,
namespace: saSecretsNamespaceName,
expectedVerbs: []string{"get", "update", "delete"},
wantErr: false,
},
{
name: "Project Manager can get, update, and delete secrets in KubeOneNamespacePrefix namespace",
groupName: ProjectManagerGroupNamePrefix + "-group",
resourceKind: secretV1Kind,
namespace: resources.KubeOneNamespacePrefix + "some-namespace",
expectedVerbs: []string{"get", "update", "delete"},
wantErr: false,
},
{
name: "Unsupported Namespace for secrets",
groupName: OwnerGroupNamePrefix + "-group",
resourceKind: secretV1Kind,
namespace: "unsupported-namespace",
expectedVerbs: nil,
wantErr: true,
},
{
name: "Unsupported Kind for secrets",
groupName: OwnerGroupNamePrefix + "-group",
resourceKind: "unsupported-kind",
namespace: saSecretsNamespaceName,
expectedVerbs: nil,
wantErr: true,
},

// CBSL in KubermaticNamespace
{
name: "Owner can fully manage CBSL in KubermaticNamespace",
groupName: OwnerGroupNamePrefix + "-group",
resourceKind: kubermaticv1.ClusterBackupStorageLocationKind,
namespace: resources.KubermaticNamespace,
expectedVerbs: []string{"get", "list", "create", "patch", "update", "delete"},
wantErr: false,
},
// Unknown Group
{
name: "Unknown Group",
groupName: "unknown-group",
resourceKind: secretV1Kind,
namespace: resources.KubermaticNamespace,
expectedVerbs: nil,
wantErr: false,
},
}

for _, tc := range tests {
actualVerbs, actualError := generateVerbsForNamedResourceInNamespace(tc.groupName, tc.resourceKind, tc.namespace)
if !reflect.DeepEqual(actualVerbs, tc.expectedVerbs) || (tc.wantErr && actualError == nil) || (!tc.wantErr && actualError != nil) {
t.Errorf("Test: %s - generateVerbsForNamedResourceInNamespace(%q, %q, %q) = %v, %v; expected %v, wantErr: %v", tc.name, tc.groupName, tc.resourceKind, tc.namespace, actualVerbs, actualError, tc.expectedVerbs, tc.wantErr)
}
}
}
Expand Up @@ -164,6 +164,15 @@ func New(ctx context.Context, metrics *Metrics, mgr manager.Manager, seedManager
},
},
},
{
object: &kubermaticv1.ClusterBackupStorageLocation{
TypeMeta: metav1.TypeMeta{
APIVersion: kubermaticv1.SchemeGroupVersion.String(),
Kind: kubermaticv1.ClusterBackupStorageLocationKind,
},
},
namespace: "kubermatic",
},
}

if err := newProjectRBACController(ctx, metrics, mgr, seedManagerMap, log, projectResources, workerPredicate); err != nil {
Expand Down

0 comments on commit 40165e5

Please sign in to comment.