diff --git a/ast/codec/declaration_decode.go b/ast/codec/declaration_decode.go index b67444f6..e3faa940 100644 --- a/ast/codec/declaration_decode.go +++ b/ast/codec/declaration_decode.go @@ -1,7 +1,6 @@ package codec import ( - "github.com/k0kubun/pp" "github.com/pkg/errors" "github.com/ysugimoto/falco/ast" ) @@ -203,7 +202,6 @@ func (c *Decoder) decodeDirectorBackendObject() (*ast.DirectorBackendObject, err } backend.Values = append(backend.Values, prop) default: - pp.Println("OK") return nil, typeMismatch(DIRECTOR_PROPERTY, frame.Type()) } } diff --git a/cmd/falco/runner.go b/cmd/falco/runner.go index a6fce4d2..290ee40a 100644 --- a/cmd/falco/runner.go +++ b/cmd/falco/runner.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "os" + "strconv" "strings" "github.com/fatih/color" @@ -469,6 +470,25 @@ func (r *Runner) Test(rslv resolver.Resolver) (*tester.TestFactory, error) { options = append(options, icontext.WithOverrideHost(tc.OverrideHost)) } + // Factory override variables. + // The order is imporotant, should do yaml -> cli order because cli could override yaml configuration + overrides := make(map[string]any) + if tc.YamlOverrideVariables != nil { + for key, val := range tc.YamlOverrideVariables { + overrides[key] = val + } + } + if tc.CLIOverrideVariables != nil { + for _, v := range tc.CLIOverrideVariables { + key, val, parsed := r.parseOverrideVariables(v) + if !parsed { + continue + } + overrides[key] = val + } + } + options = append(options, icontext.WithOverrideVariales(overrides)) + r.message(white, "Running tests...") factory, err := tester.New(tc, options).Run(r.config.Commands.At(1)) if err != nil { @@ -480,6 +500,28 @@ func (r *Runner) Test(rslv resolver.Resolver) (*tester.TestFactory, error) { return factory, nil } +func (r *Runner) parseOverrideVariables(v string) (string, any, bool) { + sep := strings.SplitN(v, "=", 2) + if len(sep) != 2 { + return "", "", false + } + key := strings.TrimSpace(sep[0]) + val := strings.TrimSpace(sep[1]) + + // Simple type assertion of primitive value + if strings.EqualFold(val, "true") { + return key, true, true // bool:true + } else if strings.EqualFold(val, "false") { + return key, false, true // bool:true + } else if v, err := strconv.ParseInt(val, 10, 64); err != nil { + return key, v, true // integer + } else if v, err := strconv.ParseFloat(val, 64); err != nil { + return key, v, true // float + } else { + return key, val, true // string + } +} + func (r *Runner) Format(rslv resolver.Resolver) error { main, err := rslv.MainVCL() if err != nil { diff --git a/cmd/falco/runner_test.go b/cmd/falco/runner_test.go index 75dfeace..13a68a99 100644 --- a/cmd/falco/runner_test.go +++ b/cmd/falco/runner_test.go @@ -352,6 +352,12 @@ func TestTester(t *testing.T) { filter: "*mock_subroutine.test.vcl", passes: 6, }, + { + name: "overriding variables test", + main: "../../examples/testing/override_variables.vcl", + filter: "*override_variables.test.vcl", + passes: 6, + }, } for _, tt := range tests { @@ -367,6 +373,15 @@ func TestTester(t *testing.T) { }, Testing: &config.TestConfig{ Filter: tt.filter, + YamlOverrideVariables: map[string]any{ + "tls.client.certificate.is_cert_missing": true, + "client.geo.area_code": 100, + "req.digest.ratio": 0.8, + "client.as.name": "Foobar", + }, + CLIOverrideVariables: []string{ + "client.geo.area_code=200", // will be overridden + }, }, Commands: config.Commands{"test", main}, } diff --git a/config/config.go b/config/config.go index 131ff5d9..9af2d8c8 100644 --- a/config/config.go +++ b/config/config.go @@ -56,6 +56,9 @@ type SimulatorConfig struct { // Override Request configuration OverrideRequest *RequestConfig + + // Inject values that the simulator returns tentative value + InjectValues map[string]any `yaml:"values"` } // Testing configuration @@ -67,6 +70,10 @@ type TestConfig struct { // Override Request configuration OverrideRequest *RequestConfig + + // Override tentative variable values + CLIOverrideVariables []string `cli:"o,override"` // from CLI + YamlOverrideVariables map[string]any `yaml:"overrides"` // from .falco.yaml } // Console configuration diff --git a/docs/rules.md b/docs/rules.md index 8c733f72..1116e323 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -908,7 +908,7 @@ Failed to load include target module. ## regex/matched-value-override -Regex matched operator `re.group.N` value will be overriden. +Regex matched operator `re.group.N` value will be overridden. These variables could use if(else) block statement when condition has regex operator like `~` or `!~`. Note that group matched variable has potential of making bugs due to its spec: diff --git a/docs/variables.md b/docs/variables.md index 88d32674..74515164 100644 --- a/docs/variables.md +++ b/docs/variables.md @@ -1,7 +1,28 @@ # Variables in simulator -Following table describes variables that will return tentative values. -Will be updated when we find or implement a way to get accurate values. +Following table describes variables that will return tentative values, +but you can override these values by configuration or cli arguments for testing. + +## Overide by Configuration + +Put override configuration in `testing.overrides` section as map. + +```yaml +... +testing: + overrides: + client.class.checker: true + client.as.name: "overridden" +... +``` + +## CLI + +Provide `-o, --override` option on the CLI command, accepts multiple options. + +```shell +falco test /path/to/main.vcl -o "client.class.checker=true" -o "client.as.name=overridden" +``` | Variable | Tentative Value | @@ -15,8 +36,8 @@ Will be updated when we find or implement a way to get accurate values. | client.platform.mediaplayer | false | | client.geo.latitude | 37.7786941 | | client.geo.longitude | -122.3981452 | -| client.as_number | 4294967294 | -| client.as_name | "Reserved" | +| client.as.number | 4294967294 | +| client.as.name | "Reserved" | | client.display.height | -1 | | client.display.ppi | -1 | | client.display.width | -1 | @@ -67,7 +88,7 @@ Will be updated when we find or implement a way to get accurate values. | obj.is_pci | false | | req.backend.is_cluster | false | | resp.is_locally_generated | false | -| req.digest_ratio | 0.4 | +| req.digest.ratio | 0.4 | | obj.stale_white_revalidate | 60s | | backend.socket.congestion_algorithm | "cubic" | | backend.socket.cwnd | 60 | @@ -184,3 +205,5 @@ Will be updated when we find or implement a way to get accurate values. "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:TLS_ECDHE_ECDSA_WITH_AES_256_CCM:TLS_DHE_RSA_WITH_AES_256_CCM_8:TLS_DHE_RSA_WITH_AES_256_CCM:TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384:TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:TLS_ECDHE_ECDSA_WITH_AES_128_CCM:TLS_DHE_RSA_WITH_AES_128_CCM_8:TLS_DHE_RSA_WITH_AES_128_CCM:TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256:TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_DSS_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_DSS_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM_8:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM_8:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_CBC_SHA256:TLS_RSA_WITH_AES_128_CBC_SHA256:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV" ``` + +These values will be updated when we find or implement a way to get accurate values. diff --git a/examples/testing/override_variables.test.vcl b/examples/testing/override_variables.test.vcl new file mode 100644 index 00000000..6ce9782f --- /dev/null +++ b/examples/testing/override_variables.test.vcl @@ -0,0 +1,32 @@ +// @scope: recv +// @suite: Default variable via function +sub test_default_server_region { + testing.call_subroutine("vcl_recv"); + + assert.equal(req.http.Region, "US"); +} + +// @scope: recv +// @suite: Override variable via function +sub test_override_server_region { + testing.inject_variable("server.region", "ASIA"); + testing.call_subroutine("vcl_recv"); + + assert.equal(req.http.Region, "ASIA"); +} + +// @scope: deliver +// @suite: Assert overridden variables via configuration +sub test_override_via_configuration { + testing.call_subroutine("overrides"); + + // bool + assert.equal(req.http.Is-Cert-Bad, "1"); + // integer + assert.equal(req.http.Geo-Area-Code, "200"); + // float + assert.equal(req.http.Digest-Ratio, "0.800"); + // string + assert.equal(req.http.Client-As-Name, "Foobar"); +} + diff --git a/examples/testing/override_variables.vcl b/examples/testing/override_variables.vcl new file mode 100644 index 00000000..1a653579 --- /dev/null +++ b/examples/testing/override_variables.vcl @@ -0,0 +1,13 @@ +sub vcl_recv { + #Fastly recv + set req.http.Region = server.region; +} + + +sub overrides { + // Following variables could be overridden by configuration + set req.http.Is-Cert-Bad = tls.client.certificate.is_cert_missing; + set req.http.Geo-Area-Code = client.geo.area_code; + set req.http.Digest-Ratio = req.digest.ratio; + set req.http.Client-As-Name = client.as.name; +} diff --git a/interpreter/context/context.go b/interpreter/context/context.go index 55532d44..cce317b7 100644 --- a/interpreter/context/context.go +++ b/interpreter/context/context.go @@ -164,6 +164,8 @@ type Context struct { // However, Fastly document says the esi will be triggered when esi statement is executed in FETCH directive. // see: https://developer.fastly.com/reference/vcl/statements/esi/ TriggerESI bool + + OverrideVariables map[string]value.Value } func New(options ...Option) *Context { @@ -258,6 +260,8 @@ func New(options ...Option) *Context { RegexMatchedValues: make(map[string]*value.String), SubroutineCalls: make(map[string]int), + + OverrideVariables: make(map[string]value.Value), } // collect options diff --git a/interpreter/context/option.go b/interpreter/context/option.go index 1259c460..a3d6772a 100644 --- a/interpreter/context/option.go +++ b/interpreter/context/option.go @@ -2,6 +2,7 @@ package context import ( "github.com/ysugimoto/falco/config" + "github.com/ysugimoto/falco/interpreter/value" "github.com/ysugimoto/falco/resolver" "github.com/ysugimoto/falco/snippets" ) @@ -61,3 +62,20 @@ func WithActualResponse(is bool) Option { c.IsActualResponse = is } } + +func WithOverrideVariales(variables map[string]any) Option { + return func(c *Context) { + for k, v := range variables { + switch t := v.(type) { + case int: + c.OverrideVariables[k] = &value.Integer{Value: int64(t)} + case string: + c.OverrideVariables[k] = &value.String{Value: t} + case float64: + c.OverrideVariables[k] = &value.Float{Value: float64(t)} + case bool: + c.OverrideVariables[k] = &value.Boolean{Value: t} + } + } + } +} diff --git a/interpreter/statement.go b/interpreter/statement.go index d8c473c0..f749ceb4 100644 --- a/interpreter/statement.go +++ b/interpreter/statement.go @@ -4,7 +4,6 @@ import ( "io" "strings" - "github.com/k0kubun/pp" "github.com/pkg/errors" "github.com/ysugimoto/falco/ast" "github.com/ysugimoto/falco/interpreter/assign" @@ -37,9 +36,6 @@ func (i *Interpreter) ProcessBlockStatement( } // Find process marker and add flow if found - if stmt.GetMeta() == nil { - pp.Println(stmt) - } if name, found := findProcessMark(stmt.GetMeta().Leading); found { i.process.Flows = append( i.process.Flows, diff --git a/interpreter/variable/all.go b/interpreter/variable/all.go index 2961e172..5daeca7c 100644 --- a/interpreter/variable/all.go +++ b/interpreter/variable/all.go @@ -55,6 +55,9 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro switch name { case BEREQ_IS_CLUSTERING: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case CLIENT_CLASS_BOT: ua := uasurfer.Parse(req.Header.Get("User-Agent")) @@ -81,6 +84,9 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro RESP_STALE_IS_ERROR, RESP_STALE_IS_REVALIDATING, WORKSPACE_OVERFLOWED: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case CLIENT_DISPLAY_TOUCHSCREEN: @@ -125,6 +131,9 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro return &value.String{}, nil } // Format is undocumented, returning the value seen with the fiddle client. + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: "|00|1:0:0:16|m,s,p,a"}, nil // Backend is always healthy on simulator @@ -140,10 +149,19 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro } return &value.String{Value: protocol}, nil case CLIENT_GEO_LATITUDE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 37.7786941}, nil case CLIENT_GEO_LONGITUDE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: -122.3981452}, nil case FASTLY_ERROR: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: ""}, nil case MATH_1_PI: return &value.Float{Value: 1 / math.Pi}, nil @@ -197,20 +215,32 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro // AS Number always indicates "Reserved" defined by RFC7300 // see: https://datatracker.ietf.org/doc/html/rfc7300 case CLIENT_AS_NUMBER: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 4294967294}, nil case CLIENT_AS_NAME: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: "Reserved"}, nil // Client display infos are unknown. Always returns -1 case CLIENT_DISPLAY_HEIGHT, CLIENT_DISPLAY_PPI, CLIENT_DISPLAY_WIDTH: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: -1}, nil // Client geo values always return 0 case CLIENT_GEO_AREA_CODE, CLIENT_GEO_METRO_CODE, CLIENT_GEO_UTC_OFFSET: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil // Alias of client.geo.utc_offset @@ -219,6 +249,9 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro // Client could not fully identified so returns false case CLIENT_IDENTIFIED: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case CLIENT_PORT: @@ -297,20 +330,38 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro return &value.Integer{Value: 1}, nil case SERVER_BILLING_REGION: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: "Asia"}, nil // always returns Asia case SERVER_PORT: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: int64(3124)}, nil // fixed server port number case SERVER_POP: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: "FALCO"}, nil // Intend to set string not exists in Fastly POP certainly // workspace related values respects Fastly fiddle one case WORKSPACE_BYTES_FREE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 125008}, nil case WORKSPACE_BYTES_TOTAL: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 139392}, nil - // backend.src_ip always incicates this server, means localhost + // backend.src_ip always indicates this server, means localhost case BERESP_BACKEND_SRC_IP: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.IP{Value: net.IPv4(127, 0, 0, 1)}, nil case SERVER_IP: addrs, err := net.InterfaceAddrs() @@ -386,6 +437,9 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro CLIENT_GEO_REGION_ASCII, CLIENT_GEO_REGION_LATIN1, CLIENT_GEO_REGION_UTF8: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: "unknown"}, nil case CLIENT_IDENTITY: @@ -418,6 +472,9 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro // Always empty string case CLIENT_PLATFORM_HWTYPE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: ""}, nil case FASTLY_INFO_STATE: @@ -556,12 +613,24 @@ func (v *AllScopeVariables) Get(s context.Scope, name string) (value.Value, erro // Fixed values case SERVER_DATACENTER: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: FALCO_DATACENTER}, nil case SERVER_HOSTNAME: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: FALCO_SERVER_HOSTNAME}, nil case SERVER_IDENTITY: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: FALCO_SERVER_HOSTNAME}, nil case SERVER_REGION: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: "US"}, nil case STALE_EXISTS: return v.ctx.StaleContents, nil diff --git a/interpreter/variable/deliver.go b/interpreter/variable/deliver.go index fe0db5bd..5710dbe1 100644 --- a/interpreter/variable/deliver.go +++ b/interpreter/variable/deliver.go @@ -63,19 +63,34 @@ func (v *DeliverScopeVariables) Get(s context.Scope, name string) (value.Value, case CLIENT_SOCKET_CONGESTION_ALGORITHM: return v.ctx.ClientSocketCongestionAlgorithm, nil case CLIENT_SOCKET_CWND: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } // Sometimes change this value but we don't know how change it without set statement return &value.Integer{Value: 60}, nil case CLIENT_SOCKET_NEXTHOP: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.IP{Value: net.IPv4(127, 0, 0, 1)}, nil case CLIENT_SOCKET_PACE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case CLIENT_SOCKET_PLOSS: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0}, nil case ESI_ALLOW_INSIDE_CDATA: return v.ctx.EsiAllowInsideCData, nil case FASTLY_INFO_IS_CLUSTER_EDGE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil // TODO: should be able to get from context after object checked @@ -216,30 +231,33 @@ func (v *DeliverScopeVariables) Get(s context.Scope, name string) (value.Value, Value: fmt.Sprint(v.ctx.RequestEndTime.UnixMicro()), }, nil - // Digest ratio will return fixed value + // Digest ratio will return fixed value if not override case REQ_DIGEST_RATIO: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0.4}, nil case FASTLY_INFO_REQUEST_ID: return v.ctx.RequestID, nil } // Look up shared variables - if val, err := GetTCPInfoVariable(name); err != nil { + if val, err := GetTCPInfoVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetQuicVariable(name); err != nil { + if val, err := GetQuicVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetTLSVariable(v.ctx.Request.TLS, name); err != nil { + if val, err := GetTLSVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetFastlyInfoVariable(name); err != nil { + if val, err := GetFastlyInfoVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil diff --git a/interpreter/variable/error.go b/interpreter/variable/error.go index 7898cf68..6444f4ec 100644 --- a/interpreter/variable/error.go +++ b/interpreter/variable/error.go @@ -27,18 +27,31 @@ func NewErrorScopeVariables(ctx *context.Context) *ErrorScopeVariables { } } +//nolint:funlen,gocyclo func (v *ErrorScopeVariables) Get(s context.Scope, name string) (value.Value, error) { switch name { case CLIENT_SOCKET_CONGESTION_ALGORITHM: return v.ctx.ClientSocketCongestionAlgorithm, nil case CLIENT_SOCKET_CWND: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } // Sometimes change this value but we don't know how change it without set statement return &value.Integer{Value: 60}, nil case CLIENT_SOCKET_NEXTHOP: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.IP{Value: net.IPv4(127, 0, 0, 1)}, nil case CLIENT_SOCKET_PACE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case CLIENT_SOCKET_PLOSS: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0}, nil case ESI_ALLOW_INSIDE_CDATA: @@ -87,8 +100,14 @@ func (v *ErrorScopeVariables) Get(s context.Scope, name string) (value.Value, er return v.ctx.ObjectTTL, nil case REQ_BACKEND_IP: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.IP{Value: net.IPv4(127, 0, 0, 1)}, nil case REQ_BACKEND_IS_CLUSTER: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case REQ_BACKEND_NAME: var name string @@ -137,7 +156,7 @@ func (v *ErrorScopeVariables) Get(s context.Scope, name string) (value.Value, er } // Look up shared variables - if val, err := GetTCPInfoVariable(name); err != nil { + if val, err := GetTCPInfoVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil diff --git a/interpreter/variable/hash.go b/interpreter/variable/hash.go index 73c9ba07..435f95fd 100644 --- a/interpreter/variable/hash.go +++ b/interpreter/variable/hash.go @@ -43,17 +43,17 @@ func (v *HashScopeVariables) Get(s context.Scope, name string) (value.Value, err } // Look up shared variables - if val, err := GetQuicVariable(name); err != nil { + if val, err := GetQuicVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetTLSVariable(v.ctx.Request.TLS, name); err != nil { + if val, err := GetTLSVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetFastlyInfoVariable(name); err != nil { + if val, err := GetFastlyInfoVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil diff --git a/interpreter/variable/hit.go b/interpreter/variable/hit.go index f36fb68b..d8b69b5a 100644 --- a/interpreter/variable/hit.go +++ b/interpreter/variable/hit.go @@ -63,14 +63,20 @@ func (v *HitScopeVariables) Get(s context.Scope, name string) (value.Value, erro // alias for obj.grace return v.ctx.ObjectGrace, nil case OBJ_STALE_WHILE_REVALIDATE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } // Return fixed value because we don't support SWR yet return &value.RTime{Value: 60 * time.Second}, nil case OBJ_STATUS: return &value.Integer{Value: int64(v.ctx.Object.StatusCode)}, nil case OBJ_TTL: return v.ctx.ObjectTTL, nil - // Digest ratio will return fixed value + // Digest ratio will return fixed value if not override case REQ_DIGEST_RATIO: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0.4}, nil } diff --git a/interpreter/variable/log.go b/interpreter/variable/log.go index d15f03b4..24b6cb61 100644 --- a/interpreter/variable/log.go +++ b/interpreter/variable/log.go @@ -63,13 +63,25 @@ func (v *LogScopeVariables) Get(s context.Scope, name string) (value.Value, erro case CLIENT_SOCKET_CONGESTION_ALGORITHM: return v.ctx.ClientSocketCongestionAlgorithm, nil case CLIENT_SOCKET_CWND: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } // Sometimes change this value but we don't know how change it without set statement return &value.Integer{Value: 60}, nil case CLIENT_SOCKET_NEXTHOP: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.IP{Value: net.IPv4(127, 0, 0, 1)}, nil case CLIENT_SOCKET_PACE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case CLIENT_SOCKET_PLOSS: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0}, nil case ESI_ALLOW_INSIDE_CDATA: @@ -125,8 +137,14 @@ func (v *LogScopeVariables) Get(s context.Scope, name string) (value.Value, erro return &value.Boolean{Value: v.ctx.Request.Method == "PURGE"}, nil case REQ_BACKEND_IP: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.IP{Value: net.IPv4(127, 0, 0, 1)}, nil case REQ_BACKEND_IS_CLUSTER: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case REQ_BACKEND_NAME: var name string @@ -174,18 +192,30 @@ func (v *LogScopeVariables) Get(s context.Scope, name string) (value.Value, erro req.Body = io.NopCloser(bytes.NewReader(buf.Bytes())) readBytes += n return &value.Integer{Value: readBytes}, nil - // Digest ratio will return fixed value + // Digest ratio will return fixed value if not override case REQ_DIGEST_RATIO: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0.4}, nil // FIXME: We need to send actual request to the backend case RESP_BODY_BYTES_WRITTEN: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case RESP_BYTES_WRITTEN: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case RESP_COMPLETED: return &value.Boolean{Value: true}, nil case RESP_HEADER_BYTES_WRITTEN: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case RESP_IS_LOCALLY_GENERATED: return v.ctx.IsLocallyGenerated, nil @@ -232,14 +262,29 @@ func (v *LogScopeVariables) Get(s context.Scope, name string) (value.Value, erro // FIXME: segmented_caching related variables is just fake value case SEGMENTED_CACHING_AUTOPURGED: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case SEGMENTED_CACHING_BLOCK_NUMBER: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 1}, nil case SEGMENTED_CACHING_BLOCK_SIZE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 1}, nil case SEGMENTED_CACHING_CANCELLED: // nolint: misspell + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case SEGMENTED_CACHING_CLIENT_REQ_IS_OPEN_ENDED: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case SEGMENTED_CACHING_CLIENT_REQ_IS_RANGE: return &value.Boolean{Value: req.Header.Get("Range") != ""}, nil @@ -264,44 +309,71 @@ func (v *LogScopeVariables) Get(s context.Scope, name string) (value.Value, erro } return &value.Integer{Value: low}, nil case SEGMENTED_CACHING_COMPLETED: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case SEGMENTED_CACHING_ERROR: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.String{Value: ""}, nil case SEGMENTED_CACHING_FAILED: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case SEGMENTED_CACHING_IS_INNER_REQ: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case SEGMENTED_CACHING_IS_OUTER_REQ: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: true}, nil case SEGMENTED_CACHING_OBJ_COMPLETE_LENGTH: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case SEGMENTED_CACHING_ROUNDED_REQ_RANGE_HIGH: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case SEGMENTED_CACHING_ROUNDED_REQ_RANGE_LOW: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case SEGMENTED_CACHING_TOTAL_BLOCKS: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case FASTLY_INFO_REQUEST_ID: return v.ctx.RequestID, nil } // Look up shared variables - if val, err := GetTCPInfoVariable(name); err != nil { + if val, err := GetTCPInfoVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetQuicVariable(name); err != nil { + if val, err := GetQuicVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetTLSVariable(v.ctx.Request.TLS, name); err != nil { + if val, err := GetTLSVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetFastlyInfoVariable(name); err != nil { + if val, err := GetFastlyInfoVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil diff --git a/interpreter/variable/miss.go b/interpreter/variable/miss.go index 796bc22a..3e9a7e67 100644 --- a/interpreter/variable/miss.go +++ b/interpreter/variable/miss.go @@ -73,16 +73,28 @@ func (v *MissScopeVariables) Get(s context.Scope, name string) (value.Value, err // Always 1 because simulator could not simulate pop behavior case FASTLY_FF_VISITS_THIS_POP_THIS_SERVICE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 1}, nil // Always false because simulator could not simulate origin-shielding case FASTLY_INFO_IS_CLUSTER_SHIELD: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil // We simulate request is always pass to the origin, not consider shielding case REQ_BACKEND_IS_ORIGIN: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: true}, nil // Digest ratio will return fixed value case REQ_DIGEST_RATIO: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0.4}, nil } diff --git a/interpreter/variable/pass.go b/interpreter/variable/pass.go index 2201e426..a582d8fc 100644 --- a/interpreter/variable/pass.go +++ b/interpreter/variable/pass.go @@ -69,9 +69,15 @@ func (v *PassScopeVariables) Get(s context.Scope, name string) (value.Value, err return &value.String{Value: bereq.URL.RawQuery}, nil // We simulate request is always pass to the origin, not consider shielding case REQ_BACKEND_IS_ORIGIN: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: true}, nil // Digest ratio will return fixed value case REQ_DIGEST_RATIO: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0.4}, nil } diff --git a/interpreter/variable/recv.go b/interpreter/variable/recv.go index 253627a3..ae8edbec 100644 --- a/interpreter/variable/recv.go +++ b/interpreter/variable/recv.go @@ -32,13 +32,25 @@ func (v *RecvScopeVariables) Get(s context.Scope, name string) (value.Value, err case CLIENT_SOCKET_CONGESTION_ALGORITHM: return v.ctx.ClientSocketCongestionAlgorithm, nil case CLIENT_SOCKET_CWND: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } // Sometimes change this value but we don't know how change it without set statement return &value.Integer{Value: 60}, nil case CLIENT_SOCKET_NEXTHOP: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.IP{Value: net.IPv4(127, 0, 0, 1)}, nil case CLIENT_SOCKET_PACE: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case CLIENT_SOCKET_PLOSS: + if v := lookupOverride(v.ctx, name); v != nil { + return v, nil + } return &value.Float{Value: 0}, nil case ESI_ALLOW_INSIDE_CDATA: @@ -73,22 +85,22 @@ func (v *RecvScopeVariables) Get(s context.Scope, name string) (value.Value, err } // Look up shared variables - if val, err := GetTCPInfoVariable(name); err != nil { + if val, err := GetTCPInfoVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetQuicVariable(name); err != nil { + if val, err := GetQuicVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetTLSVariable(v.ctx.Request.TLS, name); err != nil { + if val, err := GetTLSVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil } - if val, err := GetFastlyInfoVariable(name); err != nil { + if val, err := GetFastlyInfoVariable(v.ctx, name); err != nil { return value.Null, errors.WithStack(err) } else if val != nil { return val, nil diff --git a/interpreter/variable/shared.go b/interpreter/variable/shared.go index 0a52023c..64f0298e 100644 --- a/interpreter/variable/shared.go +++ b/interpreter/variable/shared.go @@ -1,7 +1,6 @@ package variable import ( - "crypto/tls" "fmt" "time" @@ -11,17 +10,23 @@ import ( "github.com/ysugimoto/falco/interpreter/value" ) -func GetFastlyInfoVariable(name string) (value.Value, error) { +func GetFastlyInfoVariable(ctx *context.Context, name string) (value.Value, error) { switch name { case FASTLY_INFO_H2_IS_PUSH: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case FASTLY_INFO_H2_STREAM_ID: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 1}, nil } return nil, nil } -func GetQuicVariable(name string) (value.Value, error) { +func GetQuicVariable(ctx *context.Context, name string) (value.Value, error) { switch name { // QUIC related values return zero case QUIC_CC_CWND, @@ -38,15 +43,21 @@ func GetQuicVariable(name string) (value.Value, error) { QUIC_RTT_MINIMUM, QUIC_RTT_SMOOTHED, QUIC_RTT_VARIANCE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil } return nil, nil } -func GetTCPInfoVariable(name string) (value.Value, error) { +func GetTCPInfoVariable(ctx *context.Context, name string) (value.Value, error) { switch name { // We treat that tcp_info is disabled so following value is zero case CLIENT_SOCKET_TCP_INFO: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case CLIENT_SOCKET_TCPI_ADVMSS, CLIENT_SOCKET_TCPI_BYTES_ACKED, @@ -74,26 +85,44 @@ func GetTCPInfoVariable(name string) (value.Value, error) { CLIENT_SOCKET_TCPI_SND_MSS, CLIENT_SOCKET_TCPI_SND_SSTHRESH, CLIENT_SOCKET_TCPI_TOTAL_RETRANS: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil - case TLS_CLIENT_CERTIFICATE_DN: - case TLS_CLIENT_CERTIFICATE_ISSUER_DN: - case TLS_CLIENT_CERTIFICATE_RAW_CERTIFICATE_B64: - case TLS_CLIENT_CERTIFICATE_SERIAL_NUMBER: + case TLS_CLIENT_CERTIFICATE_DN, + TLS_CLIENT_CERTIFICATE_ISSUER_DN, + TLS_CLIENT_CERTIFICATE_RAW_CERTIFICATE_B64, + TLS_CLIENT_CERTIFICATE_SERIAL_NUMBER: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.String{Value: ""}, nil - case TLS_CLIENT_CERTIFICATE_IS_CERT_BAD: - case TLS_CLIENT_CERTIFICATE_IS_CERT_EXPIRED: - case TLS_CLIENT_CERTIFICATE_IS_CERT_MISSING: - case TLS_CLIENT_CERTIFICATE_IS_CERT_REVOKED: - case TLS_CLIENT_CERTIFICATE_IS_CERT_UNKNOWN: - case TLS_CLIENT_CERTIFICATE_IS_UNKNOWN_CA: + case TLS_CLIENT_CERTIFICATE_IS_CERT_BAD, + TLS_CLIENT_CERTIFICATE_IS_CERT_EXPIRED, + TLS_CLIENT_CERTIFICATE_IS_CERT_MISSING, + TLS_CLIENT_CERTIFICATE_IS_CERT_REVOKED, + TLS_CLIENT_CERTIFICATE_IS_CERT_UNKNOWN, + TLS_CLIENT_CERTIFICATE_IS_UNKNOWN_CA: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: false}, nil case TLS_CLIENT_CERTIFICATE_IS_VERIFIED: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Boolean{Value: true}, nil case TLS_CLIENT_CERTIFICATE_NOT_BEFORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Time{Value: time.Now().Add(-24 * time.Hour)}, nil case TLS_CLIENT_CERTIFICATE_NOT_AFTER: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Time{Value: time.Now().Add(-24 * time.Hour).Add(24 * time.Hour * 365)}, nil } @@ -102,7 +131,9 @@ func GetTCPInfoVariable(name string) (value.Value, error) { // TODO: consider we need to construct TLS server manually instead of net/http server // Temporarily return tentative data found in Fastly fiddle -func GetTLSVariable(s *tls.ConnectionState, name string) (value.Value, error) { +func GetTLSVariable(ctx *context.Context, name string) (value.Value, error) { + s := ctx.Request.TLS + switch name { case TLS_CLIENT_CIPHER: if s == nil { @@ -112,26 +143,50 @@ func GetTLSVariable(s *tls.ConnectionState, name string) (value.Value, error) { } case TLS_CLIENT_CIPHERS_LIST: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.String{ // nolint: lll Value: "130213031301C02FC02BC030C02C009EC0270067C028006B00A3009FCCA9CCA8CCAAC0AFC0ADC0A3C09FC05DC061C057C05300A2C0AEC0ACC0A2C09EC05CC060C056C052C024006AC0230040C00AC01400390038C009C01300330032009DC0A1C09DC051009CC0A0C09CC050003D003C0035002F00FF", }, nil case TLS_CLIENT_CIPHERS_LIST_SHA: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.String{Value: "JZtiTn8H/ntxORk+XXvU2EvNoz8="}, nil case TLS_CLIENT_CIPHERS_LIST_TXT: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.String{ // nolint: lll Value: "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256:TLS_DHE_RSA_WITH_AES_128_CBC_SHA256:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384:TLS_DHE_RSA_WITH_AES_256_CBC_SHA256:TLS_DHE_DSS_WITH_AES_256_GCM_SHA384:TLS_DHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CCM_8:TLS_ECDHE_ECDSA_WITH_AES_256_CCM:TLS_DHE_RSA_WITH_AES_256_CCM_8:TLS_DHE_RSA_WITH_AES_256_CCM:TLS_ECDHE_ECDSA_WITH_ARIA_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_DHE_DSS_WITH_ARIA_256_GCM_SHA384:TLS_DHE_RSA_WITH_ARIA_256_GCM_SHA384:TLS_DHE_DSS_WITH_AES_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8:TLS_ECDHE_ECDSA_WITH_AES_128_CCM:TLS_DHE_RSA_WITH_AES_128_CCM_8:TLS_DHE_RSA_WITH_AES_128_CCM:TLS_ECDHE_ECDSA_WITH_ARIA_128_GCM_SHA256:TLS_ECDHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_DHE_DSS_WITH_ARIA_128_GCM_SHA256:TLS_DHE_RSA_WITH_ARIA_128_GCM_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384:TLS_DHE_DSS_WITH_AES_256_CBC_SHA256:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256:TLS_DHE_DSS_WITH_AES_128_CBC_SHA256:TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_RSA_WITH_AES_256_CBC_SHA:TLS_DHE_DSS_WITH_AES_256_CBC_SHA:TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_RSA_WITH_AES_128_CBC_SHA:TLS_DHE_DSS_WITH_AES_128_CBC_SHA:TLS_RSA_WITH_AES_256_GCM_SHA384:TLS_RSA_WITH_AES_256_CCM_8:TLS_RSA_WITH_AES_256_CCM:TLS_RSA_WITH_ARIA_256_GCM_SHA384:TLS_RSA_WITH_AES_128_GCM_SHA256:TLS_RSA_WITH_AES_128_CCM_8:TLS_RSA_WITH_AES_128_CCM:TLS_RSA_WITH_ARIA_128_GCM_SHA256:TLS_RSA_WITH_AES_256_CBC_SHA256:TLS_RSA_WITH_AES_128_CBC_SHA256:TLS_RSA_WITH_AES_256_CBC_SHA:TLS_RSA_WITH_AES_128_CBC_SHA:TLS_EMPTY_RENEGOTIATION_INFO_SCSV", }, nil case TLS_CLIENT_CIPHERS_SHA: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.String{Value: "+7dB1w3Ov9S4Ct3HG3Qed68pSko="}, nil case TLS_CLIENT_HANDSHAKE_SENT_BYTES: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 4759}, nil case TLS_CLIENT_IANA_CHOSEN_CIPHER_ID: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 49199}, nil case TLS_CLIENT_JA3_MD5: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.String{Value: "582a3b42ab84f78a5b376b1e29d6d367"}, nil case TLS_CLIENT_JA4: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } // Use fastly fiddle value // https://fiddle.fastly.dev/fiddle/67edbddf // Actually we may be able to calculate, algorithm is here: @@ -148,12 +203,21 @@ func GetTLSVariable(s *tls.ConnectionState, name string) (value.Value, error) { TLS_CLIENT_TLSEXTS_LIST_SHA, TLS_CLIENT_TLSEXTS_LIST_TXT, TLS_CLIENT_TLSEXTS_SHA: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.String{Value: ""}, nil // We could not simulate following variable, return with zero/empty case TRANSPORT_BW_ESTIMATE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case TRANSPORT_TYPE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } // TODO: will be "quic" if we have support quic protocol return &value.String{Value: "tcp"}, nil } @@ -166,42 +230,99 @@ func GetTLSVariable(s *tls.ConnectionState, name string) (value.Value, error) { func GetWafVariables(ctx *context.Context, name string) (value.Value, error) { switch name { case WAF_ANOMALY_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafAnomalyScore, nil case WAF_BLOCKED: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafBlocked, nil case WAF_COUNTER: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafCounter, nil case WAF_EXECUTED: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafExecuted, nil case WAF_FAILURES: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return &value.Integer{Value: 0}, nil case WAF_HTTP_VIOLATION_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafHttpViolationScore, nil case WAF_INBOUND_ANOMALY_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafInboundAnomalyScore, nil case WAF_LFI_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafLFIScore, nil case WAF_LOGDATA: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafLogData, nil case WAF_LOGGED: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafLogged, nil case WAF_MESSAGE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafMessage, nil case WAF_PASSED: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafPassed, nil case WAF_PHP_INJECTION_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafPHPInjectionScore, nil case WAF_RCE_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafRCEScore, nil case WAF_RFI_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafRFIScore, nil case WAF_RULE_ID: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafRuleId, nil case WAF_SESSION_FIXATION_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafSessionFixationScore, nil case WAF_SEVERITY: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafSeverity, nil case WAF_XSS_SCORE: + if v := lookupOverride(ctx, name); v != nil { + return v, nil + } return ctx.WafXSSScore, nil } return nil, nil diff --git a/interpreter/variable/variable.go b/interpreter/variable/variable.go index 0fdb7ff5..f5f5e2f3 100644 --- a/interpreter/variable/variable.go +++ b/interpreter/variable/variable.go @@ -62,3 +62,10 @@ func doAssign(left value.Value, operator string, right value.Value) error { return assign.Assign(left, right.Copy()) } } + +func lookupOverride(ctx *context.Context, name string) value.Value { + if v, ok := ctx.OverrideVariables[name]; ok { + return v + } + return nil +} diff --git a/tester/function/testing_functions.go b/tester/function/testing_functions.go index 38460b3d..fead375b 100644 --- a/tester/function/testing_functions.go +++ b/tester/function/testing_functions.go @@ -141,6 +141,14 @@ func testingFunctions(i *interpreter.Interpreter, defs *Definiions) Functions { return false }, }, + "testing.inject_variable": { + Scope: allScope, + Call: Testing_inject_variable, + CanStatementCall: true, + IsIdentArgument: func(i int) bool { + return false + }, + }, } } diff --git a/tester/function/testing_inject_variable.go b/tester/function/testing_inject_variable.go new file mode 100644 index 00000000..9dddf1bc --- /dev/null +++ b/tester/function/testing_inject_variable.go @@ -0,0 +1,33 @@ +package function + +import ( + "github.com/ysugimoto/falco/interpreter/context" + "github.com/ysugimoto/falco/interpreter/function/errors" + "github.com/ysugimoto/falco/interpreter/value" +) + +const Testing_inject_variable_Name = "testing.inject_variable" + +func Testing_inject_variable_Validate(args []value.Value) error { + if len(args) != 2 { + return errors.ArgumentNotEnough(Testing_inject_variable_Name, 2, args) + } + if args[0].Type() != value.StringType { + return errors.TypeMismatch(Testing_inject_variable_Name, 1, value.StringType, args[0].Type()) + } + return nil +} + +func Testing_inject_variable( + ctx *context.Context, + args ...value.Value, +) (value.Value, error) { + + if err := Testing_inject_variable_Validate(args); err != nil { + return nil, errors.NewTestingError(err.Error()) + } + + name := value.Unwrap[*value.String](args[0]) + ctx.OverrideVariables[name.Value] = args[1] + return value.Null, nil +} diff --git a/tester/function/testing_inject_variable_test.go b/tester/function/testing_inject_variable_test.go new file mode 100644 index 00000000..e12fbb3e --- /dev/null +++ b/tester/function/testing_inject_variable_test.go @@ -0,0 +1,59 @@ +package function + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/ysugimoto/falco/interpreter/context" + "github.com/ysugimoto/falco/interpreter/value" + "github.com/ysugimoto/falco/interpreter/variable" +) + +func Test_inject_variable(t *testing.T) { + + t.Run("inject tentative variable", func(t *testing.T) { + tests := []struct { + name string + tentative string + override string + }{ + { + name: "server.region", + tentative: "US", + override: "ASIA", + }, + } + + for _, tt := range tests { + c := &context.Context{ + OverrideVariables: map[string]value.Value{}, + } + v := variable.NewAllScopeVariables(c) + before, err := v.Get(context.RecvScope, tt.name) + if err != nil { + t.Errorf("Unexpected error on getting %s variable, %s", tt.name, err) + return + } + bv := value.Unwrap[*value.String](before) + if diff := cmp.Diff(bv.Value, tt.tentative); diff != "" { + t.Errorf("tentative value is different, diff=%s", diff) + } + + _, err = Testing_inject_variable(c, &value.String{Value: tt.name}, &value.String{Value: tt.override}) + if err != nil { + t.Errorf("Unexpected error on Test_inject_variable, %s", err) + return + } + + after, err := v.Get(context.RecvScope, tt.name) + if err != nil { + t.Errorf("Unexpected error on getting %s variable, %s", tt.name, err) + return + } + av := value.Unwrap[*value.String](after) + if diff := cmp.Diff(av.Value, tt.override); diff != "" { + t.Errorf("Overridden value is different, diff=%s", diff) + } + } + }) +}