2024-10-11 19:19:27 +00:00
|
|
|
package ext
|
|
|
|
|
|
|
|
import (
|
2025-01-15 17:41:44 +00:00
|
|
|
"context"
|
2024-10-11 19:19:27 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
2025-01-15 17:41:44 +00:00
|
|
|
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
2024-10-11 19:19:27 +00:00
|
|
|
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"
|
2025-01-15 17:41:44 +00:00
|
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
2024-10-11 19:19:27 +00:00
|
|
|
"k8s.io/apiserver/pkg/server/options"
|
|
|
|
)
|
|
|
|
|
2025-01-15 17:41:44 +00:00
|
|
|
var _ rest.Storage = (*authnTestStore)(nil)
|
|
|
|
var _ rest.Lister = (*authnTestStore)(nil)
|
|
|
|
|
2024-10-11 19:19:27 +00:00
|
|
|
type authnTestStore struct {
|
2025-01-15 17:41:44 +00:00
|
|
|
*testStore[*TestType, *TestTypeList]
|
2024-10-11 19:19:27 +00:00
|
|
|
userCh chan user.Info
|
|
|
|
}
|
|
|
|
|
2025-01-15 17:41:44 +00:00
|
|
|
func (t *authnTestStore) List(ctx context.Context, _ *metainternalversion.ListOptions) (runtime.Object, error) {
|
|
|
|
userInfo, ok := request.UserFrom(ctx)
|
|
|
|
if !ok {
|
|
|
|
return nil, convertError(fmt.Errorf("missing user info"))
|
|
|
|
}
|
|
|
|
|
|
|
|
t.userCh <- userInfo
|
2024-10-11 19:19:27 +00:00
|
|
|
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{
|
2025-01-15 17:41:44 +00:00
|
|
|
testStore: newDefaultTestStore(),
|
2024-10-11 19:19:27 +00:00
|
|
|
userCh: make(chan user.Info, 100),
|
|
|
|
}
|
2025-01-15 17:41:44 +00:00
|
|
|
extensionAPIServer, cleanup, err := setupExtensionAPIServer(t, scheme, store, func(opts *ExtensionAPIServerOptions) {
|
2024-10-11 19:19:27 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|