mirror of
https://github.com/rancher/steve.git
synced 2025-09-04 08:55:55 +00:00
Implement /ext in Steve for Imperative API (#287)
This implements the Imperative API that is served at /ext with Steve. The imperative API is compatible with Kubernetes' API server and will be used as an extension API server.
This commit is contained in:
170
pkg/ext/apiserver_authentication_test.go
Normal file
170
pkg/ext/apiserver_authentication_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package ext
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
)
|
||||
|
||||
type authnTestStore struct {
|
||||
*testStore
|
||||
userCh chan user.Info
|
||||
}
|
||||
|
||||
func (t *authnTestStore) List(ctx Context, opts *metav1.ListOptions) (*TestTypeList, error) {
|
||||
t.userCh <- ctx.User
|
||||
return &testTypeListFixture, nil
|
||||
}
|
||||
|
||||
func (t *authnTestStore) getUser() (user.Info, bool) {
|
||||
timer := time.NewTimer(time.Second * 5)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case user := <-t.userCh:
|
||||
return user, true
|
||||
case <-timer.C:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticationCustom(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
AddToScheme(scheme)
|
||||
|
||||
ln, _, err := options.CreateListener("", ":0", net.ListenConfig{})
|
||||
require.NoError(t, err)
|
||||
|
||||
store := &authnTestStore{
|
||||
testStore: &testStore{},
|
||||
userCh: make(chan user.Info, 100),
|
||||
}
|
||||
extensionAPIServer, cleanup, err := setupExtensionAPIServer(t, scheme, &TestType{}, &TestTypeList{}, store, func(opts *ExtensionAPIServerOptions) {
|
||||
opts.Listener = ln
|
||||
opts.Authorizer = authorizer.AuthorizerFunc(authzAllowAll)
|
||||
opts.Authenticator = authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
user, ok := request.UserFrom(req.Context())
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
if user.GetName() == "error" {
|
||||
return nil, false, fmt.Errorf("fake error")
|
||||
}
|
||||
return &authenticator.Response{
|
||||
User: user,
|
||||
}, true, nil
|
||||
})
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
defer cleanup()
|
||||
|
||||
unauthorized := apierrors.NewUnauthorized("Unauthorized")
|
||||
unauthorized.ErrStatus.Kind = "Status"
|
||||
unauthorized.ErrStatus.APIVersion = "v1"
|
||||
|
||||
allPaths := []string{
|
||||
"/",
|
||||
"/apis",
|
||||
"/apis/ext.cattle.io",
|
||||
"/apis/ext.cattle.io/v1",
|
||||
"/apis/ext.cattle.io/v1/testtypes",
|
||||
"/apis/ext.cattle.io/v1/testtypes/foo",
|
||||
"/openapi/v2",
|
||||
"/openapi/v3",
|
||||
"/openapi/v3/apis/ext.cattle.io/v1",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
user *user.DefaultInfo
|
||||
paths []string
|
||||
|
||||
expectedStatusCode int
|
||||
expectedStatus apierrors.APIStatus
|
||||
expectedUser *user.DefaultInfo
|
||||
}{
|
||||
{
|
||||
name: "authenticated request check user",
|
||||
paths: []string{"/apis/ext.cattle.io/v1/testtypes"},
|
||||
user: &user.DefaultInfo{Name: "my-user", Groups: []string{"my-group", "system:authenticated"}, Extra: map[string][]string{}},
|
||||
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedUser: &user.DefaultInfo{Name: "my-user", Groups: []string{"my-group", "system:authenticated"}, Extra: map[string][]string{}},
|
||||
},
|
||||
{
|
||||
name: "authenticated request all paths",
|
||||
user: &user.DefaultInfo{Name: "my-user", Groups: []string{"my-group", "system:authenticated"}, Extra: map[string][]string{}},
|
||||
paths: allPaths,
|
||||
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "authenticated request to unknown endpoint",
|
||||
user: &user.DefaultInfo{Name: "my-user", Groups: []string{"my-group", "system:authenticated"}, Extra: map[string][]string{}},
|
||||
paths: []string{"/unknown"},
|
||||
|
||||
expectedStatusCode: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "unauthenticated request",
|
||||
paths: append(allPaths, "/unknown"),
|
||||
|
||||
expectedStatusCode: http.StatusUnauthorized,
|
||||
expectedStatus: unauthorized,
|
||||
},
|
||||
{
|
||||
name: "authentication error",
|
||||
user: &user.DefaultInfo{Name: "error"},
|
||||
paths: append(allPaths, "/unknown"),
|
||||
|
||||
expectedStatusCode: http.StatusUnauthorized,
|
||||
expectedStatus: unauthorized,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for _, path := range test.paths {
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
if test.user != nil {
|
||||
ctx := request.WithUser(req.Context(), test.user)
|
||||
req = req.WithContext(ctx)
|
||||
}
|
||||
|
||||
extensionAPIServer.ServeHTTP(w, req)
|
||||
resp := w.Result()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
responseStatus := metav1.Status{}
|
||||
json.Unmarshal(body, &responseStatus)
|
||||
|
||||
require.Equal(t, test.expectedStatusCode, resp.StatusCode, "for path "+path)
|
||||
if test.expectedStatus != nil {
|
||||
require.Equal(t, test.expectedStatus.Status(), responseStatus, "for path "+path)
|
||||
}
|
||||
if test.expectedUser != nil {
|
||||
authUser, found := store.getUser()
|
||||
require.True(t, found)
|
||||
require.Equal(t, test.expectedUser, authUser)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user