Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ jobs:
with:
version: ${{ env.GOLANG_CI_LINT_VERSION }}
install-only: true
- name: Install env-test
run: go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
- name: Build
run: make build-bin
- name: Test
Expand Down
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -930,22 +930,20 @@ Refer to [tests/integration/README.md](./tests/integration/README.md) for docume

## Running Tests

Some of steve's tests make use of [envtest](https://book.kubebuilder.io/reference/envtest) to run. Envtest allows tests to run against a "fake" kubernetes server with little/no overhead.

To install the required `setup-envtest` binary, use the following command:
Run the unit tests with:

```bash
go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest
make test
```

Before running the tests, you must run the following command to setup the fake server:
Run the integration tests with:

```bash
# note that this will use a new/latest version of k8s. Our CI will run against the version of k8s that corresponds to steve's
# current client-go version, as seen in scripts/test.sh
export KUBEBUILDER_ASSETS=$(setup-envtest use -p path)
make integration-tests
```

See [tests/integration/README.md](./tests/integration/README.md) for more details on integration tests.

# Versioning

See [VERSION.md](VERSION.md).
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ replace (

require (
github.com/adrg/xdg v0.5.3
github.com/evanphx/json-patch v5.9.11+incompatible
github.com/go-logr/logr v1.4.3
github.com/golang/protobuf v1.5.4
github.com/google/gnostic-models v0.7.1
Expand Down Expand Up @@ -74,6 +73,7 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
Expand Down
264 changes: 0 additions & 264 deletions pkg/ext/apiserver_authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package ext

import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
Expand All @@ -13,7 +12,6 @@ import (
"time"

testapisv1 "github.com/rancher/steve/pkg/ext/testapis/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
Expand Down Expand Up @@ -182,266 +180,4 @@ func TestAuthenticationCustom(t *testing.T) {
}
}

func (s *ExtensionAPIServerSuite) TestAuthenticationDefault() {
t := s.T()

// Same CA but CN not in the list allowed
notAllowedCertPair, err := s.ca.NewClientCert("system:not-allowed")
require.NoError(t, err)
notAllowedCert, notAllowedKey, err := notAllowedCertPair.AsBytes()
require.NoError(t, err)

badCA, err := NewTinyCA()
require.NoError(t, err)
badCertPair, err := badCA.NewClientCert("system:auth-proxy")
require.NoError(t, err)
badCert, badKey, err := badCertPair.AsBytes()
require.NoError(t, err)

cert, key, err := s.cert.AsBytes()
require.NoError(t, err)
certificate, err := tls.X509KeyPair(cert, key)
require.NoError(t, err)

badCACertificate, err := tls.X509KeyPair(badCert, badKey)
require.NoError(t, err)

notAllowedCertificate, err := tls.X509KeyPair(notAllowedCert, notAllowedKey)
require.NoError(t, err)

scheme := runtime.NewScheme()
AddToScheme(scheme)

store := &authnTestStore{
testStore: newDefaultTestStore(),
userCh: make(chan user.Info, 100),
}
defaultAuth, err := NewDefaultAuthenticator(s.client)
require.NoError(t, err)

func() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err = defaultAuth.RunOnce(ctx)
require.NoError(t, err)
}()

ln, port, err := options.CreateListener("", ":0", net.ListenConfig{})
require.NoError(t, err)

_, err = setupExtensionAPIServer(t, scheme, store, func(opts *ExtensionAPIServerOptions) {
opts.Listener = ln
opts.Authenticator = defaultAuth
opts.Authorizer = authorizer.AuthorizerFunc(authzAllowAll)
}, nil)
require.NoError(t, err)

allPaths := []string{
"/",
"/openapi/v2",
"/openapi/v3",
"/openapi/v3/apis/ext.cattle.io/v1",
"/apis",
"/apis/ext.cattle.io",
"/apis/ext.cattle.io/v1",
"/apis/ext.cattle.io/v1/testtypes",
"/apis/ext.cattle.io/v1/testtypes/foo",
}

type test struct {
name string
certs []tls.Certificate
paths []string
user string
groups []string

expectedStatusCode int
expectedUser *user.DefaultInfo
}
tests := []test{
{
name: "authenticated request check user",
certs: []tls.Certificate{certificate},
paths: []string{"/apis/ext.cattle.io/v1/testtypes"},
user: "my-user",
groups: []string{"my-group"},

expectedStatusCode: http.StatusOK,
expectedUser: &user.DefaultInfo{Name: "my-user", Groups: []string{"my-group", "system:authenticated"}, Extra: map[string][]string{}},
},
{
name: "authenticated request all paths",
certs: []tls.Certificate{certificate},
paths: allPaths,
user: "my-user",
groups: []string{"my-group"},

expectedStatusCode: http.StatusOK,
},
{
name: "authenticated request to unknown endpoint",
certs: []tls.Certificate{certificate},
paths: []string{"/unknown"},
user: "my-user",
groups: []string{"my-group"},

expectedStatusCode: http.StatusNotFound,
},
{
name: "no client certs",
paths: append(allPaths, "/unknown"),
user: "my-user",
groups: []string{"my-group"},

expectedStatusCode: http.StatusUnauthorized,
},
{
name: "client certs from bad CA",
certs: []tls.Certificate{badCACertificate},
paths: append(allPaths, "/unknown"),
user: "my-user",
groups: []string{"my-group"},

expectedStatusCode: http.StatusUnauthorized,
},
{
name: "client certs with CN not allowed",
certs: []tls.Certificate{notAllowedCertificate},
paths: append(allPaths, "/unknown"),
user: "my-user",
groups: []string{"my-group"},

expectedStatusCode: http.StatusUnauthorized,
},
{
name: "no user",
paths: append(allPaths, "/unknown"),
groups: []string{"my-group"},

expectedStatusCode: http.StatusUnauthorized,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
httpClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
Certificates: test.certs,
},
},
}

for _, path := range test.paths {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://127.0.0.1:%d%s", port, path), nil)
require.NoError(t, err)
if test.user != "" {
req.Header.Set("X-Remote-User", test.user)
}
for _, group := range test.groups {
req.Header.Add("X-Remote-Group", group)
}

// Eventually because the cache for auth might not be synced yet
require.EventuallyWithT(t, func(c *assert.CollectT) {
resp, err := httpClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()

require.Equal(t, test.expectedStatusCode, resp.StatusCode)
}, 5*time.Second, 110*time.Millisecond)

if test.expectedUser != nil {
authUser, found := store.getUser()
require.True(t, found)
require.Equal(t, test.expectedUser, authUser)
}
}
})
}
}

