Skip to content

Commit d2e3d9e

Browse files
authored
Merge pull request #3624 from xrstf/token-reviews
Fix TokenReviews when using WorkspaceAuthentication
2 parents bccdaa6 + 1a32a18 commit d2e3d9e

File tree

4 files changed

+135
-16
lines changed

4 files changed

+135
-16
lines changed

pkg/authentication/authenticators.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,45 @@ import (
2121
"net/http"
2222

2323
"k8s.io/apiserver/pkg/authentication/authenticator"
24+
"k8s.io/apiserver/pkg/authentication/group"
2425
"k8s.io/apiserver/pkg/authentication/user"
26+
"k8s.io/apiserver/pkg/endpoints/request"
2527

28+
"github.com/kcp-dev/kcp/pkg/index"
2629
"github.com/kcp-dev/kcp/pkg/proxy/lookup"
2730
)
2831

32+
func NewStandaloneWorkspaceAuthenticator(clusterIndex *index.State, authIndex AuthenticatorIndex) authenticator.Request {
33+
return authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
34+
clusterName, err := request.ClusterNameFrom(req.Context())
35+
if err != nil {
36+
return nil, false, fmt.Errorf("request has no cluster context: %w", err)
37+
}
38+
39+
result, found := clusterIndex.Lookup(clusterName.Path())
40+
if !found {
41+
return nil, false, nil
42+
}
43+
44+
// workspacemounts have no type
45+
if result.Type.Empty() {
46+
return nil, false, nil
47+
}
48+
49+
reqAuthenticator, ok := authIndex.Lookup(result.Type)
50+
if !ok {
51+
return nil, false, nil
52+
}
53+
54+
reqAuthenticator = group.NewAuthenticatedGroupAdder(reqAuthenticator)
55+
56+
// make the authenticator always add the target cluster to the user scopes
57+
reqAuthenticator = withClusterScope(reqAuthenticator)
58+
59+
return reqAuthenticator.AuthenticateRequest(req)
60+
})
61+
}
62+
2963
func NewWorkspaceAuthenticator() authenticator.Request {
3064
return authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
3165
reqAuthenticator, ok := WorkspaceAuthenticatorFrom(req.Context())

pkg/server/config.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,16 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co
405405
virtualWorkspaceServerProxyTransport = transport
406406
}
407407

408+
// Prepare a local cluster index that can be used both by the LocalProxy, as well as the authenticator
409+
// (the authenticator cannot always use the data the localproxy puts into the context because the
410+
// authentication rest storage provider calls the authenticator outside of the handler chain).
411+
clusterIndex := newLocalClusterIndex(
412+
opts.Extra.ShardName,
413+
opts.Extra.ShardBaseURL,
414+
c.KcpSharedInformerFactory.Tenancy().V1alpha1().Workspaces(),
415+
c.KcpSharedInformerFactory.Core().V1alpha1().LogicalClusters(),
416+
)
417+
408418
// Prepare an authentication index to be used later by a middleware. We start it early
409419
// because it can potentially fail and the BuildHandlerChainFunc() has no way to return
410420
// an error.
@@ -420,6 +430,7 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co
420430

421431
authIndex = authIndexState
422432
}
433+
423434
// preHandlerChainMux is called before the actual handler chain. Note that BuildHandlerChainFunc below
424435
// is called multiple times, but only one of the handler chain will actually be used. Hence, we wrap it
425436
// to give handlers below one mux.Handle func to call.
@@ -477,7 +488,7 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co
477488
if kcpfeatures.DefaultFeatureGate.Enabled(kcpfeatures.WorkspaceAuthentication) {
478489
genericConfig.Authentication.Authenticator = authenticatorunion.New(
479490
genericConfig.Authentication.Authenticator,
480-
authentication.NewWorkspaceAuthenticator(),
491+
authentication.NewStandaloneWorkspaceAuthenticator(clusterIndex, authIndex),
481492
)
482493
}
483494

