mirror of
https://github.com/rancher/steve.git
synced 2025-04-27 19:05:09 +00:00
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.
171 lines
4.6 KiB
Go
171 lines
4.6 KiB
Go
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)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|