Skip to content

Commit

Permalink
feat: implement Lua EnvoyExtensionPolicy
Browse files Browse the repository at this point in the history
Signed-off-by: Rudrakh Panigrahi <[email protected]>
  • Loading branch information
rudrakhp committed Feb 7, 2025
1 parent 581dc42 commit 3bce57a
Show file tree
Hide file tree
Showing 30 changed files with 3,105 additions and 14 deletions.
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 @@ import (
"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 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(
) error {
var (
wasms []ir.Wasm
luas []ir.Lua
err, errs error
)

Expand All @@ -301,6 +304,11 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(
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 @@ func (t *Translator) translateEnvoyExtensionPolicyForRoute(
r.EnvoyExtensions = &ir.EnvoyExtensionFeatures{
ExtProcs: extProcs,
Wasms: wasms,
Luas: luas,
}
}
}
Expand All @@ -352,6 +361,7 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
var (
extProcs []ir.ExtProc
wasms []ir.Wasm
luas []ir.Lua
err, errs error
)

Expand All @@ -363,6 +373,10 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
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)
}

irKey := t.getIRKey(gateway.Gateway)
// Should exist since we've validated this
Expand Down Expand Up @@ -395,13 +409,80 @@ func (t *Translator) translateEnvoyExtensionPolicyForGateway(
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
}

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
}
return &b, nil
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 @@ func irConfigNameForExtProc(policy *egv1a1.EnvoyExtensionPolicy, index int) stri
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)
}
}
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

0 comments on commit 3bce57a

Please sign in to comment.