github.com/argoproj/argo-cd/v2@v2.10.9/server/application/terminal_test.go (about)

     1  package application
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
    10  	"github.com/stretchr/testify/assert"
    11  
    12  	appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
    13  	"github.com/argoproj/argo-cd/v2/util/argo"
    14  	"github.com/argoproj/argo-cd/v2/util/security"
    15  )
    16  
    17  func TestPodExists(t *testing.T) {
    18  	for _, tcase := range []struct {
    19  		name           string
    20  		podName        string
    21  		namespace      string
    22  		treeNodes      []appv1.ResourceNode
    23  		expectedResult bool
    24  	}{
    25  		{
    26  			name:           "empty tree nodes",
    27  			podName:        "test-pod",
    28  			namespace:      "test",
    29  			treeNodes:      []appv1.ResourceNode{},
    30  			expectedResult: false,
    31  		},
    32  		{
    33  			name:           "matched Pod but empty UID",
    34  			podName:        "test-pod",
    35  			namespace:      "test",
    36  			treeNodes:      []appv1.ResourceNode{{ResourceRef: appv1.ResourceRef{Name: "test-pod", Namespace: "test", UID: "", Kind: kube.PodKind}}},
    37  			expectedResult: false,
    38  		},
    39  		{
    40  			name:           "matched Pod",
    41  			podName:        "test-pod",
    42  			namespace:      "test",
    43  			treeNodes:      []appv1.ResourceNode{{ResourceRef: appv1.ResourceRef{Name: "test-pod", Namespace: "test", UID: "testUID", Kind: kube.PodKind}}},
    44  			expectedResult: true,
    45  		},
    46  		{
    47  			name:           "unmatched Pod Namespace",
    48  			podName:        "test-pod",
    49  			namespace:      "test",
    50  			treeNodes:      []appv1.ResourceNode{{ResourceRef: appv1.ResourceRef{Name: "test-pod", Namespace: "test-A", UID: "testUID", Kind: kube.PodKind}}},
    51  			expectedResult: false,
    52  		},
    53  		{
    54  			name:           "unmatched Kind",
    55  			podName:        "test-pod",
    56  			namespace:      "test",
    57  			treeNodes:      []appv1.ResourceNode{{ResourceRef: appv1.ResourceRef{Name: "test-pod", Namespace: "test-A", UID: "testUID", Kind: kube.DeploymentKind}}},
    58  			expectedResult: false,
    59  		},
    60  		{
    61  			name:           "unmatched Group",
    62  			podName:        "test-pod",
    63  			namespace:      "test",
    64  			treeNodes:      []appv1.ResourceNode{{ResourceRef: appv1.ResourceRef{Name: "test-pod", Namespace: "test", UID: "testUID", Group: "A", Kind: kube.PodKind}}},
    65  			expectedResult: false,
    66  		},
    67  		{
    68  			name:           "unmatched Pod Name",
    69  			podName:        "test-pod",
    70  			namespace:      "test",
    71  			treeNodes:      []appv1.ResourceNode{{ResourceRef: appv1.ResourceRef{Name: "test", Namespace: "test", UID: "testUID", Kind: kube.PodKind}}},
    72  			expectedResult: false,
    73  		},
    74  	} {
    75  		t.Run(tcase.name, func(t *testing.T) {
    76  			result := podExists(tcase.treeNodes, tcase.podName, tcase.namespace)
    77  			if result != tcase.expectedResult {
    78  				t.Errorf("Expected result %v, but got %v", tcase.expectedResult, result)
    79  			}
    80  		})
    81  	}
    82  }
    83  
    84  func TestIsValidPodName(t *testing.T) {
    85  	for _, tcase := range []struct {
    86  		name           string
    87  		resourceName   string
    88  		expectedResult bool
    89  	}{
    90  		{
    91  			name:           "valid pod name",
    92  			resourceName:   "argocd-server-794644486d-r8v9d",
    93  			expectedResult: true,
    94  		},
    95  		{
    96  			name:           "not valid contains spaces",
    97  			resourceName:   "kubectl delete pods",
    98  			expectedResult: false,
    99  		},
   100  		{
   101  			name:           "not valid",
   102  			resourceName:   "kubectl -n kube-system delete pods --all",
   103  			expectedResult: false,
   104  		},
   105  		{
   106  			name:           "not valid contains special characters",
   107  			resourceName:   "delete+*+from+etcd%3b",
   108  			expectedResult: false,
   109  		},
   110  	} {
   111  		t.Run(tcase.name, func(t *testing.T) {
   112  			result := argo.IsValidPodName(tcase.resourceName)
   113  			if result != tcase.expectedResult {
   114  				t.Errorf("Expected result %v, but got %v", tcase.expectedResult, result)
   115  			}
   116  		})
   117  	}
   118  }
   119  
   120  func TestIsValidNamespaceName(t *testing.T) {
   121  	for _, tcase := range []struct {
   122  		name           string
   123  		resourceName   string
   124  		expectedResult bool
   125  	}{
   126  		{
   127  			name:           "valid pod namespace name",
   128  			resourceName:   "argocd",
   129  			expectedResult: true,
   130  		},
   131  		{
   132  			name:           "not valid contains spaces",
   133  			resourceName:   "kubectl delete ns argocd",
   134  			expectedResult: false,
   135  		},
   136  		{
   137  			name:           "not valid contains special characters",
   138  			resourceName:   "delete+*+from+etcd%3b",
   139  			expectedResult: false,
   140  		},
   141  	} {
   142  		t.Run(tcase.name, func(t *testing.T) {
   143  			result := argo.IsValidNamespaceName(tcase.resourceName)
   144  			if result != tcase.expectedResult {
   145  				t.Errorf("Expected result %v, but got %v", tcase.expectedResult, result)
   146  			}
   147  		})
   148  	}
   149  }
   150  
   151  func TestIsValidContainerNameName(t *testing.T) {
   152  	for _, tcase := range []struct {
   153  		name           string
   154  		resourceName   string
   155  		expectedResult bool
   156  	}{
   157  		{
   158  			name:           "valid container name",
   159  			resourceName:   "argocd-server",
   160  			expectedResult: true,
   161  		},
   162  		{
   163  			name:           "not valid contains spaces",
   164  			resourceName:   "kubectl delete pods",
   165  			expectedResult: false,
   166  		},
   167  		{
   168  			name:           "not valid contains special characters",
   169  			resourceName:   "delete+*+from+etcd%3b",
   170  			expectedResult: false,
   171  		},
   172  	} {
   173  		t.Run(tcase.name, func(t *testing.T) {
   174  			result := argo.IsValidContainerName(tcase.resourceName)
   175  			if result != tcase.expectedResult {
   176  				t.Errorf("Expected result %v, but got %v", tcase.expectedResult, result)
   177  			}
   178  		})
   179  	}
   180  }
   181  
   182  func TestTerminalHandler_ServeHTTP_empty_params(t *testing.T) {
   183  	testKeys := []string{
   184  		"pod",
   185  		"container",
   186  		"app",
   187  		"project",
   188  		"namespace",
   189  	}
   190  
   191  	// test both empty and invalid
   192  	testValues := []string{"", "invalid%20name"}
   193  
   194  	for _, testKey := range testKeys {
   195  		testKeyCopy := testKey
   196  
   197  		for _, testValue := range testValues {
   198  			testValueCopy := testValue
   199  
   200  			t.Run(testKeyCopy+" "+testValueCopy, func(t *testing.T) {
   201  				t.Parallel()
   202  
   203  				handler := terminalHandler{}
   204  				params := map[string]string{
   205  					"pod":       "valid",
   206  					"container": "valid",
   207  					"app":       "valid",
   208  					"project":   "valid",
   209  					"namespace": "valid",
   210  				}
   211  				params[testKeyCopy] = testValueCopy
   212  				var paramsArray []string
   213  				for key, value := range params {
   214  					paramsArray = append(paramsArray, key+"="+value)
   215  				}
   216  				paramsString := strings.Join(paramsArray, "&")
   217  				request := httptest.NewRequest(http.MethodGet, "https://argocd.example.com/api/v1/terminal?"+paramsString, nil)
   218  				recorder := httptest.NewRecorder()
   219  				handler.ServeHTTP(recorder, request)
   220  				response := recorder.Result()
   221  				assert.Equal(t, http.StatusBadRequest, response.StatusCode)
   222  			})
   223  		}
   224  	}
   225  }
   226  
   227  func TestTerminalHandler_ServeHTTP_disallowed_namespace(t *testing.T) {
   228  	handler := terminalHandler{namespace: "argocd", enabledNamespaces: []string{"allowed"}}
   229  	request := httptest.NewRequest(http.MethodGet, "https://argocd.example.com/api/v1/terminal?pod=valid&container=valid&appName=valid&projectName=valid&namespace=test&appNamespace=disallowed", nil)
   230  	recorder := httptest.NewRecorder()
   231  	handler.ServeHTTP(recorder, request)
   232  	response := recorder.Result()
   233  	assert.Equal(t, http.StatusForbidden, response.StatusCode)
   234  	assert.Equal(t, security.NamespaceNotPermittedError("disallowed").Error()+"\n", recorder.Body.String())
   235  }