-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat(gateway): add default landing page when RootRedirect is not set #11091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
lidel
wants to merge
1
commit into
master
Choose a base branch
from
feat/gateway-landing-page
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+400
−10
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
| <meta name="robots" content="noindex"> | ||
| <meta name="description" content="Default landing page for Kubo, an IPFS node implementation."> | ||
| <meta property="og:title" content="Welcome to Kubo IPFS Node!"> | ||
| <meta property="og:description" content="Default landing page for Kubo, an IPFS node implementation."> | ||
| <meta property="og:type" content="website"> | ||
| <title>Welcome to Kubo IPFS Node!</title> | ||
| <style> | ||
| html { color-scheme: light dark; } | ||
| body { | ||
| max-width: 40em; | ||
| margin: 2em auto; | ||
| padding: 0 1em; | ||
| font-family: system-ui, -apple-system, sans-serif; | ||
| line-height: 1.6; | ||
| } | ||
| h1 { | ||
| border-bottom: 2px solid; | ||
| padding-bottom: 0.3em; | ||
| } | ||
| .note { | ||
| background: rgba(128, 128, 128, 0.1); | ||
| border-left: 4px solid rgba(128, 128, 128, 0.5); | ||
| padding: 0.5em 1em; | ||
| margin: 1.5em 0; | ||
| } | ||
| a { color: #0969da; } | ||
| @media (prefers-color-scheme: dark) { | ||
| a { color: #58a6ff; } | ||
| } | ||
| ul { padding-left: 1.5em; } | ||
| li { margin: 0.3em 0; } | ||
| code { | ||
| background: rgba(128, 128, 128, 0.15); | ||
| padding: 0.1em 0.3em; | ||
| border-radius: 3px; | ||
| font-size: 0.9em; | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <h1>Welcome to Kubo!</h1> | ||
|
|
||
| <p>If you see this page, the <a href="https://github.com/ipfs/kubo" target="_blank" rel="noopener noreferrer">Kubo IPFS node</a> has been successfully installed and is working.</p> | ||
|
|
||
| <p>For configuration options, please refer to the <a href="https://github.com/ipfs/kubo/blob/master/docs/config.md" target="_blank" rel="noopener noreferrer">documentation</a>.</p> | ||
|
|
||
| <div class="note"> | ||
| <strong>Note to operators:</strong> This is the default landing page. | ||
| Set <code>Gateway.RootRedirect</code> in the <a href="https://github.com/ipfs/kubo/blob/master/docs/config.md#gatewayrootredirect" target="_blank" rel="noopener noreferrer">configuration</a> to redirect to your own content. | ||
| </div> | ||
|
|
||
| <h2>Resources</h2> | ||
| <ul> | ||
| <li><a href="https://github.com/ipfs/kubo" target="_blank" rel="noopener noreferrer">Kubo on GitHub</a></li> | ||
| <li><a href="https://github.com/ipfs/kubo/blob/master/docs/config.md" target="_blank" rel="noopener noreferrer">Kubo Configuration Reference</a></li> | ||
| <li><a href="https://docs.ipfs.tech" target="_blank" rel="noopener noreferrer">IPFS Documentation</a></li> | ||
| <li><a href="https://docs.ipfs.tech/concepts/glossary/#gateway" target="_blank" rel="noopener noreferrer">IPFS Gateway Documentation</a></li> | ||
| <li><a href="https://specs.ipfs.tech/http-gateways/" target="_blank" rel="noopener noreferrer">IPFS HTTP Gateway Specifications</a></li> | ||
| </ul> | ||
|
|
||
| <div id="abuse-section"> | ||
| <h2>Abuse Reports</h2> | ||
| <p> | ||
| This gateway is operated by a third party. To report abuse, contact the operator or owner of | ||
| <span id="gateway-host"></span>. | ||
| </p> | ||
| </div> | ||
| <script> | ||
| (function() { | ||
| var hostname = window.location.hostname; | ||
| var section = document.getElementById('abuse-section'); | ||
| if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') { | ||
| section.style.display = 'none'; | ||
| return; | ||
| } | ||
| var host = document.getElementById('gateway-host'); | ||
| var link = document.createElement('a'); | ||
| link.href = 'https://whois.domaintools.com/' + hostname; | ||
| link.target = '_blank'; | ||
| link.rel = 'noopener noreferrer'; | ||
| link.textContent = hostname; | ||
| host.appendChild(link); | ||
| })(); | ||
| </script> | ||
| <noscript> | ||
| <p>To report abuse, look up the domain owner using a WHOIS service.</p> | ||
| </noscript> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| package corehttp | ||
|
|
||
| import ( | ||
| "bufio" | ||
| _ "embed" | ||
| "net" | ||
| "net/http" | ||
|
|
||
| core "github.com/ipfs/kubo/core" | ||
| ) | ||
|
|
||
| //go:embed assets/landing.html | ||
| var landingPageHTML []byte | ||
|
|
||
| // LandingPageOption returns a ServeOption that serves a default landing page | ||
| // for the gateway root ("/") when Gateway.RootRedirect is not configured. | ||
| // This helps third-party gateway operators by clearly indicating that the | ||
| // gateway software is working but needs configuration, and provides guidance | ||
| // for abuse reporting. | ||
| func LandingPageOption() ServeOption { | ||
| return func(n *core.IpfsNode, _ net.Listener, mux *http.ServeMux) (*http.ServeMux, error) { | ||
| cfg, err := n.Repo.Config() | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| headers := cfg.Gateway.HTTPHeaders | ||
| mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| if r.URL.Path != "/" { | ||
| http.NotFound(w, r) | ||
| return | ||
| } | ||
| serveLandingPage(w, headers) | ||
| })) | ||
| return mux, nil | ||
| } | ||
| } | ||
|
|
||
| // serveLandingPage writes the landing page HTML with appropriate headers. | ||
| func serveLandingPage(w http.ResponseWriter, headers map[string][]string) { | ||
| for k, v := range headers { | ||
| w.Header()[http.CanonicalHeaderKey(k)] = v | ||
| } | ||
| w.Header().Set("Content-Type", "text/html; charset=utf-8") | ||
| _, _ = w.Write(landingPageHTML) | ||
| } | ||
|
|
||
| // withLandingPageFallback wraps an http.Handler to intercept 404 responses for | ||
| // the root path "/" on loopback addresses and serve a landing page instead. | ||
| // | ||
| // This is needed because boxo's HostnameHandler returns 404 for bare gateway | ||
| // hostnames (like "localhost") that don't have content configured. Without this | ||
| // fallback, users would see a confusing 404 instead of a helpful landing page. | ||
| // | ||
| // The middleware only intercepts requests to loopback addresses (127.0.0.1, | ||
| // localhost, ::1) because these cannot have DNSLink configured, so any 404 on | ||
| // "/" is guaranteed to be "no content configured" rather than "content not | ||
| // found". This avoids false positives where a real 404 (e.g., from DNSLink | ||
| // pointing to missing content) would incorrectly show the landing page. | ||
| func withLandingPageFallback(next http.Handler, headers map[string][]string) http.Handler { | ||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| // Only intercept requests to exactly "/" | ||
| if r.URL.Path != "/" { | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Only intercept for loopback addresses. These cannot have DNSLink | ||
| // configured, so any 404 is genuinely "no content configured". | ||
| // For other hosts, pass through to avoid intercepting real 404s | ||
| // from DNSLink or other content resolution. | ||
| host := r.Host | ||
| if h, _, err := net.SplitHostPort(r.Host); err == nil { | ||
| host = h | ||
| } | ||
| switch host { | ||
| case "localhost", "127.0.0.1", "::1", "[::1]": | ||
| // Continue to intercept | ||
| default: | ||
| next.ServeHTTP(w, r) | ||
| return | ||
| } | ||
|
|
||
| // Wrap ResponseWriter to intercept 404 responses | ||
| lw := &landingResponseWriter{ResponseWriter: w} | ||
| next.ServeHTTP(lw, r) | ||
|
|
||
| // If 404 was suppressed, serve the landing page | ||
| if lw.suppressed404 { | ||
| serveLandingPage(w, headers) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| // landingResponseWriter wraps http.ResponseWriter to intercept 404 responses. | ||
| // It suppresses the 404 status and body so we can serve a landing page instead. | ||
| type landingResponseWriter struct { | ||
| http.ResponseWriter | ||
| wroteHeader bool | ||
| suppressed404 bool | ||
| } | ||
|
|
||
| func (w *landingResponseWriter) WriteHeader(code int) { | ||
| if w.wroteHeader { | ||
| return | ||
| } | ||
| w.wroteHeader = true | ||
| if code == http.StatusNotFound { | ||
| w.suppressed404 = true | ||
| return // Suppress 404 - we'll serve landing page instead | ||
| } | ||
| w.ResponseWriter.WriteHeader(code) | ||
| } | ||
|
|
||
| func (w *landingResponseWriter) Write(b []byte) (int, error) { | ||
| if !w.wroteHeader { | ||
| w.WriteHeader(http.StatusOK) | ||
| } | ||
| if w.suppressed404 { | ||
| return len(b), nil // Discard 404 body | ||
| } | ||
| return w.ResponseWriter.Write(b) | ||
| } | ||
|
|
||
| // Flush implements http.Flusher for streaming responses. | ||
| func (w *landingResponseWriter) Flush() { | ||
| if f, ok := w.ResponseWriter.(http.Flusher); ok { | ||
| f.Flush() | ||
| } | ||
| } | ||
|
|
||
| // Hijack implements http.Hijacker for websocket support. | ||
| func (w *landingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { | ||
| if h, ok := w.ResponseWriter.(http.Hijacker); ok { | ||
| return h.Hijack() | ||
| } | ||
| return nil, nil, http.ErrNotSupported | ||
| } | ||
|
|
||
| // Unwrap returns the underlying ResponseWriter for http.ResponseController. | ||
| func (w *landingResponseWriter) Unwrap() http.ResponseWriter { | ||
| return w.ResponseWriter | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check warning
Code scanning / CodeQL
Information exposure through a stack trace Medium
Copilot Autofix
AI 9 days ago
To fix the issue, we must ensure that stack trace information (the contents of
bufinprofile/goroutines.go) is not sent to the user via an HTTP response. When writing such diagnostics, the correct action is to log the stack trace on the server (for administrator/developer analysis) and, for the client, to send a generic error message instead.Specifically:
profile/goroutines.go, ifWriteAllGoroutineStacksis used as a handler to write goroutine stacks to a user-facing HTTP response, it should instead:logpackage (or an equivalent).core/corehttp/landing.gowhere stack trace information flows into an HTTP response (specifically via theWritemethod), this must be intercepted and only a generic error or status be sent.As the data flow is traced from
profile/goroutines.go:WriteAllGoroutineStacks, the fix is to log the stack trace and only send a generic message to the writer (ideally, the HTTP response writer).Required changes:
profile/goroutines.go, updateWriteAllGoroutineStacksso that instead of writingbufto theio.Writer, it logs the stack trace server-side and writes a generic message to the writer."log"if not present.