core/envoy: format envoy local replies (#5067)

This commit is contained in:
Caleb Doxsey 2024-04-18 09:22:15 -06:00 committed by GitHub
parent fab2181be4
commit 494dc4accc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 56 additions and 12 deletions

View file

@ -1,12 +1,16 @@
package envoyconfig
import (
"fmt"
envoy_config_accesslog_v3 "github.com/envoyproxy/go-control-plane/envoy/config/accesslog/v3"
envoy_config_core_v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
envoy_config_route_v3 "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
envoy_http_connection_manager "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/ui"
)
func (b *Builder) buildVirtualHost(
@ -33,7 +37,7 @@ func (b *Builder) buildVirtualHost(
// coming directly from envoy
func (b *Builder) buildLocalReplyConfig(
options *config.Options,
) *envoy_http_connection_manager.LocalReplyConfig {
) (*envoy_http_connection_manager.LocalReplyConfig, error) {
// add global headers for HSTS headers (#2110)
var headers []*envoy_config_core_v3.HeaderValueOption
// if we're the proxy or authenticate service, add our global headers
@ -41,6 +45,18 @@ func (b *Builder) buildLocalReplyConfig(
headers = toEnvoyHeaders(options.GetSetResponseHeaders())
}
data := map[string]any{
"status": "%RESPONSE_CODE%",
"statusText": "%RESPONSE_CODE_DETAILS%",
"requestId": "%STREAM_ID%",
}
httputil.AddBrandingOptionsToMap(data, options.BrandingOptions)
bs, err := ui.RenderPage("Error", "Error", data)
if err != nil {
return nil, fmt.Errorf("error rendering error page for local reply: %w", err)
}
return &envoy_http_connection_manager.LocalReplyConfig{
Mappers: []*envoy_http_connection_manager.ResponseMapper{{
Filter: &envoy_config_accesslog_v3.AccessLogFilter{
@ -48,7 +64,17 @@ func (b *Builder) buildLocalReplyConfig(
ResponseFlagFilter: &envoy_config_accesslog_v3.ResponseFlagFilter{},
},
},
BodyFormatOverride: &envoy_config_core_v3.SubstitutionFormatString{
ContentType: "text/html; charset=UTF-8",
Format: &envoy_config_core_v3.SubstitutionFormatString_TextFormatSource{
TextFormatSource: &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_InlineBytes{
InlineBytes: bs,
},
},
},
},
HeadersToAdd: headers,
}},
}
}, nil
}

View file

@ -284,6 +284,11 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
return nil, err
}
localReply, err := b.buildLocalReplyConfig(cfg.Options)
if err != nil {
return nil, err
}
mgr := &envoy_http_connection_manager.HttpConnectionManager{
AlwaysSetRequestIdInResponse: true,
CodecType: cfg.Options.GetCodecType().ToEnvoy(),
@ -304,7 +309,7 @@ func (b *Builder) buildMainHTTPConnectionManagerFilter(
UseRemoteAddress: &wrapperspb.BoolValue{Value: true},
SkipXffAppend: cfg.Options.SkipXffAppend,
XffNumTrustedHops: cfg.Options.XffNumTrustedHops,
LocalReplyConfig: b.buildLocalReplyConfig(cfg.Options),
LocalReplyConfig: localReply,
NormalizePath: wrapperspb.Bool(true),
}

View file

@ -52,9 +52,7 @@
},
"timeout": "10s"
},
"metadataContextNamespaces": [
"com.pomerium.client-certificate-info"
],
"metadataContextNamespaces": ["com.pomerium.client-certificate-info"],
"statusOnError": {
"code": "InternalServerError"
},
@ -108,6 +106,12 @@
"localReplyConfig": {
"mappers": [
{
"bodyFormatOverride": {
"contentType": "text/html; charset=UTF-8",
"textFormatSource": {
"inlineBytes": "PCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KICA8aGVhZD4KICAgIDxtZXRhIGNoYXJzZXQ9InV0Zi04IiAvPgogICAgPGxpbmsgaWQ9ImZhdmljb24iIHJlbD0ic2hvcnRjdXQgaWNvbiIgaHJlZj0iLy5wb21lcml1bS9mYXZpY29uLmljbz92PTIiIC8+CiAgICA8bGluawogICAgICBjbGFzcz0icG9tZXJpdW1fZmF2aWNvbiIKICAgICAgcmVsPSJhcHBsZS10b3VjaC1pY29uIgogICAgICBzaXplcz0iMTgweDE4MCIKICAgICAgaHJlZj0iLy5wb21lcml1bS9hcHBsZS10b3VjaC1pY29uLnBuZyIKICAgIC8+CiAgICA8bGluawogICAgICBjbGFzcz0icG9tZXJpdW1fZmF2aWNvbiIKICAgICAgcmVsPSJpY29uIgogICAgICBzaXplcz0iMzJ4MzIiCiAgICAgIGhyZWY9Ii8ucG9tZXJpdW0vZmF2aWNvbi0zMngzMi5wbmciCiAgICAvPgogICAgPGxpbmsKICAgICAgY2xhc3M9InBvbWVyaXVtX2Zhdmljb24iCiAgICAgIHJlbD0iaWNvbiIKICAgICAgc2l6ZXM9IjE2eDE2IgogICAgICBocmVmPSIvLnBvbWVyaXVtL2Zhdmljb24tMTZ4MTYucG5nIgogICAgLz4KICAgIDxtZXRhCiAgICAgIG5hbWU9InZpZXdwb3J0IgogICAgICBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSwgc2hyaW5rLXRvLWZpdD1ubyIKICAgIC8+CiAgICA8dGl0bGU+RXJyb3I8L3RpdGxlPgogICAgPGxpbmsgcmVsPSJzdHlsZXNoZWV0IiBocmVmPSIvLnBvbWVyaXVtL2luZGV4LmNzcyIgLz4KICA8L2hlYWQ+CiAgPGJvZHk+CiAgICA8bm9zY3JpcHQ+WW91IG5lZWQgdG8gZW5hYmxlIEphdmFTY3JpcHQgdG8gcnVuIHRoaXMgYXBwLjwvbm9zY3JpcHQ+CiAgICA8ZGl2IGlkPSJyb290Ij48L2Rpdj4KICAgIDxzY3JpcHQ+CiAgICAgIHdpbmRvdy5QT01FUklVTV9EQVRBID0geyJwYWdlIjoiRXJyb3IiLCJyZXF1ZXN0SWQiOiIlU1RSRUFNX0lEJSIsInN0YXR1cyI6IiVSRVNQT05TRV9DT0RFJSIsInN0YXR1c1RleHQiOiIlUkVTUE9OU0VfQ09ERV9ERVRBSUxTJSJ9OwogICAgPC9zY3JpcHQ+CiAgICA8c2NyaXB0IHNyYz0iLy5wb21lcml1bS9pbmRleC5qcyI+PC9zY3JpcHQ+CiAgPC9ib2R5Pgo8L2h0bWw+Cg=="
}
},
"filter": {
"responseFlagFilter": {}
},

View file

@ -27,18 +27,27 @@ func ServeFile(w http.ResponseWriter, r *http.Request, filePath string) error {
return nil
}
// RenderPage rends the index.html page.
func RenderPage(page, title string, data map[string]any) ([]byte, error) {
if data == nil {
data = make(map[string]any)
}
data["page"] = page
return renderIndex(map[string]any{
"Title": title,
"Data": data,
})
}
// ServePage serves the index.html page.
func ServePage(w http.ResponseWriter, r *http.Request, page, title string, data map[string]interface{}) error {
func ServePage(w http.ResponseWriter, r *http.Request, page, title string, data map[string]any) error {
if data == nil {
data = make(map[string]any)
}
data["csrfToken"] = csrf.Token(r)
data["page"] = page
bs, err := renderIndex(map[string]any{
"Title": title,
"Data": data,
})
bs, err := RenderPage(page, title, data)
if err != nil {
return err
}