From efa92a73bd4a2d37d4f16658b2e5baa6be6366af Mon Sep 17 00:00:00 2001 From: Pim van Pelt Date: Sun, 24 Aug 2025 14:05:26 +0200 Subject: [PATCH] Add gen-nginx for the read path --- tesseract/genconf/main.go | 4 ++ tesseract/genconf/nginx.go | 135 +++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 tesseract/genconf/nginx.go diff --git a/tesseract/genconf/main.go b/tesseract/genconf/main.go index 3ed64be..e30c39d 100644 --- a/tesseract/genconf/main.go +++ b/tesseract/genconf/main.go @@ -56,6 +56,8 @@ func main() { generateEnv(*configFile) case "gen-key": generateKeys(*configFile) + case "gen-nginx": + generateNginx(*configFile) case "gen-roots": generateRoots(args[1:]) default: @@ -103,6 +105,8 @@ func showHelp() { fmt.Printf(" Combines global roots and log-specific extraroots into roots.pem.\n\n") fmt.Printf(" gen-key Generate prime256v1 private keys for each log (only if they don't exist).\n") fmt.Printf(" Creates EC private key files at the path specified in log.secret.\n\n") + fmt.Printf(" gen-nginx Generate nginx configuration files for each log's monitoring endpoint.\n") + fmt.Printf(" Creates nginx-.conf files in each log's localdirectory.\n\n") fmt.Printf(" gen-roots Download root certificates from a Certificate Transparency log.\n") fmt.Printf(" Options: --source (default: https://rennet2027h2.log.ct.ipng.ch/)\n") fmt.Printf(" --output (default: roots.pem)\n\n") diff --git a/tesseract/genconf/nginx.go b/tesseract/genconf/nginx.go new file mode 100644 index 0000000..b5fc892 --- /dev/null +++ b/tesseract/genconf/nginx.go @@ -0,0 +1,135 @@ +package main + +import ( + "fmt" + "net/url" + "os" + "path/filepath" + "strings" + "text/template" +) + +const nginxTemplate = `server { + listen 8080; + listen [::]:8080; + + # Replace with your actual domain(s) + server_name {{.MonitoringHost}}; + + # Document root for static files + root {{.LocalDirectory}}; + + location = / { + try_files /index.html =404; + + add_header Content-Type "text/html; charset=utf-8" always; + add_header Access-Control-Allow-Origin "*" always; + } + + # Checkpoint endpoint - no caching + location = /checkpoint { + try_files /checkpoint =404; + + add_header Content-Type "text/plain; charset=utf-8" always; + add_header Access-Control-Allow-Origin "*" always; + add_header Cache-Control "no-store" always; + } + + # Log info endpoint + location = /log.v3.json { + try_files /log.v3.json =404; + + add_header Content-Type "application/json" always; + add_header Access-Control-Allow-Origin "*" always; + add_header Cache-Control "public, max-age=3600, immutable" always; + } + + # Issuer certificate endpoint - long cache + location ~ ^/issuer/(.+)$ { + try_files /issuer/$1 =404; + + add_header Content-Type "application/pkix-cert" always; + add_header Access-Control-Allow-Origin "*" always; + add_header Cache-Control "public, max-age=604800, immutable" always; + } + + # Tile data endpoint - long cache, may have gzip + location ~ ^/tile/(.+)$ { + try_files /tile/$1 =404; + + add_header Content-Type "application/octet-stream" always; + add_header Access-Control-Allow-Origin "*" always; + add_header Cache-Control "public, max-age=604800, immutable" always; + + # Gzip encoding for .gz files + location ~ \.gz$ { + add_header Content-Encoding "gzip" always; + } + } +} +` + +type NginxTemplateData struct { + MonitoringHost string + LocalDirectory string +} + +func generateNginx(yamlFile string) { + config := loadConfig(yamlFile) + + for _, log := range config.Logs { + // Extract hostname from monitoring prefix + hostname, err := extractHostname(log.MonitoringPrefix) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to extract hostname from %s: %v\n", log.MonitoringPrefix, err) + continue + } + + // Create template data + data := NginxTemplateData{ + MonitoringHost: hostname, + LocalDirectory: log.LocalDirectory, + } + + // Parse and execute template + tmpl, err := template.New("nginx").Parse(nginxTemplate) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to parse nginx template: %v\n", err) + continue + } + + // Generate output filename using only hostname part + outputFilename := fmt.Sprintf("%s.conf", hostname) + outputPath := filepath.Join(log.LocalDirectory, outputFilename) + + // Create output file + file, err := os.Create(outputPath) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create nginx config file %s: %v\n", outputPath, err) + continue + } + defer file.Close() + + // Execute template + err = tmpl.Execute(file, data) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to execute nginx template for %s: %v\n", outputPath, err) + continue + } + + fmt.Printf("Generated nginx config: %s\n", outputPath) + } +} + +func extractHostname(urlStr string) (string, error) { + if !strings.HasPrefix(urlStr, "http://") && !strings.HasPrefix(urlStr, "https://") { + urlStr = "https://" + urlStr + } + + parsedURL, err := url.Parse(urlStr) + if err != nil { + return "", err + } + + return parsedURL.Hostname(), nil +}