func (s *ExtensionAPIServerSuite) TestAuthenticationUnion() {
t := s.T()

scheme := runtime.NewScheme()
AddToScheme(scheme)

cert, key, err := s.cert.AsBytes()
require.NoError(t, err)
certificate, err := tls.X509KeyPair(cert, key)
require.NoError(t, err)

defaultAuth, err := NewDefaultAuthenticator(s.client)
require.NoError(t, err)

customAuth := 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
})
auth := NewUnionAuthenticator(customAuth, defaultAuth)
func() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
err = auth.RunOnce(ctx)
require.NoError(t, err)
}()

ln, port, err := options.CreateListener("", ":0", net.ListenConfig{})
require.NoError(t, err)

store := &authnTestStore{
testStore: newDefaultTestStore(),
userCh: make(chan user.Info, 100),
}
extensionAPIServer, err := setupExtensionAPIServer(t, scheme, store, func(opts *ExtensionAPIServerOptions) {
opts.Listener = ln
opts.Authorizer = authorizer.AuthorizerFunc(authzAllowAll)
opts.Authenticator = auth
}, nil)
require.NoError(t, err)

httpClient := http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
Certificates: []tls.Certificate{certificate},
},
},
}
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://127.0.0.1:%d/openapi/v2", port), nil)
require.NoError(t, err)

userInfo := &user.DefaultInfo{
Name: "my-user",
Groups: []string{"my-group"},
}

req.Header.Set("X-Remote-User", userInfo.GetName())
req.Header.Add("X-Remote-Group", userInfo.GetGroups()[0])
require.EventuallyWithT(t, func(c *assert.CollectT) {
resp, err := httpClient.Do(req)
require.NoError(t, err)
defer resp.Body.Close()

require.Equal(t, http.StatusOK, resp.StatusCode)
}, 5*time.Second, 110*time.Millisecond)

req = httptest.NewRequest(http.MethodGet, "/openapi/v2", nil)
w := httptest.NewRecorder()

ctx := request.WithUser(req.Context(), userInfo)
req = req.WithContext(ctx)

extensionAPIServer.ServeHTTP(w, req)
resp := w.Result()
require.Equal(t, http.StatusOK, resp.StatusCode)
}
Loading
Loading