Skip to content

Commit f79d082

Browse files
committed
Emit RTR access logs for denied mTLS requests
Set RouteEndpoint on RequestInfo before returning 401/403 responses so that access logs are emitted to the target app's log stream. This allows operators to see denied requests in 'cf logs <app>' for the backend app, which is essential for debugging authorization issues in mTLS app-to-app communication.
1 parent ec5aece commit f79d082

2 files changed

Lines changed: 225 additions & 0 deletions

File tree

src/code.cloudfoundry.org/gorouter/handlers/mtls_authorization.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99

1010
"code.cloudfoundry.org/gorouter/config"
1111
"code.cloudfoundry.org/gorouter/logger"
12+
"code.cloudfoundry.org/gorouter/route"
1213
)
1314

1415
// mtlsAuthorization enforces authorization checks on mTLS domains by verifying
@@ -26,6 +27,20 @@ func NewMtlsAuthorization(cfg *config.Config, logger *slog.Logger) negroni.Handl
2627
}
2728
}
2829

30+
// setRouteEndpointForAccessLog sets the RouteEndpoint on reqInfo so that access logs
31+
// are emitted to the target app even when the request is denied by authorization.
32+
// This allows operators to see denied requests in the app's log stream.
33+
func setRouteEndpointForAccessLog(reqInfo *RequestInfo, pool *route.EndpointPool, logger *slog.Logger) {
34+
if pool == nil || reqInfo.RouteEndpoint != nil {
35+
return
36+
}
37+
// Get an endpoint from the pool for access logging purposes
38+
iter := pool.Endpoints(logger, "", false, "", "")
39+
if endpoint := iter.Next(0); endpoint != nil {
40+
reqInfo.RouteEndpoint = endpoint
41+
}
42+
}
43+
2944
func (h *mtlsAuthorization) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
3045
reqInfo, err := ContextRequestInfo(r)
3146
if err != nil {
@@ -66,6 +81,7 @@ func (h *mtlsAuthorization) ServeHTTP(w http.ResponseWriter, r *http.Request, ne
6681
slog.String("host", r.Host),
6782
slog.String("endpoint-app", applicationId),
6883
slog.String("reason", "no-mtls-allowed-sources"))
84+
setRouteEndpointForAccessLog(reqInfo, pool, h.logger)
6985
w.WriteHeader(http.StatusForbidden)
7086
return
7187
}
@@ -78,6 +94,7 @@ func (h *mtlsAuthorization) ServeHTTP(w http.ResponseWriter, r *http.Request, ne
7894
slog.String("host", r.Host),
7995
slog.String("endpoint-app", applicationId),
8096
slog.String("reason", "no-caller-identity"))
97+
setRouteEndpointForAccessLog(reqInfo, pool, h.logger)
8198
w.WriteHeader(http.StatusUnauthorized)
8299
return
83100
}
@@ -99,6 +116,7 @@ func (h *mtlsAuthorization) ServeHTTP(w http.ResponseWriter, r *http.Request, ne
99116
slog.String("host", r.Host),
100117
slog.String("endpoint-app", applicationId),
101118
slog.String("reason", "empty-mtls-allowed-sources"))
119+
setRouteEndpointForAccessLog(reqInfo, pool, h.logger)
102120
w.WriteHeader(http.StatusForbidden)
103121
return
104122
}
@@ -109,6 +127,7 @@ func (h *mtlsAuthorization) ServeHTTP(w http.ResponseWriter, r *http.Request, ne
109127
slog.String("host", r.Host),
110128
slog.String("endpoint-app", applicationId),
111129
slog.String("reason", "no-caller-identity"))
130+
setRouteEndpointForAccessLog(reqInfo, pool, h.logger)
112131
w.WriteHeader(http.StatusUnauthorized)
113132
return
114133
}
@@ -158,5 +177,6 @@ func (h *mtlsAuthorization) ServeHTTP(w http.ResponseWriter, r *http.Request, ne
158177
slog.String("caller-space", identity.SpaceGUID),
159178
slog.String("caller-org", identity.OrgGUID),
160179
slog.String("reason", "not-in-mtls-allowed-sources"))
180+
setRouteEndpointForAccessLog(reqInfo, pool, h.logger)
161181
w.WriteHeader(http.StatusForbidden)
162182
}

