Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support session persistence in HTTPRouteRule #3841

Merged
merged 20 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion api/v1alpha1/envoyproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ type EnvoyProxySpec struct {
//
// - envoy.filters.http.jwt_authn
//
// - envoy.filters.http.stateful_session
//
// - envoy.filters.http.ext_proc
//
// - envoy.filters.http.wasm
Expand Down Expand Up @@ -172,7 +174,7 @@ type FilterPosition struct {
}

// EnvoyFilter defines the type of Envoy HTTP filter.
// +kubebuilder:validation:Enum=envoy.filters.http.health_check;envoy.filters.http.fault;envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.basic_auth;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.ext_proc;envoy.filters.http.wasm;envoy.filters.http.rbac;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit
// +kubebuilder:validation:Enum=envoy.filters.http.health_check;envoy.filters.http.fault;envoy.filters.http.cors;envoy.filters.http.ext_authz;envoy.filters.http.basic_auth;envoy.filters.http.oauth2;envoy.filters.http.jwt_authn;envoy.filters.http.stateful_session;envoy.filters.http.ext_proc;envoy.filters.http.wasm;envoy.filters.http.rbac;envoy.filters.http.local_ratelimit;envoy.filters.http.ratelimit
type EnvoyFilter string

const (
Expand All @@ -197,6 +199,9 @@ const (
// EnvoyFilterJWTAuthn defines the Envoy HTTP JWT authentication filter.
EnvoyFilterJWTAuthn EnvoyFilter = "envoy.filters.http.jwt_authn"

// EnvoyFilterSessionPersistence defines the Envoy HTTP session persistence filter.
EnvoyFilterSessionPersistence EnvoyFilter = "envoy.filters.http.stateful_session"

// EnvoyFilterExtProc defines the Envoy HTTP external process filter.
EnvoyFilterExtProc EnvoyFilter = "envoy.filters.http.ext_proc"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,9 @@ spec:
- envoy.filters.http.jwt_authn


- envoy.filters.http.stateful_session


- envoy.filters.http.ext_proc


Expand Down Expand Up @@ -286,6 +289,7 @@ spec:
- envoy.filters.http.basic_auth
- envoy.filters.http.oauth2
- envoy.filters.http.jwt_authn
- envoy.filters.http.stateful_session
- envoy.filters.http.ext_proc
- envoy.filters.http.wasm
- envoy.filters.http.rbac
Expand All @@ -304,6 +308,7 @@ spec:
- envoy.filters.http.basic_auth
- envoy.filters.http.oauth2
- envoy.filters.http.jwt_authn
- envoy.filters.http.stateful_session
- envoy.filters.http.ext_proc
- envoy.filters.http.wasm
- envoy.filters.http.rbac
Expand All @@ -320,6 +325,7 @@ spec:
- envoy.filters.http.basic_auth
- envoy.filters.http.oauth2
- envoy.filters.http.jwt_authn
- envoy.filters.http.stateful_session
- envoy.filters.http.ext_proc
- envoy.filters.http.wasm
- envoy.filters.http.rbac
Expand Down
4 changes: 2 additions & 2 deletions examples/extension-server/cmd/extension-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import (
"os/signal"
"syscall"

pb "github.com/envoyproxy/gateway/proto/extension"
"github.com/exampleorg/envoygateway-extension/internal/extensionserver"
"github.com/urfave/cli/v2"
"google.golang.org/grpc"

"github.com/exampleorg/envoygateway-extension/internal/extensionserver"
pb "github.com/envoyproxy/gateway/proto/extension"
)

func main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ import (
"fmt"
"log/slog"

pb "github.com/envoyproxy/gateway/proto/extension"
corev3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
listenerv3 "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
bav3 "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/basic_auth/v3"
hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
"github.com/exampleorg/envoygateway-extension/api/v1alpha1"
"google.golang.org/protobuf/types/known/anypb"

"github.com/exampleorg/envoygateway-extension/api/v1alpha1"
pb "github.com/envoyproxy/gateway/proto/extension"
)

type Server struct {
Expand Down
51 changes: 50 additions & 1 deletion internal/gatewayapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,12 +314,60 @@
ruleRoutes = append(ruleRoutes, irRoute)
}

var sessionPersistence *ir.SessionPersistence
if rule.SessionPersistence != nil {
if rule.SessionPersistence.IdleTimeout != nil {
return nil, fmt.Errorf("idle timeout is not supported in envoy gateway")

Check warning on line 320 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L319-L320

Added lines #L319 - L320 were not covered by tests
}

var sessionName string
if rule.SessionPersistence.SessionName == nil {

Check warning on line 324 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L323-L324

Added lines #L323 - L324 were not covered by tests
// SessionName is optional on the gateway-api, but envoy requires it
// so we generate the one here.

// We generate a unique session name per route.
// `/` isn't allowed in the header key, so we just replace it with `-`.
sessionName = strings.ReplaceAll(irRouteDestinationName(httpRoute, ruleIdx), "/", "-")
} else {
sanposhiho marked this conversation as resolved.
Show resolved Hide resolved
sessionName = *rule.SessionPersistence.SessionName

Check warning on line 332 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L330-L332

Added lines #L330 - L332 were not covered by tests
}

switch {

Check warning on line 335 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L335

Added line #L335 was not covered by tests
case rule.SessionPersistence.Type == nil || // Cookie-based session persistence is default.
*rule.SessionPersistence.Type == gwapiv1.CookieBasedSessionPersistence:
sessionPersistence = &ir.SessionPersistence{
Cookie: &ir.CookieBasedSessionPersistence{
Name: sessionName,
},

Check warning on line 341 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L337-L341

Added lines #L337 - L341 were not covered by tests
}
if rule.SessionPersistence.AbsoluteTimeout != nil &&
rule.SessionPersistence.CookieConfig != nil && rule.SessionPersistence.CookieConfig.LifetimeType != nil &&
*rule.SessionPersistence.CookieConfig.LifetimeType == gwapiv1.PermanentCookieLifetimeType {
ttl, err := time.ParseDuration(string(*rule.SessionPersistence.AbsoluteTimeout))
if err != nil {
return nil, err

Check warning on line 348 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L343-L348

Added lines #L343 - L348 were not covered by tests
}
sessionPersistence.Cookie.TTL = &metav1.Duration{Duration: ttl}

Check warning on line 350 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L350

Added line #L350 was not covered by tests
}
case *rule.SessionPersistence.Type == gwapiv1.HeaderBasedSessionPersistence:
sessionPersistence = &ir.SessionPersistence{
Header: &ir.HeaderBasedSessionPersistence{
Name: sessionName,
},

Check warning on line 356 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L352-L356

Added lines #L352 - L356 were not covered by tests
}
default:

Check warning on line 358 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L358

Added line #L358 was not covered by tests
// Unknown session persistence type is specified.
return nil, fmt.Errorf("unknown session persistence type %s", *rule.SessionPersistence.Type)

Check warning on line 360 in internal/gatewayapi/route.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/route.go#L360

Added line #L360 was not covered by tests
}
}

// A rule is matched if any one of its matches
// is satisfied (i.e. a logical "OR"), so generate
// a unique Xds IR HTTPRoute per match.
for matchIdx, match := range rule.Matches {
irRoute := &ir.HTTPRoute{
Name: irRouteName(httpRoute, ruleIdx, matchIdx),
Name: irRouteName(httpRoute, ruleIdx, matchIdx),
SessionPersistence: sessionPersistence,
}
processTimeout(irRoute, rule)

Expand Down Expand Up @@ -699,6 +747,7 @@
Mirrors: routeRoute.Mirrors,
ExtensionRefs: routeRoute.ExtensionRefs,
IsHTTP2: routeRoute.IsHTTP2,
SessionPersistence: routeRoute.SessionPersistence,
}
if routeRoute.Traffic != nil {
hostRoute.Traffic = &ir.TrafficFeatures{
Expand Down
29 changes: 29 additions & 0 deletions internal/ir/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,8 @@ type HTTPRoute struct {
UseClientProtocol *bool `json:"useClientProtocol,omitempty" yaml:"useClientProtocol,omitempty"`
// Metadata is used to enrich envoy route metadata with user and provider-specific information
Metadata *ResourceMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"`
// SessionPersistence holds the configuration for session persistence.
SessionPersistence *SessionPersistence `json:"sessionPersistence,omitempty" yaml:"sessionPersistence,omitempty"`
}

// DNS contains configuration options for DNS resolution.
Expand All @@ -576,6 +578,33 @@ type DNS struct {
RespectDNSTTL *bool `json:"respectDnsTtl,omitempty"`
}

// SessionPersistence defines the desired state of SessionPersistence.
// +k8s:deepcopy-gen=true
type SessionPersistence struct {
sanposhiho marked this conversation as resolved.
Show resolved Hide resolved
// Cookie defines the configuration for cookie-based session persistence.
// Either Cookie or Header must be non-empty.
Cookie *CookieBasedSessionPersistence `json:"cookie,omitempty" yaml:"cookie,omitempty"`
// Header defines the configuration for header-based session persistence.
// Either Cookie or Header must be non-empty.
Header *HeaderBasedSessionPersistence `json:"header,omitempty" yaml:"header,omitempty"`
}

// CookieBasedSessionPersistence defines the configuration for cookie-based session persistence.
// +k8s:deepcopy-gen=true
type CookieBasedSessionPersistence struct {
// Name defines the name of the persistent session token.
Name string `json:"name"`

TTL *metav1.Duration `json:"ttl,omitempty" yaml:"ttl,omitempty"`
}

// HeaderBasedSessionPersistence defines the configuration for header-based session persistence.
// +k8s:deepcopy-gen=true
type HeaderBasedSessionPersistence struct {
// Name defines the name of the persistent session token.
Name string `json:"name"`
}

// TrafficFeatures holds the information associated with the Backend Traffic Policy.
// +k8s:deepcopy-gen=true
type TrafficFeatures struct {
Expand Down
65 changes: 65 additions & 0 deletions internal/ir/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion internal/xds/translator/httpfilters.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,10 @@ func newOrderedHTTPFilter(filter *hcmv3.HttpFilter) *OrderedHTTPFilter {
order = 5
case isFilterType(filter, egv1a1.EnvoyFilterJWTAuthn):
order = 6
case isFilterType(filter, egv1a1.EnvoyFilterSessionPersistence):
order = 7
case isFilterType(filter, egv1a1.EnvoyFilterExtProc):
order = 7 + mustGetFilterIndex(filter.Name)
order = 8 + mustGetFilterIndex(filter.Name)
case isFilterType(filter, egv1a1.EnvoyFilterWasm):
order = 100 + mustGetFilterIndex(filter.Name)
case isFilterType(filter, egv1a1.EnvoyFilterRBAC):
Expand Down
Loading
Loading