core/config: implement direct response (#4960)

* implement direct response

* proto

* fix tests

* update
This commit is contained in:
Caleb Doxsey 2024-02-15 14:33:56 -07:00 committed by GitHub
parent 2db2bd09a1
commit 513d8bf615
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 967 additions and 843 deletions

View file

@ -14,13 +14,13 @@ const (
) )
var ( var (
errKeysMustBeStrings = errors.New("cannot convert nested map: all keys must be strings") errKeysMustBeStrings = errors.New("cannot convert nested map: all keys must be strings")
errZeroWeight = errors.New("zero load balancing weight not permitted") errZeroWeight = errors.New("zero load balancing weight not permitted")
errEndpointWeightsSpec = errors.New("either no weights should be provided, or all endpoints must have non-zero weight specified") errEndpointWeightsSpec = errors.New("either no weights should be provided, or all endpoints must have non-zero weight specified")
errHostnameMustBeSpecified = errors.New("endpoint hostname must be specified") errHostnameMustBeSpecified = errors.New("endpoint hostname must be specified")
errSchemeMustBeSpecified = errors.New("url scheme must be provided") errSchemeMustBeSpecified = errors.New("url scheme must be provided")
errEmptyUrls = errors.New("url list is empty") errEmptyUrls = errors.New("url list is empty")
errEitherToOrRedirectRequired = errors.New("policy should have either `to` or `redirect` defined") errEitherToOrRedirectOrResponseRequired = errors.New("policy should have either `to` or `redirect` or `response` defined")
) )
var protoPartial = protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true} var protoPartial = protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}

View file

@ -77,7 +77,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
], ],
"route": { "route": {
"autoHostRewrite": true, "autoHostRewrite": true,
"cluster": "route-5feb9fe8bd89aa97", "cluster": "route-5d678ee30d16332b",
"hashPolicy": [ "hashPolicy": [
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true }, { "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
{ "connectionProperties": { "sourceIp": true }, "terminal": true } { "connectionProperties": { "sourceIp": true }, "terminal": true }
@ -94,7 +94,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "6911793875091303063" "route_id": "6730505273956774699"
} }
} }
} }
@ -130,7 +130,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
], ],
"route": { "route": {
"autoHostRewrite": true, "autoHostRewrite": true,
"cluster": "route-5feb9fe8bd89aa97", "cluster": "route-5d678ee30d16332b",
"hashPolicy": [ "hashPolicy": [
{ "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true }, { "header": { "headerName": "x-pomerium-routing-key" }, "terminal": true },
{ "connectionProperties": { "sourceIp": true }, "terminal": true } { "connectionProperties": { "sourceIp": true }, "terminal": true }
@ -147,7 +147,7 @@ func TestBuilder_buildMainRouteConfiguration(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "6911793875091303063" "route_id": "6730505273956774699"
} }
} }
} }

View file

