Skip to content

Commit

Permalink
Added request ID logging to trace individual requests (#28)
Browse files Browse the repository at this point in the history
* Added request ID logging to trace individual requests

* Fixed tests
  • Loading branch information
rorylshanks authored Mar 17, 2024
1 parent 644c3e9 commit 91b5444
Show file tree
Hide file tree
Showing 11 changed files with 101 additions and 57 deletions.
3 changes: 2 additions & 1 deletion lib/authz.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ async function authZRequest(req, res, route) {
var userId = req.session.userId
var user = await idp.getUserById(userId)
if (!user) {
log.info({ "action": "userDoesNotExistInIdp", "user": userId, context: { url: requestUrl } })
log.infoWithContext(req, { "action": "userDoesNotExistInIdp", "user": userId, context: { url: requestUrl } })
return false
}
var allowedGroups = route.allowed_groups

var discoveredGroups = await checkUserGroupMembership(user, allowedGroups)
if (discoveredGroups.length >= 1) {
log.infoWithContext(req, { discoveredGroups })
log.access("userIsAllowedBasedOnGroupMembership", route, user, req)
await addRequestedHeaders(req, res, route, user, discoveredGroups)
return "OK - Groups"
Expand Down
2 changes: 1 addition & 1 deletion lib/dynamic-backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async function checkDynamicBackend(req, res, route, requestUrl) {
const responseBody = response.data
const proxyUrl = responseBody.url
const responseHeaders = responseBody.headers
log.info({ "action": "dynamicBackendSessionOk", "url": responseBody });
log.infoWithContext(req, { "action": "dynamicBackendSessionOk", "url": responseBody });
for (let header of responseHeaders) {
res.set(header.key, header.value)
}
Expand Down
17 changes: 13 additions & 4 deletions lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,17 @@ const app = express();
import session from 'express-session';
import ssoController from './sso.js';


import redisHelper from "../util/redis.js"
import log from '../util/logging.js'
import { getConfig, getRedirectBasepath, getAuthListenPort } from '../util/config.js';
import { pem2jwk } from 'pem-jwk';
import crypto from 'crypto';
import errorpages from '../util/errorpage.js'
import metrics from '../util/metrics.js'

import { randomUUID } from 'node:crypto'

var trusted_ranges = ["loopback"].concat(getConfig().trusted_ranges || [])
log.info({ message: `Setting trusted proxies to ${trusted_ranges}` })
log.debug({ message: `Setting trusted proxies to ${trusted_ranges}` })
app.set('trust proxy', trusted_ranges)

var defaultCookieOptions = { maxAge: 3600000 }
Expand Down Expand Up @@ -58,6 +57,16 @@ generateJwks()
app.use(express.json());
app.use(express.urlencoded({ extended: true }))

// Request ID setting middleware
app.use((req, res, next) => {
var requestId = req.get("x-veriflow-request-id")
if (!requestId) {
requestId = randomUUID()
}
req.headers["X-Veriflow-Request-Id"] = requestId
next()
})

var redirectBasePath = getRedirectBasepath()

app.get('/ping', (req, res) => {
Expand All @@ -71,7 +80,7 @@ app.get(redirectBasePath + '/logout', async (req, res) => {
if (req.session.loggedin) {
await redisHelper.logUserOutAllSessions(req.session.userId)
}
var html = await errorpages.renderErrorPage(200, "LOGOUT_SUCCESS")
var html = await errorpages.renderErrorPage(200, "LOGOUT_SUCCESS", req)
res.send(html)
})

Expand Down
4 changes: 2 additions & 2 deletions lib/token-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ async function checkAuthHeader(req, res, route) {
}
var token = await getConfigFromToken(headerValue, route)
if (!token) {
log.info({ message: "User tried to use invalid token", context: { token: headerValue, route } })
log.infoWithContext(req, { message: "User tried to use invalid token", context: { token: headerValue, route } })
return null
}
var domainIsAllowed = pm.isMatch((route.from.url || route.from), token.valid_domains) || pm.isMatch((route.to.url || route.to), token.valid_domains)
if (domainIsAllowed) {
return token
} else {
log.info({ message: "User tried to use token for invalid domain", context: { user: token.userId, route } })
log.infoWithContext(req, { message: "User tried to use token for invalid domain", context: { user: token.userId, route } })
return null
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/tests/basic_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ Scenario('Testing Token Auth', async ({ I }) => {
Scenario('Testing Unauthorized Flow', async ({ I }) => {
I.amOnPage('http://test-unauthorized-login.localtest.me/');
I.login()
I.see("ERR_NOT_AUTHORIZED")
I.see("Unauthorized")
});

Scenario('Advanced matchers test', async ({ I }) => {
I.amOnPage('http://test-advanced-matchers.localtest.me/get');
I.login();
I.see("x-veriflow-user-id")
I.amOnPage('http://test-advanced-matchers.localtest.me/should404');
I.see("ERR_ROUTE_NOT_FOUND")
I.see("Page Not Found")
});

Scenario('Dynamic Upstreams Test', async ({ I }) => {
Expand Down
84 changes: 53 additions & 31 deletions util/caddyModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ async function saturateRoute(route, routeId) {
"Host": [
route.preserve_host_header ? "{http.request.host}" : "{http.reverse_proxy.upstream.host}"
],
"X-Veriflow-Request": [
"true"
"X-Veriflow-Request-Id": [
"{http.request.uuid}"
]
}
if (route.set_request_headers) {
Expand Down Expand Up @@ -233,6 +233,9 @@ async function saturateRoute(route, routeId) {
],
"X-Veriflow-Route-Id": [
routeId
],
"X-Veriflow-Request-Id": [
"{http.request.uuid}"
]
}
}
Expand Down Expand Up @@ -303,43 +306,60 @@ async function generateCaddyConfig() {
var config = getConfig()
var routes = await saturateAllRoutesFromConfig(config)

const E_LOOP_DETECTED_HTML = await errorpage.renderErrorPage(503, "ERR_LOOP_DETECTED")
const E_NOT_FOUND_HTML = await errorpage.renderErrorPage(404, "ERR_ROUTE_NOT_FOUND")

var circuitBreakerRoute = {
var requestIdRoute = {
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": E_LOOP_DETECTED_HTML,
"close": true,
"handler": "static_response",
"status_code": 503,
"headers": {
"Content-Type": [
"text/html"
]
}
}
"handler": "headers",
"response": {
"set": {
"X-Veriflow-Request-Id": [
"{http.request.uuid}"
]
}
]
}
],
"match": [
{
"header": {
"X-Veriflow-Request": [
"true"
]
}
}
]
}
routes.unshift(circuitBreakerRoute)
routes.unshift(requestIdRoute)

if (config.enable_circuit_breaker_route !== false) {
log.debug("Enabling circuit breaker route")
const E_LOOP_DETECTED_HTML = await errorpage.renderErrorPage(503, "ERR_LOOP_DETECTED")
var circuitBreakerRoute = {
"handle": [
{
"handler": "subroute",
"routes": [
{
"handle": [
{
"body": E_LOOP_DETECTED_HTML,
"close": true,
"handler": "static_response",
"status_code": 503,
"headers": {
"Content-Type": [
"text/html"
]
}
}
]
}
]
}
],
"match": [
{
"header": {
"X-Veriflow-Request-Id": [
]
}
}
]
}
routes.unshift(circuitBreakerRoute)
}


var serviceUrl = new URL(config.service_url)
var serviceRoute = {
Expand Down Expand Up @@ -371,6 +391,8 @@ async function generateCaddyConfig() {
],
"terminal": true
}

const E_NOT_FOUND_HTML = await errorpage.renderErrorPage(404, "ERR_ROUTE_NOT_FOUND")
var defaultRoute = {
"handle": [
{
Expand Down
2 changes: 1 addition & 1 deletion util/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ function getAuthListenPort() {
authListenPort++
} else {
foundPort = authListenPort
log.info({ message: `Found port ${foundPort} for the auth service to listen on`})
log.debug({ message: `Found port ${foundPort} for the auth service to listen on`})
}
}
return foundPort
Expand Down
7 changes: 4 additions & 3 deletions util/errorpage.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async function renderErrorPage(status_code, error_code_override, req) {
var logo_image_src = config?.ui?.logo_image_src || false
var page_title = config?.ui?.page_title || "Veriflow"

var description = "An internal server error occoured. Please try again."
var description = "An internal server error occurred. Please try again."
var error_code = "ERR_INTERNAL_ERROR"
if (status_code == 404) {
header = "Page Not Found"
Expand All @@ -31,7 +31,7 @@ async function renderErrorPage(status_code, error_code_override, req) {
}
if (status_code == 403) {
header = "Unauthorized"
description = "You are not authorized to acces the requested resource."
description = "You are not authorized to access the requested resource."
error_code = "ERR_NOT_AUTHORIZED"
}
if (status_code == 503) {
Expand Down Expand Up @@ -62,7 +62,8 @@ async function renderErrorPage(status_code, error_code_override, req) {
background_image_url: background_image_url,
additional_css: additional_css,
show_error_code: show_error_code,
logo_image_src: logo_image_src
logo_image_src: logo_image_src,
request_id: req?.headers["X-Veriflow-Request-Id"] || "{http.request.uuid}"
});
return html
}
Expand Down
11 changes: 10 additions & 1 deletion util/logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import pino from 'pino';

const log = pino();

log.level = 10
log.level = 30

log.access = (action, route, user, req) => {
let reqId = req.headers["X-Veriflow-Request-Id"]
let method = req.get("X-Forwarded-Method")
let path = req.get("X-Forwarded-Path")
let query = req.get("X-Forwarded-Query")
Expand All @@ -18,6 +19,7 @@ log.access = (action, route, user, req) => {
userInfo.mail = user.mail
}
log.info({
reqId,
action: action,
request: {
method,
Expand All @@ -37,4 +39,11 @@ log.access = (action, route, user, req) => {

}

log.infoWithContext = (req, message) => {
log.info({
reqId: req.headers["X-Veriflow-Request-Id"],
...message
})
}

export default log;
23 changes: 12 additions & 11 deletions views/error_fullpage.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,29 @@
<div class="container">
<div class="header">
<div class="logo"><%- include('includes/logo'); %></div>
<% if(user){ %>

<div class="menu">
<div class="hamburger-menu" onclick="toggleMenu()">☰</div>
<div class="menu-content" id="menu">
<a>Logged in as <%= user %></a>
<a href="<%= logout_url %>">Logout</a>
<% if(show_error_code){ %>
<a>Status Code: <%= error_code %></a>
<% } %>
<a>Request ID: <%= request_id %></a>

<% if(user){ %>
<a>Logged in as <%= user %></a>
<hr>
<a href="<%= logout_url %>">Logout of Veriflow</a>
<% }%>
</div>
</div>
<% }%>

</div>
<div class="content">
<h1><%= header %></h1>
<hr>
<p class="error-message"><%= description %></p>
</div>
<div class="footer">
<% if(show_error_code){ %>
<div><%= error_code %></div>
<% } %>

</div>

</div>
</body>
<footer>
Expand Down
1 change: 1 addition & 0 deletions views/includes/style.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
}
.menu-content {
text-align: left;
display: none;
right: 0;
position: absolute;
Expand Down

0 comments on commit 91b5444

Please sign in to comment.