Merge pull request #756 from rhafer/issue/206

proxy(router): Allow to set some outgoing headers
This commit is contained in:
Ralf Haferkamp
2025-05-05 11:07:08 +02:00
committed by GitHub
15 changed files with 93 additions and 37 deletions
+1 -1
View File
@@ -42,7 +42,7 @@ func ExtractAccountUUID(opts ...account.Option) func(http.Handler) http.Handler
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("x-access-token")
token := r.Header.Get(revactx.TokenHeader)
if len(token) == 0 {
roleIDsJSON, _ := json.Marshal([]string{})
ctx := metadata.Set(r.Context(), RoleIDs, string(roleIDsJSON))
+1 -1
View File
@@ -45,7 +45,7 @@ func (s *ActivitylogService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// HandleGetItemActivities handles the request to get the activities of an item.
func (s *ActivitylogService) HandleGetItemActivities(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
ctx = metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, r.Header.Get("X-Access-Token"))
ctx = metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, r.Header.Get(revactx.TokenHeader))
activeUser, ok := revactx.ContextGetUser(ctx)
if !ok {
+1 -1
View File
@@ -278,7 +278,7 @@ func (a *AuthAppService) authenticateUser(userID, userName string, gwc gateway.G
func getContext(r *http.Request) context.Context {
ctx := r.Context()
return metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, r.Header.Get("X-Access-Token"))
return metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, r.Header.Get(ctxpkg.TokenHeader))
}
func buildClientID(userID, userName string) string {
+4 -5
View File
@@ -11,7 +11,6 @@ import (
opkgm "github.com/opencloud-eu/opencloud/pkg/middleware"
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
"github.com/opencloud-eu/reva/v2/pkg/auth/scope"
ctxpkg "github.com/opencloud-eu/reva/v2/pkg/ctx"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/token/manager/jwt"
)
@@ -43,7 +42,7 @@ func Auth(opts ...account.Option) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
t := r.Header.Get("x-access-token")
t := r.Header.Get(revactx.TokenHeader)
if t == "" {
errorcode.InvalidAuthenticationToken.Render(w, r, http.StatusUnauthorized, "Access token is empty.")
/* msgraph error for GET https://graph.microsoft.com/v1.0/me
@@ -84,10 +83,10 @@ func Auth(opts ...account.Option) func(http.Handler) http.Handler {
}
ctx = metadata.AppendToOutgoingContext(ctx, revactx.TokenHeader, t)
initiatorID := r.Header.Get(ctxpkg.InitiatorHeader)
initiatorID := r.Header.Get(revactx.InitiatorHeader)
if initiatorID != "" {
ctx = ctxpkg.ContextSetInitiator(ctx, initiatorID)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.InitiatorHeader, initiatorID)
ctx = revactx.ContextSetInitiator(ctx, initiatorID)
ctx = metadata.AppendToOutgoingContext(ctx, revactx.InitiatorHeader, initiatorID)
}
next.ServeHTTP(w, r.WithContext(ctx))
+1
View File
@@ -294,6 +294,7 @@ func loadMiddlewares(logger log.Logger, cfg *config.Config,
authenticators = append(authenticators, middleware.AppAuthAuthenticator{
Logger: logger,
RevaGatewaySelector: gatewaySelector,
UserRoleAssigner: roleAssigner,
})
}
authenticators = append(authenticators, middleware.NewOIDCAuthenticator(
+6 -3
View File
@@ -64,9 +64,12 @@ type Route struct {
// Backend is a static URL to forward the request to
Backend string `yaml:"backend,omitempty"`
// Service name to look up in the registry
Service string `yaml:"service,omitempty"`
ApacheVHost bool `yaml:"apache_vhost,omitempty"`
Unprotected bool `yaml:"unprotected,omitempty"`
Service string `yaml:"service,omitempty"`
ApacheVHost bool `yaml:"apache_vhost,omitempty"`
Unprotected bool `yaml:"unprotected,omitempty"`
AdditionalHeaders map[string]string `yaml:"additional_headers,omitempty"`
RemoteUserHeader string `yaml:"remote_user_header,omitempty"`
SkipXAccessToken bool `yaml:"skip_x_access_token"`
}
// RouteType defines the type of route
@@ -7,6 +7,7 @@ import (
"time"
"github.com/jellydator/ttlcache/v3"
"github.com/opencloud-eu/opencloud/services/proxy/pkg/router"
"github.com/opencloud-eu/opencloud/services/proxy/pkg/user/backend"
"github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles"
@@ -99,8 +100,7 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
claims := oidc.FromContext(ctx)
user, ok := revactx.ContextGetUser(ctx)
token := ""
// TODO what if an X-Access-Token is set? happens eg for download requests to the /data endpoint in the reva frontend
token, hasToken := revactx.ContextGetToken(ctx)
if claims == nil && !ok {
m.next.ServeHTTP(w, req)
@@ -192,10 +192,11 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req = req.WithContext(ctx)
m.logger.Debug().Interface("claims", claims).Interface("user", user).Msg("associated claims with user")
} else if user != nil {
} else if user != nil && !hasToken {
// If we already have a token (e.g. the app auth middleware adds the token to the context) there is no need
// to get yet another one here.
var err error
_, token, err = m.userProvider.GetUserByClaims(req.Context(), "username", user.Username)
if errors.Is(err, backend.ErrAccountDisabled) {
m.logger.Debug().Interface("user", user).Msg("Disabled")
w.WriteHeader(http.StatusUnauthorized)
@@ -209,7 +210,13 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
}
req.Header.Set(revactx.TokenHeader, token)
ri := router.ContextRoutingInfo(ctx)
if ri.RemoteUserHeader() != "" {
req.Header.Set(ri.RemoteUserHeader(), user.GetId().GetOpaqueId())
}
if !ri.SkipXAccessToken() {
req.Header.Set(revactx.TokenHeader, token)
}
m.next.ServeHTTP(w, req)
}
@@ -9,6 +9,7 @@ import (
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/pkg/oidc"
"github.com/opencloud-eu/opencloud/services/proxy/pkg/router"
"github.com/opencloud-eu/opencloud/services/proxy/pkg/user/backend"
"github.com/opencloud-eu/opencloud/services/proxy/pkg/user/backend/mocks"
userRoleMocks "github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles/mocks"
@@ -206,6 +207,7 @@ func mockRequest(claims map[string]interface{}) (*http.Request, *httptest.Respon
}
ctx := oidc.NewContext(context.Background(), claims)
ctx = router.SetRoutingInfo(ctx, router.RoutingInfo{})
req := httptest.NewRequest("GET", "http://example.com/foo", nil).WithContext(ctx)
rw := httptest.NewRecorder()
+13 -2
View File
@@ -6,6 +6,7 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
)
@@ -14,6 +15,7 @@ import (
type AppAuthAuthenticator struct {
Logger log.Logger
RevaGatewaySelector pool.Selectable[gateway.GatewayAPIClient]
UserRoleAssigner userroles.UserRoleAssigner
}
// Authenticate implements the authenticator interface to authenticate requests via app auth.
@@ -43,11 +45,20 @@ func (m AppAuthAuthenticator) Authenticate(r *http.Request) (*http.Request, bool
return nil, false
}
if authenticateResponse.GetStatus().GetCode() != cs3rpc.Code_CODE_OK {
// TODO: log???
m.Logger.Debug().Str("msg", authenticateResponse.GetStatus().GetMessage()).Str("clientid", username).Msg("app auth failed")
return nil, false
}
r.Header.Set(revactx.TokenHeader, authenticateResponse.GetToken())
user := authenticateResponse.GetUser()
if user, err = m.UserRoleAssigner.ApplyUserRole(r.Context(), user); err != nil {
m.Logger.Error().Err(err).Str("clientid", username).Msg("app auth: failed to load user roles")
return nil, false
}
ctx := revactx.ContextSetUser(r.Context(), user)
ctx = revactx.ContextSetToken(ctx, authenticateResponse.GetToken())
r = r.WithContext(ctx)
return r, true
}
+15 -4
View File
@@ -5,19 +5,24 @@ import (
"net/http/httptest"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"google.golang.org/grpc"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/opencloud-eu/opencloud/pkg/log"
userRoleMocks "github.com/opencloud-eu/opencloud/services/proxy/pkg/userroles/mocks"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/stretchr/testify/mock"
"google.golang.org/grpc"
)
var _ = Describe("Authenticating requests", Label("AppAuthAuthenticator"), func() {
var authenticator Authenticator
BeforeEach(func() {
pool.RemoveSelector("GatewaySelector" + "eu.opencloud.api.gateway")
ra := &userRoleMocks.UserRoleAssigner{}
ra.On("ApplyUserRole", mock.Anything, mock.Anything, mock.Anything).Return(&userv1beta1.User{}, nil)
authenticator = AppAuthAuthenticator{
Logger: log.NewLogger(),
RevaGatewaySelector: pool.GetSelector[gateway.GatewayAPIClient](
@@ -39,6 +44,7 @@ var _ = Describe("Authenticating requests", Label("AppAuthAuthenticator"), func(
}
},
),
UserRoleAssigner: ra,
}
})
@@ -51,7 +57,12 @@ var _ = Describe("Authenticating requests", Label("AppAuthAuthenticator"), func(
Expect(valid).To(Equal(true))
Expect(req2).ToNot(BeNil())
Expect(req2.Header.Get("x-access-token")).To(Equal("reva-token"))
user, ok := revactx.ContextGetUser(req2.Context())
Expect(ok).To(BeTrue())
Expect(user).ToNot(BeNil())
token, ok := revactx.ContextGetToken(req2.Context())
Expect(ok).To(BeTrue())
Expect(token).To(Equal("reva-token"))
})
})
@@ -163,7 +163,7 @@ var _ = Describe("Authenticating requests", Label("Authentication"), func() {
EnableBasicAuth(true),
)
testHandler := handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expect(r.Header.Get(_headerRevaAccessToken)).To(Equal("otherexampletoken"))
Expect(r.Header.Get(headerRevaAccessToken)).To(Equal("otherexampletoken"))
}))
rr := httptest.NewRecorder()
testHandler.ServeHTTP(rr, req)
@@ -178,7 +178,7 @@ var _ = Describe("Authenticating requests", Label("Authentication"), func() {
EnableBasicAuth(true),
)
testHandler := handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expect(r.Header.Get(_headerRevaAccessToken)).To(Equal("exampletoken"))
Expect(r.Header.Get(headerRevaAccessToken)).To(Equal("exampletoken"))
}))
rr := httptest.NewRecorder()
testHandler.ServeHTTP(rr, req)
@@ -193,7 +193,7 @@ var _ = Describe("Authenticating requests", Label("Authentication"), func() {
EnableBasicAuth(true),
)
testHandler := handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expect(r.Header.Get(_headerRevaAccessToken)).To(Equal("otherexampletoken"))
Expect(r.Header.Get(headerRevaAccessToken)).To(Equal("otherexampletoken"))
}))
rr := httptest.NewRecorder()
testHandler.ServeHTTP(rr, req)
+2 -2
View File
@@ -45,7 +45,7 @@ func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
token := req.Header.Get("x-access-token")
token := req.Header.Get(revactx.TokenHeader)
// we need to pass the token to authenticate the CreateHome request.
//ctx := tokenpkg.ContextSetToken(r.Context(), token)
@@ -84,7 +84,7 @@ func (m createHome) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
func (m createHome) shouldServe(req *http.Request) bool {
return req.Header.Get("x-access-token") != ""
return req.Header.Get(revactx.TokenHeader) != ""
}
func (m createHome) getUserRoles(user *userv1beta1.User) ([]string, error) {
@@ -6,11 +6,12 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
"github.com/opencloud-eu/opencloud/pkg/log"
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
)
const (
_headerRevaAccessToken = "x-access-token"
headerRevaAccessToken = revactx.TokenHeader
headerShareToken = "public-token"
basicAuthPasswordPrefix = "password|"
authenticationType = "publicshares"
@@ -118,7 +119,7 @@ func (a PublicShareAuthenticator) Authenticate(r *http.Request) (*http.Request,
return nil, false
}
r.Header.Add(_headerRevaAccessToken, authResp.Token)
r.Header.Add(headerRevaAccessToken, authResp.Token)
a.Logger.Debug().
Str("authenticator", "public_share").
@@ -58,7 +58,7 @@ var _ = Describe("Authenticating requests", Label("PublicShareAuthenticator"), f
Expect(req2).ToNot(BeNil())
h := req2.Header
Expect(h.Get(_headerRevaAccessToken)).To(Equal("exampletoken"))
Expect(h.Get(headerRevaAccessToken)).To(Equal("exampletoken"))
})
})
Context("using signature authentication", func() {
@@ -71,7 +71,7 @@ var _ = Describe("Authenticating requests", Label("PublicShareAuthenticator"), f
Expect(req2).ToNot(BeNil())
h := req2.Header
Expect(h.Get(_headerRevaAccessToken)).To(Equal("exampletoken"))
Expect(h.Get(headerRevaAccessToken)).To(Equal("exampletoken"))
})
})
})
@@ -85,7 +85,7 @@ var _ = Describe("Authenticating requests", Label("PublicShareAuthenticator"), f
Expect(req2).ToNot(BeNil())
h := req2.Header
Expect(h.Get(_headerRevaAccessToken)).To(Equal("otherexampletoken"))
Expect(h.Get(headerRevaAccessToken)).To(Equal("otherexampletoken"))
})
})
Context("not using a public-token", func() {
+26 -5
View File
@@ -86,9 +86,11 @@ func New(serviceSelector selector.Selector, policySelectorCfg *config.PolicySele
// RoutingInfo contains the proxy rewrite hook and some information about the route.
type RoutingInfo struct {
rewrite func(*httputil.ProxyRequest)
endpoint string
unprotected bool
rewrite func(*httputil.ProxyRequest)
endpoint string
unprotected bool
remoteUserHeader string
skipXAccessToken bool
}
// Rewrite returns the proxy rewrite hook.
@@ -101,6 +103,17 @@ func (r RoutingInfo) IsRouteUnprotected() bool {
return r.unprotected
}
// RemoteUserHeader returns the name of Header for setting the remote user value
func (r RoutingInfo) RemoteUserHeader() string {
return r.remoteUserHeader
}
// SkipXAccessToken return true if the reva access token should not be added to the
// outgoing request
func (r RoutingInfo) SkipXAccessToken() bool {
return r.skipXAccessToken
}
// Router handles the routing of HTTP requests according to the given policies.
type Router struct {
logger log.Logger
@@ -126,8 +139,10 @@ func (rt Router) addHost(policy string, target *url.URL, route config.Route) {
}
rt.rewriters[policy][routeType][route.Method] = append(rt.rewriters[policy][routeType][route.Method], RoutingInfo{
endpoint: route.Endpoint,
unprotected: route.Unprotected,
endpoint: route.Endpoint,
unprotected: route.Unprotected,
remoteUserHeader: route.RemoteUserHeader,
skipXAccessToken: route.SkipXAccessToken,
rewrite: func(req *httputil.ProxyRequest) {
if route.Service != "" {
// select next node
@@ -161,6 +176,10 @@ func (rt Router) addHost(policy string, target *url.URL, route config.Route) {
req.Out.Host = target.Host
}
for k, v := range route.AdditionalHeaders {
req.Out.Header.Set(k, v)
}
req.Out.URL.Path = singleJoiningSlash(target.Path, req.Out.URL.Path)
if targetQuery == "" || req.Out.URL.RawQuery == "" {
req.Out.URL.RawQuery = targetQuery + req.Out.URL.RawQuery
@@ -209,6 +228,8 @@ func (rt Router) Route(r *http.Request) (RoutingInfo, bool) {
if rt.rewriters[pol][rtype][r.Method] != nil {
// use specific method
method = r.Method
} else {
method = ""
}
for _, ri := range rt.rewriters[pol][rtype][method] {