1
0
mirror of https://github.com/rancher/steve.git synced 2025-04-27 19:05:09 +00:00
steve/pkg/ext/apiserver_authentication_test.go
Tom Lebreux 1f21e5e515
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.
2024-10-11 15:19:27 -04:00

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)
}
}
})
}
}