mirror of
https://github.com/rancher/steve.git
synced 2025-09-07 02:10:30 +00:00
Imperative api pls (#434)
* Add aggregation layer support * prefer testing.Cleanup * add sni certs to server opts * test cleanup * append snicerts instead of overwriting --------- Co-authored-by: Tom Lebreux <tom.lebreux@suse.com> Co-authored-by: joshmeranda <joshua.meranda@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package ext
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
@@ -64,7 +66,7 @@ func TestAuthenticationCustom(t *testing.T) {
|
||||
testStore: newDefaultTestStore(),
|
||||
userCh: make(chan user.Info, 100),
|
||||
}
|
||||
extensionAPIServer, cleanup, err := setupExtensionAPIServer(t, scheme, store, func(opts *ExtensionAPIServerOptions) {
|
||||
extensionAPIServer, err := setupExtensionAPIServer(t, scheme, store, func(opts *ExtensionAPIServerOptions) {
|
||||
opts.Listener = ln
|
||||
opts.Authorizer = authorizer.AuthorizerFunc(authzAllowAll)
|
||||
opts.Authenticator = authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
|
||||
@@ -81,7 +83,6 @@ func TestAuthenticationCustom(t *testing.T) {
|
||||
})
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
defer cleanup()
|
||||
|
||||
unauthorized := apierrors.NewUnauthorized("Unauthorized")
|
||||
unauthorized.ErrStatus.Kind = "Status"
|
||||
@@ -179,3 +180,267 @@ 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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user