feat: templatized proxy error page
Cadoles/bouncer/pipeline/head This commit looks good
Details
Cadoles/bouncer/pipeline/head This commit looks good
Details
This commit is contained in:
parent
8a751afc97
commit
49f2ccbc7a
|
@ -63,6 +63,9 @@ nfpms:
|
||||||
- src: layers
|
- src: layers
|
||||||
dst: /etc/bouncer/layers
|
dst: /etc/bouncer/layers
|
||||||
type: config
|
type: config
|
||||||
|
- src: templates
|
||||||
|
dst: /etc/bouncer/templates
|
||||||
|
type: config
|
||||||
- dst: /etc/bouncer/bootstrap.d
|
- dst: /etc/bouncer/bootstrap.d
|
||||||
type: dir
|
type: dir
|
||||||
file_info:
|
file_info:
|
||||||
|
|
|
@ -22,6 +22,7 @@ RUN make GORELEASER_ARGS='build --rm-dist --single-target --snapshot' goreleaser
|
||||||
|
|
||||||
# Patch config
|
# Patch config
|
||||||
RUN /src/dist/bouncer_linux_amd64_v1/bouncer -c '' config dump > /src/dist/bouncer_linux_amd64_v1/config.yml \
|
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.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 '.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 \
|
&& yq -i '.admin.auth.privateKey = "/etc/bouncer/admin-key.json"' /src/dist/bouncer_linux_amd64_v1/config.yml \
|
||||||
|
|
|
@ -7,22 +7,26 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProxyServerConfig struct {
|
type ProxyServerConfig struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
Debug InterpolatedBool `yaml:"debug"`
|
||||||
Metrics MetricsConfig `yaml:"metrics"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
Transport TransportConfig `yaml:"transport"`
|
Metrics MetricsConfig `yaml:"metrics"`
|
||||||
Dial DialConfig `yaml:"dial"`
|
Transport TransportConfig `yaml:"transport"`
|
||||||
Sentry SentryConfig `yaml:"sentry"`
|
Dial DialConfig `yaml:"dial"`
|
||||||
Cache CacheConfig `yaml:"cache"`
|
Sentry SentryConfig `yaml:"sentry"`
|
||||||
|
Cache CacheConfig `yaml:"cache"`
|
||||||
|
Templates TemplatesConfig `yaml:"templates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultProxyServerConfig() ProxyServerConfig {
|
func NewDefaultProxyServerConfig() ProxyServerConfig {
|
||||||
return ProxyServerConfig{
|
return ProxyServerConfig{
|
||||||
|
Debug: false,
|
||||||
HTTP: NewHTTPConfig("0.0.0.0", 8080),
|
HTTP: NewHTTPConfig("0.0.0.0", 8080),
|
||||||
Metrics: NewDefaultMetricsConfig(),
|
Metrics: NewDefaultMetricsConfig(),
|
||||||
Transport: NewDefaultTransportConfig(),
|
Transport: NewDefaultTransportConfig(),
|
||||||
Dial: NewDefaultDialConfig(),
|
Dial: NewDefaultDialConfig(),
|
||||||
Sentry: NewDefaultSentryConfig(),
|
Sentry: NewDefaultSentryConfig(),
|
||||||
Cache: NewDefaultCacheConfig(),
|
Cache: NewDefaultCacheConfig(),
|
||||||
|
Templates: NewDefaultTemplatesConfig(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,3 +119,13 @@ func NewDefaultCacheConfig() CacheConfig {
|
||||||
TTL: *NewInterpolatedDuration(time.Second * 30),
|
TTL: *NewInterpolatedDuration(time.Second * 30),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TemplatesConfig struct {
|
||||||
|
Dir InterpolatedString `yaml:"dir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultTemplatesConfig() TemplatesConfig {
|
||||||
|
return TemplatesConfig{
|
||||||
|
Dir: "./templates",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,11 +3,14 @@ package proxy
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"forge.cadoles.com/Cadoles/go-proxy"
|
"forge.cadoles.com/Cadoles/go-proxy"
|
||||||
|
@ -17,6 +20,8 @@ import (
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
"forge.cadoles.com/cadoles/bouncer/internal/config"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
"forge.cadoles.com/cadoles/bouncer/internal/proxy/director"
|
||||||
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
"forge.cadoles.com/cadoles/bouncer/internal/store"
|
||||||
|
|
||||||
|
"github.com/Masterminds/sprig/v3"
|
||||||
"github.com/getsentry/sentry-go"
|
"github.com/getsentry/sentry-go"
|
||||||
sentryhttp "github.com/getsentry/sentry-go/http"
|
sentryhttp "github.com/getsentry/sentry-go/http"
|
||||||
"github.com/go-chi/chi/v5"
|
"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))
|
logger.Error(r.Context(), "proxy error", logger.E(err))
|
||||||
sentry.CaptureException(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 {
|
func NewServer(funcs ...OptionFunc) *Server {
|
||||||
|
|
|
@ -70,6 +70,12 @@ admin:
|
||||||
|
|
||||||
# Configuration du service "proxy"
|
# Configuration du service "proxy"
|
||||||
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:
|
http:
|
||||||
# Hôte d'écoute du service,
|
# Hôte d'écoute du service,
|
||||||
# 0.0.0.0 pour écouter sur toutes les interfaces
|
# 0.0.0.0 pour écouter sur toutes les interfaces
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
{{ define "default" }}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
{{ $title := print .StatusCode " - " .Status }}
|
||||||
|
<title>{{ $title }}</title>
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background-color: #ffe4e4;
|
||||||
|
}
|
||||||
|
|
||||||
|
#container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#card {
|
||||||
|
padding: 1.5em 1em;
|
||||||
|
border: 1px solid #d90202;
|
||||||
|
background-color: #feb0b0;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 2px 2px #cccccc1c;
|
||||||
|
color: #810000 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 0.7em;
|
||||||
|
margin-top: 2em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="container">
|
||||||
|
<div id="card">
|
||||||
|
<h2 class="title">{{ $title }}</h2>
|
||||||
|
{{ if .Debug }}
|
||||||
|
<pre>{{ .Err }}</pre>
|
||||||
|
{{ end }}
|
||||||
|
<p class="footer">
|
||||||
|
Propulsé par
|
||||||
|
<a href="https://forge.cadoles.com/Cadoles/bouncer">Bouncer</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
{{ end }}
|
Loading…
Reference in New Issue