fix: subdomain-based certs only, unknown subdomains return error
This commit is contained in:
parent
672653f731
commit
897275c659
1 changed files with 47 additions and 36 deletions
77
main.go
77
main.go
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue