Files
2026-05-07 14:39:58 +00:00

1097 lines
34 KiB
Go

package templateagent
import (
"bytes"
"context"
"fmt"
"net"
"os"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
certificatesv1 "k8s.io/api/certificates/v1"
certificates "k8s.io/api/certificates/v1beta1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
fakekube "k8s.io/client-go/kubernetes/fake"
certutil "k8s.io/client-go/util/cert"
"k8s.io/klog/v2/ktesting"
"open-cluster-management.io/addon-framework/pkg/agent"
addonapiv1alpha1 "open-cluster-management.io/api/addon/v1alpha1"
addonv1beta1 "open-cluster-management.io/api/addon/v1beta1"
fakeaddon "open-cluster-management.io/api/client/addon/clientset/versioned/fake"
addoninformers "open-cluster-management.io/api/client/addon/informers/externalversions"
clusterv1 "open-cluster-management.io/api/cluster/v1"
)
func TestTemplateCSRConfigurationsFunc(t *testing.T) {
cases := []struct {
name string
cluster *clusterv1.ManagedCluster
addon *addonv1beta1.ManagedClusterAddOn
template *addonapiv1alpha1.AddOnTemplate
expectedConfigs []agent.RegistrationConfig
expectedErr string
}{
{
name: "empty",
cluster: NewFakeManagedCluster("cluster1"),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "", ""),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{}),
expectedConfigs: []agent.RegistrationConfig{},
expectedErr: "CSRConfigurations failed to get addon template for addon cluster1/addon1, template is nil",
},
{
name: "kubeclient",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeKubeClient,
KubeClient: &addonapiv1alpha1.KubeClientRegistrationConfig{
HubPermissions: []addonapiv1alpha1.HubPermissionConfig{
{
Type: addonapiv1alpha1.HubPermissionsBindingSingleNamespace,
SingleNamespace: &addonapiv1alpha1.SingleNamespaceBindingConfig{
Namespace: "test",
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: "test",
},
},
},
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
expectedConfigs: []agent.RegistrationConfig{
&agent.KubeClientRegistration{
User: "system:open-cluster-management:cluster:cluster1:addon:addon1:agent:addon1-agent",
Groups: []string{
"system:open-cluster-management:cluster:cluster1:addon:addon1",
"system:open-cluster-management:addon:addon1",
},
},
},
},
{
name: "customsigner",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeCustomSigner,
CustomSigner: &addonapiv1alpha1.CustomSignerRegistrationConfig{
SignerName: "s1",
Subject: &addonapiv1alpha1.Subject{
User: "u1",
Groups: []string{
"g1",
"g2",
},
OrganizationUnits: []string{},
},
SigningCA: addonapiv1alpha1.SigningCARef{
Name: "name1",
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
expectedConfigs: []agent.RegistrationConfig{
&agent.CustomSignerRegistration{
SignerName: "s1",
User: "u1",
Groups: []string{
"g1",
"g2",
},
},
},
},
}
for _, c := range cases {
_, ctx := ktesting.NewTestContext(t)
addonClient := fakeaddon.NewSimpleClientset(c.template, c.addon)
addonInformerFactory := addoninformers.NewSharedInformerFactory(addonClient, 30*time.Minute)
mcaStore := addonInformerFactory.Addon().V1beta1().ManagedClusterAddOns().Informer().GetStore()
if err := mcaStore.Add(c.addon); err != nil {
t.Fatal(err)
}
atStore := addonInformerFactory.Addon().V1alpha1().AddOnTemplates().Informer().GetStore()
if err := atStore.Add(c.template); err != nil {
t.Fatal(err)
}
agent := NewCRDTemplateAgentAddon(ctx, c.addon.Name, nil, addonClient, addonInformerFactory, nil, nil)
f := agent.TemplateCSRConfigurationsFunc()
registrationConfigs, err := f(ctx, c.cluster, c.addon)
if c.expectedErr == "" {
if err != nil {
t.Fatalf("case: %s, expected no error but got: %v", c.name, err)
}
} else {
if err == nil {
t.Fatalf("case: %s, expected error but got none", c.name)
}
if !strings.Contains(err.Error(), c.expectedErr) {
t.Fatalf("case: %s, expected error containing %q but got: %v", c.name, c.expectedErr, err)
}
}
if !equality.Semantic.DeepEqual(registrationConfigs, c.expectedConfigs) {
t.Errorf("expected registrationConfigs %v, but got %v", c.expectedConfigs, registrationConfigs)
}
}
}
func TestTemplateCSRApproveCheckFunc(t *testing.T) {
cases := []struct {
name string
cluster *clusterv1.ManagedCluster
addon *addonv1beta1.ManagedClusterAddOn
template *addonapiv1alpha1.AddOnTemplate
csr *certificatesv1.CertificateSigningRequest
expectedApprove bool
}{
{
name: "empty",
cluster: NewFakeManagedCluster("cluster1"),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "", ""),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{}),
expectedApprove: false,
},
{
name: "kubeclient",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeKubeClient,
KubeClient: &addonapiv1alpha1.KubeClientRegistrationConfig{
HubPermissions: []addonapiv1alpha1.HubPermissionConfig{
{
Type: addonapiv1alpha1.HubPermissionsBindingSingleNamespace,
SingleNamespace: &addonapiv1alpha1.SingleNamespaceBindingConfig{
Namespace: "test",
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: "test",
},
},
},
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
csr: &certificatesv1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "csr1",
},
Spec: certificatesv1.CertificateSigningRequestSpec{
SignerName: certificates.KubeAPIServerClientSignerName,
},
},
expectedApprove: false, // fake csr data
},
{
name: "customsigner",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeCustomSigner,
CustomSigner: &addonapiv1alpha1.CustomSignerRegistrationConfig{
SignerName: "s1",
Subject: &addonapiv1alpha1.Subject{
User: "u1",
Groups: []string{
"g1",
"g2",
},
OrganizationUnits: []string{},
},
SigningCA: addonapiv1alpha1.SigningCARef{
Name: "name1",
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
csr: &certificatesv1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "csr1",
},
Spec: certificatesv1.CertificateSigningRequestSpec{
SignerName: "s1",
},
},
expectedApprove: true,
},
}
for _, c := range cases {
_, ctx := ktesting.NewTestContext(t)
addonClient := fakeaddon.NewSimpleClientset(c.template, c.addon)
addonInformerFactory := addoninformers.NewSharedInformerFactory(addonClient, 30*time.Minute)
mcaStore := addonInformerFactory.Addon().V1beta1().ManagedClusterAddOns().Informer().GetStore()
if err := mcaStore.Add(c.addon); err != nil {
t.Fatal(err)
}
atStore := addonInformerFactory.Addon().V1alpha1().AddOnTemplates().Informer().GetStore()
if err := atStore.Add(c.template); err != nil {
t.Fatal(err)
}
agent := NewCRDTemplateAgentAddon(ctx, c.addon.Name, nil, addonClient, addonInformerFactory, nil, nil)
f := agent.TemplateCSRApproveCheckFunc()
approve := f(ctx, c.cluster, c.addon, c.csr)
if approve != c.expectedApprove {
t.Errorf("expected approve result %v, but got %v", c.expectedApprove, approve)
}
}
}
func TestTemplateCSRSignFunc(t *testing.T) {
ca, key, err := certutil.GenerateSelfSignedCertKey("test", []net.IP{}, []string{})
if err != nil {
t.Errorf("Failed to generate self signed CA config: %v", err)
}
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test-ns",
},
Data: map[string][]byte{
corev1.TLSCertKey: ca,
corev1.TLSPrivateKeyKey: key,
},
Type: corev1.SecretTypeTLS,
}
cases := []struct {
name string
cluster *clusterv1.ManagedCluster
addon *addonv1beta1.ManagedClusterAddOn
template *addonapiv1alpha1.AddOnTemplate
casecret *corev1.Secret
csr *certificatesv1.CertificateSigningRequest
expectedCert []byte
expectedErr string
}{
{
name: "kubeclient",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeKubeClient,
KubeClient: &addonapiv1alpha1.KubeClientRegistrationConfig{
HubPermissions: []addonapiv1alpha1.HubPermissionConfig{
{
Type: addonapiv1alpha1.HubPermissionsBindingSingleNamespace,
SingleNamespace: &addonapiv1alpha1.SingleNamespaceBindingConfig{
Namespace: "test",
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: "test",
},
},
},
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
csr: &certificatesv1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "csr1",
},
Spec: certificatesv1.CertificateSigningRequestSpec{
SignerName: certificates.KubeAPIServerClientSignerName,
Username: "system:open-cluster-management:cluster1:adcde",
},
},
expectedCert: nil,
},
{
name: "customsigner no ca secret",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeCustomSigner,
CustomSigner: &addonapiv1alpha1.CustomSignerRegistrationConfig{
SignerName: "s1",
Subject: &addonapiv1alpha1.Subject{
User: "u1",
Groups: []string{
"g1",
"g2",
},
OrganizationUnits: []string{},
},
SigningCA: addonapiv1alpha1.SigningCARef{
Name: "name1",
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
csr: &certificatesv1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "csr1",
},
Spec: certificatesv1.CertificateSigningRequestSpec{
SignerName: "s1",
Username: "system:open-cluster-management:cluster1:adcde",
},
},
expectedCert: nil,
expectedErr: `get custom signer ca open-cluster-management-hub/name1 failed: secrets "name1" not found`,
},
{
name: "customsigner with ca secret",
casecret: secret,
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeCustomSigner,
CustomSigner: &addonapiv1alpha1.CustomSignerRegistrationConfig{
SignerName: "s1",
Subject: &addonapiv1alpha1.Subject{
User: "u1",
Groups: []string{
"g1",
"g2",
},
OrganizationUnits: []string{},
},
SigningCA: addonapiv1alpha1.SigningCARef{
Name: secret.Name,
Namespace: secret.Namespace,
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
csr: &certificatesv1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: "csr1",
Labels: map[string]string{
clusterv1.ClusterNameLabelKey: "cluster1",
},
},
Spec: certificatesv1.CertificateSigningRequestSpec{
SignerName: "s1",
Username: "system:open-cluster-management:cluster1:adcde",
},
},
expectedCert: nil,
expectedErr: "failed to sign csr: PEM block type must be CERTIFICATE REQUEST",
},
}
for _, c := range cases {
_, ctx := ktesting.NewTestContext(t)
addonClient := fakeaddon.NewSimpleClientset(c.template, c.addon)
hubKubeClient := fakekube.NewSimpleClientset()
if c.casecret != nil {
hubKubeClient = fakekube.NewSimpleClientset(c.casecret)
}
addonInformerFactory := addoninformers.NewSharedInformerFactory(addonClient, 30*time.Minute)
mcaStore := addonInformerFactory.Addon().V1beta1().ManagedClusterAddOns().Informer().GetStore()
if err := mcaStore.Add(c.addon); err != nil {
t.Fatal(err)
}
atStore := addonInformerFactory.Addon().V1alpha1().AddOnTemplates().Informer().GetStore()
if err := atStore.Add(c.template); err != nil {
t.Fatal(err)
}
agent := NewCRDTemplateAgentAddon(ctx, c.addon.Name, hubKubeClient, addonClient, addonInformerFactory, nil, nil)
f := agent.TemplateCSRSignFunc()
cert, err := f(ctx, c.cluster, c.addon, c.csr)
if c.expectedErr == "" {
if err != nil {
t.Fatalf("case: %s, expected no error but got: %v", c.name, err)
}
} else {
if err == nil {
t.Fatalf("case: %s, expected error but got none", c.name)
}
if !strings.Contains(err.Error(), c.expectedErr) {
t.Fatalf("case: %s, expected error containing %q but got: %v", c.name, c.expectedErr, err)
}
}
if !bytes.Equal(cert, c.expectedCert) {
t.Errorf("expected cert %v, but got %v", c.expectedCert, cert)
}
}
}
func NewFakeManagedCluster(name string) *clusterv1.ManagedCluster {
return &clusterv1.ManagedCluster{
TypeMeta: metav1.TypeMeta{
Kind: "ManagedCluster",
APIVersion: clusterv1.GroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: clusterv1.ManagedClusterSpec{},
}
}
func NewFakeTemplateManagedClusterAddon(name, clusterName, addonTemplateName, addonTemplateSpecHash string) *addonv1beta1.ManagedClusterAddOn {
addon := &addonv1beta1.ManagedClusterAddOn{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: clusterName,
UID: "fakeuid",
},
Spec: addonv1beta1.ManagedClusterAddOnSpec{},
Status: addonv1beta1.ManagedClusterAddOnStatus{
// Add default registration status with subjects for dynamic RBAC binding
Registrations: []addonv1beta1.RegistrationConfig{
{
Type: addonv1beta1.KubeClient,
KubeClient: &addonv1beta1.KubeClientConfig{
Driver: "csr",
Subject: addonv1beta1.KubeClientSubject{
BaseSubject: addonv1beta1.BaseSubject{
User: fmt.Sprintf("system:open-cluster-management:cluster:%s:addon:%s:agent:%s-agent",
clusterName, name, name),
Groups: []string{
fmt.Sprintf("system:open-cluster-management:cluster:%s:addon:%s", clusterName, name),
fmt.Sprintf("system:open-cluster-management:addon:%s", name),
},
},
},
},
},
},
},
}
if addonTemplateName != "" {
addon.Status.ConfigReferences = []addonv1beta1.ConfigReference{
{
ConfigGroupResource: addonv1beta1.ConfigGroupResource{
Group: "addon.open-cluster-management.io",
Resource: "addontemplates",
},
DesiredConfig: &addonv1beta1.ConfigSpecHash{
ConfigReferent: addonv1beta1.ConfigReferent{
Name: addonTemplateName,
},
SpecHash: addonTemplateSpecHash,
},
},
}
}
return addon
}
func NewFakeAddonTemplate(name string,
registrationSpec []addonapiv1alpha1.RegistrationSpec) *addonapiv1alpha1.AddOnTemplate {
return &addonapiv1alpha1.AddOnTemplate{
TypeMeta: metav1.TypeMeta{},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: addonapiv1alpha1.AddOnTemplateSpec{
Registration: registrationSpec,
},
}
}
func NewFakeRoleBinding(addonName, namespace string, subject []rbacv1.Subject, roleRef rbacv1.RoleRef,
owner metav1.OwnerReference) *rbacv1.RoleBinding {
return &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("open-cluster-management:%s:%s:agent",
addonName, strings.ToLower(roleRef.Kind)),
Namespace: namespace,
OwnerReferences: []metav1.OwnerReference{owner},
Labels: map[string]string{
addonapiv1alpha1.AddonLabelKey: addonName,
},
},
RoleRef: roleRef,
Subjects: subject,
}
}
func TestTemplatePermissionConfigFunc(t *testing.T) {
cases := []struct {
name string
cluster *clusterv1.ManagedCluster
addon *addonv1beta1.ManagedClusterAddOn
template *addonapiv1alpha1.AddOnTemplate
rolebinding *rbacv1.RoleBinding
expectedErr error
validatePermissionFunc func(*testing.T, kubernetes.Interface)
}{
{
name: "kubeclient current cluster binding, rolebinding not exist",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeKubeClient,
KubeClient: &addonapiv1alpha1.KubeClientRegistrationConfig{
HubPermissions: []addonapiv1alpha1.HubPermissionConfig{
{
Type: addonapiv1alpha1.HubPermissionsBindingCurrentCluster,
CurrentCluster: &addonapiv1alpha1.CurrentClusterBindingConfig{
ClusterRoleName: "test",
},
},
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
expectedErr: nil,
validatePermissionFunc: func(t *testing.T, kubeClient kubernetes.Interface) {
rb, err := kubeClient.RbacV1().RoleBindings("cluster1").Get(context.TODO(),
fmt.Sprintf("open-cluster-management:%s:%s:agent", "addon1", strings.ToLower("ClusterRole")),
metav1.GetOptions{},
)
if err != nil {
t.Errorf("failed to get rolebinding: %v", err)
}
if rb.RoleRef.Name != "test" {
t.Errorf("expected rolebinding %s, got %s", "test", rb.RoleRef.Name)
}
if rb.RoleRef.Kind != "ClusterRole" {
t.Errorf("expected rolebinding kind %s, got %s", "ClusterRole", rb.RoleRef.Kind)
}
if len(rb.OwnerReferences) != 1 {
t.Errorf("expected rolebinding to have 1 owner reference, got %d", len(rb.OwnerReferences))
}
if rb.OwnerReferences[0].Kind != "ManagedClusterAddOn" {
t.Errorf("expected rolebinding owner reference kind to be ManagedClusterAddOn, got %s",
rb.OwnerReferences[0].Kind)
}
if rb.OwnerReferences[0].Name != "addon1" {
t.Errorf("expected rolebinding owner reference name to be addon1, got %s",
rb.OwnerReferences[0].Name)
}
},
},
{
name: "kubeclient current cluster binding, rolebinding exists",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeKubeClient,
KubeClient: &addonapiv1alpha1.KubeClientRegistrationConfig{
HubPermissions: []addonapiv1alpha1.HubPermissionConfig{
{
Type: addonapiv1alpha1.HubPermissionsBindingCurrentCluster,
CurrentCluster: &addonapiv1alpha1.CurrentClusterBindingConfig{
ClusterRoleName: "test",
},
},
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
rolebinding: NewFakeRoleBinding("addon1", "cluster1",
[]rbacv1.Subject{{
Kind: "Group",
APIGroup: "rbac.authorization.k8s.io",
Name: "system:authenticated"},
}, rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: "test",
},
metav1.OwnerReference{
APIVersion: addonv1beta1.GroupVersion.String(),
Kind: "ManagedClusterAddOn",
Name: "addon1",
UID: "fakeuid",
}),
expectedErr: nil,
validatePermissionFunc: func(t *testing.T, kubeClient kubernetes.Interface) {
rb, err := kubeClient.RbacV1().RoleBindings("cluster1").Get(context.TODO(),
fmt.Sprintf("open-cluster-management:%s:%s:agent", "addon1", strings.ToLower("ClusterRole")),
metav1.GetOptions{},
)
if err != nil {
t.Errorf("failed to get rolebinding: %v", err)
}
if rb.RoleRef.Name != "test" {
t.Errorf("expected rolebinding %s, got %s", "test", rb.RoleRef.Name)
}
if rb.RoleRef.Kind != "ClusterRole" {
t.Errorf("expected rolebinding kind %s, got %s", "ClusterRole", rb.RoleRef.Kind)
}
if len(rb.OwnerReferences) != 1 {
t.Errorf("expected rolebinding to have 1 owner reference, got %d", len(rb.OwnerReferences))
}
if rb.OwnerReferences[0].Kind != "ManagedClusterAddOn" {
t.Errorf("expected rolebinding owner reference kind to be ManagedClusterAddOn, got %s",
rb.OwnerReferences[0].Kind)
}
if rb.OwnerReferences[0].Name != "addon1" {
t.Errorf("expected rolebinding owner reference name to be addon1, got %s",
rb.OwnerReferences[0].Name)
}
},
},
{
name: "kubeclient single namespace binding",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeKubeClient,
KubeClient: &addonapiv1alpha1.KubeClientRegistrationConfig{
HubPermissions: []addonapiv1alpha1.HubPermissionConfig{
{
Type: addonapiv1alpha1.HubPermissionsBindingSingleNamespace,
SingleNamespace: &addonapiv1alpha1.SingleNamespaceBindingConfig{
Namespace: "test",
RoleRef: rbacv1.RoleRef{
APIGroup: rbacv1.GroupName,
Kind: "ClusterRole",
Name: "test",
},
},
},
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
expectedErr: nil,
validatePermissionFunc: func(t *testing.T, kubeClient kubernetes.Interface) {
rb, err := kubeClient.RbacV1().RoleBindings("test").Get(context.TODO(),
fmt.Sprintf("open-cluster-management:%s:%s:agent", "addon1", strings.ToLower("ClusterRole")),
metav1.GetOptions{},
)
if err != nil {
t.Errorf("failed to get rolebinding: %v", err)
}
if rb.RoleRef.Name != "test" {
t.Errorf("expected rolebinding %s, got %s", "test", rb.RoleRef.Name)
}
if len(rb.OwnerReferences) != 0 {
t.Errorf("expected rolebinding to have 0 owner reference, got %d", len(rb.OwnerReferences))
}
// Dynamic subjects: 1 user + 2 groups
if len(rb.Subjects) != 3 {
t.Errorf("expected rolebinding to have 3 subjects, got %d", len(rb.Subjects))
}
// Verify user subject
if rb.Subjects[0].Kind != rbacv1.UserKind {
t.Errorf("expected first subject to be User, got %s", rb.Subjects[0].Kind)
}
if rb.Subjects[0].Name != "system:open-cluster-management:cluster:cluster1:addon:addon1:agent:addon1-agent" {
t.Errorf("expected user subject name to be system:open-cluster-management:cluster:cluster1:addon:addon1:agent:addon1-agent, got %s",
rb.Subjects[0].Name)
}
// Verify group subjects
if rb.Subjects[1].Kind != rbacv1.GroupKind {
t.Errorf("expected second subject to be Group, got %s", rb.Subjects[1].Kind)
}
if rb.Subjects[2].Kind != rbacv1.GroupKind {
t.Errorf("expected third subject to be Group, got %s", rb.Subjects[2].Kind)
}
},
},
{
name: "customsigner",
cluster: NewFakeManagedCluster("cluster1"),
template: NewFakeAddonTemplate("template1", []addonapiv1alpha1.RegistrationSpec{
{
Type: addonapiv1alpha1.RegistrationTypeCustomSigner,
CustomSigner: &addonapiv1alpha1.CustomSignerRegistrationConfig{
SignerName: "s1",
Subject: &addonapiv1alpha1.Subject{
User: "u1",
Groups: []string{
"g1",
"g2",
},
OrganizationUnits: []string{},
},
SigningCA: addonapiv1alpha1.SigningCARef{
Name: "name1",
},
},
},
}),
addon: NewFakeTemplateManagedClusterAddon("addon1", "cluster1", "template1", "fakehash"),
expectedErr: nil,
},
}
for _, c := range cases {
_, ctx := ktesting.NewTestContext(t)
addonClient := fakeaddon.NewSimpleClientset(c.template, c.addon)
hubKubeClient := fakekube.NewSimpleClientset()
if c.rolebinding != nil {
hubKubeClient = fakekube.NewSimpleClientset(c.rolebinding)
}
addonInformerFactory := addoninformers.NewSharedInformerFactory(addonClient, 30*time.Minute)
mcaStore := addonInformerFactory.Addon().V1beta1().ManagedClusterAddOns().Informer().GetStore()
if err := mcaStore.Add(c.addon); err != nil {
t.Fatal(err)
}
atStore := addonInformerFactory.Addon().V1alpha1().AddOnTemplates().Informer().GetStore()
if err := atStore.Add(c.template); err != nil {
t.Fatal(err)
}
kubeInformers := kubeinformers.NewSharedInformerFactoryWithOptions(hubKubeClient, 10*time.Minute)
if c.rolebinding != nil {
rbStore := kubeInformers.Rbac().V1().RoleBindings().Informer().GetStore()
if err := rbStore.Add(c.rolebinding); err != nil {
t.Fatal(err)
}
}
agent := NewCRDTemplateAgentAddon(ctx, c.addon.Name, hubKubeClient, addonClient, addonInformerFactory,
kubeInformers.Rbac().V1().RoleBindings().Lister())
f := agent.TemplatePermissionConfigFunc()
err := f(ctx, c.cluster, c.addon)
if err != c.expectedErr {
t.Errorf("expected registrationConfigs %v, but got %v", c.expectedErr, err)
}
if c.validatePermissionFunc != nil {
c.validatePermissionFunc(t, hubKubeClient)
}
}
}
func TestAddonManagerNamespace(t *testing.T) {
cases := []struct {
name string
podNamespace string
envNs string
expected string
}{
{
name: "pod namespace is not empty",
podNamespace: "test",
envNs: "",
expected: "test",
},
{
name: "pod namespace is empty, env is not empty",
podNamespace: "",
envNs: "test-env",
expected: "test-env",
},
{
name: "default namespace",
podNamespace: "",
envNs: "",
expected: "open-cluster-management-hub",
},
}
for _, c := range cases {
if c.podNamespace != "" {
podNamespace = c.podNamespace
}
if c.envNs != "" {
os.Setenv("POD_NAMESPACE", c.envNs)
}
ns := AddonManagerNamespace()
assert.Equal(t, c.expected, ns)
// reset podNamespace and env
podNamespace = ""
os.Setenv("POD_NAMESPACE", "")
}
}
func TestBuildSubjectsFromRegistration(t *testing.T) {
cases := []struct {
name string
addon *addonv1beta1.ManagedClusterAddOn
signerName string
expectedSubjects []rbacv1.Subject
}{
{
name: "no registrations",
addon: &addonv1beta1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: "test-addon",
Namespace: "cluster1",
},
Status: addonv1beta1.ManagedClusterAddOnStatus{},
},
signerName: certificatesv1.KubeAPIServerClientSignerName,
expectedSubjects: nil,
},
{
name: "registration with user and groups",
addon: &addonv1beta1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: "test-addon",
Namespace: "cluster1",
},
Status: addonv1beta1.ManagedClusterAddOnStatus{
Registrations: []addonv1beta1.RegistrationConfig{
{
Type: addonv1beta1.KubeClient,
KubeClient: &addonv1beta1.KubeClientConfig{
Subject: addonv1beta1.KubeClientSubject{
BaseSubject: addonv1beta1.BaseSubject{
User: "system:open-cluster-management:cluster:cluster1:addon:test-addon:agent:test-addon-agent",
Groups: []string{
"system:open-cluster-management:cluster:cluster1:addon:test-addon",
"system:open-cluster-management:addon:test-addon",
},
},
},
},
},
},
},
},
signerName: certificatesv1.KubeAPIServerClientSignerName,
expectedSubjects: []rbacv1.Subject{
{
Kind: rbacv1.UserKind,
APIGroup: rbacv1.GroupName,
Name: "system:open-cluster-management:cluster:cluster1:addon:test-addon:agent:test-addon-agent",
},
{
Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName,
Name: "system:open-cluster-management:cluster:cluster1:addon:test-addon",
},
{
Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName,
Name: "system:open-cluster-management:addon:test-addon",
},
},
},
{
name: "registration with only groups",
addon: &addonv1beta1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: "test-addon",
Namespace: "cluster1",
},
Status: addonv1beta1.ManagedClusterAddOnStatus{
Registrations: []addonv1beta1.RegistrationConfig{
{
Type: addonv1beta1.KubeClient,
KubeClient: &addonv1beta1.KubeClientConfig{
Subject: addonv1beta1.KubeClientSubject{
BaseSubject: addonv1beta1.BaseSubject{
Groups: []string{
"system:open-cluster-management:cluster:cluster1:addon:test-addon",
},
},
},
},
},
},
},
},
signerName: certificatesv1.KubeAPIServerClientSignerName,
expectedSubjects: []rbacv1.Subject{
{
Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName,
Name: "system:open-cluster-management:cluster:cluster1:addon:test-addon",
},
},
},
{
name: "registration with only user",
addon: &addonv1beta1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: "test-addon",
Namespace: "cluster1",
},
Status: addonv1beta1.ManagedClusterAddOnStatus{
Registrations: []addonv1beta1.RegistrationConfig{
{
Type: addonv1beta1.KubeClient,
KubeClient: &addonv1beta1.KubeClientConfig{
Subject: addonv1beta1.KubeClientSubject{
BaseSubject: addonv1beta1.BaseSubject{
User: "system:serviceaccount:cluster1:test-addon-sa",
},
},
},
},
},
},
},
signerName: certificatesv1.KubeAPIServerClientSignerName,
expectedSubjects: []rbacv1.Subject{
{
Kind: rbacv1.UserKind,
APIGroup: rbacv1.GroupName,
Name: "system:serviceaccount:cluster1:test-addon-sa",
},
},
},
{
name: "empty subject",
addon: &addonv1beta1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: "test-addon",
Namespace: "cluster1",
},
Status: addonv1beta1.ManagedClusterAddOnStatus{
Registrations: []addonv1beta1.RegistrationConfig{
{
Type: addonv1beta1.KubeClient,
},
},
},
},
signerName: certificatesv1.KubeAPIServerClientSignerName,
expectedSubjects: nil,
},
{
name: "signer name not found",
addon: &addonv1beta1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: "test-addon",
Namespace: "cluster1",
},
Status: addonv1beta1.ManagedClusterAddOnStatus{
Registrations: []addonv1beta1.RegistrationConfig{
{
Type: addonv1beta1.CustomSigner,
CustomSigner: &addonv1beta1.CustomSignerConfig{
SignerName: "custom.signer/name",
Subject: addonv1beta1.Subject{
BaseSubject: addonv1beta1.BaseSubject{
User: "test-user",
},
},
},
},
},
},
},
signerName: certificatesv1.KubeAPIServerClientSignerName,
expectedSubjects: nil,
},
{
name: "multiple registrations - finds correct one",
addon: &addonv1beta1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: "test-addon",
Namespace: "cluster1",
},
Status: addonv1beta1.ManagedClusterAddOnStatus{
Registrations: []addonv1beta1.RegistrationConfig{
{
Type: addonv1beta1.CustomSigner,
CustomSigner: &addonv1beta1.CustomSignerConfig{
SignerName: "custom.signer/name",
Subject: addonv1beta1.Subject{
BaseSubject: addonv1beta1.BaseSubject{
User: "custom-user",
},
},
},
},
{
Type: addonv1beta1.KubeClient,
KubeClient: &addonv1beta1.KubeClientConfig{
Subject: addonv1beta1.KubeClientSubject{
BaseSubject: addonv1beta1.BaseSubject{
User: "kube-user",
Groups: []string{
"kube-group",
},
},
},
},
},
},
},
},
signerName: certificatesv1.KubeAPIServerClientSignerName,
expectedSubjects: []rbacv1.Subject{
{
Kind: rbacv1.UserKind,
APIGroup: rbacv1.GroupName,
Name: "kube-user",
},
{
Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName,
Name: "kube-group",
},
},
},
{
name: "groups with system:authenticated should be filtered out",
addon: &addonv1beta1.ManagedClusterAddOn{
ObjectMeta: metav1.ObjectMeta{
Name: "test-addon",
Namespace: "cluster1",
},
Status: addonv1beta1.ManagedClusterAddOnStatus{
Registrations: []addonv1beta1.RegistrationConfig{
{
Type: addonv1beta1.KubeClient,
KubeClient: &addonv1beta1.KubeClientConfig{
Subject: addonv1beta1.KubeClientSubject{
BaseSubject: addonv1beta1.BaseSubject{
User: "system:serviceaccount:cluster1:test-addon-sa",
Groups: []string{
"system:authenticated",
"system:open-cluster-management:cluster:cluster1:addon:test-addon",
"system:serviceaccounts",
},
},
},
},
},
},
},
},
signerName: certificatesv1.KubeAPIServerClientSignerName,
expectedSubjects: []rbacv1.Subject{
{
Kind: rbacv1.UserKind,
APIGroup: rbacv1.GroupName,
Name: "system:serviceaccount:cluster1:test-addon-sa",
},
{
Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName,
Name: "system:open-cluster-management:cluster:cluster1:addon:test-addon",
},
{
Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName,
Name: "system:serviceaccounts",
},
},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
subjects := buildSubjectsFromRegistration(c.addon)
if !equality.Semantic.DeepEqual(subjects, c.expectedSubjects) {
t.Errorf("expected subjects %v, got %v", c.expectedSubjects, subjects)
}
})
}
}