github.com/jenkins-x/jx/v2@v2.1.155/pkg/cmd/controller/controller_role_test.go (about)

     1  // +build unit
     2  
     3  package controller_test
     4  
     5  import (
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/jenkins-x/jx/v2/pkg/cmd/controller"
    10  	"github.com/jenkins-x/jx/v2/pkg/cmd/testhelpers"
    11  
    12  	"github.com/jenkins-x/jx-logging/pkg/log"
    13  	"github.com/stretchr/testify/require"
    14  	"k8s.io/client-go/kubernetes"
    15  
    16  	v1 "github.com/jenkins-x/jx-api/pkg/apis/jenkins.io/v1"
    17  	"github.com/jenkins-x/jx/v2/pkg/cmd/opts"
    18  	"github.com/jenkins-x/jx/v2/pkg/gits"
    19  	"github.com/jenkins-x/jx/v2/pkg/helm"
    20  	"github.com/jenkins-x/jx/v2/pkg/kube"
    21  	resources_test "github.com/jenkins-x/jx/v2/pkg/kube/resources/mocks"
    22  	"github.com/jenkins-x/jx/v2/pkg/tests"
    23  	"github.com/jenkins-x/jx/v2/pkg/util"
    24  	"github.com/stretchr/testify/assert"
    25  	rbacv1 "k8s.io/api/rbac/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  )
    29  
    30  func TestEnvironmentRoleBinding(t *testing.T) {
    31  	t.Parallel()
    32  	o := &controller.ControllerRoleOptions{
    33  		ControllerOptions: controller.ControllerOptions{
    34  			CommonOptions: &opts.CommonOptions{},
    35  		},
    36  		NoWatch: true,
    37  	}
    38  	roleName := "myrole"
    39  	roleBindingName := roleName
    40  	roleNameWithoutLabel := "myroleWithoutLabel"
    41  	teamNs := "jx"
    42  	roleLabels := make(map[string]string)
    43  	roleLabels[kube.LabelKind] = kube.ValueKindEnvironmentRole
    44  
    45  	role := &rbacv1.Role{
    46  		ObjectMeta: metav1.ObjectMeta{
    47  			Name:      roleName,
    48  			Namespace: teamNs,
    49  			Labels:    roleLabels,
    50  		},
    51  		Rules: []rbacv1.PolicyRule{
    52  			{
    53  				Verbs:     []string{"get", "watch", "list"},
    54  				APIGroups: []string{""},
    55  				Resources: []string{"configmaps", "pods", "services"},
    56  			},
    57  		},
    58  	}
    59  
    60  	roleWithLabel := &rbacv1.Role{
    61  		ObjectMeta: metav1.ObjectMeta{
    62  			Name:      roleNameWithoutLabel,
    63  			Namespace: teamNs,
    64  		},
    65  		Rules: []rbacv1.PolicyRule{
    66  			{
    67  				Verbs:     []string{"get", "watch", "list"},
    68  				APIGroups: []string{""},
    69  				Resources: []string{"configmaps", "pods", "services"},
    70  			},
    71  		},
    72  	}
    73  
    74  	envRoleBinding := &v1.EnvironmentRoleBinding{
    75  		ObjectMeta: metav1.ObjectMeta{
    76  			Name:      roleBindingName,
    77  			Namespace: teamNs,
    78  		},
    79  		Spec: v1.EnvironmentRoleBindingSpec{
    80  			Subjects: []rbacv1.Subject{
    81  				{
    82  					Kind:      "ServiceAccount",
    83  					Name:      "jenkins",
    84  					Namespace: teamNs,
    85  				},
    86  			},
    87  			RoleRef: rbacv1.RoleRef{
    88  				APIGroup: "rbac.authorization.k8s.io",
    89  				Kind:     "Role",
    90  				Name:     roleName,
    91  			},
    92  			Environments: []v1.EnvironmentFilter{
    93  				{
    94  					Includes: []string{"*"},
    95  				},
    96  			},
    97  		},
    98  	}
    99  
   100  	testhelpers.ConfigureTestOptionsWithResources(o.CommonOptions,
   101  		[]runtime.Object{
   102  			role,
   103  			roleWithLabel,
   104  		},
   105  		[]runtime.Object{
   106  			kube.NewPermanentEnvironment("staging"),
   107  			kube.NewPermanentEnvironment("production"),
   108  			kube.NewPreviewEnvironment(teamNs + "-jstrachan-demo96-pr-1"),
   109  			kube.NewPreviewEnvironment(teamNs + "-jstrachan-another-pr-3"),
   110  			envRoleBinding,
   111  		},
   112  		gits.NewGitCLI(),
   113  		nil,
   114  		helm.NewHelmCLI("helm", helm.V2, "", true),
   115  		resources_test.NewMockInstaller(),
   116  	)
   117  
   118  	err := o.Run()
   119  	assert.NoError(t, err)
   120  
   121  	nsNames := []string{teamNs, teamNs + "-staging", teamNs + "-production", teamNs + "-preview-jx-jstrachan-demo96-pr-1", teamNs + "-preview-jx-jstrachan-another-pr-3"}
   122  
   123  	kubeClient, err := o.KubeClient()
   124  	require.NoError(t, err)
   125  	jxClient, _, err := o.JXClient()
   126  	require.NoError(t, err)
   127  
   128  	for _, ns := range nsNames {
   129  		roleBinding, err := kubeClient.RbacV1().RoleBindings(ns).Get(roleBindingName, metav1.GetOptions{})
   130  		assert.NoError(t, err, "Failed to find RoleBinding in namespace %s for name %s", ns, roleBindingName)
   131  
   132  		if roleBinding != nil && err == nil {
   133  			assert.Equal(t, envRoleBinding.Spec.RoleRef, roleBinding.RoleRef,
   134  				"RoleBinding.RoleRef for name %s in namespace %s", roleBindingName, ns)
   135  		}
   136  
   137  		r, err := kubeClient.RbacV1().Roles(ns).Get(roleName, metav1.GetOptions{})
   138  		assert.NoError(t, err, "Failed to find Role in namespace %s for name %s", ns, roleName)
   139  
   140  		if r != nil && err == nil {
   141  			assert.Equal(t, role.Rules, r.Rules,
   142  				"Role.Rules for name %s in namespace %s", roleBindingName, ns)
   143  		}
   144  		if util.StringMatchesPattern(ns, teamNs) {
   145  			jxClient, ns, err := o.JXClient()
   146  			if err == nil {
   147  				envRoleBindings, err := jxClient.JenkinsV1().EnvironmentRoleBindings(ns).Get(roleName, metav1.GetOptions{})
   148  				if err != nil {
   149  					assert.NotNil(t, envRoleBindings, "No EnvironmentRoleBinding called %s in namespace %s", roleName, ns)
   150  				}
   151  			}
   152  		}
   153  	}
   154  
   155  	if tests.IsDebugLog() {
   156  		namespaces, err := kubeClient.CoreV1().Namespaces().List(metav1.ListOptions{})
   157  		assert.NoError(t, err)
   158  		if err == nil {
   159  			for _, ns := range namespaces.Items {
   160  				tests.Debugf("Has namespace %s\n", ns.Name)
   161  			}
   162  		}
   163  	}
   164  
   165  	// now lets add new user to the EnvironmentRoleBinding
   166  	newUserKind := "ServiceAccount"
   167  	newUser := "cheese"
   168  	envRoleBinding, err = jxClient.JenkinsV1().EnvironmentRoleBindings(teamNs).Get(roleBindingName, metav1.GetOptions{})
   169  	require.NoError(t, err, "Loading EnvironmentRoleBinding in ns %s with name %s", teamNs, roleBindingName)
   170  	envRoleBinding.Spec.Subjects = append(envRoleBinding.Spec.Subjects, rbacv1.Subject{
   171  		Kind:      "ServiceAccount",
   172  		Name:      newUser,
   173  		Namespace: teamNs,
   174  	})
   175  
   176  	envRoleBinding, err = jxClient.JenkinsV1().EnvironmentRoleBindings(teamNs).PatchUpdate(envRoleBinding)
   177  	require.NoError(t, err, "Updating EnvironmentRoleBinding in ns %s with name %s", teamNs, roleBindingName)
   178  
   179  	// now lets simulate the watch...
   180  	err = o.UpsertEnvironmentRoleBinding(envRoleBinding)
   181  	require.NoError(t, err, "Failed to respond to updated EnvironmentRoleBinding in ns %s with name %s", teamNs, roleBindingName)
   182  
   183  	AssertRoleBindingsInEnvironmentsContainsSubject(t, kubeClient, nsNames, roleBindingName, newUserKind, teamNs, newUser)
   184  
   185  	message := fmt.Sprintf("For EnvironmentRoleBinding in namespace %s for name %s", teamNs, roleBindingName)
   186  
   187  	// lets add a new preview environment
   188  	newEnv := kube.NewPreviewEnvironment(teamNs + "-jstrachan-newthingy-pr-1")
   189  	newPreviewNS := newEnv.Spec.Namespace
   190  	_, err = jxClient.JenkinsV1().Environments(teamNs).Create(newEnv)
   191  	require.NoError(t, err, "Failed to create an Environment %s in ns %s", newPreviewNS, teamNs)
   192  
   193  	log.Logger().Infof("Created Preview Environment %s", newPreviewNS)
   194  
   195  	// now lets simulate the watch...
   196  	err = o.UpsertEnvironmentRoleBinding(envRoleBinding)
   197  
   198  	nsNames = append(nsNames, newPreviewNS)
   199  	AssertRoleBindingsInEnvironmentsContainsSubject(t, kubeClient, nsNames, roleBindingName, newUserKind, teamNs, newUser)
   200  
   201  	// now lets remove the user...
   202  	envRoleBinding.Spec.Subjects = AssertRemoveSubject(t, envRoleBinding.Spec.Subjects, message, newUserKind, teamNs, newUser)
   203  	envRoleBinding, err = jxClient.JenkinsV1().EnvironmentRoleBindings(teamNs).PatchUpdate(envRoleBinding)
   204  	require.NoError(t, err, "Updating EnvironmentRoleBinding in ns %s with name %s", teamNs, roleBindingName)
   205  
   206  	// now lets simulate the watch...
   207  	err = o.UpsertEnvironmentRoleBinding(envRoleBinding)
   208  
   209  	AssertRoleBindingsInEnvironmentsNotContainsSubject(t, kubeClient, nsNames, roleBindingName, newUserKind, teamNs, newUser)
   210  
   211  	// lets assert that roles get updated in all the namespaces
   212  	AssertRolesInEnvironmentsNotContainsPolicyRule(t, kubeClient, nsNames, roleName, "", "get", "secrets")
   213  	role, err = kubeClient.RbacV1().Roles(teamNs).Get(roleName, metav1.GetOptions{})
   214  	require.NoError(t, err, "Failed to get Role in ns %s with name %s", teamNs, roleName)
   215  
   216  	lastIdx := len(role.Rules) - 1
   217  	role.Rules[lastIdx].Resources = append(role.Rules[lastIdx].Resources, "secrets")
   218  	log.Logger().Infof("Updated Role %s to be policies %#v", roleName, role.Rules)
   219  	_, err = kubeClient.RbacV1().Roles(teamNs).Update(role)
   220  	require.NoError(t, err, "Updating EnvironmentRoleBinding in ns %s with name %s", teamNs, roleBindingName)
   221  
   222  	// now lets simulate the watch...
   223  	err = o.UpsertRole(role)
   224  
   225  	AssertRolesInEnvironmentsContainsPolicyRule(t, kubeClient, nsNames, roleName, "", "get", "secrets")
   226  }
   227  
   228  // AssertRemoveSubject removes the subject from the slice of subjects for the given kind, ns, name or fails the test
   229  func AssertRemoveSubject(t *testing.T, subjects []rbacv1.Subject, message string, kind string, ns string, name string) []rbacv1.Subject {
   230  	idx := -1
   231  	for i, subject := range subjects {
   232  		if subject.Kind == kind && subject.Namespace == ns && subject.Name == name {
   233  			idx = i
   234  			break
   235  		}
   236  	}
   237  	if idx < 0 {
   238  		assert.Fail(t, "Should not contain subject (%s,%s,%s) for %s - has subjects %#v", kind, ns, name, message, subjects)
   239  		return subjects
   240  	}
   241  	return append(subjects[0:idx], subjects[idx+1:]...)
   242  }
   243  
   244  // AssertRoleBindingsInEnvironmentsContainsSubject asserts that all the environments contain a role binding of the given name which contains the given subject
   245  func AssertRoleBindingsInEnvironmentsContainsSubject(t *testing.T, kubeClient kubernetes.Interface, nsNames []string, roleBindingName string, kind string, teamNs string, newUser string) {
   246  	for _, ns := range nsNames {
   247  		roleBinding, err := kubeClient.RbacV1().RoleBindings(ns).Get(roleBindingName, metav1.GetOptions{})
   248  		require.NoError(t, err, "Failed to find RoleBinding in namespace %s for name %s", ns, roleBindingName)
   249  		require.NotNil(t, roleBinding, "Failed to find RoleBinding in namespace %s for name %s", ns, roleBindingName)
   250  
   251  		messsage := fmt.Sprintf("RoleBinding in namespace %s for name %s", ns, roleBindingName)
   252  		AssertContainsSubject(t, roleBinding.Subjects, messsage, kind, teamNs, newUser)
   253  	}
   254  }
   255  
   256  // AssertRoleBindingsInEnvironmentsNotContainsSubject asserts that all the environments do not contain a role binding of the given name which contains the given subject
   257  func AssertRoleBindingsInEnvironmentsNotContainsSubject(t *testing.T, kubeClient kubernetes.Interface, nsNames []string, roleBindingName string, kind string, teamNs string, newUser string) {
   258  	for _, ns := range nsNames {
   259  		roleBinding, err := kubeClient.RbacV1().RoleBindings(ns).Get(roleBindingName, metav1.GetOptions{})
   260  		require.NoError(t, err, "Failed to find RoleBinding in namespace %s for name %s", ns, roleBindingName)
   261  		require.NotNil(t, roleBinding, "Failed to find RoleBinding in namespace %s for name %s", ns, roleBindingName)
   262  
   263  		messsage := fmt.Sprintf("RoleBinding in namespace %s for name %s", ns, roleBindingName)
   264  		AssertNotContainsSubject(t, roleBinding.Subjects, messsage, kind, teamNs, newUser)
   265  	}
   266  }
   267  
   268  // AssertContainsSubject asserts that the given array of subjects contains the given kind, namespace and name subject
   269  func AssertContainsSubject(t *testing.T, subjects []rbacv1.Subject, message string, kind string, ns string, name string) bool {
   270  	for _, subject := range subjects {
   271  		if subject.Kind == kind && subject.Namespace == ns && subject.Name == name {
   272  			return true
   273  		}
   274  	}
   275  	log.Logger().Warnf("Does not contain Subject: (%s,%s,%s) for %s - has subjects %#v", kind, ns, name, message, subjects)
   276  	return assert.Fail(t, "Does not contain Subject: (%s,%s,%s) for %s - has subjects %#v", kind, ns, name, message, subjects)
   277  }
   278  
   279  // AssertNotContainsSubject asserts that the given array of subjects contains the given kind, namespace and name subject
   280  func AssertNotContainsSubject(t *testing.T, subjects []rbacv1.Subject, message string, kind string, ns string, name string) bool {
   281  	for _, subject := range subjects {
   282  		if subject.Kind == kind && subject.Namespace == ns && subject.Name == name {
   283  			log.Logger().Warnf("Should not contain Subject (%s,%s,%s) for %s - has subjects %#v", kind, ns, name, message, subjects)
   284  			return assert.Fail(t, "Should not contain Subject (%s,%s,%s) for %s - has subjects %#v", kind, ns, name, message, subjects)
   285  		}
   286  	}
   287  	return true
   288  }
   289  
   290  // AssertRolesInEnvironmentsContainsPolicyRule asserts that all the environments contain a Role of the given name which contains the given policy rule
   291  func AssertRolesInEnvironmentsContainsPolicyRule(t *testing.T, kubeClient kubernetes.Interface, nsNames []string, roleName string, apiGroup string, verb string, resource string) {
   292  	for _, ns := range nsNames {
   293  		role, err := kubeClient.RbacV1().Roles(ns).Get(roleName, metav1.GetOptions{})
   294  		require.NoError(t, err, "Failed to find Role in namespace %s for name %s", ns, roleName)
   295  		require.NotNil(t, role, "Failed to find Role in namespace %s for name %s", ns, roleName)
   296  
   297  		messsage := fmt.Sprintf("Role in namespace %s for name %s", ns, roleName)
   298  		AssertContainsPolicyRule(t, role.Rules, messsage, apiGroup, verb, resource)
   299  	}
   300  }
   301  
   302  // AssertRolesInEnvironmentsNotContainsPolicyRule asserts that all the environments do not contain a Role of the given name which contains the given policy rule
   303  func AssertRolesInEnvironmentsNotContainsPolicyRule(t *testing.T, kubeClient kubernetes.Interface, nsNames []string, roleName string, apiGroup string, verb string, resource string) {
   304  	for _, ns := range nsNames {
   305  		role, err := kubeClient.RbacV1().Roles(ns).Get(roleName, metav1.GetOptions{})
   306  		require.NoError(t, err, "Failed to find RoleBinding in namespace %s for name %s", ns, roleName)
   307  		require.NotNil(t, role, "Failed to find RoleBinding in namespace %s for name %s", ns, roleName)
   308  
   309  		messsage := fmt.Sprintf("Role in namespace %s for name %s", ns, roleName)
   310  		AssertNotContainsPolicyRule(t, role.Rules, messsage, apiGroup, verb, resource)
   311  	}
   312  }
   313  
   314  // AssertContainsPolicyRule asserts that the given array of policy rules contains the given apiGroup, verb and resource subject
   315  func AssertContainsPolicyRule(t *testing.T, rules []rbacv1.PolicyRule, message string, apiGroup string, verb string, resource string) bool {
   316  	for _, rule := range rules {
   317  		if util.StringArrayIndex(rule.APIGroups, apiGroup) >= 0 && util.StringArrayIndex(rule.Verbs, verb) >= 0 && util.StringArrayIndex(rule.Resources, resource) >= 0 {
   318  			return true
   319  		}
   320  	}
   321  	log.Logger().Warnf("Does not contain PolicyRule: (%s,%s,%s) for %s - has rules %#v", apiGroup, verb, resource, message, rules)
   322  	return assert.Fail(t, "Does not contain PolicyRule: (%s,%s,%s) for %s - has rules %#v", apiGroup, verb, resource, message, rules)
   323  }
   324  
   325  // AssertNotContainsPolicyRule asserts that the given array of policy rules contains the given apiGroup, verb and resource subject
   326  func AssertNotContainsPolicyRule(t *testing.T, rules []rbacv1.PolicyRule, message string, apiGroup string, verb string, resource string) bool {
   327  	for _, rule := range rules {
   328  		if util.StringArrayIndex(rule.APIGroups, apiGroup) >= 0 && util.StringArrayIndex(rule.Verbs, verb) >= 0 && util.StringArrayIndex(rule.Resources, resource) >= 0 {
   329  			log.Logger().Warnf("Should not contain PolicyRule (%s,%s,%s) for %s - has rules %#v", apiGroup, verb, resource, message, rules)
   330  			return assert.Fail(t, "Should not contain PolicyRule (%s,%s,%s) for %s - has rules %#v", apiGroup, verb, resource, message, rules)
   331  		}
   332  	}
   333  	return true
   334  }