diff --git a/.github/workflows/acceptance_tests.yml b/.github/workflows/acceptance_tests.yml index 0e1860e6d..c11cee56f 100644 --- a/.github/workflows/acceptance_tests.yml +++ b/.github/workflows/acceptance_tests.yml @@ -24,6 +24,16 @@ jobs: - name: Setup acceptance test run: source ./acceptanceTests/setup.sh + - name: Create k8s users and change context + env: + USERNAME_UNRESTRICTED: user-with-clusterwide-access + USERNAME_RESTRICTED: user-with-restricted-access + run: | + ./acceptanceTests/create_user.sh "${USERNAME_UNRESTRICTED}" + ./acceptanceTests/create_user.sh "${USERNAME_RESTRICTED}" + kubectl apply -f cli/cmd/permissionFiles/permissions-all-namespaces-tap.yaml + kubectl config use-context ${USERNAME_UNRESTRICTED} + - name: Test run: make acceptance-test diff --git a/acceptanceTests/create_user.sh b/acceptanceTests/create_user.sh new file mode 100755 index 000000000..ae3543063 --- /dev/null +++ b/acceptanceTests/create_user.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Create a user in Minikube cluster "minikube" +# Create context for user +# Usage: +# ./create_user.sh + +set -e + +NEW_USERNAME=$1 +CERT_DIR="${HOME}/certs" +KEY_FILE="${CERT_DIR}/${NEW_USERNAME}.key" +CRT_FILE="${CERT_DIR}/${NEW_USERNAME}.crt" +MINIKUBE_KEY_FILE="${HOME}/.minikube/ca.key" +MINIKUBE_CRT_FILE="${HOME}/.minikube/ca.crt" +DAYS=1 + +echo "Creating user and context for username \"${NEW_USERNAME}\" in Minikube cluster" + +if ! command -v openssl &> /dev/null +then + echo "Installing openssl" + sudo apt-get update + sudo apt-get install openssl +fi + +echo "Creating certificate for user \"${NEW_USERNAME}\"" +mkdir -p ${CERT_DIR} +echo "Generating key \"${KEY_FILE}\"" +openssl genrsa -out "${KEY_FILE}" 2048 +echo "Generating crt \"${CRT_FILE}\"" +openssl req -new -key "${KEY_FILE}" -out "${CRT_FILE}" -subj "/CN=${NEW_USERNAME}/O=group1" +openssl x509 -req -in "${CRT_FILE}" -CA "${MINIKUBE_CRT_FILE}" -CAkey "${MINIKUBE_KEY_FILE}" -CAcreateserial -out "${CRT_FILE}" -days $DAYS + +echo "Creating context for user \"${NEW_USERNAME}\"" +kubectl config set-credentials "${NEW_USERNAME}" --client-certificate="${CRT_FILE}" --client-key="${KEY_FILE}" +kubectl config set-context "${NEW_USERNAME}" --cluster=minikube --user="${NEW_USERNAME}" diff --git a/acceptanceTests/extensions_test.go b/acceptanceTests/extensions_test.go index 81ee6b262..73f9a030f 100644 --- a/acceptanceTests/extensions_test.go +++ b/acceptanceTests/extensions_test.go @@ -49,7 +49,13 @@ func TestRedis(t *testing.T) { ctx := context.Background() - redisExternalIp, err := GetServiceExternalIp(ctx, defaultNamespaceName, "redis") + kubernetesProvider, err := NewKubernetesProvider() + if err != nil { + t.Errorf("failed to create k8s provider, err %v", err) + return + } + + redisExternalIp, err := kubernetesProvider.GetServiceExternalIp(ctx, defaultNamespaceName, "redis") if err != nil { t.Errorf("failed to get redis external ip, err: %v", err) return @@ -141,7 +147,13 @@ func TestAmqp(t *testing.T) { ctx := context.Background() - rabbitmqExternalIp, err := GetServiceExternalIp(ctx, defaultNamespaceName, "rabbitmq") + kubernetesProvider, err := NewKubernetesProvider() + if err != nil { + t.Errorf("failed to create k8s provider, err %v", err) + return + } + + rabbitmqExternalIp, err := kubernetesProvider.GetServiceExternalIp(ctx, defaultNamespaceName, "rabbitmq") if err != nil { t.Errorf("failed to get RabbitMQ external ip, err: %v", err) return diff --git a/acceptanceTests/setup.sh b/acceptanceTests/setup.sh index 3153569c2..06bb8492d 100644 --- a/acceptanceTests/setup.sh +++ b/acceptanceTests/setup.sh @@ -57,9 +57,6 @@ kubectl expose deployment rabbitmq --type=LoadBalancer --port=5672 -n mizu-tests echo "Starting proxy" kubectl proxy --port=8080 & -echo "Starting tunnel" -minikube tunnel & - echo "Setting minikube docker env" eval $(minikube docker-env) @@ -68,3 +65,6 @@ make build-docker-ci echo "Build cli" make build-cli-ci + +echo "Starting tunnel" +minikube tunnel & diff --git a/acceptanceTests/tap_test.go b/acceptanceTests/tap_test.go index 2caf00b7d..020b0d877 100644 --- a/acceptanceTests/tap_test.go +++ b/acceptanceTests/tap_test.go @@ -14,6 +14,10 @@ import ( ) func TestTap(t *testing.T) { + basicTapTest(t, false) +} + +func basicTapTest(t *testing.T, shouldCheckSrcAndDest bool, extraArgs... string) { if testing.Short() { t.Skip("ignored acceptance test") } @@ -33,6 +37,8 @@ func TestTap(t *testing.T) { tapNamespace := GetDefaultTapNamespace() tapCmdArgs = append(tapCmdArgs, tapNamespace...) + tapCmdArgs = append(tapCmdArgs, extraArgs...) + tapCmd := exec.Command(cliPath, tapCmdArgs...) t.Logf("running command: %v", tapCmd.String()) @@ -72,7 +78,6 @@ func TestTap(t *testing.T) { expectedPodsStr += fmt.Sprintf("Name:%vNamespace:%v", expectedPods[i].Name, expectedPods[i].Namespace) } - const shouldCheckSrcAndDest = false RunCypressTests(t, fmt.Sprintf("npx cypress run --spec \"cypress/integration/tests/UiTest.js\" --env entriesCount=%d,arrayDict=%v,shouldCheckSrcAndDest=%v", entriesCount, expectedPodsStr, shouldCheckSrcAndDest)) }) @@ -644,3 +649,44 @@ func TestTapDumpLogs(t *testing.T) { return } } + +func TestIpResolving(t *testing.T) { + namespace := allNamespaces + + t.Log("add permissions for ip-resolution for current user") + if err := ApplyKubeFilesForTest( + t, + "minikube", + namespace, + "../cli/cmd/permissionFiles/permissions-all-namespaces-ip-resolution-optional.yaml", + ); err != nil { + t.Errorf("failed to create k8s permissions, %v", err) + return + } + + basicTapTest(t, true) +} + +func TestRestrictedMode(t *testing.T) { + namespace := "mizu-tests" + + t.Log("creating permissions for restricted user") + if err := ApplyKubeFilesForTest( + t, + "minikube", + namespace, + "../cli/cmd/permissionFiles/permissions-ns-tap.yaml", + ); err != nil { + t.Errorf("failed to create k8s permissions, %v", err) + return + } + + t.Log("switching k8s context to user") + if err := SwitchKubeContextForTest(t, "user-with-restricted-access"); err != nil { + t.Errorf("failed to switch k8s context, %v", err) + return + } + + extraArgs := []string{"--set", fmt.Sprintf("mizu-resources-namespace=%s", namespace)} + t.Run("basic tap", func (testingT *testing.T) {basicTapTest(testingT, false, extraArgs...)}) +} diff --git a/acceptanceTests/testsUtils.go b/acceptanceTests/testsUtils.go index 2b502c743..3f1d119c2 100644 --- a/acceptanceTests/testsUtils.go +++ b/acceptanceTests/testsUtils.go @@ -31,6 +31,7 @@ const ( defaultServiceName = "httpbin" defaultEntriesCount = 50 waitAfterTapPodsReady = 3 * time.Second + allNamespaces = "" ) type PodDescriptor struct { @@ -74,7 +75,7 @@ func GetApiServerUrl(port uint16) string { return fmt.Sprintf("http://localhost:%v", port) } -func GetServiceExternalIp(ctx context.Context, namespace string, service string) (string, error) { +func NewKubernetesProvider() (*KubernetesProvider, error) { home := homedir.HomeDir() configLoadingRules := &clientcmd.ClientConfigLoadingRules{ExplicitPath: filepath.Join(home, ".kube", "config")} clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( @@ -86,15 +87,23 @@ func GetServiceExternalIp(ctx context.Context, namespace string, service string) restClientConfig, err := clientConfig.ClientConfig() if err != nil { - return "", err + return nil, err } clientSet, err := kubernetes.NewForConfig(restClientConfig) if err != nil { - return "", err + return nil, err } - serviceObj, err := clientSet.CoreV1().Services(namespace).Get(ctx, service, metav1.GetOptions{}) + return &KubernetesProvider{clientSet}, nil +} + +type KubernetesProvider struct { + clientSet *kubernetes.Clientset +} + +func (kp *KubernetesProvider) GetServiceExternalIp(ctx context.Context, namespace string, service string) (string, error) { + serviceObj, err := kp.clientSet.CoreV1().Services(namespace).Get(ctx, service, metav1.GetOptions{}) if err != nil { return "", err } @@ -103,6 +112,105 @@ func GetServiceExternalIp(ctx context.Context, namespace string, service string) return externalIp, nil } +func SwitchKubeContextForTest(t *testing.T, newContextName string) error { + prevKubeContextName, err := GetKubeCurrentContextName() + if err != nil { + return err + } + + if err := SetKubeCurrentContext(newContextName); err != nil { + return err + } + + t.Cleanup(func() { + if err := SetKubeCurrentContext(prevKubeContextName); err != nil { + t.Errorf("failed to set Kubernetes context to %s, err: %v", prevKubeContextName, err) + t.Errorf("cleanup failed, subsequent tests may be affected") + } + }) + + return nil +} + +func GetKubeCurrentContextName() (string, error) { + cmd := exec.Command("kubectl", "config", "current-context") + + output, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("%v, %s", err, string(output)) + } + + return string(bytes.TrimSpace(output)), nil +} + +func SetKubeCurrentContext(contextName string) error { + cmd := exec.Command("kubectl", "config", "use-context", contextName) + + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("%v, %s", err, string(output)) + } + + return nil +} + +func ApplyKubeFilesForTest(t *testing.T, kubeContext string, namespace string, filename ...string) error { + for i := range filename { + fname := filename[i] + if err := ApplyKubeFile(kubeContext, namespace, fname); err != nil { + return err + } + + t.Cleanup(func() { + if err := DeleteKubeFile(kubeContext, namespace, fname); err != nil { + t.Errorf( + "failed to delete Kubernetes resources in namespace %s from filename %s, err: %v", + namespace, + fname, + err, + ) + } + }) + } + + return nil +} + +func ApplyKubeFile(kubeContext string, namespace string, filename string) (error) { + cmdArgs := []string{ + "apply", + "--context", kubeContext, + "-f", filename, + } + if namespace != allNamespaces { + cmdArgs = append(cmdArgs, "-n", namespace) + } + cmd := exec.Command("kubectl", cmdArgs...) + + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("%v, %s", err, string(output)) + } + + return nil +} + +func DeleteKubeFile(kubeContext string, namespace string, filename string) error { + cmdArgs := []string{ + "delete", + "--context", kubeContext, + "-f", filename, + } + if namespace != allNamespaces { + cmdArgs = append(cmdArgs, "-n", namespace) + } + cmd := exec.Command("kubectl", cmdArgs...) + + if output, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("%v, %s", err, string(output)) + } + + return nil +} + func getDefaultCommandArgs() []string { setFlag := "--set" telemetry := "telemetry=false" diff --git a/cli/cmd/permissionFiles/permissions-all-namespaces-debug-optional.yaml b/cli/cmd/permissionFiles/permissions-all-namespaces-debug-optional.yaml index b13583cac..93fabd504 100644 --- a/cli/cmd/permissionFiles/permissions-all-namespaces-debug-optional.yaml +++ b/cli/cmd/permissionFiles/permissions-all-namespaces-debug-optional.yaml @@ -17,7 +17,7 @@ metadata: name: mizu-runner-debug-clusterrolebindings subjects: - kind: User - name: user1 + name: user-with-clusterwide-access apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole diff --git a/cli/cmd/permissionFiles/permissions-all-namespaces-ip-resolution-optional.yaml b/cli/cmd/permissionFiles/permissions-all-namespaces-ip-resolution-optional.yaml index 35580bcb4..859d1a86b 100644 --- a/cli/cmd/permissionFiles/permissions-all-namespaces-ip-resolution-optional.yaml +++ b/cli/cmd/permissionFiles/permissions-all-namespaces-ip-resolution-optional.yaml @@ -29,7 +29,7 @@ metadata: name: mizu-resolver-clusterrolebindings subjects: - kind: User - name: user1 + name: user-with-clusterwide-access apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole diff --git a/cli/cmd/permissionFiles/permissions-all-namespaces-tap.yaml b/cli/cmd/permissionFiles/permissions-all-namespaces-tap.yaml index 3f038e179..65fa5f38b 100644 --- a/cli/cmd/permissionFiles/permissions-all-namespaces-tap.yaml +++ b/cli/cmd/permissionFiles/permissions-all-namespaces-tap.yaml @@ -22,6 +22,9 @@ rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["create"] +- apiGroups: [""] + resources: ["pods/log"] + verbs: ["get"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 @@ -29,7 +32,7 @@ metadata: name: mizu-runner-clusterrolebindings subjects: - kind: User - name: user1 + name: user-with-clusterwide-access apiGroup: rbac.authorization.k8s.io roleRef: kind: ClusterRole diff --git a/cli/cmd/permissionFiles/permissions-ns-debug-optional.yaml b/cli/cmd/permissionFiles/permissions-ns-debug-optional.yaml index d1ea290a4..631f3ae71 100644 --- a/cli/cmd/permissionFiles/permissions-ns-debug-optional.yaml +++ b/cli/cmd/permissionFiles/permissions-ns-debug-optional.yaml @@ -3,7 +3,6 @@ kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: mizu-runner-debug-role - namespace: user1 rules: - apiGroups: ["events.k8s.io"] resources: ["events"] @@ -16,10 +15,9 @@ kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: mizu-runner-debug-rolebindings - namespace: user1 subjects: - kind: User - name: user1 + name: user-with-restricted-access apiGroup: rbac.authorization.k8s.io roleRef: kind: Role diff --git a/cli/cmd/permissionFiles/permissions-ns-ip-resolution-optional.yaml b/cli/cmd/permissionFiles/permissions-ns-ip-resolution-optional.yaml index 96a85cc00..54bf50a56 100644 --- a/cli/cmd/permissionFiles/permissions-ns-ip-resolution-optional.yaml +++ b/cli/cmd/permissionFiles/permissions-ns-ip-resolution-optional.yaml @@ -3,7 +3,6 @@ kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: mizu-resolver-role - namespace: user1 rules: - apiGroups: [""] resources: ["serviceaccounts"] @@ -28,10 +27,9 @@ kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: mizu-resolver-rolebindings - namespace: user1 subjects: - kind: User - name: user1 + name: user-with-restricted-access apiGroup: rbac.authorization.k8s.io roleRef: kind: Role diff --git a/cli/cmd/permissionFiles/permissions-ns-tap.yaml b/cli/cmd/permissionFiles/permissions-ns-tap.yaml index 462e6d5bc..6bfe9b816 100644 --- a/cli/cmd/permissionFiles/permissions-ns-tap.yaml +++ b/cli/cmd/permissionFiles/permissions-ns-tap.yaml @@ -3,7 +3,6 @@ kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: mizu-runner-role - namespace: user1 rules: - apiGroups: [""] resources: ["pods"] @@ -20,15 +19,17 @@ rules: - apiGroups: [""] resources: ["configmaps"] verbs: ["create", "delete"] +- apiGroups: [""] + resources: ["pods/log"] + verbs: ["get"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: mizu-runner-rolebindings - namespace: user1 subjects: - kind: User - name: user1 + name: user-with-restricted-access apiGroup: rbac.authorization.k8s.io roleRef: kind: Role