diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b8c8a15..f7c557a 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -63,6 +63,9 @@ nfpms: - src: layers dst: /etc/bouncer/layers type: config + - src: templates + dst: /etc/bouncer/templates + type: config - dst: /etc/bouncer/bootstrap.d type: dir file_info: diff --git a/Dockerfile b/Dockerfile index deed5a6..2b0e13e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN make GORELEASER_ARGS='build --rm-dist --single-target --snapshot' goreleaser # Patch config RUN /src/dist/bouncer_linux_amd64_v1/bouncer -c '' config dump > /src/dist/bouncer_linux_amd64_v1/config.yml \ +&& yq -i '.proxy.templates.dir = "/usr/share/bouncer/templates"' /src/dist/bouncer_linux_amd64_v1/config.yml \ && yq -i '.layers.queue.templateDir = "/usr/share/bouncer/layers/queue/templates"' /src/dist/bouncer_linux_amd64_v1/config.yml \ && yq -i '.layers.authn.templateDir = "/usr/share/bouncer/layers/authn/templates"' /src/dist/bouncer_linux_amd64_v1/config.yml \ && yq -i '.admin.auth.privateKey = "/etc/bouncer/admin-key.json"' /src/dist/bouncer_linux_amd64_v1/config.yml \ diff --git a/internal/config/proxy_server.go b/internal/config/proxy_server.go index 781b635..104ba28 100644 --- a/internal/config/proxy_server.go +++ b/internal/config/proxy_server.go @@ -7,22 +7,26 @@ import ( ) type ProxyServerConfig struct { - HTTP HTTPConfig `yaml:"http"` - Metrics MetricsConfig `yaml:"metrics"` - Transport TransportConfig `yaml:"transport"` - Dial DialConfig `yaml:"dial"` - Sentry SentryConfig `yaml:"sentry"` - Cache CacheConfig `yaml:"cache"` + Debug InterpolatedBool `yaml:"debug"` + HTTP HTTPConfig `yaml:"http"` + Metrics MetricsConfig `yaml:"metrics"` + Transport TransportConfig `yaml:"transport"` + Dial DialConfig `yaml:"dial"` + Sentry SentryConfig `yaml:"sentry"` + Cache CacheConfig `yaml:"cache"` + Templates TemplatesConfig `yaml:"templates"` } func NewDefaultProxyServerConfig() ProxyServerConfig { return ProxyServerConfig{ + Debug: false, HTTP: NewHTTPConfig("0.0.0.0", 8080), Metrics: NewDefaultMetricsConfig(), Transport: NewDefaultTransportConfig(), Dial: NewDefaultDialConfig(), Sentry: NewDefaultSentryConfig(), Cache: NewDefaultCacheConfig(), + Templates: NewDefaultTemplatesConfig(), } } @@ -115,3 +119,13 @@ func NewDefaultCacheConfig() CacheConfig { TTL: *NewInterpolatedDuration(time.Second * 30), } } + +type TemplatesConfig struct { + Dir InterpolatedString `yaml:"dir"` +} + +func NewDefaultTemplatesConfig() TemplatesConfig { + return TemplatesConfig{ + Dir: "./templates", + } +} diff --git a/internal/proxy/server.go b/internal/proxy/server.go index 0668974..c55daa8 100644 --- a/internal/proxy/server.go +++ b/internal/proxy/server.go @@ -3,11 +3,14 @@ package proxy import ( "context" "fmt" + "html/template" "log" "net" "net/http" "net/http/httputil" "net/url" + "path/filepath" + "strconv" "time" "forge.cadoles.com/Cadoles/go-proxy" @@ -17,6 +20,8 @@ import ( "forge.cadoles.com/cadoles/bouncer/internal/config" "forge.cadoles.com/cadoles/bouncer/internal/proxy/director" "forge.cadoles.com/cadoles/bouncer/internal/store" + + "github.com/Masterminds/sprig/v3" "github.com/getsentry/sentry-go" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/go-chi/chi/v5" @@ -191,7 +196,63 @@ func (s *Server) errorHandler(w http.ResponseWriter, r *http.Request, err error) logger.Error(r.Context(), "proxy error", logger.E(err)) sentry.CaptureException(err) - http.Error(w, http.StatusText(http.StatusBadGateway), http.StatusBadGateway) + s.renderErrorPage(w, r, err, http.StatusBadGateway, http.StatusText(http.StatusBadGateway)) +} + +func (s *Server) renderErrorPage(w http.ResponseWriter, r *http.Request, err error, statusCode int, status string) { + templateData := struct { + StatusCode int + Status string + Err error + Debug bool + }{ + Debug: bool(s.serverConfig.Debug), + StatusCode: statusCode, + Status: status, + Err: err, + } + + w.WriteHeader(statusCode) + s.renderPage(w, r, "error", strconv.FormatInt(int64(statusCode), 10), templateData) +} + +func (s *Server) renderPage(w http.ResponseWriter, r *http.Request, page string, block string, templateData any) { + ctx := r.Context() + + templatesConf := s.serverConfig.Templates + + pattern := filepath.Join(string(templatesConf.Dir), page+".gohtml") + + logger.Info(ctx, "loading proxy templates", logger.F("pattern", pattern)) + + tmpl, err := template.New("").Funcs(sprig.FuncMap()).ParseGlob(pattern) + if err != nil { + logger.Error(ctx, "could not load proxy templates", logger.E(errors.WithStack(err))) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + + return + } + + w.Header().Add("Cache-Control", "no-cache") + + blockTmpl := tmpl.Lookup(block) + if blockTmpl == nil { + blockTmpl = tmpl.Lookup("default") + } + + if blockTmpl == nil { + logger.Error(ctx, "could not find template block nor default one", logger.F("block", block)) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + + return + } + + if err := blockTmpl.Execute(w, templateData); err != nil { + logger.Error(ctx, "could not render proxy page", logger.E(errors.WithStack(err))) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + + return + } } func NewServer(funcs ...OptionFunc) *Server { diff --git a/misc/packaging/common/config.yml b/misc/packaging/common/config.yml index 3dfd1c1..9ca6e0d 100644 --- a/misc/packaging/common/config.yml +++ b/misc/packaging/common/config.yml @@ -70,6 +70,12 @@ admin: # Configuration du service "proxy" proxy: + # Activer/désactiver le mode debug (affichage ou non des messages d'erreur) + debug: false + # Configuration des templates utilisés par le proxy + templates: + # Répertoire contenant les templates + dir: /etc/bouncer/templates http: # Hôte d'écoute du service, # 0.0.0.0 pour écouter sur toutes les interfaces diff --git a/templates/error.gohtml b/templates/error.gohtml new file mode 100644 index 0000000..9e4c9ae --- /dev/null +++ b/templates/error.gohtml @@ -0,0 +1,96 @@ +{{ define "default" }} + + + + + + {{ $title := print .StatusCode " - " .Status }} + {{ $title }} + + + +
+
+

{{ $title }}

+ {{ if .Debug }} +
{{ .Err }}
+ {{ end }} + +
+
+ + +{{ end }}