diff --git a/api/types/role.go b/api/types/role.go index 98263f1973d4f..fd29e06fca395 100644 --- a/api/types/role.go +++ b/api/types/role.go @@ -1836,9 +1836,10 @@ func setDefaultKubernetesVerbs(spec *RoleSpecV6) { // - Namespace is not empty func validateKubeResources(roleVersion string, kubeResources []KubernetesResource) error { for _, kubeResource := range kubeResources { - if !slices.Contains(KubernetesResourcesKinds, kubeResource.Kind) && kubeResource.Kind != Wildcard { - return trace.BadParameter("KubernetesResource kind %q is invalid or unsupported; Supported: %v", kubeResource.Kind, append([]string{Wildcard}, KubernetesResourcesKinds...)) - } + // TODO(creack): Move the validation to the server side so we can lookup the list of valid CRDs. + // if !slices.Contains(KubernetesResourcesKinds, kubeResource.Kind) && kubeResource.Kind != Wildcard { + // return trace.BadParameter("KubernetesResource kind %q is invalid or unsupported; Supported: %v", kubeResource.Kind, append([]string{Wildcard}, KubernetesResourcesKinds...)) + // } for _, verb := range kubeResource.Verbs { if !slices.Contains(KubernetesVerbs, verb) && verb != Wildcard && !strings.Contains(verb, "{{") { diff --git a/lib/kube/proxy/forwarder.go b/lib/kube/proxy/forwarder.go index b3df6d6c0b153..f2303ec1e6162 100644 --- a/lib/kube/proxy/forwarder.go +++ b/lib/kube/proxy/forwarder.go @@ -30,6 +30,7 @@ import ( "net" "net/http" "net/url" + "path" "slices" "strconv" "strings" @@ -801,6 +802,9 @@ func (f *Forwarder) setupContext( return nil, trace.Wrap(err) } } + if kubeResource != nil && kubeResource.Kind == "CustomResource" { + kubeResource.Kind = path.Join(apiResource.apiGroup, apiResource.apiGroupVersion, apiResource.resourceKind) + } netConfig, err := f.cfg.CachingAuthClient.GetClusterNetworkingConfig(f.ctx) if err != nil { diff --git a/lib/kube/proxy/resource_list.go b/lib/kube/proxy/resource_list.go index d0401a600fe5d..63bfd2f4eb57d 100644 --- a/lib/kube/proxy/resource_list.go +++ b/lib/kube/proxy/resource_list.go @@ -22,6 +22,7 @@ import ( "bytes" "io" "net/http" + "path" "strings" "sync" "time" @@ -59,6 +60,9 @@ func (f *Forwarder) listResources(sess *clusterSession, w http.ResponseWriter, r if isLocalKubeCluster { resourceKind, supportsType = sess.rbacSupportedResources.getTeleportResourceKindFromAPIResource(sess.apiResource) } + if resourceKind == "CustomResource" { + resourceKind = path.Join(sess.apiResource.apiGroup, sess.apiResource.apiGroupVersion, sess.apiResource.resourceKind) + } // status holds the returned response code. var status int @@ -119,6 +123,9 @@ func (f *Forwarder) listResourcesList(req *http.Request, w http.ResponseWriter, if !ok { return http.StatusBadRequest, trace.BadParameter("unknown resource kind %q", sess.apiResource.resourceKind) } + if resourceKind == "CustomResource" { + resourceKind = path.Join(sess.apiResource.apiGroup, sess.apiResource.apiGroupVersion, sess.apiResource.resourceKind) + } verb := sess.requestVerb // filterBuffer filters the response to exclude resources the user doesn't have access to. // The filtered payload will be written into memBuffer again.