@ -282,6 +282,9 @@ func (b *Builder) buildRouteForPolicyAndMatch(
return nil, err return nil, err
} }
route.Action = &envoy_config_route_v3.Route_Redirect{Redirect: action} route.Action = &envoy_config_route_v3.Route_Redirect{Redirect: action}
} else if policy.Response != nil {
action := b.buildPolicyRouteDirectResponseAction(policy.Response)
route.Action = &envoy_config_route_v3.Route_DirectResponse{DirectResponse: action}
} else { } else {
action, err := b.buildPolicyRouteRouteAction(cfg.Options, policy) action, err := b.buildPolicyRouteRouteAction(cfg.Options, policy)
if err != nil { if err != nil {
@ -343,6 +346,17 @@ func (b *Builder) buildRouteForPolicyAndMatch(
return route, nil return route, nil
} }
func (b *Builder) buildPolicyRouteDirectResponseAction(r *config.DirectResponse) *envoy_config_route_v3.DirectResponseAction {
return &envoy_config_route_v3.DirectResponseAction{
Status: uint32(r.Status),
Body: &envoy_config_core_v3.DataSource{
Specifier: &envoy_config_core_v3.DataSource_InlineString{
InlineString: r.Body,
},
},
}
}
func (b *Builder) buildPolicyRouteRedirectAction(r *config.PolicyRedirect) (*envoy_config_route_v3.RedirectAction, error) { func (b *Builder) buildPolicyRouteRedirectAction(r *config.PolicyRedirect) (*envoy_config_route_v3.RedirectAction, error) {
action := &envoy_config_route_v3.RedirectAction{} action := &envoy_config_route_v3.RedirectAction{}
switch { switch {

View file

@ -444,7 +444,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "11444765232398592404" "route_id": "16913502743845432363"
} }
} }
} }
@ -515,7 +515,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "2990091139764155677" "route_id": "911713133804109577"
} }
} }
} }
@ -585,7 +585,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "2544588842279234006" "route_id": "6407864870815560799"
} }
} }
} }
@ -657,7 +657,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "10244970664102670752" "route_id": "1103677309004574500"
} }
} }
} }
@ -728,7 +728,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "2544588842279234006" "route_id": "6407864870815560799"
} }
} }
} }
@ -798,7 +798,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "2990091139764155677" "route_id": "911713133804109577"
} }
} }
} }
@ -869,7 +869,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "2990091139764155677" "route_id": "911713133804109577"
} }
} }
} }
@ -940,7 +940,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "1052418080698022187" "route_id": "17831746838845374842"
} }
} }
} }
@ -1123,7 +1123,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "2226589900561460978" "route_id": "15730681265277585877"
} }
} }
} }
@ -1195,7 +1195,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "2226589900561460978" "route_id": "15730681265277585877"
} }
} }
} }
@ -1293,7 +1293,7 @@ func Test_buildPolicyRoutes(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "15508081512033148378" "route_id": "16598125949405432745"
} }
} }
} }
@ -1423,7 +1423,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "285016060542193864" "route_id": "13828028232508831592"
} }
} }
} }
@ -1494,7 +1494,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "285016060542193864" "route_id": "13828028232508831592"
} }
} }
} }
@ -1570,7 +1570,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "285016060542193864" "route_id": "13828028232508831592"
} }
} }
} }
@ -1641,7 +1641,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "285016060542193864" "route_id": "13828028232508831592"
} }
} }
} }
@ -1712,7 +1712,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "285016060542193864" "route_id": "13828028232508831592"
} }
} }
} }
@ -1788,7 +1788,7 @@ func Test_buildPolicyRoutesRewrite(t *testing.T) {
"checkSettings": { "checkSettings": {
"contextExtensions": { "contextExtensions": {
"internal": "false", "internal": "false",
"route_id": "285016060542193864" "route_id": "13828028232508831592"
} }
} }
} }

View file

