fix: subdomain-based certs only, unknown subdomains return error

This commit is contained in:
Claus Lohmar 2026-07-06 15:52:51 +01:00
parent 672653f731
commit 897275c659

77
main.go
View file

@ -72,7 +72,7 @@ func extractSubdomain(host, domain string) string {
host = host[:idx]
}
// Bare domain — no subdomain
// Bare domain — no subdomain (e.g. "nextwks.eu")
domainWithDot := "." + domain
if host == domain {
return ""
@ -86,22 +86,6 @@ func extractSubdomain(host, domain string) string {
// --- Handlers ---
func boilerplateHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>NextWorkspace</title>
</head>
<body>
<h1>NextWorkspace</h1>
<p>The Self-Hosted Workspace for Startups</p>
</body>
</html>`)
}
func coreAppHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
@ -118,6 +102,22 @@ func coreAppHandler(w http.ResponseWriter, r *http.Request) {
</html>`)
}
func unknownSubdomainHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Not Found</title>
</head>
<body>
<h1>Not Found</h1>
<p>No application registered for this subdomain.</p>
</body>
</html>`)
}
// --- Main ---
func main() {
@ -138,19 +138,19 @@ func main() {
case "core":
handlers[subdomain] = coreAppHandler
default:
log.Printf("Warning: unknown app %q for subdomain %q, using boilerplate", appName, subdomain)
handlers[subdomain] = boilerplateHandler
log.Printf("Warning: unknown app %q for subdomain %q", appName, subdomain)
handlers[subdomain] = unknownSubdomainHandler
}
}
// Main router
// Main router (for HTTPS or non-TLS mode)
router := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
subdomain := extractSubdomain(r.Host, cfg.Domain)
if handler, ok := handlers[subdomain]; ok {
handler(w, r)
return
}
boilerplateHandler(w, r)
unknownSubdomainHandler(w, r)
})
httpAddr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.Port)
@ -164,12 +164,12 @@ func main() {
Path: filepath.Join(configDir, "certs"),
}
// Collect domains (from proxies.yaml + bare domain)
// Collect domains from proxies.yaml only (no bare domain, no wildcard)
// Each subdomain gets its own certificate independently.
var domains []string
for subdomain := range proxies.Apps {
domains = append(domains, subdomain+"."+cfg.Domain)
}
domains = append(domains, cfg.Domain)
// Obtain and manage certificates
magic := certmagic.NewDefault()
@ -177,27 +177,38 @@ func main() {
log.Fatalf("Failed to manage certificates: %v", err)
}
// HTTP redirect handler (on :80)
redirectHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Extract ACME issuer for http-01 challenge handling
var acmeIssuer *certmagic.ACMEIssuer
if len(magic.Issuers) > 0 {
acmeIssuer, _ = magic.Issuers[0].(*certmagic.ACMEIssuer)
}
// HTTP handler on :80
// - Known subdomains → redirect to HTTPS
// - Unknown subdomains → error directly on HTTP (no redirect to broken HTTPS)
// - ACME challenge paths → handled by certmagic before reaching us
var httpHandler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
subdomain := extractSubdomain(r.Host, cfg.Domain)
if _, ok := handlers[subdomain]; ok {
target := "https://" + r.Host + r.URL.RequestURI()
http.Redirect(w, r, target, http.StatusMovedPermanently)
return
}
unknownSubdomainHandler(w, r)
})
// Wrap with ACME challenge handler for Let's Encrypt http-01 validation
var httpHandler http.Handler = redirectHandler
if len(magic.Issuers) > 0 {
if ai, ok := magic.Issuers[0].(*certmagic.ACMEIssuer); ok {
httpHandler = ai.HTTPChallengeHandler(redirectHandler)
if acmeIssuer != nil {
httpHandler = acmeIssuer.HTTPChallengeHandler(httpHandler)
log.Println("ACME challenge handler enabled on :80")
}
}
httpServer := &http.Server{
Addr: httpAddr,
Handler: httpHandler,
}
// HTTPS server with TLS
// HTTPS server on :443 with TLS
tlsConfig := magic.TLSConfig()
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "h2") // HTTP/2 support
@ -207,9 +218,9 @@ func main() {
TLSConfig: tlsConfig,
}
// Start HTTP redirect server
// Start HTTP server
go func() {
log.Printf("HTTP redirect listening on %s", httpAddr)
log.Printf("HTTP listening on %s", httpAddr)
if err := httpServer.ListenAndServe(); err != nil {
log.Fatalf("HTTP server error: %v", err)
}