src/code.cloudfoundry.org/gorouter/handlers/mtls_authorization_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,4 +728,209 @@ var _ = Describe("MtlsAuthorization", func() {
728728
})
729729
})
730730
})
731+
732+
Context("RouteEndpoint is set for access logging on denial", func() {
733+
// These tests verify that when a request is denied, RouteEndpoint is set
734+
// so that RTR logs are emitted to the target app's log stream
735+
BeforeEach(func() {
736+
request = test_util.NewRequest("GET", "backend.apps.mtls.internal", "/", nil)
737+
})
738+
739+
Context("when route pool has no allowed sources (403)", func() {
740+
var capturedReqInfo *handlers.RequestInfo
741+
742+
BeforeEach(func() {
743+
endpoint := route.NewEndpoint(&route.EndpointOpts{
744+
AppId: "backend-app-id",
745+
Host: "192.168.1.1",
746+
Port: 8080,
747+
PrivateInstanceId: "backend-instance-id",
748+
})
749+
pool := createPoolWithEndpoint(endpoint)
750+
751+
reqInfoHandler := handlers.NewRequestInfo()
752+
n := negroni.New()
753+
n.Use(reqInfoHandler)
754+
n.UseFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
755+
reqInfo, err := handlers.ContextRequestInfo(r)
756+
Expect(err).NotTo(HaveOccurred())
757+
reqInfo.RoutePool = pool
758+
capturedReqInfo = reqInfo
759+
request = r
760+
next(w, r)
761+
})
762+
n.Use(handler)
763+
n.UseHandlerFunc(nextHandler)
764+
765+
n.ServeHTTP(recorder, request)
766+
})
767+
768+
It("sets RouteEndpoint for access logging", func() {
769+
Expect(recorder.Code).To(Equal(http.StatusForbidden))
770+
Expect(capturedReqInfo.RouteEndpoint).NotTo(BeNil())
771+
Expect(capturedReqInfo.RouteEndpoint.ApplicationId).To(Equal("backend-app-id"))
772+
})
773+
})
774+
775+
Context("when route pool has empty allowed sources (403)", func() {
776+
var capturedReqInfo *handlers.RequestInfo
777+
778+
BeforeEach(func() {
779+
endpoint := route.NewEndpoint(&route.EndpointOpts{
780+
AppId: "backend-app-id",
781+
Host: "192.168.1.1",
782+
Port: 8080,
783+
PrivateInstanceId: "backend-instance-id",
784+
MtlsAllowedSources: &route.MtlsAllowedSources{},
785+
})
786+
pool := createPoolWithEndpoint(endpoint)
787+
788+
reqInfoHandler := handlers.NewRequestInfo()
789+
n := negroni.New()
790+
n.Use(reqInfoHandler)
791+
n.UseFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
792+
reqInfo, err := handlers.ContextRequestInfo(r)
793+
Expect(err).NotTo(HaveOccurred())
794+
reqInfo.RoutePool = pool
795+
capturedReqInfo = reqInfo
796+
request = r
797+
next(w, r)
798+
})
799+
n.Use(handler)
800+
n.UseHandlerFunc(nextHandler)
801+
802+
n.ServeHTTP(recorder, request)
803+
})
804+
805+
It("sets RouteEndpoint for access logging", func() {
806+
Expect(recorder.Code).To(Equal(http.StatusForbidden))
807+
Expect(capturedReqInfo.RouteEndpoint).NotTo(BeNil())
808+
Expect(capturedReqInfo.RouteEndpoint.ApplicationId).To(Equal("backend-app-id"))
809+
})
810+
})
811+
812+
Context("when caller is not authenticated with Any=true (401)", func() {
813+
var capturedReqInfo *handlers.RequestInfo
814+
815+
BeforeEach(func() {
816+
endpoint := route.NewEndpoint(&route.EndpointOpts{
817+
AppId: "backend-app-id",
818+
Host: "192.168.1.1",
819+
Port: 8080,
820+
PrivateInstanceId: "backend-instance-id",
821+
MtlsAllowedSources: &route.MtlsAllowedSources{
822+
Any: true,
823+
},
824+
})
825+
pool := createPoolWithEndpoint(endpoint)
826+
827+
reqInfoHandler := handlers.NewRequestInfo()
828+
n := negroni.New()
829+
n.Use(reqInfoHandler)
830+
n.UseFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
831+
reqInfo, err := handlers.ContextRequestInfo(r)
832+
Expect(err).NotTo(HaveOccurred())
833+
reqInfo.RoutePool = pool
834+
// Don't set CallerIdentity
835+
capturedReqInfo = reqInfo
836+
request = r
837+
next(w, r)
838+
})
839+
n.Use(handler)
840+
n.UseHandlerFunc(nextHandler)
841+
842+
n.ServeHTTP(recorder, request)
843+
})
844+
845+
It("sets RouteEndpoint for access logging", func() {
846+
Expect(recorder.Code).To(Equal(http.StatusUnauthorized))
847+
Expect(capturedReqInfo.RouteEndpoint).NotTo(BeNil())
848+
Expect(capturedReqInfo.RouteEndpoint.ApplicationId).To(Equal("backend-app-id"))
849+
})
850+
})
851+
852+
Context("when caller is not authenticated with specific sources (401)", func() {
853+
var capturedReqInfo *handlers.RequestInfo
854+
855+
BeforeEach(func() {
856+
endpoint := route.NewEndpoint(&route.EndpointOpts{
857+
AppId: "backend-app-id",
858+
Host: "192.168.1.1",
859+
Port: 8080,
860+
PrivateInstanceId: "backend-instance-id",
861+
MtlsAllowedSources: &route.MtlsAllowedSources{
862+
Apps: []string{"allowed-app"},
863+
},
864+
})
865+
pool := createPoolWithEndpoint(endpoint)
866+
867+
reqInfoHandler := handlers.NewRequestInfo()
868+
n := negroni.New()
869+
n.Use(reqInfoHandler)
870+
n.UseFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
871+
reqInfo, err := handlers.ContextRequestInfo(r)
872+
Expect(err).NotTo(HaveOccurred())
873+
reqInfo.RoutePool = pool
874+
// Don't set CallerIdentity
875+
capturedReqInfo = reqInfo
876+
request = r
877+
next(w, r)
878+
})
879+
n.Use(handler)
880+
n.UseHandlerFunc(nextHandler)
881+
882+
n.ServeHTTP(recorder, request)
883+
})
884+
885+
It("sets RouteEndpoint for access logging", func() {
886+
Expect(recorder.Code).To(Equal(http.StatusUnauthorized))
887+
Expect(capturedReqInfo.RouteEndpoint).NotTo(BeNil())
888+
Expect(capturedReqInfo.RouteEndpoint.ApplicationId).To(Equal("backend-app-id"))
889+
})
890+
})
891+
892+
Context("when caller is not in allowed sources list (403)", func() {
893+
var capturedReqInfo *handlers.RequestInfo
894+
895+
BeforeEach(func() {
896+
endpoint := route.NewEndpoint(&route.EndpointOpts{
897+
AppId: "backend-app-id",
898+
Host: "192.168.1.1",
899+
Port: 8080,
900+
PrivateInstanceId: "backend-instance-id",
901+
MtlsAllowedSources: &route.MtlsAllowedSources{
902+
Apps: []string{"allowed-app-1", "allowed-app-2"},
903+
},
904+
})
905+
pool := createPoolWithEndpoint(endpoint)
906+
907+
reqInfoHandler := handlers.NewRequestInfo()
908+
n := negroni.New()
909+
n.Use(reqInfoHandler)
910+
n.UseFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
911+
reqInfo, err := handlers.ContextRequestInfo(r)
912+
Expect(err).NotTo(HaveOccurred())
913+
reqInfo.RoutePool = pool
914+
reqInfo.CallerIdentity = &handlers.CallerIdentity{
915+
AppGUID: "unauthorized-app",
916+
SpaceGUID: "some-space",
917+
OrgGUID: "some-org",
918+
}
919+
capturedReqInfo = reqInfo
920+
request = r
921+
next(w, r)
922+
})
923+
n.Use(handler)
924+
n.UseHandlerFunc(nextHandler)
925+
926+
n.ServeHTTP(recorder, request)
927+
})
928+
929+
It("sets RouteEndpoint for access logging", func() {
930+
Expect(recorder.Code).To(Equal(http.StatusForbidden))
931+
Expect(capturedReqInfo.RouteEndpoint).NotTo(BeNil())
932+
Expect(capturedReqInfo.RouteEndpoint.ApplicationId).To(Equal("backend-app-id"))
933+
})
934+
})
935+
})
731936
})

0 commit comments

Comments
 (0)