Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fdf9b22
docs(adr): Add ADR-009 for UI base-path auto-detection
yurishkuro May 12, 2026
a76b750
feat(query): Remove <base href> injection from static handler
yurishkuro May 12, 2026
88d1b3e
docs(adr): Add test plan to ADR-009
yurishkuro May 12, 2026
cd97bf3
ci(e2e): Add UI reverse-proxy integration tests for ADR-009 use cases
yurishkuro May 12, 2026
c4131aa
test(e2e): Check all static assets (favicon, CSS, JS) in reverse-prox…
yurishkuro May 12, 2026
a64d409
refactor(e2e): Consolidate reverse-proxy UCs into a single docker-com…
yurishkuro May 12, 2026
7fe5aa3
fix(e2e): Use go env GOARCH instead of uname -m translation
yurishkuro May 12, 2026
11ca97f
fix(ci): Use go env GOARCH instead of hardcoded amd64 in ui-reverse-p…
yurishkuro May 12, 2026
08b4674
fix(e2e): Fix GitHub Actions log folding for docker compose logs
yurishkuro May 12, 2026
3a04a73
fix(query): Validate BasePath in RegisterRoutes; fix test assertions;…
yurishkuro May 12, 2026
8c7413b
fix(ci): Set GH_TOKEN for gh pr checkout in ui-reverse-proxy workflow
yurishkuro May 12, 2026
173445a
fix(ui): Skip nvm in CI where Node.js is set up by actions/setup-node
yurishkuro May 12, 2026
59ed87e
refactor(e2e): Consolidate reverse-proxy configs into examples/revers…
yurishkuro May 13, 2026
1cc5dbc
fix(e2e): Add trailing-slash redirects to httpd configs for UC-1 and …
yurishkuro May 13, 2026
159b8e8
docs(e2e): Rewrite reverse-proxy README to document all three use cases
yurishkuro May 13, 2026
4eeea36
upgrade UI
yurishkuro May 13, 2026
e87e718
fix(e2e): Address CI review comments — pin httpd, remove UI PR fallba…
yurishkuro May 13, 2026
6766a29
refactor(ci): Simplify ui-reverse-proxy workflow — delegate build to …
yurishkuro May 13, 2026
25d4cca
fix(query): Validate BasePath in NewStaticAssetsHandler instead of pa…
yurishkuro May 13, 2026
5f0e89f
test(query): Remove dead expectedBaseHTML field from TestRegisterStat…
yurishkuro May 13, 2026
9f10d59
docs(e2e): Replace v1 --query.base-path with v2 extensions.jaeger_que…
yurishkuro May 13, 2026
bbb9648
docs(adr): Remove v1 flag reference from ADR-009
yurishkuro May 13, 2026
5a695bd
docs(adr): Mark ADR-009 as Implemented; fix stale references
yurishkuro May 13, 2026
c5432b6
docs(adr): Rewrite ADR-009 Context section to past tense
yurishkuro May 13, 2026
9f8c277
fix
yurishkuro May 13, 2026
1a6a284
docs(adr): Add Future Improvements section to ADR-009
yurishkuro May 13, 2026
3e97965
docs(adr): Expand X-Forwarded-Prefix section with security and comple…
yurishkuro May 13, 2026
70a5ecd
fix(e2e): Add trailing-slash redirect for /alt in UC-2 httpd config
yurishkuro May 13, 2026
6dbd541
fix(query): Normalize and validate BasePath once in Config.Validate()
yurishkuro May 13, 2026
26b73f2
test(query): Assert backend does not inject path-specific <base href>…
yurishkuro May 13, 2026
a18c795
fix(e2e): Exclude /alt from ProxyPass so Redirect permanent can issue…
yurishkuro May 13, 2026
9fccd50
fix(e2e): Use mod_rewrite for exact /alt→/alt/ redirect in UC-2 httpd…
yurishkuro May 13, 2026
8d58cb6
fix(query): Reject base_path with duplicate slashes or path traversal
yurishkuro May 13, 2026
a414b84
fix(query): Improve error message and test assertion clarity
yurishkuro May 13, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/ci-e2e-all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,6 @@ jobs:

tailsampling:
uses: ./.github/workflows/ci-e2e-tailsampling.yml

ui-reverse-proxy:
uses: ./.github/workflows/ci-e2e-ui-reverse-proxy.yml
48 changes: 48 additions & 0 deletions .github/workflows/ci-e2e-ui-reverse-proxy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) 2026 The Jaeger Authors.
# SPDX-License-Identifier: Apache-2.0

