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 }