@@ -515,13 +526,7 @@ func NewConfig(ctx context.Context, opts kcpserveroptions.CompletedOptions) (*Co
515526
apiHandler = mux
516527

517528
apiHandler = filters.WithAuditInit(apiHandler) // Must run before any audit annotation is made
518-
apiHandler, err = WithLocalProxy(apiHandler,
519-
opts.Extra.ShardName,
520-
opts.Extra.ShardBaseURL,
521-
opts.Extra.AdditionalMappingsFile,
522-
c.KcpSharedInformerFactory.Tenancy().V1alpha1().Workspaces(),
523-
c.KcpSharedInformerFactory.Core().V1alpha1().LogicalClusters(),
524-
)
529+
apiHandler, err = WithLocalProxy(apiHandler, opts.Extra.ShardName, opts.Extra.AdditionalMappingsFile, clusterIndex)
525530
if err != nil {
526531
panic(err) // shouldn't happen due to flag validation
527532
}

pkg/server/localproxy.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,11 @@ import (
4949
tenancyv1alpha1informers "github.com/kcp-dev/kcp/sdk/client/informers/externalversions/tenancy/v1alpha1"
5050
)
5151

52-
// WithLocalProxy returns a handler with a local-only mini-front-proxy. It is
53-
// able to translate logical clusters with the data on the local shard. This is
54-
// mainly interesting for standalone mode, without a real front-proxy in-front.
55-
func WithLocalProxy(
56-
handler http.Handler,
57-
shardName, shardBaseURL, additionalMappingsFile string,
52+
func newLocalClusterIndex(
53+
shardName, shardBaseURL string,
5854
workspaceInformer tenancyv1alpha1informers.WorkspaceClusterInformer,
5955
logicalClusterInformer corev1alpha1informers.LogicalClusterClusterInformer,
60-
) (http.Handler, error) {
56+
) *index.State {
6157
indexState := index.New([]index.PathRewriter{
6258
indexrewriters.UserRewriter,
6359
})
@@ -99,6 +95,17 @@ func WithLocalProxy(
9995
},
10096
})
10197

98+
return indexState
99+
}
100+
101+
// WithLocalProxy returns a handler with a local-only mini-front-proxy. It is
102+
// able to translate logical clusters with the data on the local shard. This is
103+
// mainly interesting for standalone mode, without a real front-proxy in-front.
104+
func WithLocalProxy(
105+
handler http.Handler,
106+
shardName, additionalMappingsFile string,
107+
clusterIndex *index.State,
108+
) (http.Handler, error) {
102109
defaultHandlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
103110
ctx := req.Context()
104111
logger := klog.FromContext(ctx)
@@ -144,7 +151,7 @@ func WithLocalProxy(
144151
}
145152

146153
// lookup in our local, potentially partial index
147-
r, found := indexState.Lookup(path)
154+
r, found := clusterIndex.Lookup(path)
148155
if found && r.ErrorCode != 0 {
149156
// return code if set.
150157
// TODO(mjudeikis): Would be nice to have a way to return a custom error message.

test/e2e/authentication/workspace_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,76 @@ func TestAcceptableWorkspaceAuthenticationConfigurations(t *testing.T) {
567567
})
568568
}
569569
}
570+
571+
func TestWorkspaceOIDCTokenReview(t *testing.T) {
572+
framework.Suite(t, "control-plane")
573+
574+
ctx := context.Background()
575+
576+
// start kcp and setup clients
577+
server := kcptesting.SharedKcpServer(t)
578+
579+
if len(server.ShardNames()) > 1 {
580+
t.Skip("This feature currently does not support multi shards because AuthConfigs are not replicated yet.")
581+
}
582+
583+
baseWsPath, _ := kcptesting.NewWorkspaceFixture(t, server, logicalcluster.NewPath("root"), kcptesting.WithNamePrefix("workspace-auth-token-review"))
584+
585+
kcpConfig := server.BaseConfig(t)
586+
kubeClusterClient, err := kcpkubernetesclientset.NewForConfig(kcpConfig)
587+
require.NoError(t, err)
588+
kcpClusterClient, err := kcpclientset.NewForConfig(kcpConfig)
589+
require.NoError(t, err)
590+
591+
mock, ca := authfixtures.StartMockOIDC(t, server)
592+
authConfig := authfixtures.CreateWorkspaceOIDCAuthentication(t, ctx, kcpClusterClient, baseWsPath, mock, ca, nil)
593+
wsType := authfixtures.CreateWorkspaceType(t, ctx, kcpClusterClient, baseWsPath, "with-oidc", authConfig)
594+
595+
// create a new workspace with our new type
596+
t.Log("Creating Workspaces...")
597+
teamPath, _ := kcptesting.NewWorkspaceFixture(t, server, baseWsPath, kcptesting.WithName("team-a"), kcptesting.WithType(baseWsPath, tenancyv1alpha1.WorkspaceTypeName(wsType)))
598+
599+
var (
600+
userName = "peter"
601+
userEmail = "[email protected]"
602+
userGroups = []string{"developers", "admins"}
603+
expectedGroups = []string{"system:authenticated"}
604+
)
605+
606+
for _, group := range userGroups {
607+
expectedGroups = append(expectedGroups, "oidc:"+group)
608+
}
609+
610+
authfixtures.GrantWorkspaceAccess(t, ctx, kubeClusterClient, teamPath, "grant-oidc-user", "cluster-admin", []rbacv1.Subject{{
611+
Kind: "User",
612+
Name: "oidc:" + userEmail,
613+
}})
614+
615+
token := authfixtures.CreateOIDCToken(t, mock, userName, userEmail, userGroups)
616+
617+
t.Logf("Creating TokenReview in %s", teamPath)
618+
619+
const kcpDefaultAudience = "https://kcp.default.svc"
620+
621+
review := &authenticationv1.TokenReview{
622+
ObjectMeta: metav1.ObjectMeta{
623+
Name: "my-review",
624+
},
625+
Spec: authenticationv1.TokenReviewSpec{
626+
Token: token,
627+
Audiences: []string{kcpDefaultAudience},
628+
},
629+
}
630+
631+
var response *authenticationv1.TokenReview
632+
require.Eventually(t, func() bool {
633+
var err error
634+
635+
response, err = kubeClusterClient.Cluster(teamPath).AuthenticationV1().TokenReviews().Create(ctx, review, metav1.CreateOptions{})
636+
require.NoError(t, err)
637+
638+
return response.Status.Authenticated
639+
}, wait.ForeverTestTimeout, 500*time.Millisecond)
640+
641+
require.Contains(t, response.Status.Audiences, kcpDefaultAudience)
642+
}

0 commit comments

Comments
 (0)