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: implement Lua EnvoyExtensionPolicy #5171

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions api/v1alpha1/envoyproxy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ const (
// EnvoyFilterWasm defines the Envoy HTTP WebAssembly filter.
EnvoyFilterWasm EnvoyFilter = "envoy.filters.http.wasm"

// EnvoyFilterLua defines the Envoy HTTP Lua filter.
EnvoyFilterLua EnvoyFilter = "envoy.filters.http.lua"

// EnvoyFilterRBAC defines the Envoy RBAC filter.
EnvoyFilterRBAC EnvoyFilter = "envoy.filters.http.rbac"

Expand Down
2 changes: 1 addition & 1 deletion api/v1alpha1/lua_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type Lua struct {
// The value of key `lua` in the ConfigMap will be used.
// If the key is not found, the first value in the ConfigMap will be used.
//
// +kubebuilder:validation:XValidation:rule="self.kind == 'ConfigMap' && (!has(self.group) || self.group == '')",message="Only a reference to an object of kind ConfigMap belonging to default core API group is supported."
// +kubebuilder:validation:XValidation:rule="self.kind == 'ConfigMap' && (self.group == 'v1' || self.group == '')",message="Only a reference to an object of kind ConfigMap belonging to default v1 API group is supported."
// +optional
// +unionMember
ValueRef *gwapiv1.LocalObjectReference `json:"valueRef,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1069,8 +1069,8 @@ spec:
type: object
x-kubernetes-validations:
- message: Only a reference to an object of kind ConfigMap belonging
to default core API group is supported.
rule: self.kind == 'ConfigMap' && (!has(self.group) || self.group
to default v1 API group is supported.
rule: self.kind == 'ConfigMap' && (self.group == 'v1' || self.group
== '')
required:
- type
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
github.com/telepresenceio/watchable v0.0.0-20220726211108-9bb86f92afa7
github.com/tetratelabs/func-e v1.1.5-0.20240822223546-c85a098d5bf0
github.com/tsaarni/certyaml v0.10.0
github.com/yuin/gopher-lua v1.1.1
go.opentelemetry.io/otel v1.34.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.34.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.34.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -865,6 +865,8 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
Expand Down
88 changes: 88 additions & 0 deletions internal/gatewayapi/envoyextensionpolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/envoyproxy/gateway/internal/gatewayapi/luavalidator"
"github.com/envoyproxy/gateway/internal/gatewayapi/resource"
"github.com/envoyproxy/gateway/internal/gatewayapi/status"
"github.com/envoyproxy/gateway/internal/ir"
Expand Down Expand Up @@ -293,6 +295,7 @@
) error {
var (
wasms []ir.Wasm
luas []ir.Lua
err, errs error
)

Expand All @@ -301,6 +304,11 @@
errs = errors.Join(errs, err)
}

if luas, err = t.buildLuas(policy, resources); err != nil {
err = perr.WithMessage(err, "Lua")
errs = errors.Join(errs, err)
}

// Apply IR to all relevant routes
prefix := irRoutePrefix(route)
parentRefs := GetParentReferences(route)
Expand Down Expand Up @@ -332,6 +340,7 @@
r.EnvoyExtensions = &ir.EnvoyExtensionFeatures{
ExtProcs: extProcs,
Wasms: wasms,
Luas: luas,
}
}
}
Expand All @@ -352,6 +361,7 @@
var (
extProcs []ir.ExtProc
wasms []ir.Wasm
luas []ir.Lua
err, errs error
)

Expand All @@ -363,6 +373,10 @@
err = perr.WithMessage(err, "Wasm")
errs = errors.Join(errs, err)
}
if luas, err = t.buildLuas(policy, resources); err != nil {
err = perr.WithMessage(err, "Lua")
errs = errors.Join(errs, err)
}

Check warning on line 379 in internal/gatewayapi/envoyextensionpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/envoyextensionpolicy.go#L377-L379

Added lines #L377 - L379 were not covered by tests

irKey := t.getIRKey(gateway.Gateway)
// Should exist since we've validated this
Expand Down Expand Up @@ -395,13 +409,80 @@
r.EnvoyExtensions = &ir.EnvoyExtensionFeatures{
ExtProcs: extProcs,
Wasms: wasms,
Luas: luas,
}
}
}

return errs
}

func (t *Translator) buildLuas(policy *egv1a1.EnvoyExtensionPolicy, resources *resource.Resources) ([]ir.Lua, error) {
var luaIRList []ir.Lua

if policy == nil {
return nil, nil
}

Check warning on line 425 in internal/gatewayapi/envoyextensionpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/envoyextensionpolicy.go#L424-L425

Added lines #L424 - L425 were not covered by tests

for idx, ep := range policy.Spec.Lua {
name := irConfigNameForLua(policy, idx)
luaIR, err := t.buildLua(name, policy, ep, resources)
if err != nil {
return nil, err
}
luaIRList = append(luaIRList, *luaIR)
}
return luaIRList, nil
}

func (t *Translator) buildLua(
name string,
policy *egv1a1.EnvoyExtensionPolicy,
lua egv1a1.Lua,
resources *resource.Resources,
) (*ir.Lua, error) {
var luaBody *string
var err error
if lua.Type == egv1a1.LuaValueTypeValueRef {
luaBody, err = getLuaBodyFromLocalObjectReference(lua.ValueRef, resources, policy.Namespace)
} else {
luaBody = lua.Inline
}
if err != nil {
return nil, err
}
if err = luavalidator.NewLuaValidator(*luaBody).Validate(); err != nil {
return nil, fmt.Errorf("validation failed for lua body in policy with name %v: %w", name, err)
}
return &ir.Lua{
Name: name,
Body: luaBody,
}, nil
}

// getLuaBodyFromLocalObjectReference assumes the local object reference points to a Kubernetes ConfigMap
func getLuaBodyFromLocalObjectReference(valueRef *gwapiv1.LocalObjectReference, resources *resource.Resources, policyNs string) (*string, error) {
cm := resources.GetConfigMap(policyNs, string(valueRef.Name))
if cm != nil {
b, dataOk := cm.Data["lua"]
switch {
case dataOk:
return &b, nil
case len(cm.Data) > 0: // Fallback to the first key if lua is not found
for _, value := range cm.Data {
b = value
break

Check warning on line 474 in internal/gatewayapi/envoyextensionpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/envoyextensionpolicy.go#L471-L474

Added lines #L471 - L474 were not covered by tests
}
return &b, nil

Check warning on line 476 in internal/gatewayapi/envoyextensionpolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/envoyextensionpolicy.go#L476

Added line #L476 was not covered by tests
default:
return nil, fmt.Errorf("can't find the key lua in the referenced configmap %s", valueRef.Name)
}

} else {
return nil, fmt.Errorf("can't find the referenced configmap %s in namespace %s", valueRef.Name, policyNs)
}
}

func (t *Translator) buildExtProcs(policy *egv1a1.EnvoyExtensionPolicy, resources *resource.Resources, envoyProxy *egv1a1.EnvoyProxy) ([]ir.ExtProc, error) {
var extProcIRList []ir.ExtProc

Expand Down Expand Up @@ -522,6 +603,13 @@
strconv.Itoa(index))
}

func irConfigNameForLua(policy *egv1a1.EnvoyExtensionPolicy, index int) string {
return fmt.Sprintf(
"%s/lua/%s",
irConfigName(policy),
strconv.Itoa(index))
}

func (t *Translator) buildWasms(
policy *egv1a1.EnvoyExtensionPolicy,
resources *resource.Resources,
Expand Down
60 changes: 60 additions & 0 deletions internal/gatewayapi/luavalidator/lua_validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package luavalidator

import (
_ "embed"
"fmt"
"strings"

lua "github.com/yuin/gopher-lua"
)

// mockData contains mocks of Envoy supported APIs for Lua filters.
// Refer: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/lua_filter#stream-handle-api
//
//go:embed mocks.lua
var mockData []byte

// LuaValidator validates user provided Lua for compatibility with Envoy supported Lua HTTP filter
type LuaValidator struct {
body string
}

// NewLuaValidator returns a LuaValidator for user provided Lua body
func NewLuaValidator(body string) *LuaValidator {
return &LuaValidator{
body: body,
}
}

// Validate runs all validations for the LuaValidator
func (l *LuaValidator) Validate() error {
if !strings.Contains(l.body, "envoy_on_request") && !strings.Contains(l.body, "envoy_on_response") {
return fmt.Errorf("expected one of envoy_on_request() or envoy_on_response() to be defined")
}
if strings.Contains(l.body, "envoy_on_request") {
if err := l.runLua(string(mockData) + "\n" + l.body + "\nenvoy_on_request(StreamHandle)"); err != nil {
return fmt.Errorf("failed to mock run envoy_on_request: %w", err)
}
}
if strings.Contains(l.body, "envoy_on_response") {
if err := l.runLua(string(mockData) + "\n" + l.body + "\nenvoy_on_response(StreamHandle)"); err != nil {
return fmt.Errorf("failed to mock run envoy_on_response: %w", err)
}

Check warning on line 47 in internal/gatewayapi/luavalidator/lua_validator.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/luavalidator/lua_validator.go#L46-L47

Added lines #L46 - L47 were not covered by tests
}
return nil
}

// runLua interprets and runs the provided Lua body in runtime
func (l *LuaValidator) runLua(body string) error {
L := lua.NewState()
defer L.Close()
if err := L.DoString(body); err != nil {
return err
}
return nil
}
Loading
Loading