Merge pull request #756 from rhafer/issue/206
proxy(router): Allow to set some outgoing headers
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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] {
|
||||
|
||||
Reference in New Issue
Block a user