# E2E integration tests for Jaeger UI served behind a reverse proxy (ADR-009).
# Validates three use cases in a single job:
#
# UC-1: proxy forwards the URL prefix unchanged to Jaeger
# (exercises the existing examples/reverse-proxy/ setup)
# UC-2: single Jaeger pod served under two different external prefixes
# simultaneously, with the proxy stripping the prefix on one path
# UC-3: proxy rewrites an external prefix to a different internal prefix
# (the case that motivated ADR-009 / issue #5157)

Comment thread
yurishkuro marked this conversation as resolved.
Comment thread
yurishkuro marked this conversation as resolved.
name: CIT UI Reverse Proxy

on:
workflow_call:

# See https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions
permissions:
contents: read
Comment thread
yurishkuro marked this conversation as resolved.

jobs:
ui-reverse-proxy:
runs-on: ubuntu-latest

steps:
- name: Harden Runner
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs

- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
submodules: true

- name: Fetch git tags
run: git fetch --prune --unshallow --tags

- uses: ./.github/actions/setup-go
with:
go-version: 1.26.x

- name: Setup Node.js version
uses: ./.github/actions/setup-node.js

- name: Run UI reverse-proxy integration tests (UC-1, UC-2, UC-3)
run: bash scripts/e2e/ui-reverse-proxy.sh
Comment thread
yurishkuro marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ var (
configJsPattern = regexp.MustCompile(`(?im)^\s*//\s*JAEGER_CONFIG_JS.*\n.*`)
versionPattern = regexp.MustCompile("JAEGER_VERSION *= *DEFAULT_VERSION;")
compabilityPattern = regexp.MustCompile("JAEGER_STORAGE_CAPABILITIES *= *DEFAULT_STORAGE_CAPABILITIES;")
basePathPattern = regexp.MustCompile(`<base href="/"`) // Note: tag is not closed
)

