github.com/oam-dev/cluster-gateway@v1.9.0/pkg/apis/cluster/v1alpha1/clustergateway_proxy_test.go (about)

     1  package v1alpha1
     2  
     3  import (
     4  	"context"
     5  	"io"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	gopath "path"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    15  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    16  	"k8s.io/apimachinery/pkg/runtime"
    17  	"k8s.io/apimachinery/pkg/runtime/schema"
    18  	"k8s.io/apiserver/pkg/authentication/user"
    19  	"k8s.io/apiserver/pkg/endpoints/request"
    20  	"k8s.io/apiserver/pkg/registry/rest"
    21  	"k8s.io/apiserver/pkg/util/feature"
    22  	clientgorest "k8s.io/client-go/rest"
    23  	"k8s.io/component-base/featuregate"
    24  	k8stesting "k8s.io/component-base/featuregate/testing"
    25  	"k8s.io/utils/pointer"
    26  	contextutil "sigs.k8s.io/apiserver-runtime/pkg/util/context"
    27  )
    28  
    29  func TestProxyHandler(t *testing.T) {
    30  	cases := []struct {
    31  		name            string
    32  		parent          *fakeParentStorage
    33  		featureGate     featuregate.Feature
    34  		objName         string
    35  		inputOption     *ClusterGatewayProxyOptions
    36  		reqInfo         request.RequestInfo
    37  		query           string
    38  		expectedQuery   string
    39  		endpointPath    string
    40  		expectedFailure bool
    41  		errorAssertFunc func(t *testing.T, err error)
    42  	}{
    43  		{
    44  			name: "normal proxy should work",
    45  			parent: &fakeParentStorage{
    46  				obj: &ClusterGateway{
    47  					ObjectMeta: metav1.ObjectMeta{
    48  						Name: "myName",
    49  					},
    50  					Spec: ClusterGatewaySpec{
    51  						Access: ClusterAccess{
    52  							Credential: &ClusterAccessCredential{
    53  								Type:                CredentialTypeServiceAccountToken,
    54  								ServiceAccountToken: "myToken",
    55  							},
    56  						},
    57  					},
    58  				},
    59  			},
    60  			objName: "myName",
    61  			inputOption: &ClusterGatewayProxyOptions{
    62  				Path: "/abc",
    63  			},
    64  			reqInfo: request.RequestInfo{
    65  				Verb: "get",
    66  			},
    67  		},
    68  		{
    69  			name: "not found",
    70  			parent: &fakeParentStorage{
    71  				err: apierrors.NewNotFound(schema.GroupResource{}, ""),
    72  			},
    73  			objName: "myName",
    74  			inputOption: &ClusterGatewayProxyOptions{
    75  				Path: "/abc",
    76  			},
    77  			reqInfo: request.RequestInfo{
    78  				Verb: "get",
    79  			},
    80  			expectedFailure: true,
    81  			errorAssertFunc: func(t *testing.T, err error) {
    82  				assert.True(t, strings.HasPrefix(err.Error(), "no such cluster"))
    83  			},
    84  		},
    85  		{
    86  			name: "normal proxy with sub-path in endpoint should work",
    87  			parent: &fakeParentStorage{
    88  				obj: &ClusterGateway{
    89  					ObjectMeta: metav1.ObjectMeta{
    90  						Name: "myName",
    91  					},
    92  					Spec: ClusterGatewaySpec{
    93  						Access: ClusterAccess{
    94  							Credential: &ClusterAccessCredential{
    95  								Type:                CredentialTypeServiceAccountToken,
    96  								ServiceAccountToken: "myToken",
    97  							},
    98  						},
    99  					},
   100  				},
   101  			},
   102  			endpointPath: "/extra",
   103  			objName:      "myName",
   104  			inputOption: &ClusterGatewayProxyOptions{
   105  				Path: "/abc",
   106  			},
   107  			reqInfo: request.RequestInfo{
   108  				Verb: "get",
   109  			},
   110  		},
   111  		{
   112  			name: "normal proxy with query in endpoint should work",
   113  			parent: &fakeParentStorage{
   114  				obj: &ClusterGateway{
   115  					ObjectMeta: metav1.ObjectMeta{
   116  						Name: "myName",
   117  					},
   118  					Spec: ClusterGatewaySpec{
   119  						Access: ClusterAccess{
   120  							Credential: &ClusterAccessCredential{
   121  								Type:                CredentialTypeServiceAccountToken,
   122  								ServiceAccountToken: "myToken",
   123  							},
   124  						},
   125  					},
   126  				},
   127  			},
   128  			objName: "myName",
   129  			inputOption: &ClusterGatewayProxyOptions{
   130  				Path: "/abc",
   131  			},
   132  			query:         "__dryRun=All&fieldValidation=Strict",
   133  			expectedQuery: "dryRun=All&fieldValidation=Strict",
   134  			reqInfo: request.RequestInfo{
   135  				Verb: "get",
   136  			},
   137  		},
   138  	}
   139  
   140  	for _, c := range cases {
   141  		t.Run(c.name, func(t *testing.T) {
   142  			if len(c.featureGate) > 0 {
   143  				defer k8stesting.SetFeatureGateDuringTest(t, feature.DefaultMutableFeatureGate, c.featureGate, true)()
   144  			}
   145  			text := "ok"
   146  			var receivingReq *http.Request
   147  			endpointSvr := httptest.NewTLSServer(http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
   148  				resp.WriteHeader(200)
   149  				resp.Write([]byte(text))
   150  				receivingReq = req
   151  			}))
   152  			defer endpointSvr.Close()
   153  			if c.parent.obj != nil {
   154  				c.parent.obj.Spec.Access.Endpoint = &ClusterEndpoint{
   155  					Type: ClusterEndpointTypeConst,
   156  					Const: &ClusterEndpointConst{
   157  						Address:  endpointSvr.URL + c.endpointPath,
   158  						Insecure: pointer.Bool(true),
   159  					},
   160  				}
   161  			}
   162  
   163  			ctx := context.TODO()
   164  			ctx = contextutil.WithParentStorage(ctx, c.parent)
   165  			ctx = request.WithRequestInfo(ctx, &c.reqInfo)
   166  
   167  			gwProxy := &ClusterGatewayProxy{}
   168  			handler, err := gwProxy.Connect(ctx, c.objName, c.inputOption, nil)
   169  			if c.expectedFailure {
   170  				if c.errorAssertFunc != nil {
   171  					c.errorAssertFunc(t, err)
   172  				}
   173  				return
   174  			}
   175  			require.NoError(t, err)
   176  			svr := httptest.NewServer(handler)
   177  			defer svr.Close()
   178  			path := "/foo"
   179  			targetPath := apiPrefix + c.objName + apiSuffix + path
   180  			resp, err := svr.Client().Get(svr.URL + targetPath + "?" + c.query)
   181  			assert.NoError(t, err)
   182  			data, err := io.ReadAll(resp.Body)
   183  			require.NoError(t, err)
   184  			assert.Equal(t, text, string(data))
   185  			assert.Equal(t, 200, resp.StatusCode)
   186  			assert.Equal(t, gopath.Join(c.endpointPath, path), receivingReq.URL.Path)
   187  			assert.Equal(t, c.expectedQuery, receivingReq.URL.RawQuery)
   188  		})
   189  	}
   190  }
   191  
   192  var _ rest.Storage = &fakeParentStorage{}
   193  var _ rest.Getter = &fakeParentStorage{}
   194  
   195  type fakeParentStorage struct {
   196  	obj *ClusterGateway
   197  	err error
   198  }
   199  
   200  func (f *fakeParentStorage) New() runtime.Object {
   201  	return f.obj
   202  }
   203  
   204  func (f *fakeParentStorage) Destroy() {}
   205  
   206  func (f *fakeParentStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
   207  	return f.obj, f.err
   208  }
   209  
   210  var _ rest.Responder = &fakeResponder{}
   211  
   212  type fakeResponder struct {
   213  	receivingCode int
   214  	receivingObj  runtime.Object
   215  	receivingErr  error
   216  }
   217  
   218  func (f *fakeResponder) Object(statusCode int, obj runtime.Object) {
   219  	f.receivingCode = statusCode
   220  	f.receivingObj = obj
   221  }
   222  
   223  func (f *fakeResponder) Error(err error) {
   224  	f.receivingErr = err
   225  }
   226  
   227  func TestGetImpersonationConfig(t *testing.T) {
   228  	baseReq, err := http.NewRequest(http.MethodGet, "", nil)
   229  	require.NoError(t, err)
   230  	base := context.Background()
   231  
   232  	GlobalClusterGatewayProxyConfiguration = &ClusterGatewayProxyConfiguration{
   233  		Spec: ClusterGatewayProxyConfigurationSpec{
   234  			ClientIdentityExchanger: ClientIdentityExchanger{Rules: []ClientIdentityExchangeRule{{
   235  				Name:   "name-matcher",
   236  				Type:   StaticMappingIdentityExchanger,
   237  				Source: &IdentityExchangerSource{User: pointer.String("test")},
   238  				Target: &IdentityExchangerTarget{User: "global"},
   239  			}}},
   240  		},
   241  	}
   242  
   243  	h := &proxyHandler{clusterGateway: &ClusterGateway{Spec: ClusterGatewaySpec{ProxyConfig: &ClusterGatewayProxyConfiguration{
   244  		Spec: ClusterGatewayProxyConfigurationSpec{
   245  			ClientIdentityExchanger: ClientIdentityExchanger{Rules: []ClientIdentityExchangeRule{{
   246  				Name:   "group-matcher",
   247  				Type:   StaticMappingIdentityExchanger,
   248  				Source: &IdentityExchangerSource{Group: pointer.String("group")},
   249  				Target: &IdentityExchangerTarget{User: "local"},
   250  			}}},
   251  		},
   252  	}}}}
   253  
   254  	ctx := request.WithUser(base, &user.DefaultInfo{Name: "test", Groups: []string{"group"}})
   255  	require.Equal(t, clientgorest.ImpersonationConfig{UserName: "local"}, h.getImpersonationConfig(baseReq.WithContext(ctx)))
   256  
   257  	ctx = request.WithUser(base, &user.DefaultInfo{Name: "test", Groups: []string{"group-test"}})
   258  	require.Equal(t, clientgorest.ImpersonationConfig{UserName: "global"}, h.getImpersonationConfig(baseReq.WithContext(ctx)))
   259  
   260  	ctx = request.WithUser(base, &user.DefaultInfo{Name: "tester", Groups: []string{"group-test"}})
   261  	require.Equal(t, clientgorest.ImpersonationConfig{UserName: "tester", Groups: []string{"group-test"}}, h.getImpersonationConfig(baseReq.WithContext(ctx)))
   262  }