Skip to content
Open
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
31 changes: 31 additions & 0 deletions pkg/yurthub/proxy/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/klog/v2"

"github.com/openyurtio/openyurt/pkg/projectinfo"
"github.com/openyurtio/openyurt/pkg/yurthub/filter"
"github.com/openyurtio/openyurt/pkg/yurthub/metrics"
"github.com/openyurtio/openyurt/pkg/yurthub/tenant"
Expand Down Expand Up @@ -457,3 +458,33 @@ func IsSubjectAccessReviewCreateGetRequest(req *http.Request) bool {
info.Resource == "subjectaccessreviews" &&
(info.Verb == "create" || info.Verb == "get")
}

// WithRequireAuthorization rejects any request that does not carry an Authorization
// header with a 401 Unauthorized response, unless the request originates from
// YurtHub itself (identified by its well-known User-Agent prefixes).
//
// This middleware MUST be applied to the insecure (plain-HTTP) proxy port.
// Without it, RemoteProxy.RoundTrip forwards credential-free requests using
// YurtHub's own node client certificate, effectively lending the node identity
// (system:node:<node>) to any pod that can reach the dummy interface IP
// (169.254.2.1:10261). See: https://github.com/openyurtio/openyurt/issues/2782
func WithRequireAuthorization(handler http.Handler, nodeName string) http.Handler {
multiplexerUserAgent := util.MultiplexerProxyClientUserAgentPrefix + nodeName
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
// Allow requests from YurtHub itself: the internal multiplexer client
// and the shared-informer clients both omit Authorization headers but
// carry well-known User-Agent values. They must not be blocked.
ua := req.UserAgent()
if ua == multiplexerUserAgent || strings.HasPrefix(ua, projectinfo.GetHubName()) {
handler.ServeHTTP(w, req)
return
}

if strings.TrimSpace(req.Header.Get("Authorization")) == "" {
klog.V(2).Infof("rejected unauthenticated request %s: missing Authorization header on insecure proxy port", util.ReqString(req))
http.Error(w, "Unauthorized: requests to the YurtHub proxy must include an Authorization header", http.StatusUnauthorized)
return
}
handler.ServeHTTP(w, req)
})
}
14 changes: 7 additions & 7 deletions pkg/yurthub/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/openyurtio/openyurt/pkg/yurthub/healthchecker"
ota "github.com/openyurtio/openyurt/pkg/yurthub/otaupdate"
otautil "github.com/openyurtio/openyurt/pkg/yurthub/otaupdate/util"
proxyutil "github.com/openyurtio/openyurt/pkg/yurthub/proxy/util"
)

// RunYurtHubServers is used to start up all servers for yurthub
Expand All @@ -53,13 +54,12 @@ func RunYurtHubServers(cfg *config.YurtHubConfiguration,

// start yurthub proxy servers for forwarding requests to cloud kube-apiserver
if cfg.YurtHubProxyServerServing != nil {
if err := cfg.YurtHubProxyServerServing.Serve(proxyHandler, 0, stopCh); err != nil {
return err
}
}

if cfg.YurtHubDummyProxyServerServing != nil {
if err := cfg.YurtHubDummyProxyServerServing.Serve(proxyHandler, 0, stopCh); err != nil {
// Wrap the proxy handler so that requests arriving on the plain-HTTP port
// without an Authorization header are rejected with 401 instead of being
// forwarded using YurtHub's own node client certificate.
// See: https://github.com/openyurtio/openyurt/issues/2782
insecureProxyHandler := proxyutil.WithRequireAuthorization(proxyHandler, cfg.NodeName)
if err := cfg.YurtHubProxyServerServing.Serve(insecureProxyHandler, 0, stopCh); err != nil {
return err
}
}
Expand Down