Skip to content

Commit b327399

Browse files
xrstfembik
authored andcommitted
redesign how shard-per-workspace-authentication works to allow TokenReviews to be handled
The original design for per-workspace-auth was in the front-proxy only, only later did I add it to each shard, primarily to make writing e2e tests easier. In the front-proxy, there is a chain of middleware handlers that * resolve the cluster path * find the authenticator for a workspace * inject the authenticator into the request context * the handle the "optional auth", where the authenticator is read from the context again This means the actual authenticator used (i.e. in genericConfig.Authentication.Authenticator) is one that simply reads the authenticator from step 3 (out of the context) and calls it. In the shard variant, this concept was kept the same, i.e. there are handlers that are involved to make the per-workspace-authenticator work. This however means that any other component in kcp that uses the .Authenticator has one that has no concept of per-workspace auth anymore, since the middlewares are not being called. Thankfully the concept of middlewares is not required to make per-workspace-auth working on the shard level, so this commit condenses the logic on the shards to instead provide a "standalone" authenticator that does all steps listed above in one big function. Required for this refactoring is untangling the previous LocalProxy initialisation routine. Now the cluster index is started separatedly (since the new standalone auther needs it) and then handed into the middleware. On-behalf-of: @SAP [email protected] Signed-off-by: Marvin Beckers <[email protected]>
1 parent bccdaa6 commit b327399

File tree

3 files changed

+62
-16
lines changed

3 files changed

+62
-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.

0 commit comments

Comments
 (0)