@ -30,14 +30,14 @@ type Policy struct {
From string `mapstructure:"from" yaml:"from"` From string `mapstructure:"from" yaml:"from"`
To WeightedURLs `mapstructure:"to" yaml:"to"` To WeightedURLs `mapstructure:"to" yaml:"to"`
// Redirect is used for a redirect action instead of `To`
Redirect *PolicyRedirect `mapstructure:"redirect" yaml:"redirect"`
Response *DirectResponse `mapstructure:"response" yaml:"response,omitempty" json:"response,omitempty"`
// LbWeights are optional load balancing weights applied to endpoints specified in To // LbWeights are optional load balancing weights applied to endpoints specified in To
// this field exists for compatibility with mapstructure // this field exists for compatibility with mapstructure
LbWeights []uint32 `mapstructure:"_to_weights,omitempty" json:"-" yaml:"-"` LbWeights []uint32 `mapstructure:"_to_weights,omitempty" json:"-" yaml:"-"`
// Redirect is used for a redirect action instead of `To`
Redirect *PolicyRedirect `mapstructure:"redirect" yaml:"redirect"`
// Identity related policy // Identity related policy
AllowedUsers []string `mapstructure:"allowed_users" yaml:"allowed_users,omitempty" json:"allowed_users,omitempty"` AllowedUsers []string `mapstructure:"allowed_users" yaml:"allowed_users,omitempty" json:"allowed_users,omitempty"`
AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty" json:"allowed_domains,omitempty"` AllowedDomains []string `mapstructure:"allowed_domains" yaml:"allowed_domains,omitempty" json:"allowed_domains,omitempty"`
@ -213,6 +213,12 @@ type PolicyRedirect struct {
StripQuery *bool `mapstructure:"strip_query" yaml:"strip_query,omitempty" json:"strip_query,omitempty"` StripQuery *bool `mapstructure:"strip_query" yaml:"strip_query,omitempty" json:"strip_query,omitempty"`
} }
// A DirectResponse is the response to an HTTP request.
type DirectResponse struct {
Status int `mapstructure:"status" yaml:"status,omitempty" json:"status,omitempty"`
Body string `mapstructure:"body" yaml:"body,omitempty" json:"body,omitempty"`
}
// NewPolicyFromProto creates a new Policy from a protobuf policy config route. // NewPolicyFromProto creates a new Policy from a protobuf policy config route.
func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) { func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
var timeout *time.Duration var timeout *time.Duration
@ -284,6 +290,11 @@ func NewPolicyFromProto(pb *configpb.Route) (*Policy, error) {
ResponseCode: pb.Redirect.ResponseCode, ResponseCode: pb.Redirect.ResponseCode,
StripQuery: pb.Redirect.StripQuery, StripQuery: pb.Redirect.StripQuery,
} }
} else if pb.Response != nil {
p.Response = &DirectResponse{
Status: int(pb.Response.GetStatus()),
Body: pb.Response.GetBody(),
}
} else { } else {
to, err := ParseWeightedUrls(pb.GetTo()...) to, err := ParseWeightedUrls(pb.GetTo()...)
if err != nil { if err != nil {
@ -405,6 +416,11 @@ func (p *Policy) ToProto() (*configpb.Route, error) {
ResponseCode: p.Redirect.ResponseCode, ResponseCode: p.Redirect.ResponseCode,
StripQuery: p.Redirect.StripQuery, StripQuery: p.Redirect.StripQuery,
} }
} else if p.Response != nil {
pb.Response = &configpb.RouteDirectResponse{
Status: uint32(p.Response.Status),
Body: p.Response.Body,
}
} else { } else {
to, weights, err := p.To.Flatten() to, weights, err := p.To.Flatten()
if err != nil { if err != nil {
@ -446,8 +462,8 @@ func (p *Policy) Validate() error {
source.String()) source.String())
} }
if len(p.To) == 0 && p.Redirect == nil { if len(p.To) == 0 && p.Redirect == nil && p.Response == nil {
return errEitherToOrRedirectRequired return errEitherToOrRedirectOrResponseRequired
} }
for _, u := range p.To { for _, u := range p.To {
@ -567,8 +583,10 @@ func (p *Policy) RouteID() (uint64, error) {
id.To = dst id.To = dst
} else if p.Redirect != nil { } else if p.Redirect != nil {
id.Redirect = p.Redirect id.Redirect = p.Redirect
} else if p.Response != nil {
id.Response = p.Response
} else { } else {
return 0, errEitherToOrRedirectRequired return 0, errEitherToOrRedirectOrResponseRequired
} }
return hashutil.Hash(id) return hashutil.Hash(id)
@ -685,6 +703,7 @@ type routeID struct {
Path string Path string
Regex string Regex string
Redirect *PolicyRedirect Redirect *PolicyRedirect
Response *DirectResponse
} }
/* /*

File diff suppressed because it is too large Load diff

View file

@ -33,12 +33,19 @@ message RouteRedirect {
optional bool strip_query = 8; optional bool strip_query = 8;
} }
// Next ID: 62. message RouteDirectResponse {
uint32 status = 1;
string body = 2;
}
// Next ID: 63.
message Route { message Route {
string name = 1; string name = 1;
string from = 2; string from = 2;
repeated string to = 3; repeated string to = 3;
RouteRedirect redirect = 34;
RouteDirectResponse response = 62;
// https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/endpoint/v3/endpoint_components.proto#envoy-v3-api-msg-config-endpoint-v3-lbendpoint // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/endpoint/v3/endpoint_components.proto#envoy-v3-api-msg-config-endpoint-v3-lbendpoint
// optional load balancing weights assigned to upstream servers defined in TO // optional load balancing weights assigned to upstream servers defined in TO
@ -47,8 +54,6 @@ message Route {
// len(load_balancing_weights) // len(load_balancing_weights)
repeated uint32 load_balancing_weights = 37; repeated uint32 load_balancing_weights = 37;
RouteRedirect redirect = 34;
repeated string allowed_users = 4 [ deprecated = true ]; repeated string allowed_users = 4 [ deprecated = true ];
// repeated string allowed_groups = 5 [ deprecated = true ]; // repeated string allowed_groups = 5 [ deprecated = true ];
repeated string allowed_domains = 6 [ deprecated = true ]; repeated string allowed_domains = 6 [ deprecated = true ];