@@ -18,6 +18,7 @@ package filters
18
18
19
19
import (
20
20
"context"
21
+ "errors"
21
22
"fmt"
22
23
"net/http"
23
24
"net/url"
@@ -35,6 +36,9 @@ import (
35
36
kaudit "k8s.io/apiserver/pkg/audit"
36
37
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
37
38
"k8s.io/apiserver/pkg/endpoints/request"
39
+
40
+ corev1alpha1 "github.com/kcp-dev/kcp/sdk/apis/core/v1alpha1"
41
+ informersv1alpha1 "github.com/kcp-dev/kcp/sdk/client/informers/externalversions/core/v1alpha1"
38
42
)
39
43
40
44
type (
@@ -44,6 +48,10 @@ type (
44
48
const (
45
49
workspaceAnnotation = "tenancy.kcp.io/workspace"
46
50
51
+ // inactiveAnnotation is the annotation denoting a logical cluster should be
52
+ // deemed unreachable.
53
+ inactiveAnnotation = "internal.kcp.io/inactive"
54
+
47
55
// clusterKey is the context key for the request namespace.
48
56
acceptHeaderContextKey acceptHeaderContextKeyType = iota
49
57
)
@@ -78,6 +86,49 @@ func WithAuditEventClusterAnnotation(handler http.Handler) http.HandlerFunc {
78
86
})
79
87
}
80
88
89
+ // WithBlockInactiveLogicalClusters ensures that any requests to logical
90
+ // clusters marked inactive are rejected.
91
+ func WithBlockInactiveLogicalClusters (handler http.Handler , kcpClusterClient informersv1alpha1.LogicalClusterClusterInformer ) http.HandlerFunc {
92
+ allowedPathPrefixes := []string {
93
+ "/openapi" ,
94
+ "/apis/core.kcp.io/v1alpha1/logicalclusters" ,
95
+ }
96
+
97
+ return http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
98
+ _ , newURL , _ , err := ClusterPathFromAndStrip (req )
99
+ if err != nil {
100
+ responsewriters .InternalError (w , req , err )
101
+ return
102
+ }
103
+
104
+ isException := false
105
+ for _ , prefix := range allowedPathPrefixes {
106
+ if strings .HasPrefix (newURL .String (), prefix ) {
107
+ isException = true
108
+ }
109
+ }
110
+
111
+ cluster := request .ClusterFrom (req .Context ())
112
+ if cluster != nil && ! cluster .Name .Empty () && ! isException {
113
+ logicalCluster , err := kcpClusterClient .Cluster (cluster .Name ).Lister ().Get (corev1alpha1 .LogicalClusterName )
114
+ if err == nil {
115
+ if ann , ok := logicalCluster .ObjectMeta .Annotations [inactiveAnnotation ]; ok && ann == "true" {
116
+ responsewriters .ErrorNegotiated (
117
+ apierrors .NewForbidden (corev1alpha1 .Resource ("logicalclusters" ), cluster .Name .String (), errors .New ("logical cluster is marked inactive" )),
118
+ errorCodecs , schema.GroupVersion {}, w , req ,
119
+ )
120
+ return
121
+ }
122
+ } else if ! apierrors .IsNotFound (err ) {
123
+ responsewriters .InternalError (w , req , err )
124
+ return
125
+ }
126
+ }
127
+
128
+ handler .ServeHTTP (w , req )
129
+ })
130
+ }
131
+
81
132
// WithClusterScope reads a cluster name from the URL path and puts it into the context.
82
133
// It also trims "/clusters/" prefix from the URL.
83
134
func WithClusterScope (apiHandler http.Handler ) http.HandlerFunc {
0 commit comments