// RegisterStaticHandler adds handler for static assets to the router.
Expand Down Expand Up @@ -73,6 +72,10 @@ type loadedConfig struct {

// NewStaticAssetsHandler returns a StaticAssetsHandler
func NewStaticAssetsHandler(staticAssetsRoot string, options StaticAssetsHandlerOptions) (*StaticAssetsHandler, error) {
if bp := options.BasePath; bp != "" && bp != "/" && !strings.HasPrefix(bp, "/") {
return nil, fmt.Errorf("invalid base path %q: must start with '/'", bp)
Comment thread
yurishkuro marked this conversation as resolved.
Outdated
}

assetsFS := ui.GetStaticFiles(options.Logger)
if staticAssetsRoot != "" {
assetsFS = http.Dir(staticAssetsRoot)
Expand Down Expand Up @@ -119,16 +122,8 @@ func (sH *StaticAssetsHandler) loadAndEnrichIndexHTML(open func(string) (http.Fi
versionJSON, _ := json.Marshal(version.Get())
versionString := fmt.Sprintf("JAEGER_VERSION = %s;", string(versionJSON))
indexBytes = versionPattern.ReplaceAll(indexBytes, []byte(versionString))
// replace base path
if sH.options.BasePath == "" {
sH.options.BasePath = "/"
}
if sH.options.BasePath != "/" {
if !strings.HasPrefix(sH.options.BasePath, "/") || strings.HasSuffix(sH.options.BasePath, "/") {
return nil, fmt.Errorf("invalid base path '%s'. Must start but not end with a slash '/', e.g. '/jaeger/ui'", sH.options.BasePath)
}
indexBytes = basePathPattern.ReplaceAll(indexBytes, fmt.Appendf(nil, `<base href="%s/"`, sH.options.BasePath))
}
// The <base href> is no longer injected here. The UI detects its own mount-point
// prefix at page-load time via an inline script in index.html (see ADR-009).

Comment thread
yurishkuro marked this conversation as resolved.
return indexBytes, nil
Comment thread
yurishkuro marked this conversation as resolved.
Comment thread
yurishkuro marked this conversation as resolved.
}
Expand Down Expand Up @@ -209,8 +204,10 @@ func (sH *StaticAssetsHandler) loggingHandler(handler http.Handler) http.Handler
// RegisterRoutes registers routes for this handler on the given router.
func (sH *StaticAssetsHandler) RegisterRoutes(router *http.ServeMux) {
basePath := sH.options.BasePath
if basePath == "" {
if basePath == "" || basePath == "/" {
basePath = "/"
} else {
basePath = strings.TrimRight(basePath, "/")
}

fileServer := http.FileServer(sH.assetsFS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,13 @@ func TestRegisterStaticHandler(t *testing.T) {
archiveStorage bool // archive storage enabled?
metricsStorage bool // metrics storage enabled?
logAccess bool
expectedBaseHTML string // substring to match in the home page
UIConfigPath string // path to UI config
expectedUIConfig string // expected UI config
expectedStorageCapabilities string // expected storage capabilities
}{
{
basePath: "",
baseURL: "/",
expectedBaseHTML: `<base href="/"`,
archiveStorage: false,
logAccess: true,
UIConfigPath: "",
Expand All @@ -83,15 +81,13 @@ func TestRegisterStaticHandler(t *testing.T) {
basePath: "/",
baseURL: "/",
archiveStorage: false,
expectedBaseHTML: `<base href="/"`,
UIConfigPath: "fixture/ui-config.json",
expectedUIConfig: `JAEGER_CONFIG = {"x":"y"};`,
expectedStorageCapabilities: `JAEGER_STORAGE_CAPABILITIES = {"archiveStorage":false,"metricsStorage":false};`,
},
{
basePath: "/jaeger",
baseURL: "/jaeger/",
expectedBaseHTML: `<base href="/jaeger/"`,
subroute: true,
archiveStorage: true,
UIConfigPath: "fixture/ui-config.js",
Expand All @@ -101,7 +97,6 @@ func TestRegisterStaticHandler(t *testing.T) {
{
basePath: "/metrics",
baseURL: "/metrics/",
expectedBaseHTML: `<base href="/metrics/"`,
subroute: true,
Comment thread
yurishkuro marked this conversation as resolved.
Comment thread
yurishkuro marked this conversation as resolved.
metricsStorage: true,
UIConfigPath: "fixture/ui-config.js",
Expand Down Expand Up @@ -148,7 +143,6 @@ func TestRegisterStaticHandler(t *testing.T) {
assert.Contains(t, html, testCase.expectedUIConfig, "actual: %v", html)
assert.Contains(t, html, testCase.expectedStorageCapabilities, "actual: %v", html)
assert.Contains(t, html, `JAEGER_VERSION = {"gitCommit":"","gitVersion":"dev","buildDate":""};`, "actual: %v", html)
Comment thread
yurishkuro marked this conversation as resolved.
assert.Contains(t, html, testCase.expectedBaseHTML, "actual: %v", html)

asset := httpGet("static/asset.txt")
assert.Contains(t, asset, "some asset", "actual: %v", asset)
Expand All @@ -169,16 +163,40 @@ func TestNewStaticAssetsHandlerErrors(t *testing.T) {
Logger: zap.NewNop(),
})
require.Error(t, err)
}

Comment thread
yurishkuro marked this conversation as resolved.
for _, base := range []string{"x", "x/", "/x/"} {
_, err := NewStaticAssetsHandler("fixture", StaticAssetsHandlerOptions{
UIConfig: UIConfig{
ConfigFile: "fixture/ui-config.json",
},
BasePath: base,
Logger: zap.NewNop(),
func TestRegisterRoutesInvalidBasePath(t *testing.T) {
for _, basePath := range []string{"no-leading-slash", "no-leading-slash/"} {
t.Run(basePath, func(t *testing.T) {
_, err := NewStaticAssetsHandler("fixture", StaticAssetsHandlerOptions{
BasePath: basePath,
Logger: zap.NewNop(),
})
require.Error(t, err)
assert.Contains(t, err.Error(), "must start with '/'")
})
}
Comment thread
yurishkuro marked this conversation as resolved.
Outdated
Comment thread
yurishkuro marked this conversation as resolved.
Outdated
}

func TestRegisterRoutesTrailingSlashNormalization(t *testing.T) {
for _, basePath := range []string{"/jaeger/", "/jaeger//", "/jaeger///"} {
t.Run(basePath, func(t *testing.T) {
h, err := NewStaticAssetsHandler("fixture", StaticAssetsHandlerOptions{
BasePath: basePath,
Logger: zap.NewNop(),
})
require.NoError(t, err)
defer h.Close()
mux := http.NewServeMux()
h.RegisterRoutes(mux)
// Verify routes are registered under /jaeger/, not /jaeger//
srv := httptest.NewServer(mux)
defer srv.Close()
resp, err := http.Get(srv.URL + "/jaeger/static/")
require.NoError(t, err)
resp.Body.Close()
assert.NotEqual(t, http.StatusNotFound, resp.StatusCode, "static route should be reachable at /jaeger/static/")
Comment thread
yurishkuro marked this conversation as resolved.
Outdated
})
assert.ErrorContainsf(t, err, "invalid base path", "basePath=%s", base)
}
}

Comment thread
yurishkuro marked this conversation as resolved.
Outdated
Expand Down
Loading
Loading