github.com/argoproj/argo-cd/v3@v3.2.1/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/v3/pkg/apis/application/v1alpha1"
    13  	"github.com/argoproj/argo-cd/v3/util/argo"
    14  	"github.com/argoproj/argo-cd/v3/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  			assert.Equalf(t, tcase.expectedResult, result, "Expected result %v, but got %v", tcase.expectedResult, result)
    78  		})
    79  	}
    80  }
    81  
    82  func TestIsValidPodName(t *testing.T) {
    83  	for _, tcase := range []struct {
    84  		name           string
    85  		resourceName   string
    86  		expectedResult bool
    87  	}{
    88  		{
    89  			name:           "valid pod name",
    90  			resourceName:   "argocd-server-794644486d-r8v9d",
    91  			expectedResult: true,
    92  		},
    93  		{
    94  			name:           "not valid contains spaces",
    95  			resourceName:   "kubectl delete pods",
    96  			expectedResult: false,
    97  		},
    98  		{
    99  			name:           "not valid",
   100  			resourceName:   "kubectl -n kube-system delete pods --all",
   101  			expectedResult: false,
   102  		},
   103  		{
   104  			name:           "not valid contains special characters",
   105  			resourceName:   "delete+*+from+etcd%3b",
   106  			expectedResult: false,
   107  		},
   108  	} {
   109  		t.Run(tcase.name, func(t *testing.T) {
   110  			result := argo.IsValidPodName(tcase.resourceName)
   111  			assert.Equalf(t, tcase.expectedResult, result, "Expected result %v, but got %v", tcase.expectedResult, result)
   112  		})
   113  	}
   114  }
   115  
   116  func TestIsValidNamespaceName(t *testing.T) {
   117  	for _, tcase := range []struct {
   118  		name           string
   119  		resourceName   string
   120  		expectedResult bool
   121  	}{
   122  		{
   123  			name:           "valid pod namespace name",
   124  			resourceName:   "argocd",
   125  			expectedResult: true,
   126  		},
   127  		{
   128  			name:           "not valid contains spaces",
   129  			resourceName:   "kubectl delete ns argocd",
   130  			expectedResult: false,
   131  		},
   132  		{
   133  			name:           "not valid contains special characters",
   134  			resourceName:   "delete+*+from+etcd%3b",
   135  			expectedResult: false,
   136  		},
   137  	} {
   138  		t.Run(tcase.name, func(t *testing.T) {
   139  			result := argo.IsValidNamespaceName(tcase.resourceName)
   140  			assert.Equalf(t, tcase.expectedResult, result, "Expected result %v, but got %v", tcase.expectedResult, result)
   141  		})
   142  	}
   143  }
   144  
   145  func TestIsValidContainerNameName(t *testing.T) {
   146  	for _, tcase := range []struct {
   147  		name           string
   148  		resourceName   string
   149  		expectedResult bool
   150  	}{
   151  		{
   152  			name:           "valid container name",
   153  			resourceName:   "argocd-server",
   154  			expectedResult: true,
   155  		},
   156  		{
   157  			name:           "not valid contains spaces",
   158  			resourceName:   "kubectl delete pods",
   159  			expectedResult: false,
   160  		},
   161  		{
   162  			name:           "not valid contains special characters",
   163  			resourceName:   "delete+*+from+etcd%3b",
   164  			expectedResult: false,
   165  		},
   166  	} {
   167  		t.Run(tcase.name, func(t *testing.T) {
   168  			result := argo.IsValidContainerName(tcase.resourceName)
   169  			assert.Equalf(t, tcase.expectedResult, result, "Expected result %v, but got %v", tcase.expectedResult, result)
   170  		})
   171  	}
   172  }
   173  
   174  func TestTerminalHandler_ServeHTTP_empty_params(t *testing.T) {
   175  	t.Parallel()
   176  
   177  	testKeys := []string{
   178  		"pod",
   179  		"container",
   180  		"app",
   181  		"project",
   182  		"namespace",
   183  	}
   184  
   185  	// test both empty and invalid
   186  	testValues := []string{"", "invalid%20name"}
   187  
   188  	for _, testKey := range testKeys {
   189  		testKeyCopy := testKey
   190  
   191  		for _, testValue := range testValues {
   192  			testValueCopy := testValue
   193  
   194  			t.Run(testKeyCopy+" "+testValueCopy, func(t *testing.T) {
   195  				t.Parallel()
   196  
   197  				handler := terminalHandler{}
   198  				params := map[string]string{
   199  					"pod":       "valid",
   200  					"container": "valid",
   201  					"app":       "valid",
   202  					"project":   "valid",
   203  					"namespace": "valid",
   204  				}
   205  				params[testKeyCopy] = testValueCopy
   206  				var paramsArray []string
   207  				for key, value := range params {
   208  					paramsArray = append(paramsArray, key+"="+value)
   209  				}
   210  				paramsString := strings.Join(paramsArray, "&")
   211  				request := httptest.NewRequest(http.MethodGet, "https://argocd.example.com/api/v1/terminal?"+paramsString, http.NoBody)
   212  				recorder := httptest.NewRecorder()
   213  				handler.ServeHTTP(recorder, request)
   214  				response := recorder.Result()
   215  				assert.Equal(t, http.StatusBadRequest, response.StatusCode)
   216  			})
   217  		}
   218  	}
   219  }
   220  
   221  func TestTerminalHandler_ServeHTTP_disallowed_namespace(t *testing.T) {
   222  	handler := terminalHandler{namespace: "argocd", enabledNamespaces: []string{"allowed"}}
   223  	request := httptest.NewRequest(http.MethodGet, "https://argocd.example.com/api/v1/terminal?pod=valid&container=valid&appName=valid&projectName=valid&namespace=test&appNamespace=disallowed", http.NoBody)
   224  	recorder := httptest.NewRecorder()
   225  	handler.ServeHTTP(recorder, request)
   226  	response := recorder.Result()
   227  	assert.Equal(t, http.StatusForbidden, response.StatusCode)
   228  	assert.Equal(t, security.NamespaceNotPermittedError("disallowed").Error()+"\n", recorder.Body.String())
   229  }