Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 7 additions & 19 deletions api/types/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,36 +894,24 @@ func (r *RoleV6) CheckAndSetDefaults() error {
r.Spec.Allow.DatabaseLabels = Labels{Wildcard: []string{Wildcard}}
}

if len(r.Spec.Allow.KubernetesResources) == 0 {
r.Spec.Allow.KubernetesResources = []KubernetesResource{
{
Kind: KindKubePod,
Namespace: Wildcard,
Name: Wildcard,
},
}
} else {
if err := validateRoleSpecKubeResources(r.Spec); err != nil {
return trace.Wrap(err)
}
}
fallthrough
case V4, V5:
// Labels default to nil/empty for v4+ roles

// Allow unrestricted access to all pods.
if len(r.Spec.Allow.KubernetesResources) == 0 {
if len(r.Spec.Allow.KubernetesResources) == 0 && len(r.Spec.Allow.KubernetesLabels) > 0 {
r.Spec.Allow.KubernetesResources = []KubernetesResource{
{
Kind: KindKubePod,
Namespace: Wildcard,
Name: Wildcard,
},
}
} else {
if err := validateRoleSpecKubeResources(r.Spec); err != nil {
return trace.Wrap(err)
}
}

if err := validateRoleSpecKubeResources(r.Spec); err != nil {
return trace.Wrap(err)
}

case V6:
if err := validateRoleSpecKubeResources(r.Spec); err != nil {
return trace.Wrap(err)
Expand Down
6 changes: 6 additions & 0 deletions lib/kube/proxy/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@ const (
// PortForwardProtocolV1Name is the subprotocol "portforward.k8s.io" is used for port forwarding
PortForwardProtocolV1Name = "portforward.k8s.io"
)

const (
// kubernetesResourcesKey is the key used to store the kubernetes resources
// in the role.
kubernetesResourcesKey = "kubernetes_resources"
)
12 changes: 10 additions & 2 deletions lib/kube/proxy/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ func NewForwarder(cfg ForwarderConfig) (*Forwarder, error) {
fwd.router.POST("/api/:ver/namespaces/:podNamespace/pods/:podName/portforward", fwd.withAuth(fwd.portForward))
fwd.router.GET("/api/:ver/namespaces/:podNamespace/pods/:podName/portforward", fwd.withAuth(fwd.portForward))

fwd.router.POST("/apis/authorization.k8s.io/:ver/selfsubjectaccessreviews", fwd.withAuth(fwd.selfSubjectAccessReviews))
fwd.router.GET("/api/:ver/pods", fwd.withAuth(fwd.listPods))
fwd.router.GET("/api/:ver/namespaces/:podNamespace/pods", fwd.withAuth(fwd.listPods))
fwd.router.DELETE("/api/:ver/namespaces/:podNamespace/pods", fwd.withAuth(fwd.deletePodsCollection))
Expand Down Expand Up @@ -2628,7 +2629,7 @@ func (f *Forwarder) deletePodsCollection(ctx *authContext, w http.ResponseWriter
func (f *Forwarder) handleDeleteCollectionReq(req *http.Request, authCtx *authContext, memWriter *responsewriters.MemoryResponseWriter, w http.ResponseWriter) (int, error) {
const internalErrStatus = http.StatusInternalServerError
// get content-type value
contentType := responsewriters.GetContentHeader(memWriter.Header())
contentType := responsewriters.GetContentTypeHeader(memWriter.Header())
encoder, decoder, err := newEncoderAndDecoderForContentType(contentType, newClientNegotiator())
if err != nil {
return internalErrStatus, trace.Wrap(err)
Expand Down Expand Up @@ -2764,14 +2765,21 @@ func kubeResourceDeniedAccessMsg(user, method string, kubeResource *types.Kubern
apiGroup := ""
// <resource> "<pod_name>" is forbidden: User "<user>" cannot create resource "<resource>" in API group "" in the namespace "<namespace>"
return fmt.Sprintf(
"%s %q is forbidden: User %q cannot %s resource %q in API group %q in the namespace %q",
"%s %q is forbidden: User %q cannot %s resource %q in API group %q in the namespace %q\n"+
"Ask your Teleport admin to ensure that your Teleport role includes access to the pod in %q field.\n"+
"Check by running: kubectl auth can-i %s %s/%s --namespace %s ",
resource,
kubeResource.Name,
user,
getRequestVerb(method),
resource,
apiGroup,
kubeResource.Namespace,
kubernetesResourcesKey,
getRequestVerb(method),
resource,
kubeResource.Name,
kubeResource.Namespace,
)
}

Expand Down
2 changes: 1 addition & 1 deletion lib/kube/proxy/pod_filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ func filterBuffer(filterWrapper responsewriters.FilterWrapper, src *responsewrit
return nil
}

filter, err := filterWrapper(responsewriters.GetContentHeader(src.Header()), src.Status())
filter, err := filterWrapper(responsewriters.GetContentTypeHeader(src.Header()), src.Status())
if err != nil {
return trace.Wrap(err)
}
Expand Down
8 changes: 7 additions & 1 deletion lib/kube/proxy/pod_rbac_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,13 @@ func TestListPodRBAC(t *testing.T) {
testPodName,
metav1.GetOptions{},
)
require.Equal(t, tt.want.getTestPodResult, err)

if tt.want.getTestPodResult == nil {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), tt.want.getTestPodResult.Error())
}
})
}
}
Expand Down
16 changes: 14 additions & 2 deletions lib/kube/proxy/responsewriters/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package responsewriters

import (
"io"
"mime"
"net/http"

"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -54,12 +55,23 @@ type Filter interface {
// - allowedPods: excluded if (namespace,name) not match a single entry.
type FilterWrapper func(contentType string, responseCode int) (Filter, error)

// GetContentHeader checks for the presence of the "Content-Type" header and
// GetContentTypeHeader checks for the presence of the "Content-Type" header and
// returns its value or returns the default content-type: "application/json".
func GetContentHeader(header http.Header) string {
func GetContentTypeHeader(header http.Header) string {
contentType := header.Get(ContentTypeHeader)
if len(contentType) > 0 {
return contentType
}
return DefaultContentType
}

// SetContentTypeHeader checks for the presence of the "Content-Type" header and
// sets its media type value or sets the default content-type: "application/json".
func SetContentTypeHeader(w http.ResponseWriter, header http.Header) {
contentType := header.Get(ContentTypeHeader)
if mediaType, _, err := mime.ParseMediaType(contentType); err == nil {
w.Header().Set(ContentTypeHeader, mediaType)
return
}
w.Header().Set(ContentTypeHeader, DefaultContentType)
}
2 changes: 1 addition & 1 deletion lib/kube/proxy/responsewriters/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func (w *WatcherResponseWriter) Header() http.Header {
func (w *WatcherResponseWriter) WriteHeader(code int) {
w.status = code
w.target.WriteHeader(code)
contentType := GetContentHeader(w.Header())
contentType := GetContentTypeHeader(w.Header())
w.group.Go(
func() error {
switch {
Expand Down
Loading