From 9b320e45aaf20741ede79373bd98fb0dd89b1b10 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 19 Jun 2023 16:48:59 +0200 Subject: [PATCH 01/24] Add Open Policy Agent based filters Signed-off-by: Magnus Jungsbluth --- config/config.go | 11 + docs/reference/filters.md | 206 ++++++++ .../authorizewithregopolicy.go | 159 +++++++ .../authorizewithregopolicy_test.go | 230 +++++++++ filters/openpolicyagent/common.go | 7 + filters/openpolicyagent/evaluation.go | 81 ++++ .../internal/envoy/envoyextauth.go | 7 + .../internal/envoy/envoyplugin.go | 121 +++++ .../internal/envoy/policytype.go | 21 + .../internal/envoy/skipperadapter.go | 45 ++ filters/openpolicyagent/internal/util/util.go | 33 ++ filters/openpolicyagent/openpolicyagent.go | 309 ++++++++++++ .../openpolicyagent/openpolicyagent_test.go | 27 ++ filters/openpolicyagent/response.go | 63 +++ .../serveresponsewithregopolicy.go | 115 +++++ .../serveresponsewithregopolicy_test.go | 210 +++++++++ go.mod | 60 ++- go.sum | 440 +++++++++++++++++- skipper.go | 17 + 19 files changed, 2147 insertions(+), 15 deletions(-) create mode 100644 filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go create mode 100644 filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go create mode 100644 filters/openpolicyagent/common.go create mode 100644 filters/openpolicyagent/evaluation.go create mode 100644 filters/openpolicyagent/internal/envoy/envoyextauth.go create mode 100644 filters/openpolicyagent/internal/envoy/envoyplugin.go create mode 100644 filters/openpolicyagent/internal/envoy/policytype.go create mode 100644 filters/openpolicyagent/internal/envoy/skipperadapter.go create mode 100644 filters/openpolicyagent/internal/util/util.go create mode 100644 filters/openpolicyagent/openpolicyagent.go create mode 100644 filters/openpolicyagent/openpolicyagent_test.go create mode 100644 filters/openpolicyagent/response.go create mode 100644 filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go create mode 100644 filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go diff --git a/config/config.go b/config/config.go index 5f9df1d99e..05e6178d94 100644 --- a/config/config.go +++ b/config/config.go @@ -273,6 +273,10 @@ type Config struct { LuaModules *listFlag `yaml:"lua-modules"` LuaSources *listFlag `yaml:"lua-sources"` + + // Open Policy Agent + EnableOpenPolicyAgent bool `yaml:"enable-open-policy-agent"` + OpenPolicyAgentConfigTemplate string `yaml:"open-policy-agent-config-template"` } const ( @@ -484,6 +488,10 @@ func NewConfig() *Config { flag.Var(cfg.CredentialPaths, "credentials-paths", "directories or files to watch for credentials to use by bearerinjector filter") flag.DurationVar(&cfg.CredentialsUpdateInterval, "credentials-update-interval", 10*time.Minute, "sets the interval to update secrets") + // Open Policy Agent + flag.BoolVar(&cfg.EnableOpenPolicyAgent, "enable-open-policy-agent", false, "enables Open Policy Agent filters") + flag.StringVar(&cfg.OpenPolicyAgentConfigTemplate, "open-policy-agent-config-template", "", "file containing a template for an Open Policy Agent configuration file that is interpolated for each OPA filter instance") + // TLS client certs flag.StringVar(&cfg.ClientKeyFile, "client-tls-key", "", "TLS Key file for backend connections, multiple keys may be given comma separated - the order must match the certs") flag.StringVar(&cfg.ClientCertFile, "client-tls-cert", "", "TLS certificate files for backend connections, multiple keys may be given comma separated - the order must match the keys") @@ -879,6 +887,9 @@ func (c *Config) ToOptions() skipper.Options { LuaModules: c.LuaModules.values, LuaSources: c.LuaSources.values, + + EnableOpenPolicyAgent: c.EnableOpenPolicyAgent, + OpenPolicyAgentConfigTemplate: c.OpenPolicyAgentConfigTemplate, } for _, rcci := range c.CloneRoute { eskipClone := eskip.NewClone(rcci.Reg, rcci.Repl) diff --git a/docs/reference/filters.md b/docs/reference/filters.md index f2478bb5b5..d4767097a1 100644 --- a/docs/reference/filters.md +++ b/docs/reference/filters.md @@ -1719,6 +1719,212 @@ oidcClaimsQuery("/:name%\"*One\"", "/path:groups.#[%\"*-Test-Users\"] groups.#[= As of now there is no negative/deny rule possible. The first matching path is evaluated against the defined query/queries and if positive, permitted. +## Open Policy Agent + +To enable [Open Policy Agent](https://www.openpolicyagent.org/) filter, use the `-enable-open-policy-agent` command line flag. + +Open Policy Agent is integrated as a Go library so no extra setup is needed to run. Every filter creates a virtual OPA instance in memory that is configured using a configuration file in the same [configuration format](https://www.openpolicyagent.org/docs/latest/configuration/) that a standalone OPA would use. To allow for configurability, the configuration file is interpolated using Go Templates to allow every virtual instance to pull different bundles. This template file is passed using the `-open-policy-agent-config-template` flag. + +### Configuration File + +As an example the following initial config can be used + +```yaml +services: + - name: bundle-service + url: https://my-example-opa-bucket.s3.eu-central-1.amazonaws.com + credentials: + s3_signing: + environment_credentials: {} +labels: + environment: production +discovery: + name: discovery + prefix: "/applications/{{ .bundlename }}" +``` + +The variable `.bundlename` is the first argument in the following filters and can be in any format that OPA can understand, so for example application IDs from a registry, uuids, ... + +### Envoy Structures + +While Envoy is an alternative OSS product similar to Skipper, it has already defined structures for how external authorization should be done and also how authorization decisions can influence the Envoy response. Open Policy Agent already has direct support for this and also commercial control planes support this. On top of this [examples and documentation](https://www.openpolicyagent.org/docs/latest/envoy-primer/) already exist. +Instead of re-inventing these structures (for example how http headers and so on are represented), this implementation maps Skipper objects onto their Envoy representation. This also allows to reuse a fair bit of the [opa-envoy-plugin](https://github.com/open-policy-agent/opa-envoy-plugin), which does the heavy lifting of evaluating decisions against the OPA Go library. + +### Passing context to the policy + +Generally there are two ways to pass context to a policy: + +1. as part of the labels on Open Policy Agent (configured in the configuration file, see below) that should be used for deployment level taxonomy, +2. as part of so called context extensions that are part of the Envoy external auth specification. + +This context can be passed as second argument to filters: + +`authorizeWithRegoPolicy("my-app-id", "com.mycompany.myprop: myvalue")` +or `authorizeWithRegoPolicy("my-app-id", "{'com.mycompany.myprop': 'my value'}")` + +The second argument is parsed as YAML, cannot be nested and values need to be strings. + +In Rego this can be used like this `input.attributes.contextExtensions["com.mycompany.myprop"] == "my value"` + +### Quick Start Rego Playground + +A quick way without setting up Backend APIs is to use the [Rego Playground](https://play.openpolicyagent.org/). + +To get started pick from examples Envoy > Hello World. Click on "Publish" and note the random ID in the section "Run OPA with playground policy". + +Place the following file in your local directory with the name `opaconfig.yaml` + +```yaml +bundles: + play: + resource: bundles/{{ .bundlename }} + polling: + long_polling_timeout_seconds: 45 +services: + - name: play + url: https://play.openpolicyagent.org +plugins: + envoy_ext_authz_grpc: + # This needs to match the package, defaulting to envoy/authz/allow + path: envoy/http/public/allow + dry-run: false +decision_logs: + console: true +``` + +Start Skipper with + +``` +skipper --enable-open-policy-agent --open-policy-agent-config-template opaconfig.yaml \ + --inline-routes 'notfound: * -> authorizeWithRegoPolicy("") -> inlineContent("

Authorized Hello

") -> ' +``` + +You can test the policy with + +- Authorized: `curl http://localhost:9090/ -i` +- Authorized: `curl http://localhost:9090/foobar -H "Authorization: Basic charlie" -i` +- Forbidden: `curl http://localhost:9090/foobar -i` + + + +### authorizeWithRegoPolicy + +The canonical use case that is also implemented with [Envoy External Authorization](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter): Use the http request to evaluate if Skipper should deny the request (with customizable response) or let the request pass to the downstream service + +Example: + +``` +authorizeWithRegoPolicy("my-app-id") +``` + +Example (passing context): +``` +authorizeWithRegoPolicy("my-app-id", "com.mydomain.xxx.myprop: myvalue") +``` + +#### Data Flows + +The data flow in case the policy allows the request looks like this + +```ascii + ┌──────────────────┐ ┌────────────────────┐ + (1) Request │ Skipper │ (4) Request │ Target Aplication │ +─────────────┤ ├──────────────►│ │ + │ │ │ │ + (6) Response│ (2)│ ▲ (3) │ (5) Response │ │ +◄────────────┤Req ->│ │ allow │◄──────────────┤ │ + │Input │ │ │ │ │ + ├──────┴───┴───────┤ └────────────────────┘ + │Open Policy Agent │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ ▼ │ │ + │ ┌────────┴─────┐ │ + │ │ Policy │ │ + │ └──────────────┘ │ + │ │ + └──────────────────┘ + +``` + +In Step (2) the http request is transformed into an input object following the [Envoy structure](https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#service-auth-v3-checkrequest) that is also used by the OPA Envoy plugin. In (3) the decision of the policy is evaluated. If it is equivalent to an "allow", the remaining steps are executed as without the filter. + +The data flow in case the policy disallows the request looks like this + +```ascii + ┌──────────────────┐ ┌────────────────────┐ + (1) Request │ Skipper │ │ Target Aplication │ +─────────────┤ ├──────────────►│ │ + │ │ │ │ + (4) Response│ (2)│ ▲ (3) │ │ │ +◄────────────┤Req ->│ │ allow │◄──────────────┤ │ + │Input │ │ =false│ │ │ + ├──────┴───┴───────┤ └────────────────────┘ + │Open Policy Agent │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ ▼ │ │ + │ ┌────────┴─────┐ │ + │ │ Policy │ │ + │ └──────────────┘ │ + │ │ + └──────────────────┘ + +``` + +The difference is that if the decision in (3) is equivalent to false, the response is handled directly from the filter. If the decision contains response body, status or headers those are used to build the response in (6) otherwise a 403 Forbidden with a generic body is returned. + +#### Manipulating Request Headers + +Headers both to the upstream and the downstream service can be manipulated the same way this works for [Envoy external authorization](https://www.openpolicyagent.org/docs/latest/envoy-primer/#example-policy-with-additional-controls) + +This allows both to add and remove unwanted headers in allow/deny cases. + +### serveResponseWithRegoPolicy + +Always serves the response even if the policy allows the request and can customize the response completely. Can be used to re-implement legacy authorization services by already using data in Open Policy Agent but implementing an old REST API. This can also be useful to support Single Page Applications to return the calling users' permissions. + +Example: + +``` +serveResponseWithRegoPolicy("my-app-id") +``` + +Example (passing context): +``` +serveResponseWithRegoPolicy("my-app-id", "com.mydomain.xxx.myprop: myvalue") +``` + +#### Data Flows + +## Diagram + +For this filter, the data flow looks like this independent of an allow/deny decision + +```ascii + ┌──────────────────┐ + (1) Request │ Skipper │ +─────────────┤ ├ + │ │ + (4) Response│ (2)│ ▲ (3) │ +◄────────────┤Req ->│ │ allow │ + │Input │ │ /deny │ + ├──────┴───┴───────┤ + │Open Policy Agent │ + │ │ │ │ + │ │ │ │ + │ │ │ │ + │ ▼ │ │ + │ ┌────────┴─────┐ │ + │ │ Policy │ │ + │ └──────────────┘ │ + │ │ + └──────────────────┘ + +``` + ## Cookie Handling ### dropRequestCookie diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go new file mode 100644 index 0000000000..2750c57b26 --- /dev/null +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -0,0 +1,159 @@ +package authorizewithregopolicy + +import ( + "net/http" + "time" + + "github.com/zalando/skipper/filters" + "gopkg.in/yaml.v2" + + "github.com/zalando/skipper/filters/openpolicyagent" + "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" + "github.com/zalando/skipper/filters/openpolicyagent/internal/util" +) + +type Spec struct { + factory openpolicyagent.OpenPolicyAgentFactory + configOpts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error +} + +func NewAuthorizeWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFactory, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) Spec { + return Spec{ + factory: factory, + configOpts: opts, + } +} + +func (s Spec) Name() string { + return "authorizeWithRegoPolicy" +} + +const ( + paramBundleName int = iota + paramEnvoyContextExtensions +) + +func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { + var err error + + sargs, err := util.GetStrings(config) + if err != nil { + return nil, err + } + + if len(sargs) < 1 { + return nil, filters.ErrInvalidFilterParameters + } + + if len(sargs) > 2 { + return nil, filters.ErrInvalidFilterParameters + } + + bundleName := sargs[paramBundleName] + + configOptions := s.configOpts + + if len(sargs) > 1 { + envoyContextExtensions := map[string]string{} + err = yaml.Unmarshal([]byte(sargs[paramEnvoyContextExtensions]), &envoyContextExtensions) + if err != nil { + return nil, err + } + configOptions = append(configOptions, openpolicyagent.WithEnvoyContextExtensions(envoyContextExtensions)) + } + + opaConfig, err := openpolicyagent.NewOpenPolicyAgentConfig(configOptions...) + if err != nil { + return nil, err + } + + opa, err := s.factory.NewOpenPolicyAgentEnvoyInstance(bundleName, *opaConfig, s.Name()) + + if err != nil { + return nil, err + } + + return authorizeWithRegoPolicyFilter{ + opa: opa, + }, nil +} + +type authorizeWithRegoPolicyFilter struct { + opa *openpolicyagent.OpenPolicyAgentInstance +} + +func (f authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { + start := time.Now() + + req := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetPolicyType(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + + result, err := f.opa.Eval(fc.Request().Context(), req) + + if err != nil { + f.opa.RejectInvalidDecisionError(fc, result, err) + return + } + + f.opa.Logger().WithFields(map[string]interface{}{ + "query": f.opa.EnvoyPluginConfig().ParsedQuery.String(), + "dry-run": f.opa.EnvoyPluginConfig().DryRun, + "decision": result.Decision, + "err": err, + "txn": result.TxnID, + "metrics": result.Metrics.All(), + "total_decision_time": time.Since(start), + }).Debug("Authorizing request with decision.") + + if !f.opa.EnvoyPluginConfig().DryRun { + allowed, err := result.IsAllowed() + + if err != nil { + f.opa.RejectInvalidDecisionError(fc, result, err) + return + } + + if !allowed { + f.opa.ServeResponse(fc, result) + return + } + + if result.HasResponseBody() { + body, err := result.GetResponseBody() + if err != nil { + f.opa.RejectInvalidDecisionError(fc, result, err) + return + } + fc.StateBag()[openpolicyagent.OpenPolicyAgentDecisionBodyKey] = body + } + + headers, err := result.GetResponseHTTPHeaders() + if err != nil { + f.opa.RejectInvalidDecisionError(fc, result, err) + return + } + addRequestHeaders(fc, headers) + + headersToRemove, err := result.GetRequestHTTPHeadersToRemove() + if err != nil { + f.opa.RejectInvalidDecisionError(fc, result, err) + return + } + removeHeaders(fc, headersToRemove) + } +} + +func removeHeaders(fc filters.FilterContext, headersToRemove []string) { + for _, header := range headersToRemove { + fc.Request().Header.Del(header) + } +} + +func addRequestHeaders(fc filters.FilterContext, headers http.Header) { + for key, values := range headers { + for _, value := range values { + fc.Request().Header.Add(key, value) + } + } +} + +func (f authorizeWithRegoPolicyFilter) Response(fc filters.FilterContext) {} diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go new file mode 100644 index 0000000000..40aa6389b0 --- /dev/null +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go @@ -0,0 +1,230 @@ +package authorizewithregopolicy + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "path" + "testing" + + opasdktest "github.com/open-policy-agent/opa/sdk/test" + "github.com/stretchr/testify/assert" + "github.com/zalando/skipper/eskip" + "github.com/zalando/skipper/filters" + "github.com/zalando/skipper/proxy/proxytest" + + "github.com/zalando/skipper/filters/openpolicyagent" +) + +func TestAuthorizeRequestFilter(t *testing.T) { + for _, ti := range []struct { + msg string + bundleName string + regoQuery string + requestPath string + expectedBody string + expectedHeaders http.Header + expectedStatus int + backendHeaders http.Header + removeHeaders http.Header + }{ + { + msg: "Allow Requests", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow", + requestPath: "allow", + expectedStatus: http.StatusOK, + expectedBody: "Welcome!", + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), + }, + { + msg: "Simple Forbidden", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow", + requestPath: "forbidden", + expectedStatus: http.StatusForbidden, + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), + }, + { + msg: "Allow With Structured Rules", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object", + requestPath: "allow/structured", + expectedStatus: http.StatusOK, + expectedBody: "Welcome!", + expectedHeaders: make(http.Header), + backendHeaders: map[string][]string{"X-Consumer": {"x-consumer header value"}}, + removeHeaders: map[string][]string{"X-Remove-Me": {"Remove me"}}, + }, + { + msg: "Forbidden With Body", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object", + requestPath: "forbidden", + expectedStatus: http.StatusUnauthorized, + expectedHeaders: map[string][]string{"X-Ext-Auth-Allow": {"no"}}, + expectedBody: "Unauthorized Request", + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), + }, + } { + t.Run(ti.msg, func(t *testing.T) { + t.Logf("Running test for %v", ti) + clientServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Welcome!")) + assert.True(t, isHeadersPresent(ti.backendHeaders, r.Header), "Enriched request header is absent.") + assert.True(t, isHeadersAbsent(ti.removeHeaders, r.Header), "Unwanted HTTP Headers present.") + })) + + opaControlPlane := opasdktest.MustNewServer( + opasdktest.MockBundle("/bundles/"+ti.bundleName, map[string]string{ + "main.rego": ` + package envoy.authz + + default allow = false + + allow { + input.parsed_path = [ "allow" ] + } + + default allow_object = { + "allowed": false, + "headers": {"x-ext-auth-allow": "no"}, + "body": "Unauthorized Request", + "http_status": 401 + } + + allow_object = response { + input.parsed_path = [ "allow", "structured" ] + response := { + "allowed": true, + "headers": { + "x-consumer": "x-consumer header value" + }, + "request_headers_to_remove" : [ + "x-remove-me", + "absent-header" + ] + } + } + `, + }), + ) + + var routeFilters []*eskip.Filter + fr := make(filters.Registry) + + config := []byte(fmt.Sprintf(`{ + "services": { + "test": { + "url": %q + } + }, + "bundles": { + "test": { + "resource": "/bundles/{{ .bundlename }}" + } + }, + "plugins": { + "envoy_ext_authz_grpc": { + "path": %q, + "dry-run": false + } + } + }`, opaControlPlane.URL(), ti.regoQuery)) + + opaFactory := openpolicyagent.NewOpenPolicyAgentFactory() + ftSpec := NewAuthorizeWithRegoPolicySpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) + filterArgs := []interface{}{ti.bundleName} + _, err := ftSpec.CreateFilter(filterArgs) + if err != nil { + t.Fatalf("error in creating filter: %v", err) + } + fr.Register(ftSpec) + routeFilters = append(routeFilters, &eskip.Filter{Name: ftSpec.Name(), Args: filterArgs}) + + r := &eskip.Route{Filters: routeFilters, Backend: clientServer.URL} + + proxy := proxytest.New(fr, r) + reqURL, err := url.Parse(proxy.URL) + if err != nil { + t.Errorf("Failed to parse url %s: %v", proxy.URL, err) + } + reqURL.Path = path.Join(reqURL.Path, ti.requestPath) + req, err := http.NewRequest("GET", reqURL.String(), nil) + for name, values := range ti.removeHeaders { + req.Header.Add(name, values[0]) //adding the headers to validate removal. + } + + if err != nil { + t.Error(err) + return + } + + rsp, err := http.DefaultClient.Do(req) + if err != nil { + t.Error(err) + } + + assert.Equal(t, ti.expectedStatus, rsp.StatusCode, "HTTP status does not match") + + assert.True(t, isHeadersPresent(ti.expectedHeaders, rsp.Header), "HTTP Headers do not match") + + defer rsp.Body.Close() + body, err := io.ReadAll(rsp.Body) + if err != nil { + t.Error(err) + } + assert.Equal(t, ti.expectedBody, string(body), "HTTP Headers do not match") + }) + } +} + +func isHeadersPresent(expectedHeaders http.Header, headers http.Header) bool { + for headerName, expectedValues := range expectedHeaders { + actualValues, headerFound := headers[headerName] + if !headerFound { + return false + } + + if !areHeaderValuesEqual(expectedValues, actualValues) { + return false + } + } + return true +} + +func areHeaderValuesEqual(expectedValues, actualValues []string) bool { + if len(expectedValues) != len(actualValues) { + return false + } + + actualValueSet := make(map[string]struct{}) + for _, val := range actualValues { + actualValueSet[val] = struct{}{} + } + + for _, val := range expectedValues { + if _, ok := actualValueSet[val]; !ok { + return false + } + delete(actualValueSet, val) + } + + return len(actualValueSet) == 0 +} + +func isHeadersAbsent(unwantedHeaders http.Header, headers http.Header) bool { + for headerName := range unwantedHeaders { + if _, headerFound := headers[headerName]; headerFound { + return false + } + } + return true +} diff --git a/filters/openpolicyagent/common.go b/filters/openpolicyagent/common.go new file mode 100644 index 0000000000..bea907e67d --- /dev/null +++ b/filters/openpolicyagent/common.go @@ -0,0 +1,7 @@ +package openpolicyagent + +const ( + OpenPolicyAgentDecisionKey = "open-policy-agent:decision" + OpenPolicyAgentDecisionBodyKey = "open-policy-agent:decision-body" + OpenPolicyAgentDecisionHeadersKey = "open-policy-agent:decision-headers" +) diff --git a/filters/openpolicyagent/evaluation.go b/filters/openpolicyagent/evaluation.go new file mode 100644 index 0000000000..3f9dfbd3d7 --- /dev/null +++ b/filters/openpolicyagent/evaluation.go @@ -0,0 +1,81 @@ +package openpolicyagent + +import ( + "context" + "time" + + ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + "github.com/open-policy-agent/opa-envoy-plugin/envoyauth" + "github.com/open-policy-agent/opa-envoy-plugin/opa/decisionlog" + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/server" + "github.com/pkg/errors" +) + +func (opa *OpenPolicyAgentInstance) Eval(ctx context.Context, req *ext_authz_v3.CheckRequest) (*envoyauth.EvalResult, error) { + resp, finalFunc, err := opa.eval(ctx, req) + + logErr := finalFunc() + if logErr != nil { + opa.Logger().WithFields(map[string]interface{}{"err": logErr}).Error("Unable to log decision to control plane.") + } + + return resp, err +} + +func (opa *OpenPolicyAgentInstance) eval(ctx context.Context, req *ext_authz_v3.CheckRequest) (*envoyauth.EvalResult, func() error, error) { + var err error + + result, stopeval, err := envoyauth.NewEvalResult() + + if err != nil { + opa.Logger().WithFields(map[string]interface{}{"err": err}).Error("Unable to generate decision ID.") + return nil, func() error { return nil }, err + } + + var input map[string]interface{} + stop := func() error { + stopeval() + logErr := opa.logDecision(ctx, input, result, err) + if logErr != nil { + return logErr + } + return nil + } + + if ctx.Err() != nil { + err = errors.Wrap(ctx.Err(), "check request timed out before query execution") + return nil, stop, err + } + + logger := opa.manager.Logger().WithFields(map[string]interface{}{"decision-id": result.DecisionID}) + input, err = envoyauth.RequestToInput(req, logger, nil, true) + if err != nil { + return nil, stop, err + } + + inputValue, err := ast.InterfaceToValue(input) + if err != nil { + return nil, stop, err + } + + err = envoyauth.Eval(ctx, opa, inputValue, result) + if err != nil { + return nil, stop, err + } + + return result, stop, nil +} + +func (opa *OpenPolicyAgentInstance) logDecision(ctx context.Context, input interface{}, result *envoyauth.EvalResult, err error) error { + info := &server.Info{ + Timestamp: time.Now(), + Input: &input, + } + + if opa.EnvoyPluginConfig().Path != "" { + info.Path = opa.EnvoyPluginConfig().Path + } + + return decisionlog.LogDecision(ctx, opa.manager, info, result, err) +} diff --git a/filters/openpolicyagent/internal/envoy/envoyextauth.go b/filters/openpolicyagent/internal/envoy/envoyextauth.go new file mode 100644 index 0000000000..2a808b846a --- /dev/null +++ b/filters/openpolicyagent/internal/envoy/envoyextauth.go @@ -0,0 +1,7 @@ +package envoy + +const defaultPath = "envoy/authz/allow" +const defaultDryRun = false + +// PluginName is the name to register with the OPA plugin manager +const PluginName = "envoy_ext_authz_grpc" diff --git a/filters/openpolicyagent/internal/envoy/envoyplugin.go b/filters/openpolicyagent/internal/envoy/envoyplugin.go new file mode 100644 index 0000000000..eba70d7770 --- /dev/null +++ b/filters/openpolicyagent/internal/envoy/envoyplugin.go @@ -0,0 +1,121 @@ +package envoy + +import ( + "context" + "strconv" + "strings" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/plugins" + "github.com/open-policy-agent/opa/util" +) + +// Factory defines the interface OPA uses to instantiate a plugin. +type Factory struct{} + +// New returns the object initialized with a valid plugin configuration. +func (Factory) New(m *plugins.Manager, cfg interface{}) plugins.Plugin { + p := &Plugin{ + manager: m, + cfg: *cfg.(*PluginConfig), + } + + m.UpdatePluginStatus(PluginName, &plugins.Status{State: plugins.StateNotReady}) + + return p +} + +// Validate returns a valid configuration to instantiate the plugin. +func (Factory) Validate(m *plugins.Manager, bs []byte) (interface{}, error) { + cfg := PluginConfig{ + DryRun: defaultDryRun, + } + + if err := util.Unmarshal(bs, &cfg); err != nil { + return nil, err + } + + if err := cfg.ParseQuery(); err != nil { + return nil, err + } + + return &cfg, nil +} + +func (p *Plugin) Reconfigure(ctx context.Context, config interface{}) { + +} + +// PluginConfig represents the plugin configuration. +type PluginConfig struct { + Path string `json:"path"` + DryRun bool `json:"dry-run"` + ParsedQuery ast.Body +} + +type Plugin struct { + cfg PluginConfig + manager *plugins.Manager +} + +func (p *Plugin) Start(ctx context.Context) error { + p.manager.UpdatePluginStatus(PluginName, &plugins.Status{State: plugins.StateOK}) + return nil +} + +func (cfg *PluginConfig) ParseQuery() error { + var parsedQuery ast.Body + var err error + + if cfg.Path == "" { + cfg.Path = defaultPath + } + path := stringPathToDataRef(cfg.Path) + parsedQuery, err = ast.ParseBody(path.String()) + + if err != nil { + return err + } + + cfg.ParsedQuery = parsedQuery + + return nil +} + +func (p *Plugin) Stop(ctx context.Context) { + p.manager.UpdatePluginStatus(PluginName, &plugins.Status{State: plugins.StateNotReady}) +} + +func (p *Plugin) GetConfig() PluginConfig { + return p.cfg +} + +func (p *Plugin) ParsedQuery() ast.Body { return p.cfg.ParsedQuery } +func (p *Plugin) Path() string { return p.cfg.Path } + +func stringPathToDataRef(s string) (r ast.Ref) { + result := ast.Ref{ast.DefaultRootDocument} + result = append(result, stringPathToRef(s)...) + return result +} + +func stringPathToRef(s string) (r ast.Ref) { + if len(s) == 0 { + return r + } + + p := strings.Split(s, "/") + for _, x := range p { + if x == "" { + continue + } + + i, err := strconv.Atoi(x) + if err != nil { + r = append(r, ast.StringTerm(x)) + } else { + r = append(r, ast.IntNumberTerm(i)) + } + } + return r +} diff --git a/filters/openpolicyagent/internal/envoy/policytype.go b/filters/openpolicyagent/internal/envoy/policytype.go new file mode 100644 index 0000000000..7883b786a6 --- /dev/null +++ b/filters/openpolicyagent/internal/envoy/policytype.go @@ -0,0 +1,21 @@ +package envoy + +import "errors" + +type PolicyType string + +const ( + IngressPolicyType PolicyType = "ingress" + + EgressPolicyType PolicyType = "egress" + + AppPolicyType PolicyType = "app" +) + +func (lt PolicyType) IsValid() error { + switch lt { + case IngressPolicyType, EgressPolicyType, AppPolicyType: + return nil + } + return errors.New("invalid policy type") +} diff --git a/filters/openpolicyagent/internal/envoy/skipperadapter.go b/filters/openpolicyagent/internal/envoy/skipperadapter.go new file mode 100644 index 0000000000..101cac39b5 --- /dev/null +++ b/filters/openpolicyagent/internal/envoy/skipperadapter.go @@ -0,0 +1,45 @@ +package envoy + +import ( + "net/http" + "strings" + + ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" + _struct "github.com/golang/protobuf/ptypes/struct" +) + +func AdaptToEnvoyExtAuthRequest(req *http.Request, pt PolicyType, contextExtensions map[string]string) *ext_authz_v3.CheckRequest { + + headers := make(map[string]string) + for h, vv := range req.Header { + // This makes headers in the input compatible with what Envoy does, i.e. allows to use policy fragments designed for envoy + // See: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/header_casing#http-1-1-header-casing + headers[strings.ToLower(h)] = strings.Join(vv, ", ") + } + + return &ext_authz_v3.CheckRequest{ + Attributes: &ext_authz_v3.AttributeContext{ + Request: &ext_authz_v3.AttributeContext_Request{ + Http: &ext_authz_v3.AttributeContext_HttpRequest{ + Host: req.Host, + Method: req.Method, + Path: req.URL.Path, + Headers: headers, + }, + }, + ContextExtensions: contextExtensions, + MetadataContext: &ext_authz_v3_core.Metadata{ + FilterMetadata: map[string]*_struct.Struct{ + "envoy.filters.http.header_to_metadata": { + Fields: map[string]*_struct.Value{ + "policy_type": { + Kind: &_struct.Value_StringValue{StringValue: string(pt)}, + }, + }, + }, + }, + }, + }, + } +} diff --git a/filters/openpolicyagent/internal/util/util.go b/filters/openpolicyagent/internal/util/util.go new file mode 100644 index 0000000000..7b3257bf55 --- /dev/null +++ b/filters/openpolicyagent/internal/util/util.go @@ -0,0 +1,33 @@ +package util + +import ( + "crypto/rand" + "fmt" + "io" + + "github.com/zalando/skipper/filters" +) + +func Uuid4() (string, error) { + bs := make([]byte, 16) + _, err := io.ReadFull(rand.Reader, bs) + if err != nil { + return "", err + } + bs[8] = bs[8]&^0xc0 | 0x80 + bs[6] = bs[6]&^0xf0 | 0x40 + return fmt.Sprintf("%x-%x-%x-%x-%x", bs[0:4], bs[4:6], bs[6:8], bs[8:10], bs[10:]), nil +} + +func GetStrings(args []interface{}) ([]string, error) { + s := make([]string, len(args)) + var ok bool + for i, a := range args { + s[i], ok = a.(string) + if !ok { + return nil, filters.ErrInvalidFilterParameters + } + } + + return s, nil +} diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go new file mode 100644 index 0000000000..fe7be343b0 --- /dev/null +++ b/filters/openpolicyagent/openpolicyagent.go @@ -0,0 +1,309 @@ +package openpolicyagent + +import ( + "bytes" + "context" + "os" + "strings" + "sync" + "text/template" + "time" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/config" + "github.com/open-policy-agent/opa/logging" + "github.com/open-policy-agent/opa/plugins" + "github.com/open-policy-agent/opa/plugins/discovery" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/runtime" + "github.com/open-policy-agent/opa/storage" + "github.com/open-policy-agent/opa/storage/inmem" + iCache "github.com/open-policy-agent/opa/topdown/cache" + opautil "github.com/open-policy-agent/opa/util" + + "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" + "github.com/zalando/skipper/filters/openpolicyagent/internal/util" +) + +type OpenPolicyAgentFactory struct { + // Ideally share one Bundle storage across many OPA "instances". + // This allows to save memory on bundles that are shared + // between different policies (i.e. global team memberships) + // This not possible due to some limitations in OPA + // See https://github.com/open-policy-agent/opa/issues/5707 + bundleStorage storage.Store +} + +func NewOpenPolicyAgentFactory() OpenPolicyAgentFactory { + return OpenPolicyAgentFactory{ + //BundleStorage: inmem.New(), + } +} + +type OpenPolicyAgentInstanceConfig struct { + policyType envoy.PolicyType + configTemplate []byte + envoyContextExtensions map[string]string +} + +func WithPolicyType(policyType envoy.PolicyType) func(*OpenPolicyAgentInstanceConfig) error { + return func(cfg *OpenPolicyAgentInstanceConfig) error { + cfg.policyType = policyType + return nil + } +} + +func WithConfigTemplate(configTemplate []byte) func(*OpenPolicyAgentInstanceConfig) error { + return func(cfg *OpenPolicyAgentInstanceConfig) error { + cfg.configTemplate = configTemplate + return nil + } +} + +func WithConfigTemplateFile(configTemplateFile string) func(*OpenPolicyAgentInstanceConfig) error { + return func(cfg *OpenPolicyAgentInstanceConfig) error { + var err error + cfg.configTemplate, err = os.ReadFile(configTemplateFile) + return err + } +} + +func WithEnvoyContextExtensions(envoyContextExtensions map[string]string) func(*OpenPolicyAgentInstanceConfig) error { + return func(cfg *OpenPolicyAgentInstanceConfig) error { + cfg.envoyContextExtensions = envoyContextExtensions + return nil + } +} + +func (cfg *OpenPolicyAgentInstanceConfig) GetPolicyType() envoy.PolicyType { + return cfg.policyType +} + +func (cfg *OpenPolicyAgentInstanceConfig) GetEnvoyContextExtensions() map[string]string { + return cfg.envoyContextExtensions +} + +func NewOpenPolicyAgentConfig(opts ...func(*OpenPolicyAgentInstanceConfig) error) (*OpenPolicyAgentInstanceConfig, error) { + cfg := OpenPolicyAgentInstanceConfig{} + + cfg.policyType = envoy.IngressPolicyType + if val, found := os.LookupEnv("OPA_ENVOY_POLICY_TYPE"); found { + cfg.policyType = envoy.PolicyType(val) + } + + for _, opt := range opts { + if err := opt(&cfg); err != nil { + return nil, err + } + } + + err := cfg.policyType.IsValid() + if err != nil { + return nil, err + } + + if cfg.configTemplate == nil { + cfg.configTemplate, err = os.ReadFile(configTemplate()) + if err != nil { + return nil, err + } + } + + return &cfg, nil +} + +func configTemplate() string { + var configTemplate string + var found bool + if configTemplate, found = os.LookupEnv("OPA_CONFIG_TEMPLATE_FILE"); !found { + configTemplate = "opaconfig.yaml" + } + + return configTemplate +} + +func (factory *OpenPolicyAgentFactory) BundleStorage() storage.Store { + if factory.bundleStorage == nil { + return inmem.New() + } else { + return factory.bundleStorage + } +} + +func (factory *OpenPolicyAgentFactory) NewOpenPolicyAgentEnvoyInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { + runtime.RegisterPlugin(envoy.PluginName, envoy.Factory{}) + + configBytes, err := interpolateConfigTemplate(config.configTemplate, bundleName) + if err != nil { + return nil, err + } + + engine, err := New(factory.BundleStorage(), configBytes, config, filterName) + + if err != nil { + return nil, err + } + + ctx := context.Background() + if err = engine.Start(ctx); err != nil { + return nil, err + } + + if err := engine.waitPluginsReady( + 100*time.Millisecond, + time.Second*time.Duration(30)); err != nil { + engine.Logger().WithFields(map[string]interface{}{"err": err}).Error("Failed to wait for plugins activation.") + return nil, err + } + + return engine, nil +} + +type OpenPolicyAgentInstance struct { + manager *plugins.Manager + instanceConfig OpenPolicyAgentInstanceConfig + opaConfig *config.Config + + preparedQuery *rego.PreparedEvalQuery + preparedQueryDoOnce *sync.Once + interQueryBuiltinCache iCache.InterQueryCache +} + +func envVariablesMap() map[string]string { + var rawEnvVariables = os.Environ() + + var envVariables = make(map[string]string) + + for _, item := range rawEnvVariables { + tokens := strings.SplitN(item, "=", 2) + + envVariables[tokens[0]] = tokens[1] + } + + return envVariables +} + +// Config sets the configuration file to use on the OPA instance. +func interpolateConfigTemplate(configTemplate []byte, bundleName string) ([]byte, error) { + var buf bytes.Buffer + + tpl := template.Must(template.New("opa-config").Parse(string(configTemplate))) + + binding := make(map[string]interface{}) + binding["bundlename"] = bundleName + binding["Env"] = envVariablesMap() + + err := tpl.ExecuteTemplate(&buf, "opa-config", binding) + + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// New returns a new OPA object. +func New(store storage.Store, configBytes []byte, instanceConfig OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { + id, err := util.Uuid4() + if err != nil { + return nil, err + } + + opaConfig, err := config.ParseConfig(configBytes, id) + if err != nil { + return nil, err + } + + runtime.RegisterPlugin(envoy.PluginName, envoy.Factory{}) + manager, err := plugins.New(configBytes, id, store, plugins.Logger(logging.New().WithFields(map[string]interface{}{"skipper-filter": filterName}))) + if err != nil { + return nil, err + } + + discovery, err := discovery.New(manager, discovery.Factories(map[string]plugins.Factory{envoy.PluginName: envoy.Factory{}})) + if err != nil { + return nil, err + } + + manager.Register("discovery", discovery) + + opa := &OpenPolicyAgentInstance{ + instanceConfig: instanceConfig, + manager: manager, + opaConfig: opaConfig, + + preparedQueryDoOnce: new(sync.Once), + interQueryBuiltinCache: iCache.NewInterQueryCache(manager.InterQueryBuiltinCacheConfig()), + } + + manager.RegisterCompilerTrigger(opa.compilerUpdated) + + return opa, nil +} + +// Start asynchronously starts the policy engine's plugins that download +// policies, report status, etc. +func (opa *OpenPolicyAgentInstance) Start(ctx context.Context) error { + return opa.manager.Start(ctx) +} + +func (opa *OpenPolicyAgentInstance) waitPluginsReady(checkInterval, timeout time.Duration) error { + if timeout <= 0 { + return nil + } + + // check readiness of all plugins + pluginsReady := func() bool { + for _, status := range opa.manager.PluginStatus() { + if status != nil && status.State != plugins.StateOK { + return false + } + } + return true + } + + opa.Logger().Debug("Waiting for plugins activation (%v).", timeout) + + return opautil.WaitFunc(pluginsReady, checkInterval, timeout) +} + +func (opa *OpenPolicyAgentInstance) InstanceConfig() *OpenPolicyAgentInstanceConfig { + return &opa.instanceConfig +} + +func (opa *OpenPolicyAgentInstance) compilerUpdated(txn storage.Transaction) { + opa.preparedQueryDoOnce = new(sync.Once) +} + +func (opa *OpenPolicyAgentInstance) EnvoyPluginConfig() envoy.PluginConfig { + if plugin, ok := opa.manager.Plugin(envoy.PluginName).(*envoy.Plugin); ok { + return plugin.GetConfig() + } + + defaultConfig := envoy.PluginConfig{ + Path: "envoy/authz/allow", + DryRun: false, + } + defaultConfig.ParseQuery() + return defaultConfig +} + +func (opa *OpenPolicyAgentInstance) ParsedQuery() ast.Body { + return opa.EnvoyPluginConfig().ParsedQuery +} + +func (opa *OpenPolicyAgentInstance) Store() storage.Store { return opa.manager.Store } +func (opa *OpenPolicyAgentInstance) Compiler() *ast.Compiler { return opa.manager.GetCompiler() } +func (opa *OpenPolicyAgentInstance) Runtime() *ast.Term { return opa.manager.Info } +func (opa *OpenPolicyAgentInstance) Logger() logging.Logger { return opa.manager.Logger() } +func (opa *OpenPolicyAgentInstance) PreparedQueryDoOnce() *sync.Once { return opa.preparedQueryDoOnce } +func (opa *OpenPolicyAgentInstance) InterQueryBuiltinCache() iCache.InterQueryCache { + return opa.interQueryBuiltinCache +} +func (opa *OpenPolicyAgentInstance) PreparedQuery() *rego.PreparedEvalQuery { return opa.preparedQuery } +func (opa *OpenPolicyAgentInstance) SetPreparedQuery(q *rego.PreparedEvalQuery) { + opa.preparedQuery = q +} +func (opa *OpenPolicyAgentInstance) Config() *config.Config { + return opa.opaConfig +} diff --git a/filters/openpolicyagent/openpolicyagent_test.go b/filters/openpolicyagent/openpolicyagent_test.go new file mode 100644 index 0000000000..73a448ee0c --- /dev/null +++ b/filters/openpolicyagent/openpolicyagent_test.go @@ -0,0 +1,27 @@ +package openpolicyagent + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInterpolateTemplate(t *testing.T) { + os.Setenv("CONTROL_PLANE_TOKEN", "testtoken") + interpolatedConfig, err := interpolateConfigTemplate([]byte(` + token: {{.Env.CONTROL_PLANE_TOKEN }} + bundle: {{ .bundlename }} + `), + "helloBundle") + + if err != nil { + t.Error(err) + } + + assert.Equal(t, ` + token: testtoken + bundle: helloBundle + `, string(interpolatedConfig)) + +} diff --git a/filters/openpolicyagent/response.go b/filters/openpolicyagent/response.go new file mode 100644 index 0000000000..c8c32457a7 --- /dev/null +++ b/filters/openpolicyagent/response.go @@ -0,0 +1,63 @@ +package openpolicyagent + +import ( + "bytes" + "io" + + "net/http" + + "github.com/open-policy-agent/opa-envoy-plugin/envoyauth" + "github.com/zalando/skipper/filters" +) + +func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.FilterContext, result *envoyauth.EvalResult, err error) { + resp := http.Response{} + + resp.StatusCode = http.StatusInternalServerError + + if result != nil { + opa.Logger().WithFields(map[string]interface{}{ + "decision": result.Decision, + "err": err, + "decision_id": result.DecisionID, + }).Info("Rejecting request because of an invalid decision") + } else { + opa.Logger().WithFields(map[string]interface{}{ + "err": err, + }).Info("Rejecting request because of an invalid decision") + } + + fc.Serve(&resp) +} + +func (opa *OpenPolicyAgentInstance) ServeResponse(fc filters.FilterContext, result *envoyauth.EvalResult) { + resp := http.Response{} + + status, err := result.GetResponseHTTPStatus() + if err != nil { + opa.RejectInvalidDecisionError(fc, result, err) + return + } + resp.StatusCode = status + + headers, err := result.GetResponseHTTPHeaders() + if err != nil { + opa.RejectInvalidDecisionError(fc, result, err) + return + } + resp.Header = headers + + hasbody := result.HasResponseBody() + + if hasbody { + body, err := result.GetResponseBody() + if err != nil { + opa.RejectInvalidDecisionError(fc, result, err) + return + } + + resp.Body = io.NopCloser(bytes.NewReader([]byte(body))) + } + + fc.Serve(&resp) +} diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go new file mode 100644 index 0000000000..ffd4c3934f --- /dev/null +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -0,0 +1,115 @@ +package serveresponsewithregopolicy + +import ( + "time" + + "github.com/zalando/skipper/filters" + "gopkg.in/yaml.v2" + + "github.com/zalando/skipper/filters/openpolicyagent" + "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" + "github.com/zalando/skipper/filters/openpolicyagent/internal/util" +) + +type Spec struct { + factory openpolicyagent.OpenPolicyAgentFactory + configOpts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error +} + +func NewServeResponseWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFactory, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) Spec { + return Spec{ + factory: factory, + configOpts: opts, + } +} + +func (s Spec) Name() string { + return "serveResponseWithRegoPolicy" +} + +const ( + paramBundleName int = iota + paramEnvoyContextExtensions +) + +func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { + var err error + + sargs, err := util.GetStrings(config) + if err != nil { + return nil, err + } + + if len(sargs) < 1 { + return nil, filters.ErrInvalidFilterParameters + } + + if len(sargs) > 2 { + return nil, filters.ErrInvalidFilterParameters + } + + bundleName := sargs[paramBundleName] + + configOptions := s.configOpts + + if len(sargs) > 1 { + envoyContextExtensions := map[string]string{} + err = yaml.Unmarshal([]byte(sargs[paramEnvoyContextExtensions]), &envoyContextExtensions) + if err != nil { + return nil, err + } + configOptions = append(configOptions, openpolicyagent.WithEnvoyContextExtensions(envoyContextExtensions)) + } + + opaConfig, err := openpolicyagent.NewOpenPolicyAgentConfig(configOptions...) + if err != nil { + return nil, err + } + + opa, err := s.factory.NewOpenPolicyAgentEnvoyInstance(bundleName, *opaConfig, s.Name()) + + if err != nil { + return nil, err + } + + return serveResponseWithRegoPolicyFilter{ + opa: opa, + }, nil +} + +type serveResponseWithRegoPolicyFilter struct { + opa *openpolicyagent.OpenPolicyAgentInstance +} + +func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { + start := time.Now() + + req := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetPolicyType(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + + result, err := f.opa.Eval(fc.Request().Context(), req) + + if err != nil { + f.opa.RejectInvalidDecisionError(fc, result, err) + return + } + + f.opa.Logger().WithFields(map[string]interface{}{ + "query": f.opa.EnvoyPluginConfig().ParsedQuery.String(), + "dry-run": f.opa.EnvoyPluginConfig().DryRun, + "decision": result.Decision, + "err": err, + "txn": result.TxnID, + "metrics": result.Metrics.All(), + "total_decision_time": time.Since(start), + }).Debug("Authorizing request with decision.") + + if !f.opa.EnvoyPluginConfig().DryRun { + if err != nil { + f.opa.RejectInvalidDecisionError(fc, result, err) + } else { + f.opa.ServeResponse(fc, result) + } + } +} + +func (f serveResponseWithRegoPolicyFilter) Response(fc filters.FilterContext) {} diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go new file mode 100644 index 0000000000..9ff122d8ab --- /dev/null +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go @@ -0,0 +1,210 @@ +package serveresponsewithregopolicy + +import ( + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "path" + "testing" + + opasdktest "github.com/open-policy-agent/opa/sdk/test" + "github.com/stretchr/testify/assert" + "github.com/zalando/skipper/eskip" + "github.com/zalando/skipper/filters" + "github.com/zalando/skipper/proxy/proxytest" + + "github.com/zalando/skipper/filters/openpolicyagent" +) + +func TestAuthorizeRequestFilter(t *testing.T) { + for _, ti := range []struct { + msg string + bundleName string + regoQuery string + requestPath string + expectedBody string + contextExtensions string + expectedHeaders http.Header + expectedStatus int + }{ + { + msg: "Allow Requests", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow", + requestPath: "allow", + expectedStatus: http.StatusInternalServerError, + expectedBody: "", + expectedHeaders: make(http.Header), + }, + { + msg: "Simple Forbidden", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow", + requestPath: "forbidden", + expectedStatus: http.StatusForbidden, + expectedHeaders: make(http.Header), + }, + { + msg: "Allow With Structured Rules", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object", + requestPath: "allow/structured", + expectedStatus: http.StatusOK, + expectedBody: "Welcome from policy!", + expectedHeaders: map[string][]string{"X-Ext-Auth-Allow": {"yes"}}, + }, + { + msg: "Allow With Structured Body", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object_structured_body", + requestPath: "allow/structured", + expectedStatus: http.StatusInternalServerError, + expectedBody: "", + expectedHeaders: map[string][]string{}, + }, + { + msg: "Allow with context extensions", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object_contextextensions", + requestPath: "allow/structured", + contextExtensions: "hello: world", + expectedStatus: http.StatusOK, + expectedHeaders: map[string][]string{"X-Ext-Auth-Allow": {"yes"}}, + expectedBody: `{"hello":"world"}`, + }, + } { + t.Run(ti.msg, func(t *testing.T) { + t.Logf("Running test for %v", ti) + clientServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Welcome!")) + })) + + opaControlPlane := opasdktest.MustNewServer( + opasdktest.MockBundle("/bundles/"+ti.bundleName, map[string]string{ + "main.rego": ` + package envoy.authz + + default allow = false + + allow { + input.parsed_path = [ "allow" ] + } + + default allow_object = { + "allowed": false, + "headers": {"x-ext-auth-allow": "no"}, + "body": "Unauthorized Request", + "http_status": 401 + } + + allow_object = response { + input.parsed_path = [ "allow", "structured" ] + response := { + "allowed": true, + "headers": {"x-ext-auth-allow": "yes"}, + "body": "Welcome from policy!", + "http_status": 200 + } + } + + allow_object_structured_body = response { + input.parsed_path = [ "allow", "structured" ] + response := { + "allowed": true, + "headers": {"x-ext-auth-allow": "yes"}, + "body": {"hello": "world"}, + "http_status": 200 + } + } + + + allow_object_contextextensions = response { + input.parsed_path = [ "allow", "structured" ] + response := { + "allowed": true, + "headers": {"x-ext-auth-allow": "yes"}, + "body": json.marshal(input.attributes.contextExtensions), + "http_status": 200 + } + } + + `, + }), + ) + + var routeFilters []*eskip.Filter + fr := make(filters.Registry) + + config := []byte(fmt.Sprintf(`{ + "services": { + "test": { + "url": %q + } + }, + "bundles": { + "test": { + "resource": "/bundles/{{ .bundlename }}" + } + }, + "plugins": { + "envoy_ext_authz_grpc": { + "path": %q, + "dry-run": false + } + } + }`, opaControlPlane.URL(), ti.regoQuery)) + + opaFactory := openpolicyagent.NewOpenPolicyAgentFactory() + ftSpec := NewServeResponseWithRegoPolicySpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) + + filterArgs := []interface{}{ti.bundleName} + if ti.contextExtensions != "" { + filterArgs = append(filterArgs, ti.contextExtensions) + } + + _, err := ftSpec.CreateFilter(filterArgs) + if err != nil { + t.Fatalf("error in creating filter: %v", err) + } + fr.Register(ftSpec) + routeFilters = append(routeFilters, &eskip.Filter{Name: ftSpec.Name(), Args: filterArgs}) + + r := &eskip.Route{Filters: routeFilters, Backend: clientServer.URL} + + proxy := proxytest.New(fr, r) + reqURL, err := url.Parse(proxy.URL) + if err != nil { + t.Errorf("Failed to parse url %s: %v", proxy.URL, err) + } + reqURL.Path = path.Join(reqURL.Path, ti.requestPath) + req, err := http.NewRequest("GET", reqURL.String(), nil) + if err != nil { + t.Error(err) + return + } + + rsp, err := http.DefaultClient.Do(req) + if err != nil { + t.Error(err) + } + + assert.Equal(t, ti.expectedStatus, rsp.StatusCode, "HTTP status does not match") + + sanitizedHeaders := rsp.Header + sanitizedHeaders.Del("Date") + sanitizedHeaders.Del("Server") + sanitizedHeaders.Del("Content-Length") + sanitizedHeaders.Del("Content-Type") + assert.Equal(t, ti.expectedHeaders, sanitizedHeaders, "HTTP Headers do not match") + + defer rsp.Body.Close() + body, err := io.ReadAll(rsp.Body) + if err != nil { + t.Error(err) + } + assert.Equal(t, ti.expectedBody, string(body), "HTTP Headers do not match") + }) + } +} diff --git a/go.mod b/go.mod index 65b66c2d6b..c921e910d9 100644 --- a/go.mod +++ b/go.mod @@ -16,16 +16,21 @@ require ( github.com/dgryski/go-mpchash v0.0.0-20200819201138-7382f34c4cd1 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 + github.com/envoyproxy/go-control-plane v0.11.1 github.com/ghodss/yaml v1.0.0 github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/golang/protobuf v1.5.3 github.com/google/go-cmp v0.5.9 github.com/hashicorp/memberlist v0.5.0 github.com/instana/go-sensor v1.55.2 github.com/lightstep/lightstep-tracer-go v0.26.0 github.com/miekg/dns v1.1.55 github.com/oklog/ulid v1.3.1 + github.com/open-policy-agent/opa v0.55.0 + github.com/open-policy-agent/opa-envoy-plugin v0.55.0-envoy github.com/opentracing/basictracer-go v1.1.0 github.com/opentracing/opentracing-go v1.2.0 + github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.16.0 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 github.com/redis/go-redis/v9 v9.0.5 @@ -51,31 +56,54 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v2 v2.4.0 layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf + ) require ( cloud.google.com/go/compute v1.20.1 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect dario.cat/mergo v1.0.0 // indirect + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/OneOfOne/xxhash v1.2.8 // indirect + github.com/agnivade/levenshtein v1.1.1 // indirect github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b // indirect - github.com/cenkalti/backoff/v4 v4.2.0 // indirect + github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect github.com/containerd/containerd v1.7.3 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect + github.com/cyphar/filepath-securejoin v0.2.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dgraph-io/badger/v3 v3.2103.5 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker v24.0.5+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.1 // indirect + github.com/felixge/httpsnoop v1.0.3 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gobwas/glob v0.2.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/glog v1.1.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.0 // indirect + github.com/google/flatbuffers v1.12.1 // indirect github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.0.0 // indirect github.com/hashicorp/go-msgpack v0.5.3 // indirect @@ -88,32 +116,51 @@ require ( github.com/looplab/fsm v1.0.1 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.0 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/moby/locker v1.0.1 // indirect github.com/moby/patternmatcher v0.5.0 // indirect github.com/moby/sys/sequential v0.5.0 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.19.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc4 // indirect github.com/opencontainers/runc v1.1.5 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/peterh/liner v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // indirect - github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d // indirect + github.com/tchap/go-patricia/v2 v2.3.1 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/yashtewari/glob-intersection v0.2.0 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/sdk v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/atomic v1.9.0 // indirect + go.uber.org/automaxprocs v1.5.3 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect - golang.org/x/mod v0.9.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect - golang.org/x/tools v0.7.0 // indirect + golang.org/x/tools v0.11.0 // indirect gonum.org/v1/gonum v0.8.2 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect @@ -122,6 +169,7 @@ require ( google.golang.org/grpc v1.57.0 // indirect google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + oras.land/oras-go/v2 v2.2.1 // indirect ) go 1.19 diff --git a/go.sum b/go.sum index e0bae0f0da..6dc16b2a7b 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,44 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg= cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1 h1:EKPd1INOIyr5hWOWhvpmQpY6tKjeG0hT1s3AMC/9fic= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h1:VzwV+t+dZ9j/H867F1M2ziD+yLHtB46oM35FxxMJ4d0= github.com/AlexanderYastrebov/noleak v0.0.0-20230711175737-345842f874fb h1:ZG/Y3/ecDXt8GFY6ZWRgP5COaph3dwoXRvTc7Mz0L7U= github.com/AlexanderYastrebov/noleak v0.0.0-20230711175737-345842f874fb/go.mod h1:Ac8KyJXsCfx2Gb9h/Eb6SUYk2tQ9At1ICaBm/1mipJQ= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= @@ -20,11 +52,20 @@ github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x9 github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= +github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0EVT0= github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM= +github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= +github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/aryszka/jobqueue v0.0.2 h1:LYPhzklo0XFpVF+QtzfP9XRQPEsbJ2EW5Pur6pxxaS4= @@ -35,27 +76,51 @@ github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b h1:AP/Y7sqYicnjGDf github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA= +github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= -github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9 h1:rdWOzitWlNYeUsXmz+IQfa9NkGEq3gA/qQ3mOEqBU6o= github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9/go.mod h1:X97UjDTXp+7bayQSFZk2hPvCTmTZIicUjZQRtkwgAKY= github.com/cjoudrey/gluaurl v0.0.0-20161028222611-31cbb9bef199 h1:cJ1E8ZwZLfercTX3dywnCAQDilbbi+m2cw3+8tCFpRo= github.com/cjoudrey/gluaurl v0.0.0-20161028222611-31cbb9bef199/go.mod h1:jC+zrjHA5CaxJzn+tojIoIOzSp/6BlkRWXnMlxNkB+g= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/containerd v1.7.3 h1:cKwYKkP1eTj54bP3wCdXXBymmKRQMrWjkLSWZZJDa8o= github.com/containerd/containerd v1.7.3/go.mod h1:32FOM4/O0RkNg7AjQj3hDzN9cUGtu+HMvaKUNiqCZB8= +github.com/containerd/continuity v0.4.1 h1:wQnVrjIyQ8vhU2sgOiL5T07jo+ouqc2bnKsv5/EqGhU= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= @@ -66,6 +131,12 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= +github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= +github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654 h1:XOPLOMn/zT4jIgxfxSsoXPxkrzz0FaCHwp33x5POJ+Q= github.com/dgryski/go-gk v0.0.0-20200319235926-a69029f61654/go.mod h1:qm+vckxRlDt0aOla0RYJJVeqHZlWfOm2UIxHaqPB46E= github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 h1:0wH6nO9QEa02Qx8sIQGw6ieKdz+BXjpccSOo9vXNl4U= @@ -74,6 +145,8 @@ github.com/dgryski/go-mpchash v0.0.0-20200819201138-7382f34c4cd1 h1:De28BM16VaAD github.com/dgryski/go-mpchash v0.0.0-20200819201138-7382f34c4cd1/go.mod h1:ut6ck43wTobSb/Jvxo/5uzEEtrfU4gj4m0vh02v7GuQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598 h1:MGKhKyiYrvMDZsmLR/+RGffQSXwEkXgfLSA08qDn9AI= github.com/dimfeld/httppath v0.0.0-20170720192232-ee938bf73598/go.mod h1:0FpDmbrt36utu8jEmeU05dPC9AB5tsLYVVi+ZHfyuwI= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= @@ -85,16 +158,45 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= +github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.1 h1:kt9FtLiooDc0vbwTLhdg3dyNX1K9Qwa1EK9LcD4jVUQ= +github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= +github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= +github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-restit/lzjson v0.0.0-20161206095556-efe3c53acc68/go.mod h1:7vXSKQt83WmbPeyVjCfNT9YDJ5BUFmcwFsEjI9SCvYM= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= @@ -105,35 +207,82 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= +github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -151,20 +300,27 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/tdigest v0.0.1 h1:XpFptwYmnEKUqmkcDjrzffswZ3nvNeevbUSLPP/ZzIY= github.com/influxdata/tdigest v0.0.1/go.mod h1:Z0kXnxzbTC2qrx4NaIzYkE1k66+6oEDQTvL95hQFh5Y= github.com/instana/go-sensor v1.55.2 h1:XdLN38mSFsHpaL+jIDkE/ZrW7pxgPeUC/bV9bSwVHyM= github.com/instana/go-sensor v1.55.2/go.mod h1:Ks06EG9Da5O3hbdJiHIePG/vNmToovkaJjMlUBd70Yc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -177,18 +333,28 @@ github.com/lightstep/lightstep-tracer-go v0.26.0 h1:ZOw8meo7+7SvvUWrL0c4IRr3bd4Y github.com/lightstep/lightstep-tracer-go v0.26.0/go.mod h1:+H6HJI7VlzXOAyxt5a/ZhsOUFbBU89BTMrBFEWSWGoY= github.com/looplab/fsm v1.0.1 h1:OEW0ORrIx095N/6lgoGkFkotqH6s7vaFPsgjLAaF5QU= github.com/looplab/fsm v1.0.1/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo= github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo= github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= @@ -202,6 +368,8 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -212,6 +380,10 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/open-policy-agent/opa v0.55.0 h1:s7Vm4ph6zDqqP/KzvUSw9fsKVsm9lhbTZhYGxxTK7mo= +github.com/open-policy-agent/opa v0.55.0/go.mod h1:2Vh8fj/bXCqSwGMbBiHGrw+O8yrho6T/fdaHt5ROmaQ= +github.com/open-policy-agent/opa-envoy-plugin v0.55.0-envoy h1:5hgZ+s8MFKA5DBNgsWs0nDJ0egE7p/aUSlGSfuokgQU= +github.com/open-policy-agent/opa-envoy-plugin v0.55.0-envoy/go.mod h1:KAM7MoXtuuu2/do4Gm4Ut3nCAT+O/hAyslI0pdOKc4Y= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0= @@ -228,16 +400,21 @@ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+ github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw= +github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 h1:0XM1XL/OFFJjXsYXlG30spTkV/E9+gmd5GD1w2HE8xM= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= @@ -246,7 +423,12 @@ github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5X github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/redis/go-redis/v9 v9.0.5 h1:CuQcn5HIEeK7BgElubPP8CGtE0KakrnbBSTLjathl5o= github.com/redis/go-redis/v9 v9.0.5/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sarslanhan/cronmask v0.0.0-20230801193303-54e29300a091 h1:L644WnBAUw4546Wrt52yzuSPoV24t0ArlMwc5iRr8U0= github.com/sarslanhan/cronmask v0.0.0-20230801193303-54e29300a091/go.mod h1:qZKxttzn8iyVLtc7edFrmQper3FUBJsc/rHCONN2wIQ= @@ -262,6 +444,15 @@ github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d h1:X4+kt6zM/OVO6gbJdAfJR60MGPsqCzbtXNnjoGqdfAs= github.com/streadway/quantile v0.0.0-20220407130108-4246515d968d/go.mod h1:lbP8tGiBjZ5YWIc2fzuRpTaz0b/53vT6PEs3QuAWzuU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -282,6 +473,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/szuecs/rate-limit-buffer v0.9.0 h1:65fBCVsaJFh0E1G5C6/sInEPlYR6dXtF9J9bAv48lLg= github.com/szuecs/rate-limit-buffer v0.9.0/go.mod h1:BxqrsmnHsCnWcvbtdcaDLEBmjNEvRFU5LQ8edoZ9B0M= +github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= +github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/testcontainers/testcontainers-go v0.22.1-0.20230806163346-d1c291e3c8e1 h1:iwspfBu8RJQeMGfkBvR+9sMoia/4CK0ndFNpuJgXEBQ= github.com/testcontainers/testcontainers-go v0.22.1-0.20230806163346-d1c291e3c8e1/go.mod h1:k0YiPa26xJCRUbUkYqy5rY6NGvSbVCeUBXCvucscBR4= github.com/tidwall/gjson v1.15.0 h1:5n/pM+v3r5ujuNl4YLZLsQ+UE5jlkLVm7jMzT5Mpolw= @@ -296,22 +489,63 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= +github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= github.com/yookoala/gofast v0.7.0 h1:wVqXc+S0FDmlkieRNDxabGRX44znHT++Hb9lEfWi4iM= github.com/yookoala/gofast v0.7.0/go.mod h1:OJU201Q6HCaE1cASckaTbMm3KB6e0cZxK0mgqfwOKvQ= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE= github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 h1:pginetY7+onl4qN1vl0xW/V/v6OBZ0vVdH+esuJgvmM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0/go.mod h1:XiYsayHc36K3EByOO6nbAXnAWbrUxdjUROCEeeROOH8= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 h1:t4ZwRPU+emrcvM2e9DHd0Fsf0JTPVcbfa/BhTDF03d0= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0/go.mod h1:vLarbg68dH2Wa77g71zmKQqlQ8+8Rq3GRG31uc0WcWI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 h1:cbsD4cUcviQGXdw8+bo5x2wazq10SKz8hEbtCRPcU78= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0/go.mod h1:JgXSGah17croqhJfhByOLVY719k1emAXC8MVhCIJlRs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0 h1:TVQp/bboR4mhZSav+MdgXB8FaRho1RC8UwVn3T0vjVc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.16.0/go.mod h1:I33vtIe0sR96wfrUcilIzLoA3mLHhRmz9S9Te0S3gDo= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/sdk v1.16.0 h1:Z1Ok1YsijYL0CSJpHt4cS3wDDh7p572grzNrBMiMWgE= +go.opentelemetry.io/otel/sdk v1.16.0/go.mod h1:tMsIuKXuuIWPBAOrH+eHtvhTL+SntFtXF9QD68aP6p4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab h1:+yW1yrZ09EYNu1spCUOHBBNRbrLnfmutwyhbhCv3b6Q= go4.org/netipx v0.0.0-20220925034521-797b0c90d8ab/go.mod h1:tgPU4N2u9RByaTN3NC2p9xOzyFpte4jYwsIIRF7XlSc= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -322,7 +556,14 @@ golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4= golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= @@ -330,33 +571,66 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -365,46 +639,78 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210217105451-b926d437f341/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -414,17 +720,50 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200908211811-12e1bf57a112/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= +golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -436,13 +775,62 @@ gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/netlib v0.0.0-20181029234149-ec6d1f5cefe6/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc h1:8DyZCyvI8mE1IdLy/60bS+52xfymkE72wv1asokgtao= google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:xZnkP7mREFX5MORlOPEzLMr+90PPZQ2QWzrVTWfAq64= google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc h1:kVKPf/IiYSBWEWtkIn6wZXwWGCnLKcC8oWfZvXjsGnM= @@ -452,6 +840,21 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc/go. google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -459,15 +862,22 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= @@ -476,6 +886,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= @@ -485,6 +896,17 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf h1:rRz0YsF7VXj9fXRF6yQgFI7DzST+hsI3TeFSGupntu0= layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf/go.mod h1:ivKkcY8Zxw5ba0jldhZCYYQfGdb2K6u9tbYK1AwMIBc= +oras.land/oras-go/v2 v2.2.1 h1:3VJTYqy5KfelEF9c2jo1MLSpr+TM3mX8K42wzZcd6qE= +oras.land/oras-go/v2 v2.2.1/go.mod h1:GeAwLuC4G/JpNwkd+bSZ6SkDMGaaYglt6YK2WvZP7uQ= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/skipper.go b/skipper.go index c331d999d6..d6b4ce5d75 100644 --- a/skipper.go +++ b/skipper.go @@ -36,6 +36,9 @@ import ( "github.com/zalando/skipper/filters/builtin" "github.com/zalando/skipper/filters/fadein" logfilter "github.com/zalando/skipper/filters/log" + "github.com/zalando/skipper/filters/openpolicyagent" + "github.com/zalando/skipper/filters/openpolicyagent/authorizewithregopolicy" + "github.com/zalando/skipper/filters/openpolicyagent/serveresponsewithregopolicy" ratelimitfilters "github.com/zalando/skipper/filters/ratelimit" "github.com/zalando/skipper/filters/shedder" teefilters "github.com/zalando/skipper/filters/tee" @@ -892,6 +895,9 @@ type Options struct { // defaults to "file","inline" and "none" disables lua // filters. LuaSources []string + + EnableOpenPolicyAgent bool + OpenPolicyAgentConfigTemplate string } func (o *Options) KubernetesDataClientOptions() kubernetes.Options { @@ -1753,6 +1759,17 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { ) } + if o.EnableOpenPolicyAgent { + factory := openpolicyagent.NewOpenPolicyAgentFactory() + + o.CustomFilters = append(o.CustomFilters, + authorizewithregopolicy.NewAuthorizeWithRegoPolicySpec(factory, + openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate)), + serveresponsewithregopolicy.NewServeResponseWithRegoPolicySpec(factory, + openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate)), + ) + } + if len(o.CompressEncodings) > 0 { compress, err := builtin.NewCompressWithOptions(builtin.CompressOptions{Encodings: o.CompressEncodings}) if err != nil { From 0a8fda4d5f5bc5a6be4191d7d731f13d66d05f9e Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 5 Jun 2023 22:24:23 +0200 Subject: [PATCH 02/24] Add tracing Signed-off-by: Magnus Jungsbluth --- .../authorizewithregopolicy.go | 20 ++++---- filters/openpolicyagent/evaluation.go | 9 +++- filters/openpolicyagent/openpolicyagent.go | 13 ++++- filters/openpolicyagent/response.go | 22 +++++++-- .../serveresponsewithregopolicy.go | 14 ++++-- filters/openpolicyagent/tracing.go | 47 +++++++++++++++++++ 6 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 filters/openpolicyagent/tracing.go diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index 2750c57b26..f062f91a97 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -85,12 +85,16 @@ type authorizeWithRegoPolicyFilter struct { func (f authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { start := time.Now() - req := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetPolicyType(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + req := fc.Request() + span, ctx := f.opa.StartSpanFromContext(req.Context()) + defer span.Finish() - result, err := f.opa.Eval(fc.Request().Context(), req) + authzreq := envoy.AdaptToEnvoyExtAuthRequest(req, f.opa.InstanceConfig().GetPolicyType(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + + result, err := f.opa.Eval(ctx, authzreq) if err != nil { - f.opa.RejectInvalidDecisionError(fc, result, err) + f.opa.RejectInvalidDecisionError(fc, span, result, err) return } @@ -108,19 +112,19 @@ func (f authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { allowed, err := result.IsAllowed() if err != nil { - f.opa.RejectInvalidDecisionError(fc, result, err) + f.opa.RejectInvalidDecisionError(fc, span, result, err) return } if !allowed { - f.opa.ServeResponse(fc, result) + f.opa.ServeResponse(fc, span, result) return } if result.HasResponseBody() { body, err := result.GetResponseBody() if err != nil { - f.opa.RejectInvalidDecisionError(fc, result, err) + f.opa.RejectInvalidDecisionError(fc, span, result, err) return } fc.StateBag()[openpolicyagent.OpenPolicyAgentDecisionBodyKey] = body @@ -128,14 +132,14 @@ func (f authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { headers, err := result.GetResponseHTTPHeaders() if err != nil { - f.opa.RejectInvalidDecisionError(fc, result, err) + f.opa.RejectInvalidDecisionError(fc, span, result, err) return } addRequestHeaders(fc, headers) headersToRemove, err := result.GetRequestHTTPHeadersToRemove() if err != nil { - f.opa.RejectInvalidDecisionError(fc, result, err) + f.opa.RejectInvalidDecisionError(fc, span, result, err) return } removeHeaders(fc, headersToRemove) diff --git a/filters/openpolicyagent/evaluation.go b/filters/openpolicyagent/evaluation.go index 3f9dfbd3d7..346c975456 100644 --- a/filters/openpolicyagent/evaluation.go +++ b/filters/openpolicyagent/evaluation.go @@ -8,7 +8,10 @@ import ( "github.com/open-policy-agent/opa-envoy-plugin/envoyauth" "github.com/open-policy-agent/opa-envoy-plugin/opa/decisionlog" "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" "github.com/open-policy-agent/opa/server" + "github.com/open-policy-agent/opa/tracing" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" ) @@ -27,6 +30,10 @@ func (opa *OpenPolicyAgentInstance) eval(ctx context.Context, req *ext_authz_v3. var err error result, stopeval, err := envoyauth.NewEvalResult() + span := opentracing.SpanFromContext(ctx) + if span != nil { + span.SetTag("decision_id", result.DecisionID) + } if err != nil { opa.Logger().WithFields(map[string]interface{}{"err": err}).Error("Unable to generate decision ID.") @@ -59,7 +66,7 @@ func (opa *OpenPolicyAgentInstance) eval(ctx context.Context, req *ext_authz_v3. return nil, stop, err } - err = envoyauth.Eval(ctx, opa, inputValue, result) + err = envoyauth.Eval(ctx, opa, inputValue, result, rego.DistributedTracingOpts(tracing.Options{opa})) if err != nil { return nil, stop, err } diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index fe7be343b0..91542343a1 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -20,6 +20,7 @@ import ( "github.com/open-policy-agent/opa/storage/inmem" iCache "github.com/open-policy-agent/opa/topdown/cache" opautil "github.com/open-policy-agent/opa/util" + "github.com/opentracing/opentracing-go" "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" "github.com/zalando/skipper/filters/openpolicyagent/internal/util" @@ -138,7 +139,7 @@ func (factory *OpenPolicyAgentFactory) NewOpenPolicyAgentEnvoyInstance(bundleNam return nil, err } - engine, err := New(factory.BundleStorage(), configBytes, config, filterName) + engine, err := New(factory.BundleStorage(), configBytes, config, filterName, bundleName) if err != nil { return nil, err @@ -163,6 +164,7 @@ type OpenPolicyAgentInstance struct { manager *plugins.Manager instanceConfig OpenPolicyAgentInstanceConfig opaConfig *config.Config + bundleName string preparedQuery *rego.PreparedEvalQuery preparedQueryDoOnce *sync.Once @@ -203,7 +205,7 @@ func interpolateConfigTemplate(configTemplate []byte, bundleName string) ([]byte } // New returns a new OPA object. -func New(store storage.Store, configBytes []byte, instanceConfig OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { +func New(store storage.Store, configBytes []byte, instanceConfig OpenPolicyAgentInstanceConfig, filterName string, bundleName string) (*OpenPolicyAgentInstance, error) { id, err := util.Uuid4() if err != nil { return nil, err @@ -231,6 +233,7 @@ func New(store storage.Store, configBytes []byte, instanceConfig OpenPolicyAgent instanceConfig: instanceConfig, manager: manager, opaConfig: opaConfig, + bundleName: bundleName, preparedQueryDoOnce: new(sync.Once), interQueryBuiltinCache: iCache.NewInterQueryCache(manager.InterQueryBuiltinCacheConfig()), @@ -288,6 +291,12 @@ func (opa *OpenPolicyAgentInstance) EnvoyPluginConfig() envoy.PluginConfig { return defaultConfig } +func (opa *OpenPolicyAgentInstance) StartSpanFromContext(ctx context.Context) (opentracing.Span, context.Context) { + span, ctx := opentracing.StartSpanFromContext(ctx, "open-policy-agent") + span.SetTag("bundle_name", opa.bundleName) + return span, ctx +} + func (opa *OpenPolicyAgentInstance) ParsedQuery() ast.Body { return opa.EnvoyPluginConfig().ParsedQuery } diff --git a/filters/openpolicyagent/response.go b/filters/openpolicyagent/response.go index c8c32457a7..b3493426a2 100644 --- a/filters/openpolicyagent/response.go +++ b/filters/openpolicyagent/response.go @@ -7,21 +7,33 @@ import ( "net/http" "github.com/open-policy-agent/opa-envoy-plugin/envoyauth" + "github.com/opentracing/opentracing-go" "github.com/zalando/skipper/filters" ) -func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.FilterContext, result *envoyauth.EvalResult, err error) { +func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.FilterContext, span opentracing.Span, result *envoyauth.EvalResult, err error) { resp := http.Response{} resp.StatusCode = http.StatusInternalServerError if result != nil { + span.LogKV( + "event", "error", + "decision_id", result.DecisionID, + "message", err.Error(), + ) + opa.Logger().WithFields(map[string]interface{}{ "decision": result.Decision, "err": err, "decision_id": result.DecisionID, }).Info("Rejecting request because of an invalid decision") } else { + span.LogKV( + "event", "error", + "message", err.Error(), + ) + opa.Logger().WithFields(map[string]interface{}{ "err": err, }).Info("Rejecting request because of an invalid decision") @@ -30,19 +42,19 @@ func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.Filter fc.Serve(&resp) } -func (opa *OpenPolicyAgentInstance) ServeResponse(fc filters.FilterContext, result *envoyauth.EvalResult) { +func (opa *OpenPolicyAgentInstance) ServeResponse(fc filters.FilterContext, span opentracing.Span, result *envoyauth.EvalResult) { resp := http.Response{} status, err := result.GetResponseHTTPStatus() if err != nil { - opa.RejectInvalidDecisionError(fc, result, err) + opa.RejectInvalidDecisionError(fc, span, result, err) return } resp.StatusCode = status headers, err := result.GetResponseHTTPHeaders() if err != nil { - opa.RejectInvalidDecisionError(fc, result, err) + opa.RejectInvalidDecisionError(fc, span, result, err) return } resp.Header = headers @@ -52,7 +64,7 @@ func (opa *OpenPolicyAgentInstance) ServeResponse(fc filters.FilterContext, resu if hasbody { body, err := result.GetResponseBody() if err != nil { - opa.RejectInvalidDecisionError(fc, result, err) + opa.RejectInvalidDecisionError(fc, span, result, err) return } diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index ffd4c3934f..17803a4e34 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -84,12 +84,16 @@ type serveResponseWithRegoPolicyFilter struct { func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { start := time.Now() - req := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetPolicyType(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + req := fc.Request() + span, ctx := f.opa.StartSpanFromContext(req.Context()) + defer span.Finish() - result, err := f.opa.Eval(fc.Request().Context(), req) + authzreq := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetPolicyType(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + + result, err := f.opa.Eval(ctx, authzreq) if err != nil { - f.opa.RejectInvalidDecisionError(fc, result, err) + f.opa.RejectInvalidDecisionError(fc, span, result, err) return } @@ -105,9 +109,9 @@ func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { if !f.opa.EnvoyPluginConfig().DryRun { if err != nil { - f.opa.RejectInvalidDecisionError(fc, result, err) + f.opa.RejectInvalidDecisionError(fc, span, result, err) } else { - f.opa.ServeResponse(fc, result) + f.opa.ServeResponse(fc, span, result) } } } diff --git a/filters/openpolicyagent/tracing.go b/filters/openpolicyagent/tracing.go new file mode 100644 index 0000000000..dca941108a --- /dev/null +++ b/filters/openpolicyagent/tracing.go @@ -0,0 +1,47 @@ +package openpolicyagent + +import ( + "net/http" + + opatracing "github.com/open-policy-agent/opa/tracing" + "github.com/opentracing/opentracing-go" +) + +func init() { + opatracing.RegisterHTTPTracing(&tracingFactory{}) +} + +type tracingFactory struct{} + +type transport struct { + opa *OpenPolicyAgentInstance + wrapped http.RoundTripper +} + +func (*tracingFactory) NewTransport(tr http.RoundTripper, opts opatracing.Options) http.RoundTripper { + return &transport{ + opa: opts[0].(*OpenPolicyAgentInstance), + wrapped: tr, + } +} + +func (*tracingFactory) NewHandler(f http.Handler, label string, opts opatracing.Options) http.Handler { + return f +} + +func (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) { + span := opentracing.SpanFromContext(req.Context()) + ctx := req.Context() + if span == nil { + span, ctx = tr.opa.StartSpanFromContext(ctx) + req = req.WithContext(ctx) + } else { + span, ctx = opentracing.StartSpanFromContext(ctx, "http.send") + req = req.WithContext(ctx) + } + + carrier := opentracing.HTTPHeadersCarrier(req.Header) + span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier) + + return tr.wrapped.RoundTrip(req) +} From b608ca4c02c03161d88c4ca6f6485d71ff8497f1 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Thu, 8 Jun 2023 08:34:36 +0200 Subject: [PATCH 03/24] Review comments + metrics Signed-off-by: Magnus Jungsbluth --- config/config.go | 5 +- docs/reference/filters.md | 100 ++---------------- docs/tutorials/auth.md | 87 +++++++++++++++ filters/filters.go | 2 + .../authorizewithregopolicy.go | 82 +++++++------- .../authorizewithregopolicy_test.go | 2 +- filters/openpolicyagent/common.go | 7 -- filters/openpolicyagent/evaluation.go | 34 ++---- .../internal/envoy/envoyplugin.go | 2 +- .../internal/envoy/policytype.go | 21 ---- .../internal/envoy/skipperadapter.go | 15 +-- filters/openpolicyagent/openpolicyagent.go | 93 ++++++++-------- .../openpolicyagent/openpolicyagent_test.go | 41 +++++++ filters/openpolicyagent/response.go | 9 +- .../serveresponsewithregopolicy.go | 13 +-- skipper.go | 7 +- 16 files changed, 256 insertions(+), 264 deletions(-) delete mode 100644 filters/openpolicyagent/common.go delete mode 100644 filters/openpolicyagent/internal/envoy/policytype.go diff --git a/config/config.go b/config/config.go index 05e6178d94..e0f00f1597 100644 --- a/config/config.go +++ b/config/config.go @@ -274,9 +274,9 @@ type Config struct { LuaModules *listFlag `yaml:"lua-modules"` LuaSources *listFlag `yaml:"lua-sources"` - // Open Policy Agent EnableOpenPolicyAgent bool `yaml:"enable-open-policy-agent"` OpenPolicyAgentConfigTemplate string `yaml:"open-policy-agent-config-template"` + OpenPolicyAgentEnvoyMetadata string `yaml:"open-policy-agent-envoy-metadata"` } const ( @@ -487,8 +487,6 @@ func NewConfig() *Config { flag.DurationVar(&cfg.OidcDistributedClaimsTimeout, "oidc-distributed-claims-timeout", 2*time.Second, "sets the default OIDC distributed claims request timeout duration to 2000ms") flag.Var(cfg.CredentialPaths, "credentials-paths", "directories or files to watch for credentials to use by bearerinjector filter") flag.DurationVar(&cfg.CredentialsUpdateInterval, "credentials-update-interval", 10*time.Minute, "sets the interval to update secrets") - - // Open Policy Agent flag.BoolVar(&cfg.EnableOpenPolicyAgent, "enable-open-policy-agent", false, "enables Open Policy Agent filters") flag.StringVar(&cfg.OpenPolicyAgentConfigTemplate, "open-policy-agent-config-template", "", "file containing a template for an Open Policy Agent configuration file that is interpolated for each OPA filter instance") @@ -890,6 +888,7 @@ func (c *Config) ToOptions() skipper.Options { EnableOpenPolicyAgent: c.EnableOpenPolicyAgent, OpenPolicyAgentConfigTemplate: c.OpenPolicyAgentConfigTemplate, + OpenPolicyAgentEnvoyMetadata: c.OpenPolicyAgentEnvoyMetadata, } for _, rcci := range c.CloneRoute { eskipClone := eskip.NewClone(rcci.Reg, rcci.Repl) diff --git a/docs/reference/filters.md b/docs/reference/filters.md index d4767097a1..5fc71d1e41 100644 --- a/docs/reference/filters.md +++ b/docs/reference/filters.md @@ -1719,95 +1719,11 @@ oidcClaimsQuery("/:name%\"*One\"", "/path:groups.#[%\"*-Test-Users\"] groups.#[= As of now there is no negative/deny rule possible. The first matching path is evaluated against the defined query/queries and if positive, permitted. -## Open Policy Agent +### Open Policy Agent -To enable [Open Policy Agent](https://www.openpolicyagent.org/) filter, use the `-enable-open-policy-agent` command line flag. +To get started with Open Policy Agent, also have a look at the [tutorial](../tutorials/auth.md#open-policy-agent). This section is only a reference for the implemented filters. -Open Policy Agent is integrated as a Go library so no extra setup is needed to run. Every filter creates a virtual OPA instance in memory that is configured using a configuration file in the same [configuration format](https://www.openpolicyagent.org/docs/latest/configuration/) that a standalone OPA would use. To allow for configurability, the configuration file is interpolated using Go Templates to allow every virtual instance to pull different bundles. This template file is passed using the `-open-policy-agent-config-template` flag. - -### Configuration File - -As an example the following initial config can be used - -```yaml -services: - - name: bundle-service - url: https://my-example-opa-bucket.s3.eu-central-1.amazonaws.com - credentials: - s3_signing: - environment_credentials: {} -labels: - environment: production -discovery: - name: discovery - prefix: "/applications/{{ .bundlename }}" -``` - -The variable `.bundlename` is the first argument in the following filters and can be in any format that OPA can understand, so for example application IDs from a registry, uuids, ... - -### Envoy Structures - -While Envoy is an alternative OSS product similar to Skipper, it has already defined structures for how external authorization should be done and also how authorization decisions can influence the Envoy response. Open Policy Agent already has direct support for this and also commercial control planes support this. On top of this [examples and documentation](https://www.openpolicyagent.org/docs/latest/envoy-primer/) already exist. -Instead of re-inventing these structures (for example how http headers and so on are represented), this implementation maps Skipper objects onto their Envoy representation. This also allows to reuse a fair bit of the [opa-envoy-plugin](https://github.com/open-policy-agent/opa-envoy-plugin), which does the heavy lifting of evaluating decisions against the OPA Go library. - -### Passing context to the policy - -Generally there are two ways to pass context to a policy: - -1. as part of the labels on Open Policy Agent (configured in the configuration file, see below) that should be used for deployment level taxonomy, -2. as part of so called context extensions that are part of the Envoy external auth specification. - -This context can be passed as second argument to filters: - -`authorizeWithRegoPolicy("my-app-id", "com.mycompany.myprop: myvalue")` -or `authorizeWithRegoPolicy("my-app-id", "{'com.mycompany.myprop': 'my value'}")` - -The second argument is parsed as YAML, cannot be nested and values need to be strings. - -In Rego this can be used like this `input.attributes.contextExtensions["com.mycompany.myprop"] == "my value"` - -### Quick Start Rego Playground - -A quick way without setting up Backend APIs is to use the [Rego Playground](https://play.openpolicyagent.org/). - -To get started pick from examples Envoy > Hello World. Click on "Publish" and note the random ID in the section "Run OPA with playground policy". - -Place the following file in your local directory with the name `opaconfig.yaml` - -```yaml -bundles: - play: - resource: bundles/{{ .bundlename }} - polling: - long_polling_timeout_seconds: 45 -services: - - name: play - url: https://play.openpolicyagent.org -plugins: - envoy_ext_authz_grpc: - # This needs to match the package, defaulting to envoy/authz/allow - path: envoy/http/public/allow - dry-run: false -decision_logs: - console: true -``` - -Start Skipper with - -``` -skipper --enable-open-policy-agent --open-policy-agent-config-template opaconfig.yaml \ - --inline-routes 'notfound: * -> authorizeWithRegoPolicy("") -> inlineContent("

Authorized Hello

") -> ' -``` - -You can test the policy with - -- Authorized: `curl http://localhost:9090/ -i` -- Authorized: `curl http://localhost:9090/foobar -H "Authorization: Basic charlie" -i` -- Forbidden: `curl http://localhost:9090/foobar -i` - - - -### authorizeWithRegoPolicy +#### authorizeWithRegoPolicy The canonical use case that is also implemented with [Envoy External Authorization](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter): Use the http request to evaluate if Skipper should deny the request (with customizable response) or let the request pass to the downstream service @@ -1876,16 +1792,18 @@ The data flow in case the policy disallows the request looks like this The difference is that if the decision in (3) is equivalent to false, the response is handled directly from the filter. If the decision contains response body, status or headers those are used to build the response in (6) otherwise a 403 Forbidden with a generic body is returned. -#### Manipulating Request Headers +##### Manipulating Request Headers Headers both to the upstream and the downstream service can be manipulated the same way this works for [Envoy external authorization](https://www.openpolicyagent.org/docs/latest/envoy-primer/#example-policy-with-additional-controls) This allows both to add and remove unwanted headers in allow/deny cases. -### serveResponseWithRegoPolicy +#### serveResponseWithRegoPolicy Always serves the response even if the policy allows the request and can customize the response completely. Can be used to re-implement legacy authorization services by already using data in Open Policy Agent but implementing an old REST API. This can also be useful to support Single Page Applications to return the calling users' permissions. +*Hint*: As there is no real allow/deny in this case and the policy computes the http response, you typically will want to [drop all decision logs](https://www.openpolicyagent.org/docs/latest/management-decision-logs/#drop-decision-logs) + Example: ``` @@ -1909,8 +1827,8 @@ For this filter, the data flow looks like this independent of an allow/deny deci ─────────────┤ ├ │ │ (4) Response│ (2)│ ▲ (3) │ -◄────────────┤Req ->│ │ allow │ - │Input │ │ /deny │ +◄────────────┤Req ->│ │ resp │ + │Input │ │ │ ├──────┴───┴───────┤ │Open Policy Agent │ │ │ │ │ diff --git a/docs/tutorials/auth.md b/docs/tutorials/auth.md index 91f7294579..c46d11f8f9 100644 --- a/docs/tutorials/auth.md +++ b/docs/tutorials/auth.md @@ -415,3 +415,90 @@ scope by doing the following: > The subject is the field that identifies the user and is often called `sub`, > especially in the context of OpenID Connect. In the example above, it is `username`. + +## Open Policy Agent + +To enable [Open Policy Agent](https://www.openpolicyagent.org/) filter, use the `-enable-open-policy-agent` command line flag. + +Open Policy Agent is integrated as a Go library so no extra setup is needed to run. Every filter creates a virtual OPA instance in memory that is configured using a configuration file in the same [configuration format](https://www.openpolicyagent.org/docs/latest/configuration/) that a standalone OPA would use. To allow for configurability, the configuration file is interpolated using Go Templates to allow every virtual instance to pull different bundles. This template file is passed using the `-open-policy-agent-config-template` flag. + +### Configuration File + +As an example the following initial config can be used + +```yaml +services: + - name: bundle-service + url: https://my-example-opa-bucket.s3.eu-central-1.amazonaws.com + credentials: + s3_signing: + environment_credentials: {} +labels: + environment: production +discovery: + name: discovery + prefix: "/applications/{{ .bundlename }}" +``` + +The variable `.bundlename` is the first argument in the following filters and can be in any format that OPA can understand, so for example application IDs from a registry, uuids, ... + +### Envoy Structures + +While Envoy is an alternative OSS product similar to Skipper, it has already defined structures for how external authorization should be done and also how authorization decisions can influence the Envoy response. Open Policy Agent already has direct support for this and also commercial control planes support this. On top of this [examples and documentation](https://www.openpolicyagent.org/docs/latest/envoy-primer/) already exist. +Instead of re-inventing these structures (for example how http headers and so on are represented), this implementation maps Skipper objects onto their Envoy representation. This also allows to reuse a fair bit of the [opa-envoy-plugin](https://github.com/open-policy-agent/opa-envoy-plugin), which does the heavy lifting of evaluating decisions against the OPA Go library. + +### Passing context to the policy + +Generally there are two ways to pass context to a policy: + +1. as part of the labels on Open Policy Agent (configured in the configuration file, see below) that should be used for deployment level taxonomy, +2. as part of so called context extensions that are part of the Envoy external auth specification. + +This context can be passed as second argument to filters: + +`authorizeWithRegoPolicy("my-app-id", "com.mycompany.myprop: myvalue")` +or `authorizeWithRegoPolicy("my-app-id", "{'com.mycompany.myprop': 'my value'}")` + +The second argument is parsed as YAML, cannot be nested and values need to be strings. + +In Rego this can be used like this `input.attributes.contextExtensions["com.mycompany.myprop"] == "my value"` + +### Quick Start Rego Playground + +A quick way without setting up Backend APIs is to use the [Rego Playground](https://play.openpolicyagent.org/). + +To get started pick from examples Envoy > Hello World. Click on "Publish" and note the random ID in the section "Run OPA with playground policy". + +Place the following file in your local directory with the name `opaconfig.yaml` + +```yaml +bundles: + play: + resource: bundles/{{ .bundlename }} + polling: + long_polling_timeout_seconds: 45 +services: + - name: play + url: https://play.openpolicyagent.org +plugins: + envoy_ext_authz_grpc: + # This needs to match the package, defaulting to envoy/authz/allow + path: envoy/http/public/allow + dry-run: false +decision_logs: + console: true +``` + +Start Skipper with + +``` +skipper --enable-open-policy-agent --open-policy-agent-config-template opaconfig.yaml \ + --inline-routes 'notfound: * -> authorizeWithRegoPolicy("") -> inlineContent("

Authorized Hello

") -> ' +``` + +You can test the policy with + +- Authorized: `curl http://localhost:9090/ -i` +- Authorized: `curl http://localhost:9090/foobar -H "Authorization: Basic charlie" -i` +- Forbidden: `curl http://localhost:9090/foobar -i` + diff --git a/filters/filters.go b/filters/filters.go index 9ac59de3a6..ffb4ed1a7c 100644 --- a/filters/filters.go +++ b/filters/filters.go @@ -341,6 +341,8 @@ const ( EndpointCreatedName = "endpointCreated" ConsistentHashKeyName = "consistentHashKey" ConsistentHashBalanceFactorName = "consistentHashBalanceFactor" + AuthorizeWithRegoPolicyName = "authorizeWithRegoPolicy" + ServeResponseWithRegoPolicyName = "serveResponseWithRegoPolicy" // Undocumented filters HealthCheckName = "healthcheck" diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index f062f91a97..d31bb1361a 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -25,14 +25,9 @@ func NewAuthorizeWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFacto } func (s Spec) Name() string { - return "authorizeWithRegoPolicy" + return filters.AuthorizeWithRegoPolicyName } -const ( - paramBundleName int = iota - paramEnvoyContextExtensions -) - func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { var err error @@ -49,13 +44,13 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { return nil, filters.ErrInvalidFilterParameters } - bundleName := sargs[paramBundleName] + bundleName := sargs[0] configOptions := s.configOpts if len(sargs) > 1 { envoyContextExtensions := map[string]string{} - err = yaml.Unmarshal([]byte(sargs[paramEnvoyContextExtensions]), &envoyContextExtensions) + err = yaml.Unmarshal([]byte(sargs[1]), &envoyContextExtensions) if err != nil { return nil, err } @@ -83,67 +78,62 @@ type authorizeWithRegoPolicyFilter struct { } func (f authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { - start := time.Now() - req := fc.Request() span, ctx := f.opa.StartSpanFromContext(req.Context()) defer span.Finish() - authzreq := envoy.AdaptToEnvoyExtAuthRequest(req, f.opa.InstanceConfig().GetPolicyType(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + authzreq := envoy.AdaptToEnvoyExtAuthRequest(req, f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + start := time.Now() result, err := f.opa.Eval(ctx, authzreq) + fc.Metrics().MeasureSince(f.opa.MetricsKey("eval_time"), start) if err != nil { f.opa.RejectInvalidDecisionError(fc, span, result, err) return } - f.opa.Logger().WithFields(map[string]interface{}{ - "query": f.opa.EnvoyPluginConfig().ParsedQuery.String(), - "dry-run": f.opa.EnvoyPluginConfig().DryRun, - "decision": result.Decision, - "err": err, - "txn": result.TxnID, - "metrics": result.Metrics.All(), - "total_decision_time": time.Since(start), - }).Debug("Authorizing request with decision.") + if f.opa.EnvoyPluginConfig().DryRun { + return + } - if !f.opa.EnvoyPluginConfig().DryRun { - allowed, err := result.IsAllowed() + allowed, err := result.IsAllowed() - if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) - return - } + if err != nil { + f.opa.RejectInvalidDecisionError(fc, span, result, err) + return + } - if !allowed { - f.opa.ServeResponse(fc, span, result) - return - } + if !allowed { + fc.Metrics().IncCounter(f.opa.MetricsKey("decision.deny")) + f.opa.ServeResponse(fc, span, result) + return + } - if result.HasResponseBody() { - body, err := result.GetResponseBody() - if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) - return - } - fc.StateBag()[openpolicyagent.OpenPolicyAgentDecisionBodyKey] = body - } + fc.Metrics().IncCounter(f.opa.MetricsKey("decision.allow")) - headers, err := result.GetResponseHTTPHeaders() + if result.HasResponseBody() { + body, err := result.GetResponseBody() if err != nil { f.opa.RejectInvalidDecisionError(fc, span, result, err) return } - addRequestHeaders(fc, headers) + fc.StateBag()[openpolicyagent.OpenPolicyAgentDecisionBodyKey] = body + } - headersToRemove, err := result.GetRequestHTTPHeadersToRemove() - if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) - return - } - removeHeaders(fc, headersToRemove) + headers, err := result.GetResponseHTTPHeaders() + if err != nil { + f.opa.RejectInvalidDecisionError(fc, span, result, err) + return + } + addRequestHeaders(fc, headers) + + headersToRemove, err := result.GetRequestHTTPHeadersToRemove() + if err != nil { + f.opa.RejectInvalidDecisionError(fc, span, result, err) + return } + removeHeaders(fc, headersToRemove) } func removeHeaders(fc filters.FilterContext, headersToRemove []string) { diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go index 40aa6389b0..b9f9545baa 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go @@ -222,7 +222,7 @@ func areHeaderValuesEqual(expectedValues, actualValues []string) bool { func isHeadersAbsent(unwantedHeaders http.Header, headers http.Header) bool { for headerName := range unwantedHeaders { - if _, headerFound := headers[headerName]; headerFound { + if _, ok := headers[headerName]; ok { return false } } diff --git a/filters/openpolicyagent/common.go b/filters/openpolicyagent/common.go deleted file mode 100644 index bea907e67d..0000000000 --- a/filters/openpolicyagent/common.go +++ /dev/null @@ -1,7 +0,0 @@ -package openpolicyagent - -const ( - OpenPolicyAgentDecisionKey = "open-policy-agent:decision" - OpenPolicyAgentDecisionBodyKey = "open-policy-agent:decision-body" - OpenPolicyAgentDecisionHeadersKey = "open-policy-agent:decision-headers" -) diff --git a/filters/openpolicyagent/evaluation.go b/filters/openpolicyagent/evaluation.go index 346c975456..62673371cd 100644 --- a/filters/openpolicyagent/evaluation.go +++ b/filters/openpolicyagent/evaluation.go @@ -16,17 +16,6 @@ import ( ) func (opa *OpenPolicyAgentInstance) Eval(ctx context.Context, req *ext_authz_v3.CheckRequest) (*envoyauth.EvalResult, error) { - resp, finalFunc, err := opa.eval(ctx, req) - - logErr := finalFunc() - if logErr != nil { - opa.Logger().WithFields(map[string]interface{}{"err": logErr}).Error("Unable to log decision to control plane.") - } - - return resp, err -} - -func (opa *OpenPolicyAgentInstance) eval(ctx context.Context, req *ext_authz_v3.CheckRequest) (*envoyauth.EvalResult, func() error, error) { var err error result, stopeval, err := envoyauth.NewEvalResult() @@ -37,41 +26,40 @@ func (opa *OpenPolicyAgentInstance) eval(ctx context.Context, req *ext_authz_v3. if err != nil { opa.Logger().WithFields(map[string]interface{}{"err": err}).Error("Unable to generate decision ID.") - return nil, func() error { return nil }, err + return nil, err } var input map[string]interface{} - stop := func() error { + defer func() { stopeval() - logErr := opa.logDecision(ctx, input, result, err) - if logErr != nil { - return logErr + err := opa.logDecision(ctx, input, result, err) + if err != nil { + opa.Logger().WithFields(map[string]interface{}{"err": err}).Error("Unable to log decision to control plane.") } - return nil - } + }() if ctx.Err() != nil { err = errors.Wrap(ctx.Err(), "check request timed out before query execution") - return nil, stop, err + return nil, err } logger := opa.manager.Logger().WithFields(map[string]interface{}{"decision-id": result.DecisionID}) input, err = envoyauth.RequestToInput(req, logger, nil, true) if err != nil { - return nil, stop, err + return nil, err } inputValue, err := ast.InterfaceToValue(input) if err != nil { - return nil, stop, err + return nil, err } err = envoyauth.Eval(ctx, opa, inputValue, result, rego.DistributedTracingOpts(tracing.Options{opa})) if err != nil { - return nil, stop, err + return nil, err } - return result, stop, nil + return result, nil } func (opa *OpenPolicyAgentInstance) logDecision(ctx context.Context, input interface{}, result *envoyauth.EvalResult, err error) error { diff --git a/filters/openpolicyagent/internal/envoy/envoyplugin.go b/filters/openpolicyagent/internal/envoy/envoyplugin.go index eba70d7770..a0a88a940f 100644 --- a/filters/openpolicyagent/internal/envoy/envoyplugin.go +++ b/filters/openpolicyagent/internal/envoy/envoyplugin.go @@ -43,7 +43,7 @@ func (Factory) Validate(m *plugins.Manager, bs []byte) (interface{}, error) { } func (p *Plugin) Reconfigure(ctx context.Context, config interface{}) { - + p.cfg = *config.(*PluginConfig) } // PluginConfig represents the plugin configuration. diff --git a/filters/openpolicyagent/internal/envoy/policytype.go b/filters/openpolicyagent/internal/envoy/policytype.go deleted file mode 100644 index 7883b786a6..0000000000 --- a/filters/openpolicyagent/internal/envoy/policytype.go +++ /dev/null @@ -1,21 +0,0 @@ -package envoy - -import "errors" - -type PolicyType string - -const ( - IngressPolicyType PolicyType = "ingress" - - EgressPolicyType PolicyType = "egress" - - AppPolicyType PolicyType = "app" -) - -func (lt PolicyType) IsValid() error { - switch lt { - case IngressPolicyType, EgressPolicyType, AppPolicyType: - return nil - } - return errors.New("invalid policy type") -} diff --git a/filters/openpolicyagent/internal/envoy/skipperadapter.go b/filters/openpolicyagent/internal/envoy/skipperadapter.go index 101cac39b5..c405b10117 100644 --- a/filters/openpolicyagent/internal/envoy/skipperadapter.go +++ b/filters/openpolicyagent/internal/envoy/skipperadapter.go @@ -6,10 +6,9 @@ import ( ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" - _struct "github.com/golang/protobuf/ptypes/struct" ) -func AdaptToEnvoyExtAuthRequest(req *http.Request, pt PolicyType, contextExtensions map[string]string) *ext_authz_v3.CheckRequest { +func AdaptToEnvoyExtAuthRequest(req *http.Request, metadata *ext_authz_v3_core.Metadata, contextExtensions map[string]string) *ext_authz_v3.CheckRequest { headers := make(map[string]string) for h, vv := range req.Header { @@ -29,17 +28,7 @@ func AdaptToEnvoyExtAuthRequest(req *http.Request, pt PolicyType, contextExtensi }, }, ContextExtensions: contextExtensions, - MetadataContext: &ext_authz_v3_core.Metadata{ - FilterMetadata: map[string]*_struct.Struct{ - "envoy.filters.http.header_to_metadata": { - Fields: map[string]*_struct.Value{ - "policy_type": { - Kind: &_struct.Value_StringValue{StringValue: string(pt)}, - }, - }, - }, - }, - }, + MetadataContext: metadata, }, } } diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 91542343a1..8dacedcaec 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -9,6 +9,7 @@ import ( "text/template" "time" + ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/config" "github.com/open-policy-agent/opa/logging" @@ -21,39 +22,36 @@ import ( iCache "github.com/open-policy-agent/opa/topdown/cache" opautil "github.com/open-policy-agent/opa/util" "github.com/opentracing/opentracing-go" + "google.golang.org/protobuf/encoding/protojson" "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" "github.com/zalando/skipper/filters/openpolicyagent/internal/util" ) +const ( + OpenPolicyAgentDecisionKey = "open-policy-agent:decision" + OpenPolicyAgentDecisionBodyKey = "open-policy-agent:decision-body" + OpenPolicyAgentDecisionHeadersKey = "open-policy-agent:decision-headers" +) + type OpenPolicyAgentFactory struct { - // Ideally share one Bundle storage across many OPA "instances". + // Ideally share one Bundle storage across many OPA "instances" using this factory. // This allows to save memory on bundles that are shared // between different policies (i.e. global team memberships) // This not possible due to some limitations in OPA // See https://github.com/open-policy-agent/opa/issues/5707 - bundleStorage storage.Store } func NewOpenPolicyAgentFactory() OpenPolicyAgentFactory { - return OpenPolicyAgentFactory{ - //BundleStorage: inmem.New(), - } + return OpenPolicyAgentFactory{} } type OpenPolicyAgentInstanceConfig struct { - policyType envoy.PolicyType + envoyMetadata *ext_authz_v3_core.Metadata configTemplate []byte envoyContextExtensions map[string]string } -func WithPolicyType(policyType envoy.PolicyType) func(*OpenPolicyAgentInstanceConfig) error { - return func(cfg *OpenPolicyAgentInstanceConfig) error { - cfg.policyType = policyType - return nil - } -} - func WithConfigTemplate(configTemplate []byte) func(*OpenPolicyAgentInstanceConfig) error { return func(cfg *OpenPolicyAgentInstanceConfig) error { cfg.configTemplate = configTemplate @@ -76,8 +74,34 @@ func WithEnvoyContextExtensions(envoyContextExtensions map[string]string) func(* } } -func (cfg *OpenPolicyAgentInstanceConfig) GetPolicyType() envoy.PolicyType { - return cfg.policyType +func WithEnvoyMetadata(metadata *ext_authz_v3_core.Metadata) func(*OpenPolicyAgentInstanceConfig) error { + return func(cfg *OpenPolicyAgentInstanceConfig) error { + cfg.envoyMetadata = metadata + return nil + } +} + +func WithEnvoyMetadataBytes(content []byte) func(*OpenPolicyAgentInstanceConfig) error { + return func(cfg *OpenPolicyAgentInstanceConfig) error { + cfg.envoyMetadata = &ext_authz_v3_core.Metadata{} + protojson.Unmarshal(content, cfg.envoyMetadata) + return nil + } +} + +func WithEnvoyMetadataFile(file string) func(*OpenPolicyAgentInstanceConfig) error { + + return func(cfg *OpenPolicyAgentInstanceConfig) error { + content, err := os.ReadFile(file) + if err != nil { + return err + } + return WithEnvoyMetadataBytes(content)(cfg) + } +} + +func (cfg *OpenPolicyAgentInstanceConfig) GetEnvoyMetadata() *ext_authz_v3_core.Metadata { + return cfg.envoyMetadata } func (cfg *OpenPolicyAgentInstanceConfig) GetEnvoyContextExtensions() map[string]string { @@ -87,23 +111,14 @@ func (cfg *OpenPolicyAgentInstanceConfig) GetEnvoyContextExtensions() map[string func NewOpenPolicyAgentConfig(opts ...func(*OpenPolicyAgentInstanceConfig) error) (*OpenPolicyAgentInstanceConfig, error) { cfg := OpenPolicyAgentInstanceConfig{} - cfg.policyType = envoy.IngressPolicyType - if val, found := os.LookupEnv("OPA_ENVOY_POLICY_TYPE"); found { - cfg.policyType = envoy.PolicyType(val) - } - for _, opt := range opts { if err := opt(&cfg); err != nil { return nil, err } } - err := cfg.policyType.IsValid() - if err != nil { - return nil, err - } - if cfg.configTemplate == nil { + var err error cfg.configTemplate, err = os.ReadFile(configTemplate()) if err != nil { return nil, err @@ -114,21 +129,10 @@ func NewOpenPolicyAgentConfig(opts ...func(*OpenPolicyAgentInstanceConfig) error } func configTemplate() string { - var configTemplate string - var found bool - if configTemplate, found = os.LookupEnv("OPA_CONFIG_TEMPLATE_FILE"); !found { - configTemplate = "opaconfig.yaml" - } - - return configTemplate -} - -func (factory *OpenPolicyAgentFactory) BundleStorage() storage.Store { - if factory.bundleStorage == nil { - return inmem.New() - } else { - return factory.bundleStorage + if s, ok := os.LookupEnv("OPA_CONFIG_TEMPLATE_FILE"); ok { + return s } + return "opaconfig.yaml" } func (factory *OpenPolicyAgentFactory) NewOpenPolicyAgentEnvoyInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { @@ -139,7 +143,7 @@ func (factory *OpenPolicyAgentFactory) NewOpenPolicyAgentEnvoyInstance(bundleNam return nil, err } - engine, err := New(factory.BundleStorage(), configBytes, config, filterName, bundleName) + engine, err := New(inmem.New(), configBytes, config, filterName, bundleName) if err != nil { return nil, err @@ -150,9 +154,8 @@ func (factory *OpenPolicyAgentFactory) NewOpenPolicyAgentEnvoyInstance(bundleNam return nil, err } - if err := engine.waitPluginsReady( - 100*time.Millisecond, - time.Second*time.Duration(30)); err != nil { + err = engine.waitPluginsReady(100*time.Millisecond, 30*time.Second) + if err != nil { engine.Logger().WithFields(map[string]interface{}{"err": err}).Error("Failed to wait for plugins activation.") return nil, err } @@ -297,6 +300,10 @@ func (opa *OpenPolicyAgentInstance) StartSpanFromContext(ctx context.Context) (o return span, ctx } +func (opa *OpenPolicyAgentInstance) MetricsKey(key string) string { + return key + "." + opa.bundleName +} + func (opa *OpenPolicyAgentInstance) ParsedQuery() ast.Body { return opa.EnvoyPluginConfig().ParsedQuery } diff --git a/filters/openpolicyagent/openpolicyagent_test.go b/filters/openpolicyagent/openpolicyagent_test.go index 73a448ee0c..59a34f7ec4 100644 --- a/filters/openpolicyagent/openpolicyagent_test.go +++ b/filters/openpolicyagent/openpolicyagent_test.go @@ -4,7 +4,10 @@ import ( "os" "testing" + ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + _struct "github.com/golang/protobuf/ptypes/struct" "github.com/stretchr/testify/assert" + "google.golang.org/protobuf/encoding/protojson" ) func TestInterpolateTemplate(t *testing.T) { @@ -25,3 +28,41 @@ func TestInterpolateTemplate(t *testing.T) { `, string(interpolatedConfig)) } + +func TestLoadEnvoyMetadata(t *testing.T) { + cfg := &OpenPolicyAgentInstanceConfig{} + WithEnvoyMetadataBytes([]byte(` + { + "filter_metadata": { + "envoy.filters.http.header_to_metadata": { + "policy_type": "ingress" + } + } + } + `))(cfg) + + expectedBytes, err := protojson.Marshal(&ext_authz_v3_core.Metadata{ + FilterMetadata: map[string]*_struct.Struct{ + "envoy.filters.http.header_to_metadata": { + Fields: map[string]*_struct.Value{ + "policy_type": { + Kind: &_struct.Value_StringValue{StringValue: "ingress"}, + }, + }, + }, + }, + }) + + if err != nil { + t.Error(err) + } + + expected := &ext_authz_v3_core.Metadata{} + err = protojson.Unmarshal(expectedBytes, expected) + if err != nil { + t.Error(err) + } + + assert.Equal(t, expected, cfg.envoyMetadata) + +} diff --git a/filters/openpolicyagent/response.go b/filters/openpolicyagent/response.go index b3493426a2..0061f9e942 100644 --- a/filters/openpolicyagent/response.go +++ b/filters/openpolicyagent/response.go @@ -14,6 +14,8 @@ import ( func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.FilterContext, span opentracing.Span, result *envoyauth.EvalResult, err error) { resp := http.Response{} + fc.Metrics().IncCounter(opa.MetricsKey("decision.err")) + resp.StatusCode = http.StatusInternalServerError if result != nil { @@ -45,19 +47,18 @@ func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.Filter func (opa *OpenPolicyAgentInstance) ServeResponse(fc filters.FilterContext, span opentracing.Span, result *envoyauth.EvalResult) { resp := http.Response{} - status, err := result.GetResponseHTTPStatus() + var err error + resp.StatusCode, err = result.GetResponseHTTPStatus() if err != nil { opa.RejectInvalidDecisionError(fc, span, result, err) return } - resp.StatusCode = status - headers, err := result.GetResponseHTTPHeaders() + resp.Header, err = result.GetResponseHTTPHeaders() if err != nil { opa.RejectInvalidDecisionError(fc, span, result, err) return } - resp.Header = headers hasbody := result.HasResponseBody() diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index 17803a4e34..55eeada1a0 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -24,14 +24,9 @@ func NewServeResponseWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentF } func (s Spec) Name() string { - return "serveResponseWithRegoPolicy" + return filters.ServeResponseWithRegoPolicyName } -const ( - paramBundleName int = iota - paramEnvoyContextExtensions -) - func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { var err error @@ -48,13 +43,13 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { return nil, filters.ErrInvalidFilterParameters } - bundleName := sargs[paramBundleName] + bundleName := sargs[0] configOptions := s.configOpts if len(sargs) > 1 { envoyContextExtensions := map[string]string{} - err = yaml.Unmarshal([]byte(sargs[paramEnvoyContextExtensions]), &envoyContextExtensions) + err = yaml.Unmarshal([]byte(sargs[1]), &envoyContextExtensions) if err != nil { return nil, err } @@ -88,7 +83,7 @@ func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { span, ctx := f.opa.StartSpanFromContext(req.Context()) defer span.Finish() - authzreq := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetPolicyType(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + authzreq := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) result, err := f.opa.Eval(ctx, authzreq) diff --git a/skipper.go b/skipper.go index d6b4ce5d75..03532251c3 100644 --- a/skipper.go +++ b/skipper.go @@ -898,6 +898,7 @@ type Options struct { EnableOpenPolicyAgent bool OpenPolicyAgentConfigTemplate string + OpenPolicyAgentEnvoyMetadata string } func (o *Options) KubernetesDataClientOptions() kubernetes.Options { @@ -1764,9 +1765,11 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { o.CustomFilters = append(o.CustomFilters, authorizewithregopolicy.NewAuthorizeWithRegoPolicySpec(factory, - openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate)), + openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate), + openpolicyagent.WithEnvoyMetadataFile(o.OpenPolicyAgentEnvoyMetadata)), serveresponsewithregopolicy.NewServeResponseWithRegoPolicySpec(factory, - openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate)), + openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate), + openpolicyagent.WithEnvoyMetadataFile(o.OpenPolicyAgentEnvoyMetadata)), ) } From 4e4e2194d25e98c937fc032542dc10bf940ad148 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Thu, 8 Jun 2023 16:15:39 +0200 Subject: [PATCH 04/24] Metrics for serveresponsewithregopolicy, review comments Signed-off-by: Magnus Jungsbluth --- .../authorizewithregopolicy.go | 10 ++--- .../serveresponsewithregopolicy.go | 38 ++++++++----------- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index d31bb1361a..884a918ade 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -13,14 +13,14 @@ import ( ) type Spec struct { - factory openpolicyagent.OpenPolicyAgentFactory - configOpts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error + factory openpolicyagent.OpenPolicyAgentFactory + opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error } func NewAuthorizeWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFactory, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) Spec { return Spec{ - factory: factory, - configOpts: opts, + factory: factory, + opts: opts, } } @@ -46,7 +46,7 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { bundleName := sargs[0] - configOptions := s.configOpts + configOptions := s.opts if len(sargs) > 1 { envoyContextExtensions := map[string]string{} diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index 55eeada1a0..1c9868b0c3 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -12,14 +12,14 @@ import ( ) type Spec struct { - factory openpolicyagent.OpenPolicyAgentFactory - configOpts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error + factory openpolicyagent.OpenPolicyAgentFactory + opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error } func NewServeResponseWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFactory, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) Spec { return Spec{ - factory: factory, - configOpts: opts, + factory: factory, + opts: opts, } } @@ -45,7 +45,7 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { bundleName := sargs[0] - configOptions := s.configOpts + configOptions := s.opts if len(sargs) > 1 { envoyContextExtensions := map[string]string{} @@ -77,37 +77,29 @@ type serveResponseWithRegoPolicyFilter struct { } func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { - start := time.Now() - req := fc.Request() span, ctx := f.opa.StartSpanFromContext(req.Context()) defer span.Finish() authzreq := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + start := time.Now() result, err := f.opa.Eval(ctx, authzreq) + fc.Metrics().MeasureSince(f.opa.MetricsKey("eval_time"), start) if err != nil { f.opa.RejectInvalidDecisionError(fc, span, result, err) return } - f.opa.Logger().WithFields(map[string]interface{}{ - "query": f.opa.EnvoyPluginConfig().ParsedQuery.String(), - "dry-run": f.opa.EnvoyPluginConfig().DryRun, - "decision": result.Decision, - "err": err, - "txn": result.TxnID, - "metrics": result.Metrics.All(), - "total_decision_time": time.Since(start), - }).Debug("Authorizing request with decision.") - - if !f.opa.EnvoyPluginConfig().DryRun { - if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) - } else { - f.opa.ServeResponse(fc, span, result) - } + if f.opa.EnvoyPluginConfig().DryRun { + return + } + + if err != nil { + f.opa.RejectInvalidDecisionError(fc, span, result, err) + } else { + f.opa.ServeResponse(fc, span, result) } } From 1abaaa7d8cea2fa8964c14b165c9d1352012b8ae Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Wed, 14 Jun 2023 22:14:41 +0200 Subject: [PATCH 05/24] Review comments (2nd batch), quiet logging Signed-off-by: Magnus Jungsbluth --- config/config.go | 1 + docs/reference/filters.md | 8 +-- docs/tutorials/auth.md | 5 +- .../authorizewithregopolicy.go | 4 +- .../authorizewithregopolicy_test.go | 10 ++-- filters/openpolicyagent/evaluation.go | 7 +-- .../internal/envoy/skipperadapter.go | 2 +- filters/openpolicyagent/openpolicyagent.go | 55 +++++++++++++++++-- filters/openpolicyagent/response.go | 1 + .../serveresponsewithregopolicy.go | 4 +- .../serveresponsewithregopolicy_test.go | 8 +-- skipper.go | 14 +++-- 12 files changed, 80 insertions(+), 39 deletions(-) diff --git a/config/config.go b/config/config.go index e0f00f1597..49e6971ec2 100644 --- a/config/config.go +++ b/config/config.go @@ -489,6 +489,7 @@ func NewConfig() *Config { flag.DurationVar(&cfg.CredentialsUpdateInterval, "credentials-update-interval", 10*time.Minute, "sets the interval to update secrets") flag.BoolVar(&cfg.EnableOpenPolicyAgent, "enable-open-policy-agent", false, "enables Open Policy Agent filters") flag.StringVar(&cfg.OpenPolicyAgentConfigTemplate, "open-policy-agent-config-template", "", "file containing a template for an Open Policy Agent configuration file that is interpolated for each OPA filter instance") + flag.StringVar(&cfg.OpenPolicyAgentEnvoyMetadata, "open-policy-agent-envoy-metadata", "", "JSON file containing meta-data passed in input for compatibility with Envoy policies in the format") // TLS client certs flag.StringVar(&cfg.ClientKeyFile, "client-tls-key", "", "TLS Key file for backend connections, multiple keys may be given comma separated - the order must match the certs") diff --git a/docs/reference/filters.md b/docs/reference/filters.md index 5fc71d1e41..4369a37251 100644 --- a/docs/reference/filters.md +++ b/docs/reference/filters.md @@ -1738,7 +1738,7 @@ Example (passing context): authorizeWithRegoPolicy("my-app-id", "com.mydomain.xxx.myprop: myvalue") ``` -#### Data Flows +*Data Flows* The data flow in case the policy allows the request looks like this @@ -1792,7 +1792,7 @@ The data flow in case the policy disallows the request looks like this The difference is that if the decision in (3) is equivalent to false, the response is handled directly from the filter. If the decision contains response body, status or headers those are used to build the response in (6) otherwise a 403 Forbidden with a generic body is returned. -##### Manipulating Request Headers +*Manipulating Request Headers* Headers both to the upstream and the downstream service can be manipulated the same way this works for [Envoy external authorization](https://www.openpolicyagent.org/docs/latest/envoy-primer/#example-policy-with-additional-controls) @@ -1815,9 +1815,7 @@ Example (passing context): serveResponseWithRegoPolicy("my-app-id", "com.mydomain.xxx.myprop: myvalue") ``` -#### Data Flows - -## Diagram +*Data Flows* For this filter, the data flow looks like this independent of an allow/deny decision diff --git a/docs/tutorials/auth.md b/docs/tutorials/auth.md index c46d11f8f9..f06591a959 100644 --- a/docs/tutorials/auth.md +++ b/docs/tutorials/auth.md @@ -442,10 +442,9 @@ discovery: The variable `.bundlename` is the first argument in the following filters and can be in any format that OPA can understand, so for example application IDs from a registry, uuids, ... -### Envoy Structures +### Input Structures -While Envoy is an alternative OSS product similar to Skipper, it has already defined structures for how external authorization should be done and also how authorization decisions can influence the Envoy response. Open Policy Agent already has direct support for this and also commercial control planes support this. On top of this [examples and documentation](https://www.openpolicyagent.org/docs/latest/envoy-primer/) already exist. -Instead of re-inventing these structures (for example how http headers and so on are represented), this implementation maps Skipper objects onto their Envoy representation. This also allows to reuse a fair bit of the [opa-envoy-plugin](https://github.com/open-policy-agent/opa-envoy-plugin), which does the heavy lifting of evaluating decisions against the OPA Go library. +Input structures to policies follow those that are used by the [opa-envoy-plugin](https://github.com/open-policy-agent/opa-envoy-plugin), the existing [examples and documentation](https://www.openpolicyagent.org/docs/latest/envoy-primer/#example-input) apply also to Skipper. ### Passing context to the policy diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index 884a918ade..793bfbe506 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -62,7 +62,7 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { return nil, err } - opa, err := s.factory.NewOpenPolicyAgentEnvoyInstance(bundleName, *opaConfig, s.Name()) + opa, err := s.factory.NewOpenPolicyAgentInstance(bundleName, *opaConfig, s.Name()) if err != nil { return nil, err @@ -82,7 +82,7 @@ func (f authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { span, ctx := f.opa.StartSpanFromContext(req.Context()) defer span.Finish() - authzreq := envoy.AdaptToEnvoyExtAuthRequest(req, f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + authzreq := envoy.AdaptToExtAuthRequest(req, f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) start := time.Now() result, err := f.opa.Eval(ctx, authzreq) diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go index b9f9545baa..9424afd979 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go @@ -154,7 +154,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { proxy := proxytest.New(fr, r) reqURL, err := url.Parse(proxy.URL) if err != nil { - t.Errorf("Failed to parse url %s: %v", proxy.URL, err) + t.Fatalf("Failed to parse url %s: %v", proxy.URL, err) } reqURL.Path = path.Join(reqURL.Path, ti.requestPath) req, err := http.NewRequest("GET", reqURL.String(), nil) @@ -163,13 +163,13 @@ func TestAuthorizeRequestFilter(t *testing.T) { } if err != nil { - t.Error(err) + t.Fatal(err) return } rsp, err := http.DefaultClient.Do(req) if err != nil { - t.Error(err) + t.Fatal(err) } assert.Equal(t, ti.expectedStatus, rsp.StatusCode, "HTTP status does not match") @@ -179,9 +179,9 @@ func TestAuthorizeRequestFilter(t *testing.T) { defer rsp.Body.Close() body, err := io.ReadAll(rsp.Body) if err != nil { - t.Error(err) + t.Fatal(err) } - assert.Equal(t, ti.expectedBody, string(body), "HTTP Headers do not match") + assert.Equal(t, ti.expectedBody, string(body), "HTTP Body does not match") }) } } diff --git a/filters/openpolicyagent/evaluation.go b/filters/openpolicyagent/evaluation.go index 62673371cd..88f9945702 100644 --- a/filters/openpolicyagent/evaluation.go +++ b/filters/openpolicyagent/evaluation.go @@ -2,6 +2,7 @@ package openpolicyagent import ( "context" + "fmt" "time" ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" @@ -12,12 +13,9 @@ import ( "github.com/open-policy-agent/opa/server" "github.com/open-policy-agent/opa/tracing" "github.com/opentracing/opentracing-go" - "github.com/pkg/errors" ) func (opa *OpenPolicyAgentInstance) Eval(ctx context.Context, req *ext_authz_v3.CheckRequest) (*envoyauth.EvalResult, error) { - var err error - result, stopeval, err := envoyauth.NewEvalResult() span := opentracing.SpanFromContext(ctx) if span != nil { @@ -39,8 +37,7 @@ func (opa *OpenPolicyAgentInstance) Eval(ctx context.Context, req *ext_authz_v3. }() if ctx.Err() != nil { - err = errors.Wrap(ctx.Err(), "check request timed out before query execution") - return nil, err + return nil, fmt.Errorf("check request timed out before query execution: %w", ctx.Err()) } logger := opa.manager.Logger().WithFields(map[string]interface{}{"decision-id": result.DecisionID}) diff --git a/filters/openpolicyagent/internal/envoy/skipperadapter.go b/filters/openpolicyagent/internal/envoy/skipperadapter.go index c405b10117..c2266d1b0e 100644 --- a/filters/openpolicyagent/internal/envoy/skipperadapter.go +++ b/filters/openpolicyagent/internal/envoy/skipperadapter.go @@ -8,7 +8,7 @@ import ( ext_authz_v3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" ) -func AdaptToEnvoyExtAuthRequest(req *http.Request, metadata *ext_authz_v3_core.Metadata, contextExtensions map[string]string) *ext_authz_v3.CheckRequest { +func AdaptToExtAuthRequest(req *http.Request, metadata *ext_authz_v3_core.Metadata, contextExtensions map[string]string) *ext_authz_v3.CheckRequest { headers := make(map[string]string) for h, vv := range req.Header { diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 8dacedcaec..a53f7839bc 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -3,6 +3,7 @@ package openpolicyagent import ( "bytes" "context" + "fmt" "os" "strings" "sync" @@ -84,19 +85,24 @@ func WithEnvoyMetadata(metadata *ext_authz_v3_core.Metadata) func(*OpenPolicyAge func WithEnvoyMetadataBytes(content []byte) func(*OpenPolicyAgentInstanceConfig) error { return func(cfg *OpenPolicyAgentInstanceConfig) error { cfg.envoyMetadata = &ext_authz_v3_core.Metadata{} - protojson.Unmarshal(content, cfg.envoyMetadata) - return nil + + return protojson.Unmarshal(content, cfg.envoyMetadata) } } func WithEnvoyMetadataFile(file string) func(*OpenPolicyAgentInstanceConfig) error { - return func(cfg *OpenPolicyAgentInstanceConfig) error { content, err := os.ReadFile(file) if err != nil { return err } - return WithEnvoyMetadataBytes(content)(cfg) + + err = WithEnvoyMetadataBytes(content)(cfg) + if err != nil { + return fmt.Errorf("cannot parse '%v': %w", file, err) + } + + return nil } } @@ -135,7 +141,7 @@ func configTemplate() string { return "opaconfig.yaml" } -func (factory *OpenPolicyAgentFactory) NewOpenPolicyAgentEnvoyInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { +func (factory *OpenPolicyAgentFactory) NewOpenPolicyAgentInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { runtime.RegisterPlugin(envoy.PluginName, envoy.Factory{}) configBytes, err := interpolateConfigTemplate(config.configTemplate, bundleName) @@ -220,7 +226,10 @@ func New(store storage.Store, configBytes []byte, instanceConfig OpenPolicyAgent } runtime.RegisterPlugin(envoy.PluginName, envoy.Factory{}) - manager, err := plugins.New(configBytes, id, store, plugins.Logger(logging.New().WithFields(map[string]interface{}{"skipper-filter": filterName}))) + + var logger logging.Logger = &QuietLogger{target: logging.Get()} + logger = logger.WithFields(map[string]interface{}{"skipper-filter": filterName}) + manager, err := plugins.New(configBytes, id, store, plugins.Logger(logger)) if err != nil { return nil, err } @@ -304,6 +313,7 @@ func (opa *OpenPolicyAgentInstance) MetricsKey(key string) string { return key + "." + opa.bundleName } +// Implementation of the envoyauth.EvalContext interface func (opa *OpenPolicyAgentInstance) ParsedQuery() ast.Body { return opa.EnvoyPluginConfig().ParsedQuery } @@ -323,3 +333,36 @@ func (opa *OpenPolicyAgentInstance) SetPreparedQuery(q *rego.PreparedEvalQuery) func (opa *OpenPolicyAgentInstance) Config() *config.Config { return opa.opaConfig } + +// logging.Logger that does not pollute info with debug logs +type QuietLogger struct { + target logging.Logger +} + +func (l *QuietLogger) WithFields(fields map[string]interface{}) logging.Logger { + return &QuietLogger{target: l.target.WithFields(fields)} +} + +func (l *QuietLogger) SetLevel(level logging.Level) { + l.target.SetLevel(level) +} + +func (l *QuietLogger) GetLevel() logging.Level { + return l.target.GetLevel() +} + +func (l *QuietLogger) Debug(fmt string, a ...interface{}) { + l.target.Debug(fmt, a) +} + +func (l *QuietLogger) Info(fmt string, a ...interface{}) { + l.target.Debug(fmt, a) +} + +func (l *QuietLogger) Error(fmt string, a ...interface{}) { + l.target.Error(fmt, a) +} + +func (l *QuietLogger) Warn(fmt string, a ...interface{}) { + l.target.Warn(fmt, a) +} diff --git a/filters/openpolicyagent/response.go b/filters/openpolicyagent/response.go index 0061f9e942..43b7d5a28c 100644 --- a/filters/openpolicyagent/response.go +++ b/filters/openpolicyagent/response.go @@ -17,6 +17,7 @@ func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.Filter fc.Metrics().IncCounter(opa.MetricsKey("decision.err")) resp.StatusCode = http.StatusInternalServerError + span.SetTag("error", true) if result != nil { span.LogKV( diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index 1c9868b0c3..82b9dbe57b 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -61,7 +61,7 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { return nil, err } - opa, err := s.factory.NewOpenPolicyAgentEnvoyInstance(bundleName, *opaConfig, s.Name()) + opa, err := s.factory.NewOpenPolicyAgentInstance(bundleName, *opaConfig, s.Name()) if err != nil { return nil, err @@ -81,7 +81,7 @@ func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { span, ctx := f.opa.StartSpanFromContext(req.Context()) defer span.Finish() - authzreq := envoy.AdaptToEnvoyExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + authzreq := envoy.AdaptToExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) start := time.Now() result, err := f.opa.Eval(ctx, authzreq) diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go index 9ff122d8ab..d9c2e85595 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go @@ -176,18 +176,18 @@ func TestAuthorizeRequestFilter(t *testing.T) { proxy := proxytest.New(fr, r) reqURL, err := url.Parse(proxy.URL) if err != nil { - t.Errorf("Failed to parse url %s: %v", proxy.URL, err) + t.Fatalf("Failed to parse url %s: %v", proxy.URL, err) } reqURL.Path = path.Join(reqURL.Path, ti.requestPath) req, err := http.NewRequest("GET", reqURL.String(), nil) if err != nil { - t.Error(err) + t.Fatal(err) return } rsp, err := http.DefaultClient.Do(req) if err != nil { - t.Error(err) + t.Fatal(err) } assert.Equal(t, ti.expectedStatus, rsp.StatusCode, "HTTP status does not match") @@ -202,7 +202,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { defer rsp.Body.Close() body, err := io.ReadAll(rsp.Body) if err != nil { - t.Error(err) + t.Fatal(err) } assert.Equal(t, ti.expectedBody, string(body), "HTTP Headers do not match") }) diff --git a/skipper.go b/skipper.go index 03532251c3..aef558ced2 100644 --- a/skipper.go +++ b/skipper.go @@ -1763,13 +1763,15 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { if o.EnableOpenPolicyAgent { factory := openpolicyagent.NewOpenPolicyAgentFactory() + opts := make([]func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error, 0) + opts = append(opts, openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate)) + if o.OpenPolicyAgentEnvoyMetadata != "" { + opts = append(opts, openpolicyagent.WithEnvoyMetadataFile(o.OpenPolicyAgentEnvoyMetadata)) + } + o.CustomFilters = append(o.CustomFilters, - authorizewithregopolicy.NewAuthorizeWithRegoPolicySpec(factory, - openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate), - openpolicyagent.WithEnvoyMetadataFile(o.OpenPolicyAgentEnvoyMetadata)), - serveresponsewithregopolicy.NewServeResponseWithRegoPolicySpec(factory, - openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate), - openpolicyagent.WithEnvoyMetadataFile(o.OpenPolicyAgentEnvoyMetadata)), + authorizewithregopolicy.NewAuthorizeWithRegoPolicySpec(factory, opts...), + serveresponsewithregopolicy.NewServeResponseWithRegoPolicySpec(factory, opts...), ) } From 6bd0c1275feadabd4fba106f957a676a93dbb8ea Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 19 Jun 2023 22:57:17 +0200 Subject: [PATCH 06/24] Correctly use filter context tracer Signed-off-by: Magnus Jungsbluth --- .../authorizewithregopolicy.go | 2 +- filters/openpolicyagent/openpolicyagent.go | 21 ++++++++++++++++--- .../serveresponsewithregopolicy.go | 3 +-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index 793bfbe506..cd85bcbc67 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -79,7 +79,7 @@ type authorizeWithRegoPolicyFilter struct { func (f authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { req := fc.Request() - span, ctx := f.opa.StartSpanFromContext(req.Context()) + span, ctx := f.opa.StartSpanFromFilterContext(fc) defer span.Finish() authzreq := envoy.AdaptToExtAuthRequest(req, f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index a53f7839bc..52eca41b8d 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -25,6 +25,7 @@ import ( "github.com/opentracing/opentracing-go" "google.golang.org/protobuf/encoding/protojson" + "github.com/zalando/skipper/filters" "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" "github.com/zalando/skipper/filters/openpolicyagent/internal/util" ) @@ -303,10 +304,24 @@ func (opa *OpenPolicyAgentInstance) EnvoyPluginConfig() envoy.PluginConfig { return defaultConfig } -func (opa *OpenPolicyAgentInstance) StartSpanFromContext(ctx context.Context) (opentracing.Span, context.Context) { - span, ctx := opentracing.StartSpanFromContext(ctx, "open-policy-agent") +func (opa *OpenPolicyAgentInstance) startSpanFromContextWithTracer(tr opentracing.Tracer, parent opentracing.Span, ctx context.Context) (opentracing.Span, context.Context) { + span := tr.StartSpan("open-policy-agent", opentracing.ChildOf(parent.Context())) + span.SetTag("bundle_name", opa.bundleName) - return span, ctx + return span, opentracing.ContextWithSpan(ctx, span) +} + +func (opa *OpenPolicyAgentInstance) StartSpanFromFilterContext(fc filters.FilterContext) (opentracing.Span, context.Context) { + return opa.startSpanFromContextWithTracer(fc.Tracer(), fc.ParentSpan(), fc.Request().Context()) +} + +func (opa *OpenPolicyAgentInstance) StartSpanFromContext(ctx context.Context) (opentracing.Span, context.Context) { + span := opentracing.SpanFromContext(ctx) + if span != nil { + return opa.startSpanFromContextWithTracer(span.Tracer(), span, ctx) + } + + return opa.startSpanFromContextWithTracer(opentracing.GlobalTracer(), span, ctx) } func (opa *OpenPolicyAgentInstance) MetricsKey(key string) string { diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index 82b9dbe57b..34057154fb 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -77,8 +77,7 @@ type serveResponseWithRegoPolicyFilter struct { } func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { - req := fc.Request() - span, ctx := f.opa.StartSpanFromContext(req.Context()) + span, ctx := f.opa.StartSpanFromFilterContext(fc) defer span.Finish() authzreq := envoy.AdaptToExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) From 9615c320b5d89ae0d89871dc7f600ab559f39484 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Tue, 20 Jun 2023 08:02:23 +0200 Subject: [PATCH 07/24] Add OPA labels to Span Signed-off-by: Magnus Jungsbluth --- filters/openpolicyagent/openpolicyagent.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 52eca41b8d..dbd7cbd07d 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -308,6 +308,11 @@ func (opa *OpenPolicyAgentInstance) startSpanFromContextWithTracer(tr opentracin span := tr.StartSpan("open-policy-agent", opentracing.ChildOf(parent.Context())) span.SetTag("bundle_name", opa.bundleName) + + for label, value := range opa.manager.Labels() { + span.SetTag("label."+label, value) + } + return span, opentracing.ContextWithSpan(ctx, span) } From 338ea1b579820cbc2be2e95cd2eaef136ce2c176 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Tue, 20 Jun 2023 21:13:30 +0200 Subject: [PATCH 08/24] Add ops documentation / prefix tags on Span Signed-off-by: Magnus Jungsbluth --- docs/operation/operation.md | 26 ++++++++++++++++++++++ filters/openpolicyagent/evaluation.go | 2 +- filters/openpolicyagent/openpolicyagent.go | 6 ++--- filters/openpolicyagent/response.go | 2 +- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/docs/operation/operation.md b/docs/operation/operation.md index b8936417c8..95e5c9fb5b 100644 --- a/docs/operation/operation.md +++ b/docs/operation/operation.md @@ -489,6 +489,22 @@ by the default, and exposed among the timers via the following keys: See more details about rate limiting at [Rate limiting](../reference/filters.md#clusterclientratelimit). +### Open Policy Agent metrics + +If Open Policy Agent filters are enabled, the following counters show up in the `/metrics` endpoint. The bundle-name is the first parameter of the filter so that for example increased error codes can be attributed to a specific source bundle / system. + +- `skipper.authorizeWithRegoPolicy.custom.decision.allow.` +- `skipper.authorizeWithRegoPolicy.custom.decision.deny.` +- `skipper.authorizeWithRegoPolicy.custom.decision.err.` +- `skipper.serveResponseWithRegoPolicy.custom.decision.allow.` +- `skipper.serveResponseWithRegoPolicy.custom.decision.deny.` +- `skipper.serveResponseWithRegoPolicy.custom.decision.err.` + +The following timer metrics are exposed per used bundle-name: + +- `skipper.authorizeWithRegoPolicy.custom.eval_time.` +- `skipper.serveResponseWithRegoPolicy.custom.eval_time.` + ## OpenTracing Skipper has support for different [OpenTracing API](http://opentracing.io/) vendors, including @@ -612,6 +628,16 @@ connect, TLS handshake and connection pool: ![tokeninfo auth filter span with logs](../img/skipper_opentracing_auth_filter_tokeninfo_span_with_logs.png) +### Open Policy Agent span + +When one of the Open Policy Agent filters is used, child spans with the operation name `openpolicyagent` are added to the Trace. + +The following tags are added to the Span, labels are taken from the OPA configuration YAML file as is and are not interpreted: +- `opa.decision_id=` +- `opa.labels.=` + +The labels can for example be used to link to a specific decision in the control plane if they contain URL fragments for the receiving entity. + ### Redis rate limiting spans #### Operation: redis_allow_check_card diff --git a/filters/openpolicyagent/evaluation.go b/filters/openpolicyagent/evaluation.go index 88f9945702..8f12986512 100644 --- a/filters/openpolicyagent/evaluation.go +++ b/filters/openpolicyagent/evaluation.go @@ -19,7 +19,7 @@ func (opa *OpenPolicyAgentInstance) Eval(ctx context.Context, req *ext_authz_v3. result, stopeval, err := envoyauth.NewEvalResult() span := opentracing.SpanFromContext(ctx) if span != nil { - span.SetTag("decision_id", result.DecisionID) + span.SetTag("opa.decision_id", result.DecisionID) } if err != nil { diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index dbd7cbd07d..58fbaa704b 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -305,12 +305,12 @@ func (opa *OpenPolicyAgentInstance) EnvoyPluginConfig() envoy.PluginConfig { } func (opa *OpenPolicyAgentInstance) startSpanFromContextWithTracer(tr opentracing.Tracer, parent opentracing.Span, ctx context.Context) (opentracing.Span, context.Context) { - span := tr.StartSpan("open-policy-agent", opentracing.ChildOf(parent.Context())) + span := tr.StartSpan("openpolicyagent", opentracing.ChildOf(parent.Context())) - span.SetTag("bundle_name", opa.bundleName) + span.SetTag("opa.bundle_name", opa.bundleName) for label, value := range opa.manager.Labels() { - span.SetTag("label."+label, value) + span.SetTag("opa.label."+label, value) } return span, opentracing.ContextWithSpan(ctx, span) diff --git a/filters/openpolicyagent/response.go b/filters/openpolicyagent/response.go index 43b7d5a28c..e24f58b1e6 100644 --- a/filters/openpolicyagent/response.go +++ b/filters/openpolicyagent/response.go @@ -22,7 +22,7 @@ func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.Filter if result != nil { span.LogKV( "event", "error", - "decision_id", result.DecisionID, + "opa.decision_id", result.DecisionID, "message", err.Error(), ) From 23f1c01daee7d1aadaa4e49e7fff17cf724c4336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandor=20Sz=C3=BCcs?= Date: Fri, 23 Jun 2023 15:45:35 +0200 Subject: [PATCH 09/24] fix: filters should be ptr receiver and spec should return a filters.Spec interface type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sandor Szücs --- .../authorizewithregopolicy.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index cd85bcbc67..45e64a353b 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -12,23 +12,23 @@ import ( "github.com/zalando/skipper/filters/openpolicyagent/internal/util" ) -type Spec struct { +type spec struct { factory openpolicyagent.OpenPolicyAgentFactory opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error } -func NewAuthorizeWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFactory, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) Spec { - return Spec{ +func NewAuthorizeWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFactory, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) filters.Spec { + return &spec{ factory: factory, opts: opts, } } -func (s Spec) Name() string { +func (s *spec) Name() string { return filters.AuthorizeWithRegoPolicyName } -func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { +func (s *spec) CreateFilter(config []interface{}) (filters.Filter, error) { var err error sargs, err := util.GetStrings(config) @@ -68,7 +68,7 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { return nil, err } - return authorizeWithRegoPolicyFilter{ + return &authorizeWithRegoPolicyFilter{ opa: opa, }, nil } @@ -77,7 +77,7 @@ type authorizeWithRegoPolicyFilter struct { opa *openpolicyagent.OpenPolicyAgentInstance } -func (f authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { +func (f *authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { req := fc.Request() span, ctx := f.opa.StartSpanFromFilterContext(fc) defer span.Finish() @@ -150,4 +150,4 @@ func addRequestHeaders(fc filters.FilterContext, headers http.Header) { } } -func (f authorizeWithRegoPolicyFilter) Response(fc filters.FilterContext) {} +func (*authorizeWithRegoPolicyFilter) Response(filters.FilterContext) {} From dd302103e07b2d90031f118790f243db403c5084 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Fri, 23 Jun 2023 16:41:59 +0200 Subject: [PATCH 10/24] Make tracing work without parent Span Signed-off-by: Magnus Jungsbluth --- filters/openpolicyagent/openpolicyagent.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 58fbaa704b..33e44776ff 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -305,7 +305,12 @@ func (opa *OpenPolicyAgentInstance) EnvoyPluginConfig() envoy.PluginConfig { } func (opa *OpenPolicyAgentInstance) startSpanFromContextWithTracer(tr opentracing.Tracer, parent opentracing.Span, ctx context.Context) (opentracing.Span, context.Context) { - span := tr.StartSpan("openpolicyagent", opentracing.ChildOf(parent.Context())) + var span opentracing.Span + if parent != nil { + span = tr.StartSpan("openpolicyagent", opentracing.ChildOf(parent.Context())) + } else { + span = tr.StartSpan("openpolicyagent") + } span.SetTag("opa.bundle_name", opa.bundleName) @@ -326,7 +331,7 @@ func (opa *OpenPolicyAgentInstance) StartSpanFromContext(ctx context.Context) (o return opa.startSpanFromContextWithTracer(span.Tracer(), span, ctx) } - return opa.startSpanFromContextWithTracer(opentracing.GlobalTracer(), span, ctx) + return opa.startSpanFromContextWithTracer(opentracing.GlobalTracer(), nil, ctx) } func (opa *OpenPolicyAgentInstance) MetricsKey(key string) string { From f0ce6372c1ed07671dadee25021ee7ac463a0316 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 26 Jun 2023 00:06:13 +0200 Subject: [PATCH 11/24] Re-use OPA instances, review comments Signed-off-by: Magnus Jungsbluth --- .../authorizewithregopolicy.go | 29 ++-- .../authorizewithregopolicy_test.go | 2 +- filters/openpolicyagent/openpolicyagent.go | 162 ++++++++++++++++-- .../openpolicyagent/openpolicyagent_test.go | 85 +++++++++ .../serveresponsewithregopolicy.go | 43 +++-- .../serveresponsewithregopolicy_test.go | 2 +- skipper.go | 7 +- 7 files changed, 277 insertions(+), 53 deletions(-) diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index 45e64a353b..88e8504b91 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -13,14 +13,14 @@ import ( ) type spec struct { - factory openpolicyagent.OpenPolicyAgentFactory - opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error + registry *openpolicyagent.OpenPolicyAgentRegistry + opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error } -func NewAuthorizeWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFactory, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) filters.Spec { +func NewAuthorizeWithRegoPolicySpec(registry *openpolicyagent.OpenPolicyAgentRegistry, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) filters.Spec { return &spec{ - factory: factory, - opts: opts, + registry: registry, + opts: opts, } } @@ -48,13 +48,12 @@ func (s *spec) CreateFilter(config []interface{}) (filters.Filter, error) { configOptions := s.opts + envoyContextExtensions := map[string]string{} if len(sargs) > 1 { - envoyContextExtensions := map[string]string{} err = yaml.Unmarshal([]byte(sargs[1]), &envoyContextExtensions) if err != nil { return nil, err } - configOptions = append(configOptions, openpolicyagent.WithEnvoyContextExtensions(envoyContextExtensions)) } opaConfig, err := openpolicyagent.NewOpenPolicyAgentConfig(configOptions...) @@ -62,19 +61,23 @@ func (s *spec) CreateFilter(config []interface{}) (filters.Filter, error) { return nil, err } - opa, err := s.factory.NewOpenPolicyAgentInstance(bundleName, *opaConfig, s.Name()) + opa, err := s.registry.NewOpenPolicyAgentInstance(bundleName, *opaConfig, s.Name()) if err != nil { return nil, err } return &authorizeWithRegoPolicyFilter{ - opa: opa, + opa: opa, + registry: s.registry, + envoyContextExtensions: envoyContextExtensions, }, nil } type authorizeWithRegoPolicyFilter struct { - opa *openpolicyagent.OpenPolicyAgentInstance + opa *openpolicyagent.OpenPolicyAgentInstance + registry *openpolicyagent.OpenPolicyAgentRegistry + envoyContextExtensions map[string]string } func (f *authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { @@ -82,7 +85,7 @@ func (f *authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { span, ctx := f.opa.StartSpanFromFilterContext(fc) defer span.Finish() - authzreq := envoy.AdaptToExtAuthRequest(req, f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + authzreq := envoy.AdaptToExtAuthRequest(req, f.opa.InstanceConfig().GetEnvoyMetadata(), f.envoyContextExtensions) start := time.Now() result, err := f.opa.Eval(ctx, authzreq) @@ -151,3 +154,7 @@ func addRequestHeaders(fc filters.FilterContext, headers http.Header) { } func (*authorizeWithRegoPolicyFilter) Response(filters.FilterContext) {} + +func (f *authorizeWithRegoPolicyFilter) Close() error { + return f.registry.ReleaseInstance(f.opa) +} diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go index 9424afd979..35384cc2f2 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go @@ -139,7 +139,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { } }`, opaControlPlane.URL(), ti.regoQuery)) - opaFactory := openpolicyagent.NewOpenPolicyAgentFactory() + opaFactory := openpolicyagent.NewOpenPolicyAgentRegistry() ftSpec := NewAuthorizeWithRegoPolicySpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) filterArgs := []interface{}{ti.bundleName} _, err := ftSpec.CreateFilter(filterArgs) diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 33e44776ff..0b55e68af1 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -36,22 +36,54 @@ const ( OpenPolicyAgentDecisionHeadersKey = "open-policy-agent:decision-headers" ) -type OpenPolicyAgentFactory struct { - // Ideally share one Bundle storage across many OPA "instances" using this factory. +type OpenPolicyAgentRegistry struct { + // Ideally share one Bundle storage across many OPA "instances" using this registry. // This allows to save memory on bundles that are shared // between different policies (i.e. global team memberships) // This not possible due to some limitations in OPA // See https://github.com/open-policy-agent/opa/issues/5707 + + instances map[string]*OpenPolicyAgentInstance + refcounts map[*OpenPolicyAgentInstance]int + lastused map[*OpenPolicyAgentInstance]time.Time + mtx sync.Mutex + once sync.Once + closed bool + quit chan struct{} + reuseDuration time.Duration +} + +func WithReuseDuration(duration time.Duration) func(*OpenPolicyAgentRegistry) error { + return func(cfg *OpenPolicyAgentRegistry) error { + cfg.reuseDuration = duration + return nil + } } -func NewOpenPolicyAgentFactory() OpenPolicyAgentFactory { - return OpenPolicyAgentFactory{} +const defaultReuseDuration = 30 * time.Second +const defaultShutdownGracePeriod = 30 * time.Second + +func NewOpenPolicyAgentRegistry(opts ...func(*OpenPolicyAgentRegistry) error) *OpenPolicyAgentRegistry { + registry := &OpenPolicyAgentRegistry{ + reuseDuration: defaultReuseDuration, + instances: make(map[string]*OpenPolicyAgentInstance), + refcounts: make(map[*OpenPolicyAgentInstance]int), + lastused: make(map[*OpenPolicyAgentInstance]time.Time), + quit: make(chan struct{}), + } + + for _, opt := range opts { + opt(registry) + } + + go registry.startCleanerDaemon() + + return registry } type OpenPolicyAgentInstanceConfig struct { - envoyMetadata *ext_authz_v3_core.Metadata - configTemplate []byte - envoyContextExtensions map[string]string + envoyMetadata *ext_authz_v3_core.Metadata + configTemplate []byte } func WithConfigTemplate(configTemplate []byte) func(*OpenPolicyAgentInstanceConfig) error { @@ -69,13 +101,6 @@ func WithConfigTemplateFile(configTemplateFile string) func(*OpenPolicyAgentInst } } -func WithEnvoyContextExtensions(envoyContextExtensions map[string]string) func(*OpenPolicyAgentInstanceConfig) error { - return func(cfg *OpenPolicyAgentInstanceConfig) error { - cfg.envoyContextExtensions = envoyContextExtensions - return nil - } -} - func WithEnvoyMetadata(metadata *ext_authz_v3_core.Metadata) func(*OpenPolicyAgentInstanceConfig) error { return func(cfg *OpenPolicyAgentInstanceConfig) error { cfg.envoyMetadata = metadata @@ -111,10 +136,6 @@ func (cfg *OpenPolicyAgentInstanceConfig) GetEnvoyMetadata() *ext_authz_v3_core. return cfg.envoyMetadata } -func (cfg *OpenPolicyAgentInstanceConfig) GetEnvoyContextExtensions() map[string]string { - return cfg.envoyContextExtensions -} - func NewOpenPolicyAgentConfig(opts ...func(*OpenPolicyAgentInstanceConfig) error) (*OpenPolicyAgentInstanceConfig, error) { cfg := OpenPolicyAgentInstanceConfig{} @@ -142,7 +163,103 @@ func configTemplate() string { return "opaconfig.yaml" } -func (factory *OpenPolicyAgentFactory) NewOpenPolicyAgentInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { +func (registry *OpenPolicyAgentRegistry) Close() { + registry.once.Do(func() { + registry.mtx.Lock() + defer registry.mtx.Unlock() + + ctx, cancel := context.WithTimeout(context.Background(), defaultShutdownGracePeriod) + defer cancel() + + for _, instance := range registry.instances { + instance.Close(ctx) + } + + registry.closed = true + close(registry.quit) + }) +} + +func (registry *OpenPolicyAgentRegistry) cleanUnusedInstances(t time.Time) { + registry.mtx.Lock() + defer registry.mtx.Unlock() + + if registry.closed { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultShutdownGracePeriod) + defer cancel() + + for key, inst := range registry.instances { + lastused, ok := registry.lastused[inst] + + if ok && lastused.Add(registry.reuseDuration).Before(t) { + inst.Close(ctx) + + delete(registry.instances, key) + delete(registry.lastused, inst) + delete(registry.refcounts, inst) + } + } +} + +func (registry *OpenPolicyAgentRegistry) startCleanerDaemon() { + ticker := time.NewTicker(10 * time.Second) + defer ticker.Stop() + + for { + select { + case <-registry.quit: + return + case t := <-ticker.C: + registry.cleanUnusedInstances(t) + } + } +} + +func (registry *OpenPolicyAgentRegistry) NewOpenPolicyAgentInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { + registry.mtx.Lock() + defer registry.mtx.Unlock() + + if registry.closed { + return nil, fmt.Errorf("open policy agent registry is already closed") + } + + if instance, ok := registry.instances[bundleName]; ok { + registry.refcounts[instance]++ + + delete(registry.lastused, instance) + + return instance, nil + } + + instance, err := registry.newOpenPolicyAgentInstance(bundleName, config, filterName) + + if err != nil { + return nil, err + } + + registry.instances[bundleName] = instance + registry.refcounts[instance] = 1 + + return instance, nil +} + +func (registry *OpenPolicyAgentRegistry) ReleaseInstance(instance *OpenPolicyAgentInstance) error { + registry.mtx.Lock() + defer registry.mtx.Unlock() + + registry.refcounts[instance]-- + + if (registry.refcounts[instance]) == 0 { + registry.lastused[instance] = time.Now() + } + + return nil +} + +func (registry *OpenPolicyAgentRegistry) newOpenPolicyAgentInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { runtime.RegisterPlugin(envoy.PluginName, envoy.Factory{}) configBytes, err := interpolateConfigTemplate(config.configTemplate, bundleName) @@ -179,6 +296,7 @@ type OpenPolicyAgentInstance struct { preparedQuery *rego.PreparedEvalQuery preparedQueryDoOnce *sync.Once interQueryBuiltinCache iCache.InterQueryCache + once sync.Once } func envVariablesMap() map[string]string { @@ -263,6 +381,12 @@ func (opa *OpenPolicyAgentInstance) Start(ctx context.Context) error { return opa.manager.Start(ctx) } +func (opa *OpenPolicyAgentInstance) Close(ctx context.Context) { + opa.once.Do(func() { + opa.manager.Stop(ctx) + }) +} + func (opa *OpenPolicyAgentInstance) waitPluginsReady(checkInterval, timeout time.Duration) error { if timeout <= 0 { return nil diff --git a/filters/openpolicyagent/openpolicyagent_test.go b/filters/openpolicyagent/openpolicyagent_test.go index 59a34f7ec4..e67cd72b09 100644 --- a/filters/openpolicyagent/openpolicyagent_test.go +++ b/filters/openpolicyagent/openpolicyagent_test.go @@ -1,11 +1,14 @@ package openpolicyagent import ( + "fmt" "os" "testing" + "time" ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" _struct "github.com/golang/protobuf/ptypes/struct" + opasdktest "github.com/open-policy-agent/opa/sdk/test" "github.com/stretchr/testify/assert" "google.golang.org/protobuf/encoding/protojson" ) @@ -66,3 +69,85 @@ func TestLoadEnvoyMetadata(t *testing.T) { assert.Equal(t, expected, cfg.envoyMetadata) } + +func TestRegistry(t *testing.T) { + opaControlPlane := opasdktest.MustNewServer( + opasdktest.MockBundle("/bundles/test", map[string]string{ + "main.rego": ` + package envoy.authz + + default allow = false + `, + }), + ) + + config := []byte(fmt.Sprintf(`{ + "services": { + "test": { + "url": %q + } + }, + "bundles": { + "test": { + "resource": "/bundles/{{ .bundlename }}" + } + }, + "plugins": { + "envoy_ext_authz_grpc": { + "path": "/envoy/authz/allow", + "dry-run": false + } + } + }`, opaControlPlane.URL())) + + registry := NewOpenPolicyAgentRegistry(WithReuseDuration(2 * time.Second)) + + cfg, err := NewOpenPolicyAgentConfig(WithConfigTemplate(config)) + if err != nil { + t.Fatal(err) + } + + inst1, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + + if err != nil { + t.Fatal(err) + } + + registry.ReleaseInstance(inst1) + + inst2, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, inst1, inst2, "same instance is reused after release") + + inst3, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, inst2, inst3, "same instance is reused multiple times") + + registry.ReleaseInstance(inst2) + registry.ReleaseInstance(inst3) + + //Allow clean up + time.Sleep(15 * time.Second) + + inst4, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + + if err != nil { + t.Fatal(err) + } + + assert.NotEqual(t, inst1, inst4, "after cleanup a new instance should be created") + + registry.Close() + + _, err = registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + + assert.Error(t, err, "should not work after close") +} diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index 34057154fb..6d63608d2b 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -11,23 +11,23 @@ import ( "github.com/zalando/skipper/filters/openpolicyagent/internal/util" ) -type Spec struct { - factory openpolicyagent.OpenPolicyAgentFactory - opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error +type spec struct { + registry *openpolicyagent.OpenPolicyAgentRegistry + opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error } -func NewServeResponseWithRegoPolicySpec(factory openpolicyagent.OpenPolicyAgentFactory, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) Spec { - return Spec{ - factory: factory, - opts: opts, +func NewServeResponseWithRegoPolicySpec(registry *openpolicyagent.OpenPolicyAgentRegistry, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) filters.Spec { + return &spec{ + registry: registry, + opts: opts, } } -func (s Spec) Name() string { +func (s *spec) Name() string { return filters.ServeResponseWithRegoPolicyName } -func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { +func (s *spec) CreateFilter(config []interface{}) (filters.Filter, error) { var err error sargs, err := util.GetStrings(config) @@ -47,13 +47,12 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { configOptions := s.opts + envoyContextExtensions := map[string]string{} if len(sargs) > 1 { - envoyContextExtensions := map[string]string{} err = yaml.Unmarshal([]byte(sargs[1]), &envoyContextExtensions) if err != nil { return nil, err } - configOptions = append(configOptions, openpolicyagent.WithEnvoyContextExtensions(envoyContextExtensions)) } opaConfig, err := openpolicyagent.NewOpenPolicyAgentConfig(configOptions...) @@ -61,26 +60,30 @@ func (s Spec) CreateFilter(config []interface{}) (filters.Filter, error) { return nil, err } - opa, err := s.factory.NewOpenPolicyAgentInstance(bundleName, *opaConfig, s.Name()) + opa, err := s.registry.NewOpenPolicyAgentInstance(bundleName, *opaConfig, s.Name()) if err != nil { return nil, err } - return serveResponseWithRegoPolicyFilter{ - opa: opa, + return &serveResponseWithRegoPolicyFilter{ + opa: opa, + registry: s.registry, + envoyContextExtensions: envoyContextExtensions, }, nil } type serveResponseWithRegoPolicyFilter struct { - opa *openpolicyagent.OpenPolicyAgentInstance + opa *openpolicyagent.OpenPolicyAgentInstance + registry *openpolicyagent.OpenPolicyAgentRegistry + envoyContextExtensions map[string]string } -func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { +func (f *serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { span, ctx := f.opa.StartSpanFromFilterContext(fc) defer span.Finish() - authzreq := envoy.AdaptToExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetEnvoyMetadata(), f.opa.InstanceConfig().GetEnvoyContextExtensions()) + authzreq := envoy.AdaptToExtAuthRequest(fc.Request(), f.opa.InstanceConfig().GetEnvoyMetadata(), f.envoyContextExtensions) start := time.Now() result, err := f.opa.Eval(ctx, authzreq) @@ -102,4 +105,8 @@ func (f serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { } } -func (f serveResponseWithRegoPolicyFilter) Response(fc filters.FilterContext) {} +func (f *serveResponseWithRegoPolicyFilter) Response(fc filters.FilterContext) {} + +func (f *serveResponseWithRegoPolicyFilter) Close() error { + return f.registry.ReleaseInstance(f.opa) +} diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go index d9c2e85595..0877ceed37 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go @@ -156,7 +156,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { } }`, opaControlPlane.URL(), ti.regoQuery)) - opaFactory := openpolicyagent.NewOpenPolicyAgentFactory() + opaFactory := openpolicyagent.NewOpenPolicyAgentRegistry() ftSpec := NewServeResponseWithRegoPolicySpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) filterArgs := []interface{}{ti.bundleName} diff --git a/skipper.go b/skipper.go index aef558ced2..f8f1833c35 100644 --- a/skipper.go +++ b/skipper.go @@ -1761,7 +1761,8 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { } if o.EnableOpenPolicyAgent { - factory := openpolicyagent.NewOpenPolicyAgentFactory() + opaRegistry := openpolicyagent.NewOpenPolicyAgentRegistry() + defer opaRegistry.Close() opts := make([]func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error, 0) opts = append(opts, openpolicyagent.WithConfigTemplateFile(o.OpenPolicyAgentConfigTemplate)) @@ -1770,8 +1771,8 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { } o.CustomFilters = append(o.CustomFilters, - authorizewithregopolicy.NewAuthorizeWithRegoPolicySpec(factory, opts...), - serveresponsewithregopolicy.NewServeResponseWithRegoPolicySpec(factory, opts...), + authorizewithregopolicy.NewAuthorizeWithRegoPolicySpec(opaRegistry, opts...), + serveresponsewithregopolicy.NewServeResponseWithRegoPolicySpec(opaRegistry, opts...), ) } From 43b2c5c87afe9f96c505a389859e9f6f4da188b2 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 26 Jun 2023 13:52:47 +0200 Subject: [PATCH 12/24] Fall back to request span as parent Signed-off-by: Magnus Jungsbluth --- filters/openpolicyagent/openpolicyagent.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 0b55e68af1..da4e83f553 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -28,6 +28,7 @@ import ( "github.com/zalando/skipper/filters" "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" "github.com/zalando/skipper/filters/openpolicyagent/internal/util" + "github.com/zalando/skipper/tracing" ) const ( @@ -429,11 +430,12 @@ func (opa *OpenPolicyAgentInstance) EnvoyPluginConfig() envoy.PluginConfig { } func (opa *OpenPolicyAgentInstance) startSpanFromContextWithTracer(tr opentracing.Tracer, parent opentracing.Span, ctx context.Context) (opentracing.Span, context.Context) { + var span opentracing.Span if parent != nil { span = tr.StartSpan("openpolicyagent", opentracing.ChildOf(parent.Context())) } else { - span = tr.StartSpan("openpolicyagent") + span = tracing.CreateSpan("open-policy-agent", ctx, tr) } span.SetTag("opa.bundle_name", opa.bundleName) From e9e161f824c953f8a608797d86aad6e7140fee85 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 26 Jun 2023 14:34:17 +0200 Subject: [PATCH 13/24] Align span name Signed-off-by: Magnus Jungsbluth --- filters/openpolicyagent/openpolicyagent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index da4e83f553..bda3d93108 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -433,7 +433,7 @@ func (opa *OpenPolicyAgentInstance) startSpanFromContextWithTracer(tr opentracin var span opentracing.Span if parent != nil { - span = tr.StartSpan("openpolicyagent", opentracing.ChildOf(parent.Context())) + span = tr.StartSpan("open-policy-agent", opentracing.ChildOf(parent.Context())) } else { span = tracing.CreateSpan("open-policy-agent", ctx, tr) } From 786fd22e4e928518f354d28a16c143b006693e22 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Wed, 28 Jun 2023 19:48:50 +0200 Subject: [PATCH 14/24] Correct operation name Signed-off-by: Magnus Jungsbluth --- docs/operation/operation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operation/operation.md b/docs/operation/operation.md index 95e5c9fb5b..a5d8a5b77c 100644 --- a/docs/operation/operation.md +++ b/docs/operation/operation.md @@ -630,7 +630,7 @@ connect, TLS handshake and connection pool: ### Open Policy Agent span -When one of the Open Policy Agent filters is used, child spans with the operation name `openpolicyagent` are added to the Trace. +When one of the Open Policy Agent filters is used, child spans with the operation name `open-policy-agent` are added to the Trace. The following tags are added to the Span, labels are taken from the OPA configuration YAML file as is and are not interpreted: - `opa.decision_id=` From 9c472ac786658630814349d26e9c3e35fec86766 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 10 Jul 2023 14:38:00 +0200 Subject: [PATCH 15/24] Review comments Signed-off-by: Magnus Jungsbluth --- docs/tutorials/auth.md | 6 +- .../authorizewithregopolicy.go | 47 ++++++------- .../authorizewithregopolicy_test.go | 69 +++++-------------- .../internal/envoy/skipperadapter.go | 2 +- filters/openpolicyagent/internal/util/util.go | 33 --------- filters/openpolicyagent/openpolicyagent.go | 51 +++++--------- .../serveresponsewithregopolicy.go | 27 ++++---- .../serveresponsewithregopolicy_test.go | 8 +-- filters/openpolicyagent/tracing.go | 5 +- 9 files changed, 82 insertions(+), 166 deletions(-) delete mode 100644 filters/openpolicyagent/internal/util/util.go diff --git a/docs/tutorials/auth.md b/docs/tutorials/auth.md index f06591a959..92c84cbe33 100644 --- a/docs/tutorials/auth.md +++ b/docs/tutorials/auth.md @@ -444,7 +444,7 @@ The variable `.bundlename` is the first argument in the following filters and ca ### Input Structures -Input structures to policies follow those that are used by the [opa-envoy-plugin](https://github.com/open-policy-agent/opa-envoy-plugin), the existing [examples and documentation](https://www.openpolicyagent.org/docs/latest/envoy-primer/#example-input) apply also to Skipper. +Input structures to policies follow those that are used by the [opa-envoy-plugin](https://github.com/open-policy-agent/opa-envoy-plugin), the existing [examples and documentation](https://www.openpolicyagent.org/docs/latest/envoy-primer/#example-input) apply also to Skipper. Please note that the filters in Skipper always generate v3 input structures. ### Passing context to the policy @@ -491,8 +491,8 @@ decision_logs: Start Skipper with ``` -skipper --enable-open-policy-agent --open-policy-agent-config-template opaconfig.yaml \ - --inline-routes 'notfound: * -> authorizeWithRegoPolicy("") -> inlineContent("

Authorized Hello

") -> ' +skipper -enable-open-policy-agent -open-policy-agent-config-template opaconfig.yaml \ + -inline-routes 'notfound: * -> authorizeWithRegoPolicy("") -> inlineContent("

Authorized Hello

") -> ' ``` You can test the policy with diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index 88e8504b91..950e2f096f 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -9,7 +9,6 @@ import ( "github.com/zalando/skipper/filters/openpolicyagent" "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" - "github.com/zalando/skipper/filters/openpolicyagent/internal/util" ) type spec struct { @@ -28,34 +27,36 @@ func (s *spec) Name() string { return filters.AuthorizeWithRegoPolicyName } -func (s *spec) CreateFilter(config []interface{}) (filters.Filter, error) { +func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) { var err error - sargs, err := util.GetStrings(config) - if err != nil { - return nil, err + if len(args) < 1 { + return nil, filters.ErrInvalidFilterParameters } - if len(sargs) < 1 { + if len(args) > 2 { return nil, filters.ErrInvalidFilterParameters } - if len(sargs) > 2 { + bundleName, ok := args[0].(string) + if !ok { return nil, filters.ErrInvalidFilterParameters } - bundleName := sargs[0] - - configOptions := s.opts - envoyContextExtensions := map[string]string{} - if len(sargs) > 1 { - err = yaml.Unmarshal([]byte(sargs[1]), &envoyContextExtensions) + if len(args) > 1 { + _, ok := args[1].(string) + if !ok { + return nil, filters.ErrInvalidFilterParameters + } + err = yaml.Unmarshal([]byte(args[1].(string)), &envoyContextExtensions) if err != nil { return nil, err } } + configOptions := s.opts + opaConfig, err := openpolicyagent.NewOpenPolicyAgentConfig(configOptions...) if err != nil { return nil, err @@ -115,31 +116,22 @@ func (f *authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { fc.Metrics().IncCounter(f.opa.MetricsKey("decision.allow")) - if result.HasResponseBody() { - body, err := result.GetResponseBody() - if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) - return - } - fc.StateBag()[openpolicyagent.OpenPolicyAgentDecisionBodyKey] = body - } - - headers, err := result.GetResponseHTTPHeaders() + headersToRemove, err := result.GetRequestHTTPHeadersToRemove() if err != nil { f.opa.RejectInvalidDecisionError(fc, span, result, err) return } - addRequestHeaders(fc, headers) + removeRequestHeaders(fc, headersToRemove) - headersToRemove, err := result.GetRequestHTTPHeadersToRemove() + headers, err := result.GetResponseHTTPHeaders() if err != nil { f.opa.RejectInvalidDecisionError(fc, span, result, err) return } - removeHeaders(fc, headersToRemove) + addRequestHeaders(fc, headers) } -func removeHeaders(fc filters.FilterContext, headersToRemove []string) { +func removeRequestHeaders(fc filters.FilterContext, headersToRemove []string) { for _, header := range headersToRemove { fc.Request().Header.Del(header) } @@ -148,6 +140,7 @@ func removeHeaders(fc filters.FilterContext, headersToRemove []string) { func addRequestHeaders(fc filters.FilterContext, headers http.Header) { for key, values := range headers { for _, value := range values { + // This is the default behavior from https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#config-core-v3-headervalueoption fc.Request().Header.Add(key, value) } } diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go index 35384cc2f2..18842848c8 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go @@ -5,8 +5,6 @@ import ( "io" "net/http" "net/http/httptest" - "net/url" - "path" "testing" opasdktest "github.com/open-policy-agent/opa/sdk/test" @@ -34,7 +32,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { msg: "Allow Requests", bundleName: "somebundle.tar.gz", regoQuery: "envoy/authz/allow", - requestPath: "allow", + requestPath: "/allow", expectedStatus: http.StatusOK, expectedBody: "Welcome!", expectedHeaders: make(http.Header), @@ -45,7 +43,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { msg: "Simple Forbidden", bundleName: "somebundle.tar.gz", regoQuery: "envoy/authz/allow", - requestPath: "forbidden", + requestPath: "/forbidden", expectedStatus: http.StatusForbidden, expectedHeaders: make(http.Header), backendHeaders: make(http.Header), @@ -55,7 +53,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { msg: "Allow With Structured Rules", bundleName: "somebundle.tar.gz", regoQuery: "envoy/authz/allow_object", - requestPath: "allow/structured", + requestPath: "/allow/structured", expectedStatus: http.StatusOK, expectedBody: "Welcome!", expectedHeaders: make(http.Header), @@ -66,7 +64,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { msg: "Forbidden With Body", bundleName: "somebundle.tar.gz", regoQuery: "envoy/authz/allow_object", - requestPath: "forbidden", + requestPath: "/forbidden", expectedStatus: http.StatusUnauthorized, expectedHeaders: map[string][]string{"X-Ext-Auth-Allow": {"no"}}, expectedBody: "Unauthorized Request", @@ -78,8 +76,8 @@ func TestAuthorizeRequestFilter(t *testing.T) { t.Logf("Running test for %v", ti) clientServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Welcome!")) - assert.True(t, isHeadersPresent(ti.backendHeaders, r.Header), "Enriched request header is absent.") - assert.True(t, isHeadersAbsent(ti.removeHeaders, r.Header), "Unwanted HTTP Headers present.") + assert.True(t, isHeadersPresent(t, ti.backendHeaders, r.Header), "Enriched request header is absent.") + assert.True(t, isHeadersAbsent(t, ti.removeHeaders, r.Header), "Unwanted HTTP Headers present.") })) opaControlPlane := opasdktest.MustNewServer( @@ -117,7 +115,6 @@ func TestAuthorizeRequestFilter(t *testing.T) { }), ) - var routeFilters []*eskip.Filter fr := make(filters.Registry) config := []byte(fmt.Sprintf(`{ @@ -141,25 +138,17 @@ func TestAuthorizeRequestFilter(t *testing.T) { opaFactory := openpolicyagent.NewOpenPolicyAgentRegistry() ftSpec := NewAuthorizeWithRegoPolicySpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) - filterArgs := []interface{}{ti.bundleName} - _, err := ftSpec.CreateFilter(filterArgs) - if err != nil { - t.Fatalf("error in creating filter: %v", err) - } fr.Register(ftSpec) - routeFilters = append(routeFilters, &eskip.Filter{Name: ftSpec.Name(), Args: filterArgs}) - r := &eskip.Route{Filters: routeFilters, Backend: clientServer.URL} + r := eskip.MustParse(fmt.Sprintf(`* -> authorizeWithRegoPolicy("%s") -> "%s"`, ti.bundleName, clientServer.URL)) - proxy := proxytest.New(fr, r) - reqURL, err := url.Parse(proxy.URL) - if err != nil { - t.Fatalf("Failed to parse url %s: %v", proxy.URL, err) - } - reqURL.Path = path.Join(reqURL.Path, ti.requestPath) - req, err := http.NewRequest("GET", reqURL.String(), nil) + proxy := proxytest.New(fr, r...) + + req, err := http.NewRequest("GET", proxy.URL+ti.requestPath, nil) for name, values := range ti.removeHeaders { - req.Header.Add(name, values[0]) //adding the headers to validate removal. + for _, value := range values { + req.Header.Add(name, value) //adding the headers to validate removal. + } } if err != nil { @@ -167,14 +156,14 @@ func TestAuthorizeRequestFilter(t *testing.T) { return } - rsp, err := http.DefaultClient.Do(req) + rsp, err := proxy.Client().Do(req) if err != nil { t.Fatal(err) } assert.Equal(t, ti.expectedStatus, rsp.StatusCode, "HTTP status does not match") - assert.True(t, isHeadersPresent(ti.expectedHeaders, rsp.Header), "HTTP Headers do not match") + assert.True(t, isHeadersPresent(t, ti.expectedHeaders, rsp.Header), "HTTP Headers do not match") defer rsp.Body.Close() body, err := io.ReadAll(rsp.Body) @@ -186,41 +175,19 @@ func TestAuthorizeRequestFilter(t *testing.T) { } } -func isHeadersPresent(expectedHeaders http.Header, headers http.Header) bool { +func isHeadersPresent(t *testing.T, expectedHeaders http.Header, headers http.Header) bool { for headerName, expectedValues := range expectedHeaders { actualValues, headerFound := headers[headerName] if !headerFound { return false } - if !areHeaderValuesEqual(expectedValues, actualValues) { - return false - } + assert.ElementsMatch(t, expectedValues, actualValues) } return true } -func areHeaderValuesEqual(expectedValues, actualValues []string) bool { - if len(expectedValues) != len(actualValues) { - return false - } - - actualValueSet := make(map[string]struct{}) - for _, val := range actualValues { - actualValueSet[val] = struct{}{} - } - - for _, val := range expectedValues { - if _, ok := actualValueSet[val]; !ok { - return false - } - delete(actualValueSet, val) - } - - return len(actualValueSet) == 0 -} - -func isHeadersAbsent(unwantedHeaders http.Header, headers http.Header) bool { +func isHeadersAbsent(t *testing.T, unwantedHeaders http.Header, headers http.Header) bool { for headerName := range unwantedHeaders { if _, ok := headers[headerName]; ok { return false diff --git a/filters/openpolicyagent/internal/envoy/skipperadapter.go b/filters/openpolicyagent/internal/envoy/skipperadapter.go index c2266d1b0e..918d9099d7 100644 --- a/filters/openpolicyagent/internal/envoy/skipperadapter.go +++ b/filters/openpolicyagent/internal/envoy/skipperadapter.go @@ -10,7 +10,7 @@ import ( func AdaptToExtAuthRequest(req *http.Request, metadata *ext_authz_v3_core.Metadata, contextExtensions map[string]string) *ext_authz_v3.CheckRequest { - headers := make(map[string]string) + headers := make(map[string]string, len(req.Header)) for h, vv := range req.Header { // This makes headers in the input compatible with what Envoy does, i.e. allows to use policy fragments designed for envoy // See: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/header_casing#http-1-1-header-casing diff --git a/filters/openpolicyagent/internal/util/util.go b/filters/openpolicyagent/internal/util/util.go deleted file mode 100644 index 7b3257bf55..0000000000 --- a/filters/openpolicyagent/internal/util/util.go +++ /dev/null @@ -1,33 +0,0 @@ -package util - -import ( - "crypto/rand" - "fmt" - "io" - - "github.com/zalando/skipper/filters" -) - -func Uuid4() (string, error) { - bs := make([]byte, 16) - _, err := io.ReadFull(rand.Reader, bs) - if err != nil { - return "", err - } - bs[8] = bs[8]&^0xc0 | 0x80 - bs[6] = bs[6]&^0xf0 | 0x40 - return fmt.Sprintf("%x-%x-%x-%x-%x", bs[0:4], bs[4:6], bs[6:8], bs[8:10], bs[10:]), nil -} - -func GetStrings(args []interface{}) ([]string, error) { - s := make([]string, len(args)) - var ok bool - for i, a := range args { - s[i], ok = a.(string) - if !ok { - return nil, filters.ErrInvalidFilterParameters - } - } - - return s, nil -} diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index bda3d93108..30a33957d9 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -11,6 +11,7 @@ import ( "time" ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + "github.com/google/uuid" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/config" "github.com/open-policy-agent/opa/logging" @@ -27,14 +28,12 @@ import ( "github.com/zalando/skipper/filters" "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" - "github.com/zalando/skipper/filters/openpolicyagent/internal/util" "github.com/zalando/skipper/tracing" ) const ( - OpenPolicyAgentDecisionKey = "open-policy-agent:decision" - OpenPolicyAgentDecisionBodyKey = "open-policy-agent:decision-body" - OpenPolicyAgentDecisionHeadersKey = "open-policy-agent:decision-headers" + defaultReuseDuration = 30 * time.Second + defaultShutdownGracePeriod = 30 * time.Second ) type OpenPolicyAgentRegistry struct { @@ -44,10 +43,11 @@ type OpenPolicyAgentRegistry struct { // This not possible due to some limitations in OPA // See https://github.com/open-policy-agent/opa/issues/5707 - instances map[string]*OpenPolicyAgentInstance - refcounts map[*OpenPolicyAgentInstance]int - lastused map[*OpenPolicyAgentInstance]time.Time - mtx sync.Mutex + mu sync.Mutex + instances map[string]*OpenPolicyAgentInstance + refcounts map[*OpenPolicyAgentInstance]int + lastused map[*OpenPolicyAgentInstance]time.Time + once sync.Once closed bool quit chan struct{} @@ -61,9 +61,6 @@ func WithReuseDuration(duration time.Duration) func(*OpenPolicyAgentRegistry) er } } -const defaultReuseDuration = 30 * time.Second -const defaultShutdownGracePeriod = 30 * time.Second - func NewOpenPolicyAgentRegistry(opts ...func(*OpenPolicyAgentRegistry) error) *OpenPolicyAgentRegistry { registry := &OpenPolicyAgentRegistry{ reuseDuration: defaultReuseDuration, @@ -148,7 +145,7 @@ func NewOpenPolicyAgentConfig(opts ...func(*OpenPolicyAgentInstanceConfig) error if cfg.configTemplate == nil { var err error - cfg.configTemplate, err = os.ReadFile(configTemplate()) + cfg.configTemplate, err = os.ReadFile("opaconfig.yaml") if err != nil { return nil, err } @@ -157,17 +154,10 @@ func NewOpenPolicyAgentConfig(opts ...func(*OpenPolicyAgentInstanceConfig) error return &cfg, nil } -func configTemplate() string { - if s, ok := os.LookupEnv("OPA_CONFIG_TEMPLATE_FILE"); ok { - return s - } - return "opaconfig.yaml" -} - func (registry *OpenPolicyAgentRegistry) Close() { registry.once.Do(func() { - registry.mtx.Lock() - defer registry.mtx.Unlock() + registry.mu.Lock() + defer registry.mu.Unlock() ctx, cancel := context.WithTimeout(context.Background(), defaultShutdownGracePeriod) defer cancel() @@ -182,8 +172,8 @@ func (registry *OpenPolicyAgentRegistry) Close() { } func (registry *OpenPolicyAgentRegistry) cleanUnusedInstances(t time.Time) { - registry.mtx.Lock() - defer registry.mtx.Unlock() + registry.mu.Lock() + defer registry.mu.Unlock() if registry.closed { return @@ -195,7 +185,7 @@ func (registry *OpenPolicyAgentRegistry) cleanUnusedInstances(t time.Time) { for key, inst := range registry.instances { lastused, ok := registry.lastused[inst] - if ok && lastused.Add(registry.reuseDuration).Before(t) { + if ok && t.Sub(lastused) > registry.reuseDuration { inst.Close(ctx) delete(registry.instances, key) @@ -220,8 +210,8 @@ func (registry *OpenPolicyAgentRegistry) startCleanerDaemon() { } func (registry *OpenPolicyAgentRegistry) NewOpenPolicyAgentInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { - registry.mtx.Lock() - defer registry.mtx.Unlock() + registry.mu.Lock() + defer registry.mu.Unlock() if registry.closed { return nil, fmt.Errorf("open policy agent registry is already closed") @@ -248,8 +238,8 @@ func (registry *OpenPolicyAgentRegistry) NewOpenPolicyAgentInstance(bundleName s } func (registry *OpenPolicyAgentRegistry) ReleaseInstance(instance *OpenPolicyAgentInstance) error { - registry.mtx.Lock() - defer registry.mtx.Unlock() + registry.mu.Lock() + defer registry.mu.Unlock() registry.refcounts[instance]-- @@ -335,10 +325,7 @@ func interpolateConfigTemplate(configTemplate []byte, bundleName string) ([]byte // New returns a new OPA object. func New(store storage.Store, configBytes []byte, instanceConfig OpenPolicyAgentInstanceConfig, filterName string, bundleName string) (*OpenPolicyAgentInstance, error) { - id, err := util.Uuid4() - if err != nil { - return nil, err - } + id := uuid.New().String() opaConfig, err := config.ParseConfig(configBytes, id) if err != nil { diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index 6d63608d2b..31a03be883 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -8,7 +8,6 @@ import ( "github.com/zalando/skipper/filters/openpolicyagent" "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" - "github.com/zalando/skipper/filters/openpolicyagent/internal/util" ) type spec struct { @@ -27,34 +26,36 @@ func (s *spec) Name() string { return filters.ServeResponseWithRegoPolicyName } -func (s *spec) CreateFilter(config []interface{}) (filters.Filter, error) { +func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) { var err error - sargs, err := util.GetStrings(config) - if err != nil { - return nil, err + if len(args) < 1 { + return nil, filters.ErrInvalidFilterParameters } - if len(sargs) < 1 { + if len(args) > 2 { return nil, filters.ErrInvalidFilterParameters } - if len(sargs) > 2 { + bundleName, ok := args[0].(string) + if !ok { return nil, filters.ErrInvalidFilterParameters } - bundleName := sargs[0] - - configOptions := s.opts - envoyContextExtensions := map[string]string{} - if len(sargs) > 1 { - err = yaml.Unmarshal([]byte(sargs[1]), &envoyContextExtensions) + if len(args) > 1 { + _, ok := args[1].(string) + if !ok { + return nil, filters.ErrInvalidFilterParameters + } + err = yaml.Unmarshal([]byte(args[1].(string)), &envoyContextExtensions) if err != nil { return nil, err } } + configOptions := s.opts + opaConfig, err := openpolicyagent.NewOpenPolicyAgentConfig(configOptions...) if err != nil { return nil, err diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go index 0877ceed37..aebbd374c1 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go @@ -134,7 +134,6 @@ func TestAuthorizeRequestFilter(t *testing.T) { }), ) - var routeFilters []*eskip.Filter fr := make(filters.Registry) config := []byte(fmt.Sprintf(`{ @@ -169,11 +168,10 @@ func TestAuthorizeRequestFilter(t *testing.T) { t.Fatalf("error in creating filter: %v", err) } fr.Register(ftSpec) - routeFilters = append(routeFilters, &eskip.Filter{Name: ftSpec.Name(), Args: filterArgs}) - r := &eskip.Route{Filters: routeFilters, Backend: clientServer.URL} + r := eskip.MustParse(fmt.Sprintf(`* -> serveResponseWithRegoPolicy("%s", "%s") -> "%s"`, ti.bundleName, ti.contextExtensions, clientServer.URL)) - proxy := proxytest.New(fr, r) + proxy := proxytest.New(fr, r...) reqURL, err := url.Parse(proxy.URL) if err != nil { t.Fatalf("Failed to parse url %s: %v", proxy.URL, err) @@ -185,7 +183,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { return } - rsp, err := http.DefaultClient.Do(req) + rsp, err := proxy.Client().Do(req) if err != nil { t.Fatal(err) } diff --git a/filters/openpolicyagent/tracing.go b/filters/openpolicyagent/tracing.go index dca941108a..cd4df923f9 100644 --- a/filters/openpolicyagent/tracing.go +++ b/filters/openpolicyagent/tracing.go @@ -30,13 +30,16 @@ func (*tracingFactory) NewHandler(f http.Handler, label string, opts opatracing. } func (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) { - span := opentracing.SpanFromContext(req.Context()) ctx := req.Context() + span := opentracing.SpanFromContext(ctx) + if span == nil { span, ctx = tr.opa.StartSpanFromContext(ctx) + defer span.Finish() req = req.WithContext(ctx) } else { span, ctx = opentracing.StartSpanFromContext(ctx, "http.send") + defer span.Finish() req = req.WithContext(ctx) } From 058101b3c3fe420d84f2624e1639eb72c63d1044 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 10 Jul 2023 14:46:03 +0200 Subject: [PATCH 16/24] Update to OPA 0.54 Signed-off-by: Magnus Jungsbluth --- go.mod | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c921e910d9..fa5d6e4f4d 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang/protobuf v1.5.3 github.com/google/go-cmp v0.5.9 + github.com/google/uuid v1.3.0 github.com/hashicorp/memberlist v0.5.0 github.com/instana/go-sensor v1.55.2 github.com/lightstep/lightstep-tracer-go v0.26.0 @@ -30,7 +31,6 @@ require ( github.com/open-policy-agent/opa-envoy-plugin v0.55.0-envoy github.com/opentracing/basictracer-go v1.1.0 github.com/opentracing/opentracing-go v1.2.0 - github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.16.0 github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 github.com/redis/go-redis/v9 v9.0.5 @@ -53,6 +53,7 @@ require ( golang.org/x/sync v0.3.0 golang.org/x/term v0.11.0 golang.org/x/time v0.3.0 + google.golang.org/protobuf v1.31.0 gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/yaml.v2 v2.4.0 layeh.com/gopher-json v0.0.0-20201124131017-552bb3c4c3bf @@ -101,7 +102,6 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.0 // indirect github.com/google/flatbuffers v1.12.1 // indirect - github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -130,6 +130,7 @@ require ( github.com/opencontainers/image-spec v1.1.0-rc4 // indirect github.com/opencontainers/runc v1.1.5 // indirect github.com/peterh/liner v1.2.2 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021 // indirect github.com/prometheus/client_model v0.4.0 // indirect @@ -167,7 +168,6 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect google.golang.org/grpc v1.57.0 // indirect - google.golang.org/protobuf v1.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect oras.land/oras-go/v2 v2.2.1 // indirect ) From 663c79aeec250b4b88c57fb351cb40323a3510c7 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 10 Jul 2023 14:55:53 +0200 Subject: [PATCH 17/24] Wrap error Signed-off-by: Magnus Jungsbluth --- filters/openpolicyagent/evaluation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filters/openpolicyagent/evaluation.go b/filters/openpolicyagent/evaluation.go index 8f12986512..cc8ed0042c 100644 --- a/filters/openpolicyagent/evaluation.go +++ b/filters/openpolicyagent/evaluation.go @@ -43,7 +43,7 @@ func (opa *OpenPolicyAgentInstance) Eval(ctx context.Context, req *ext_authz_v3. logger := opa.manager.Logger().WithFields(map[string]interface{}{"decision-id": result.DecisionID}) input, err = envoyauth.RequestToInput(req, logger, nil, true) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to convert request to input: %w", err) } inputValue, err := ast.InterfaceToValue(input) From 6efffd992ee9ba32cbe68b3f7818e99f16ba749f Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 10 Jul 2023 20:57:54 +0200 Subject: [PATCH 18/24] Change from Close() to PostProcessor for clean-up Signed-off-by: Magnus Jungsbluth --- .../authorizewithregopolicy.go | 4 +- filters/openpolicyagent/openpolicyagent.go | 40 ++++++++++++++----- .../openpolicyagent/openpolicyagent_test.go | 5 +-- .../serveresponsewithregopolicy.go | 4 +- skipper.go | 7 +++- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index 950e2f096f..adc1abc44d 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -148,6 +148,6 @@ func addRequestHeaders(fc filters.FilterContext, headers http.Header) { func (*authorizeWithRegoPolicyFilter) Response(filters.FilterContext) {} -func (f *authorizeWithRegoPolicyFilter) Close() error { - return f.registry.ReleaseInstance(f.opa) +func (f *authorizeWithRegoPolicyFilter) OpenPolicyAgent() *openpolicyagent.OpenPolicyAgentInstance { + return f.opa } diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 30a33957d9..f83b649003 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -28,6 +28,7 @@ import ( "github.com/zalando/skipper/filters" "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" + "github.com/zalando/skipper/routing" "github.com/zalando/skipper/tracing" ) @@ -45,7 +46,6 @@ type OpenPolicyAgentRegistry struct { mu sync.Mutex instances map[string]*OpenPolicyAgentInstance - refcounts map[*OpenPolicyAgentInstance]int lastused map[*OpenPolicyAgentInstance]time.Time once sync.Once @@ -54,6 +54,10 @@ type OpenPolicyAgentRegistry struct { reuseDuration time.Duration } +type OpenPolicyAgentFilter interface { + OpenPolicyAgent() *OpenPolicyAgentInstance +} + func WithReuseDuration(duration time.Duration) func(*OpenPolicyAgentRegistry) error { return func(cfg *OpenPolicyAgentRegistry) error { cfg.reuseDuration = duration @@ -65,7 +69,6 @@ func NewOpenPolicyAgentRegistry(opts ...func(*OpenPolicyAgentRegistry) error) *O registry := &OpenPolicyAgentRegistry{ reuseDuration: defaultReuseDuration, instances: make(map[string]*OpenPolicyAgentInstance), - refcounts: make(map[*OpenPolicyAgentInstance]int), lastused: make(map[*OpenPolicyAgentInstance]time.Time), quit: make(chan struct{}), } @@ -190,7 +193,6 @@ func (registry *OpenPolicyAgentRegistry) cleanUnusedInstances(t time.Time) { delete(registry.instances, key) delete(registry.lastused, inst) - delete(registry.refcounts, inst) } } } @@ -209,6 +211,25 @@ func (registry *OpenPolicyAgentRegistry) startCleanerDaemon() { } } +// Do implements routing.PostProcessor and cleans unused OPA instances +func (registry *OpenPolicyAgentRegistry) Do(routes []*routing.Route) []*routing.Route { + rr := make([]*routing.Route, len(routes)) + inUse := make(map[*OpenPolicyAgentInstance]struct{}) + + for i, ri := range routes { + rr[i] = ri + for _, fi := range ri.Filters { + if ff, ok := fi.Filter.(OpenPolicyAgentFilter); ok { + inUse[ff.OpenPolicyAgent()] = struct{}{} + } + } + } + + registry.markUnused(inUse) + + return rr +} + func (registry *OpenPolicyAgentRegistry) NewOpenPolicyAgentInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { registry.mu.Lock() defer registry.mu.Unlock() @@ -218,8 +239,6 @@ func (registry *OpenPolicyAgentRegistry) NewOpenPolicyAgentInstance(bundleName s } if instance, ok := registry.instances[bundleName]; ok { - registry.refcounts[instance]++ - delete(registry.lastused, instance) return instance, nil @@ -232,19 +251,18 @@ func (registry *OpenPolicyAgentRegistry) NewOpenPolicyAgentInstance(bundleName s } registry.instances[bundleName] = instance - registry.refcounts[instance] = 1 return instance, nil } -func (registry *OpenPolicyAgentRegistry) ReleaseInstance(instance *OpenPolicyAgentInstance) error { +func (registry *OpenPolicyAgentRegistry) markUnused(inUse map[*OpenPolicyAgentInstance]struct{}) error { registry.mu.Lock() defer registry.mu.Unlock() - registry.refcounts[instance]-- - - if (registry.refcounts[instance]) == 0 { - registry.lastused[instance] = time.Now() + for _, instance := range registry.instances { + if _, ok := inUse[instance]; !ok { + registry.lastused[instance] = time.Now() + } } return nil diff --git a/filters/openpolicyagent/openpolicyagent_test.go b/filters/openpolicyagent/openpolicyagent_test.go index e67cd72b09..0c4e25abe1 100644 --- a/filters/openpolicyagent/openpolicyagent_test.go +++ b/filters/openpolicyagent/openpolicyagent_test.go @@ -113,7 +113,7 @@ func TestRegistry(t *testing.T) { t.Fatal(err) } - registry.ReleaseInstance(inst1) + registry.markUnused(map[*OpenPolicyAgentInstance]struct{}{}) inst2, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") @@ -131,8 +131,7 @@ func TestRegistry(t *testing.T) { assert.Equal(t, inst2, inst3, "same instance is reused multiple times") - registry.ReleaseInstance(inst2) - registry.ReleaseInstance(inst3) + registry.markUnused(map[*OpenPolicyAgentInstance]struct{}{}) //Allow clean up time.Sleep(15 * time.Second) diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index 31a03be883..c21c54b67c 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -108,6 +108,6 @@ func (f *serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { func (f *serveResponseWithRegoPolicyFilter) Response(fc filters.FilterContext) {} -func (f *serveResponseWithRegoPolicyFilter) Close() error { - return f.registry.ReleaseInstance(f.opa) +func (f *serveResponseWithRegoPolicyFilter) OpenPolicyAgent() *openpolicyagent.OpenPolicyAgentInstance { + return f.opa } diff --git a/skipper.go b/skipper.go index f8f1833c35..845b9f6592 100644 --- a/skipper.go +++ b/skipper.go @@ -1760,8 +1760,9 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { ) } + var opaRegistry *openpolicyagent.OpenPolicyAgentRegistry if o.EnableOpenPolicyAgent { - opaRegistry := openpolicyagent.NewOpenPolicyAgentRegistry() + opaRegistry = openpolicyagent.NewOpenPolicyAgentRegistry() defer opaRegistry.Close() opts := make([]func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error, 0) @@ -1905,6 +1906,10 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { ro.PreProcessors = append(ro.PreProcessors, oauthConfig.NewGrantPreprocessor()) } + if o.EnableOpenPolicyAgent { + ro.PostProcessors = append(ro.PostProcessors, opaRegistry) + } + if o.CustomRoutingPreProcessors != nil { ro.PreProcessors = append(ro.PreProcessors, o.CustomRoutingPreProcessors...) } From d675798e2d115c9527ec2e7a52b4b810746b274e Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 10 Jul 2023 21:15:39 +0200 Subject: [PATCH 19/24] Clean tracing Signed-off-by: Magnus Jungsbluth --- filters/openpolicyagent/tracing.go | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/filters/openpolicyagent/tracing.go b/filters/openpolicyagent/tracing.go index cd4df923f9..e8a98f895f 100644 --- a/filters/openpolicyagent/tracing.go +++ b/filters/openpolicyagent/tracing.go @@ -31,20 +31,16 @@ func (*tracingFactory) NewHandler(f http.Handler, label string, opts opatracing. func (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) { ctx := req.Context() - span := opentracing.SpanFromContext(ctx) + parentSpan := opentracing.SpanFromContext(ctx) - if span == nil { - span, ctx = tr.opa.StartSpanFromContext(ctx) + if parentSpan != nil { + span := opentracing.StartSpan("http.send", opentracing.ChildOf(parentSpan.Context())) defer span.Finish() - req = req.WithContext(ctx) - } else { - span, ctx = opentracing.StartSpanFromContext(ctx, "http.send") - defer span.Finish() - req = req.WithContext(ctx) - } + req = req.WithContext(opentracing.ContextWithSpan(ctx, span)) - carrier := opentracing.HTTPHeadersCarrier(req.Header) - span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier) + carrier := opentracing.HTTPHeadersCarrier(req.Header) + span.Tracer().Inject(span.Context(), opentracing.HTTPHeaders, carrier) + } return tr.wrapped.RoundTrip(req) } From 28975930dbefb1182c973ab4a8641dcc0151ae8b Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Tue, 8 Aug 2023 08:46:23 +0200 Subject: [PATCH 20/24] Implement missing method after OPA upgrade Signed-off-by: Magnus Jungsbluth --- filters/openpolicyagent/openpolicyagent.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index f83b649003..50ff5b596a 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -22,6 +22,7 @@ import ( "github.com/open-policy-agent/opa/storage" "github.com/open-policy-agent/opa/storage/inmem" iCache "github.com/open-policy-agent/opa/topdown/cache" + opatracing "github.com/open-policy-agent/opa/tracing" opautil "github.com/open-policy-agent/opa/util" "github.com/opentracing/opentracing-go" "google.golang.org/protobuf/encoding/protojson" @@ -486,8 +487,9 @@ func (opa *OpenPolicyAgentInstance) PreparedQuery() *rego.PreparedEvalQuery { re func (opa *OpenPolicyAgentInstance) SetPreparedQuery(q *rego.PreparedEvalQuery) { opa.preparedQuery = q } -func (opa *OpenPolicyAgentInstance) Config() *config.Config { - return opa.opaConfig +func (opa *OpenPolicyAgentInstance) Config() *config.Config { return opa.opaConfig } +func (opa *OpenPolicyAgentInstance) DistributedTracing() opatracing.Options { + return opatracing.NewOptions(opa) } // logging.Logger that does not pollute info with debug logs From 52276019351f0c9f663a70db382dba4adc24036f Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Tue, 8 Aug 2023 14:15:03 +0200 Subject: [PATCH 21/24] Avoid making a copy of the routes. Signed-off-by: Magnus Jungsbluth --- filters/openpolicyagent/openpolicyagent.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 50ff5b596a..1148124e57 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -214,11 +214,9 @@ func (registry *OpenPolicyAgentRegistry) startCleanerDaemon() { // Do implements routing.PostProcessor and cleans unused OPA instances func (registry *OpenPolicyAgentRegistry) Do(routes []*routing.Route) []*routing.Route { - rr := make([]*routing.Route, len(routes)) inUse := make(map[*OpenPolicyAgentInstance]struct{}) - for i, ri := range routes { - rr[i] = ri + for _, ri := range routes { for _, fi := range ri.Filters { if ff, ok := fi.Filter.(OpenPolicyAgentFilter); ok { inUse[ff.OpenPolicyAgent()] = struct{}{} @@ -228,7 +226,7 @@ func (registry *OpenPolicyAgentRegistry) Do(routes []*routing.Route) []*routing. registry.markUnused(inUse) - return rr + return routes } func (registry *OpenPolicyAgentRegistry) NewOpenPolicyAgentInstance(bundleName string, config OpenPolicyAgentInstanceConfig, filterName string) (*OpenPolicyAgentInstance, error) { From 2025717e866a69c1ced4fa37be8747e2798680c3 Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Tue, 8 Aug 2023 14:26:54 +0200 Subject: [PATCH 22/24] Use assert.NoError Signed-off-by: Magnus Jungsbluth --- .../authorizewithregopolicy_test.go | 13 ++------ .../openpolicyagent/openpolicyagent_test.go | 33 +++++-------------- .../serveresponsewithregopolicy_test.go | 23 ++++--------- 3 files changed, 18 insertions(+), 51 deletions(-) diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go index 18842848c8..36b40efa7e 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go @@ -151,15 +151,10 @@ func TestAuthorizeRequestFilter(t *testing.T) { } } - if err != nil { - t.Fatal(err) - return - } + assert.NoError(t, err) rsp, err := proxy.Client().Do(req) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) assert.Equal(t, ti.expectedStatus, rsp.StatusCode, "HTTP status does not match") @@ -167,9 +162,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { defer rsp.Body.Close() body, err := io.ReadAll(rsp.Body) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) assert.Equal(t, ti.expectedBody, string(body), "HTTP Body does not match") }) } diff --git a/filters/openpolicyagent/openpolicyagent_test.go b/filters/openpolicyagent/openpolicyagent_test.go index 0c4e25abe1..a20db25bf3 100644 --- a/filters/openpolicyagent/openpolicyagent_test.go +++ b/filters/openpolicyagent/openpolicyagent_test.go @@ -21,9 +21,7 @@ func TestInterpolateTemplate(t *testing.T) { `), "helloBundle") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.Equal(t, ` token: testtoken @@ -56,18 +54,13 @@ func TestLoadEnvoyMetadata(t *testing.T) { }, }) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) expected := &ext_authz_v3_core.Metadata{} err = protojson.Unmarshal(expectedBytes, expected) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.Equal(t, expected, cfg.envoyMetadata) - } func TestRegistry(t *testing.T) { @@ -103,31 +96,23 @@ func TestRegistry(t *testing.T) { registry := NewOpenPolicyAgentRegistry(WithReuseDuration(2 * time.Second)) cfg, err := NewOpenPolicyAgentConfig(WithConfigTemplate(config)) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) inst1, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) registry.markUnused(map[*OpenPolicyAgentInstance]struct{}{}) inst2, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) assert.Equal(t, inst1, inst2, "same instance is reused after release") inst3, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) assert.Equal(t, inst2, inst3, "same instance is reused multiple times") @@ -138,9 +123,7 @@ func TestRegistry(t *testing.T) { inst4, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) assert.NotEqual(t, inst1, inst4, "after cleanup a new instance should be created") diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go index aebbd374c1..1806c2fd3d 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go @@ -164,29 +164,22 @@ func TestAuthorizeRequestFilter(t *testing.T) { } _, err := ftSpec.CreateFilter(filterArgs) - if err != nil { - t.Fatalf("error in creating filter: %v", err) - } + assert.NoErrorf(t, err, "error in creating filter: %v", err) + fr.Register(ftSpec) r := eskip.MustParse(fmt.Sprintf(`* -> serveResponseWithRegoPolicy("%s", "%s") -> "%s"`, ti.bundleName, ti.contextExtensions, clientServer.URL)) proxy := proxytest.New(fr, r...) reqURL, err := url.Parse(proxy.URL) - if err != nil { - t.Fatalf("Failed to parse url %s: %v", proxy.URL, err) - } + assert.NoErrorf(t, err, "Failed to parse url %s: %v", proxy.URL, err) + reqURL.Path = path.Join(reqURL.Path, ti.requestPath) req, err := http.NewRequest("GET", reqURL.String(), nil) - if err != nil { - t.Fatal(err) - return - } + assert.NoError(t, err) rsp, err := proxy.Client().Do(req) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) assert.Equal(t, ti.expectedStatus, rsp.StatusCode, "HTTP status does not match") @@ -199,9 +192,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { defer rsp.Body.Close() body, err := io.ReadAll(rsp.Body) - if err != nil { - t.Fatal(err) - } + assert.NoError(t, err) assert.Equal(t, ti.expectedBody, string(body), "HTTP Headers do not match") }) } From f7689c7a7ecb35bfce247c99062d8c21e2d474ea Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 14 Aug 2023 10:42:18 +0200 Subject: [PATCH 23/24] Review comments. Test coverage. Signed-off-by: Magnus Jungsbluth --- config/config.go | 18 +- config/config_test.go | 1 + docs/reference/filters.md | 2 +- docs/tutorials/auth.md | 2 +- .../authorizewithregopolicy.go | 10 +- .../authorizewithregopolicy_test.go | 184 ++++++++++---- filters/openpolicyagent/openpolicyagent.go | 63 +++-- .../openpolicyagent/openpolicyagent_test.go | 225 +++++++++++++++++- filters/openpolicyagent/response.go | 25 +- .../serveresponsewithregopolicy.go | 13 +- .../serveresponsewithregopolicy_test.go | 29 +++ filters/openpolicyagent/tracing.go | 2 +- filters/openpolicyagent/tracing_test.go | 41 ++++ skipper.go | 9 +- 14 files changed, 503 insertions(+), 121 deletions(-) create mode 100644 filters/openpolicyagent/tracing_test.go diff --git a/config/config.go b/config/config.go index 49e6971ec2..53231eb4f0 100644 --- a/config/config.go +++ b/config/config.go @@ -19,6 +19,7 @@ import ( "github.com/zalando/skipper" "github.com/zalando/skipper/dataclients/kubernetes" "github.com/zalando/skipper/eskip" + "github.com/zalando/skipper/filters/openpolicyagent" "github.com/zalando/skipper/net" "github.com/zalando/skipper/proxy" "github.com/zalando/skipper/swarm" @@ -274,9 +275,10 @@ type Config struct { LuaModules *listFlag `yaml:"lua-modules"` LuaSources *listFlag `yaml:"lua-sources"` - EnableOpenPolicyAgent bool `yaml:"enable-open-policy-agent"` - OpenPolicyAgentConfigTemplate string `yaml:"open-policy-agent-config-template"` - OpenPolicyAgentEnvoyMetadata string `yaml:"open-policy-agent-envoy-metadata"` + EnableOpenPolicyAgent bool `yaml:"enable-open-policy-agent"` + OpenPolicyAgentConfigTemplate string `yaml:"open-policy-agent-config-template"` + OpenPolicyAgentEnvoyMetadata string `yaml:"open-policy-agent-envoy-metadata"` + OpenPolicyAgentCleanerInterval time.Duration `yaml:"open-policy-agent-cleaner-interval"` } const ( @@ -489,7 +491,8 @@ func NewConfig() *Config { flag.DurationVar(&cfg.CredentialsUpdateInterval, "credentials-update-interval", 10*time.Minute, "sets the interval to update secrets") flag.BoolVar(&cfg.EnableOpenPolicyAgent, "enable-open-policy-agent", false, "enables Open Policy Agent filters") flag.StringVar(&cfg.OpenPolicyAgentConfigTemplate, "open-policy-agent-config-template", "", "file containing a template for an Open Policy Agent configuration file that is interpolated for each OPA filter instance") - flag.StringVar(&cfg.OpenPolicyAgentEnvoyMetadata, "open-policy-agent-envoy-metadata", "", "JSON file containing meta-data passed in input for compatibility with Envoy policies in the format") + flag.StringVar(&cfg.OpenPolicyAgentEnvoyMetadata, "open-policy-agent-envoy-metadata", "", "JSON file containing meta-data passed as input for compatibility with Envoy policies in the format") + flag.DurationVar(&cfg.OpenPolicyAgentCleanerInterval, "open-policy-agent-cleaner-interval", openpolicyagent.DefaultCleanIdlePeriod, "JSON file containing meta-data passed as input for compatibility with Envoy policies in the format") // TLS client certs flag.StringVar(&cfg.ClientKeyFile, "client-tls-key", "", "TLS Key file for backend connections, multiple keys may be given comma separated - the order must match the certs") @@ -887,9 +890,10 @@ func (c *Config) ToOptions() skipper.Options { LuaModules: c.LuaModules.values, LuaSources: c.LuaSources.values, - EnableOpenPolicyAgent: c.EnableOpenPolicyAgent, - OpenPolicyAgentConfigTemplate: c.OpenPolicyAgentConfigTemplate, - OpenPolicyAgentEnvoyMetadata: c.OpenPolicyAgentEnvoyMetadata, + EnableOpenPolicyAgent: c.EnableOpenPolicyAgent, + OpenPolicyAgentConfigTemplate: c.OpenPolicyAgentConfigTemplate, + OpenPolicyAgentEnvoyMetadata: c.OpenPolicyAgentEnvoyMetadata, + OpenPolicyAgentCleanerInterval: c.OpenPolicyAgentCleanerInterval, } for _, rcci := range c.CloneRoute { eskipClone := eskip.NewClone(rcci.Reg, rcci.Repl) diff --git a/config/config_test.go b/config/config_test.go index f70172a3ab..c833717040 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -160,6 +160,7 @@ func defaultConfig() *Config { ValidateQueryLog: true, LuaModules: commaListFlag(), LuaSources: commaListFlag(), + OpenPolicyAgentCleanerInterval: 10 * time.Second, } } diff --git a/docs/reference/filters.md b/docs/reference/filters.md index 4369a37251..50fdb35ef0 100644 --- a/docs/reference/filters.md +++ b/docs/reference/filters.md @@ -1721,7 +1721,7 @@ As of now there is no negative/deny rule possible. The first matching path is ev ### Open Policy Agent -To get started with Open Policy Agent, also have a look at the [tutorial](../tutorials/auth.md#open-policy-agent). This section is only a reference for the implemented filters. +To get started with [Open Policy Agent](https://www.openpolicyagent.org/), also have a look at the [tutorial](../tutorials/auth.md#open-policy-agent). This section is only a reference for the implemented filters. #### authorizeWithRegoPolicy diff --git a/docs/tutorials/auth.md b/docs/tutorials/auth.md index 92c84cbe33..cd00d9f05e 100644 --- a/docs/tutorials/auth.md +++ b/docs/tutorials/auth.md @@ -450,7 +450,7 @@ Input structures to policies follow those that are used by the [opa-envoy-plugin Generally there are two ways to pass context to a policy: -1. as part of the labels on Open Policy Agent (configured in the configuration file, see below) that should be used for deployment level taxonomy, +1. as part of the labels in Open Policy Agent (configured in the configuration file, see below) that should be used for deployment level taxonomy, 2. as part of so called context extensions that are part of the Envoy external auth specification. This context can be passed as second argument to filters: diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go index adc1abc44d..1909019cb4 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go @@ -93,7 +93,7 @@ func (f *authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { fc.Metrics().MeasureSince(f.opa.MetricsKey("eval_time"), start) if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) + f.opa.HandleInvalidDecisionError(fc, span, result, err, !f.opa.EnvoyPluginConfig().DryRun) return } @@ -102,12 +102,10 @@ func (f *authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { } allowed, err := result.IsAllowed() - if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) + f.opa.HandleInvalidDecisionError(fc, span, result, err, !f.opa.EnvoyPluginConfig().DryRun) return } - if !allowed { fc.Metrics().IncCounter(f.opa.MetricsKey("decision.deny")) f.opa.ServeResponse(fc, span, result) @@ -118,14 +116,14 @@ func (f *authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { headersToRemove, err := result.GetRequestHTTPHeadersToRemove() if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) + f.opa.HandleInvalidDecisionError(fc, span, result, err, !f.opa.EnvoyPluginConfig().DryRun) return } removeRequestHeaders(fc, headersToRemove) headers, err := result.GetResponseHTTPHeaders() if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) + f.opa.HandleInvalidDecisionError(fc, span, result, err, !f.opa.EnvoyPluginConfig().DryRun) return } addRequestHeaders(fc, headers) diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go index 36b40efa7e..ea56d6e975 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go +++ b/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go @@ -18,58 +18,123 @@ import ( func TestAuthorizeRequestFilter(t *testing.T) { for _, ti := range []struct { - msg string - bundleName string - regoQuery string - requestPath string - expectedBody string - expectedHeaders http.Header - expectedStatus int - backendHeaders http.Header - removeHeaders http.Header + msg string + bundleName string + regoQuery string + requestPath string + contextExtensions string + expectedBody string + expectedHeaders http.Header + expectedStatus int + backendHeaders http.Header + removeHeaders http.Header }{ { - msg: "Allow Requests", - bundleName: "somebundle.tar.gz", - regoQuery: "envoy/authz/allow", - requestPath: "/allow", - expectedStatus: http.StatusOK, - expectedBody: "Welcome!", - expectedHeaders: make(http.Header), - backendHeaders: make(http.Header), - removeHeaders: make(http.Header), + msg: "Allow Requests", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow", + requestPath: "/allow", + contextExtensions: "", + expectedStatus: http.StatusOK, + expectedBody: "Welcome!", + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), }, { - msg: "Simple Forbidden", - bundleName: "somebundle.tar.gz", - regoQuery: "envoy/authz/allow", - requestPath: "/forbidden", - expectedStatus: http.StatusForbidden, - expectedHeaders: make(http.Header), - backendHeaders: make(http.Header), - removeHeaders: make(http.Header), + msg: "Allow Matching Context Extension", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_context_extensions", + requestPath: "/allow", + contextExtensions: "com.mycompany.myprop: myvalue", + expectedStatus: http.StatusOK, + expectedBody: "Welcome!", + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), }, { - msg: "Allow With Structured Rules", - bundleName: "somebundle.tar.gz", - regoQuery: "envoy/authz/allow_object", - requestPath: "/allow/structured", - expectedStatus: http.StatusOK, - expectedBody: "Welcome!", - expectedHeaders: make(http.Header), - backendHeaders: map[string][]string{"X-Consumer": {"x-consumer header value"}}, - removeHeaders: map[string][]string{"X-Remove-Me": {"Remove me"}}, + msg: "Simple Forbidden", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow", + requestPath: "/forbidden", + contextExtensions: "", + expectedStatus: http.StatusForbidden, + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), }, { - msg: "Forbidden With Body", - bundleName: "somebundle.tar.gz", - regoQuery: "envoy/authz/allow_object", - requestPath: "/forbidden", - expectedStatus: http.StatusUnauthorized, - expectedHeaders: map[string][]string{"X-Ext-Auth-Allow": {"no"}}, - expectedBody: "Unauthorized Request", - backendHeaders: make(http.Header), - removeHeaders: make(http.Header), + msg: "Allow With Structured Rules", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object", + requestPath: "/allow/structured", + contextExtensions: "", + expectedStatus: http.StatusOK, + expectedBody: "Welcome!", + expectedHeaders: make(http.Header), + backendHeaders: map[string][]string{"X-Consumer": {"x-consumer header value"}}, + removeHeaders: map[string][]string{"X-Remove-Me": {"Remove me"}}, + }, + { + msg: "Forbidden With Body", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object", + requestPath: "/forbidden", + contextExtensions: "", + expectedStatus: http.StatusUnauthorized, + expectedHeaders: map[string][]string{"X-Ext-Auth-Allow": {"no"}}, + expectedBody: "Unauthorized Request", + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), + }, + { + msg: "Misconfigured Rego Query", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/invalid_path", + requestPath: "/allow", + contextExtensions: "", + expectedStatus: http.StatusInternalServerError, + expectedBody: "", + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), + }, + { + msg: "Wrong Query Data Type", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_wrong_type", + requestPath: "/allow", + contextExtensions: "", + expectedStatus: http.StatusInternalServerError, + expectedBody: "", + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), + }, + { + msg: "Wrong Query Data Type", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object_invalid_headers_to_remove", + requestPath: "/allow", + contextExtensions: "", + expectedStatus: http.StatusInternalServerError, + expectedBody: "", + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), + }, + { + msg: "Wrong Query Data Type", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/allow_object_invalid_headers", + requestPath: "/allow", + contextExtensions: "", + expectedStatus: http.StatusInternalServerError, + expectedBody: "", + expectedHeaders: make(http.Header), + backendHeaders: make(http.Header), + removeHeaders: make(http.Header), }, } { t.Run(ti.msg, func(t *testing.T) { @@ -90,7 +155,11 @@ func TestAuthorizeRequestFilter(t *testing.T) { allow { input.parsed_path = [ "allow" ] } - + + allow_context_extensions { + input.attributes.contextExtensions["com.mycompany.myprop"] == "myvalue" + } + default allow_object = { "allowed": false, "headers": {"x-ext-auth-allow": "no"}, @@ -111,6 +180,18 @@ func TestAuthorizeRequestFilter(t *testing.T) { ] } } + + allow_wrong_type := "true" + + allow_object_invalid_headers_to_remove := { + "allowed": true, + "request_headers_to_remove": "bogus string instead of object" + } + + allow_object_invalid_headers := { + "allowed": true, + "headers": "bogus string instead of object" + } `, }), ) @@ -140,7 +221,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { ftSpec := NewAuthorizeWithRegoPolicySpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) fr.Register(ftSpec) - r := eskip.MustParse(fmt.Sprintf(`* -> authorizeWithRegoPolicy("%s") -> "%s"`, ti.bundleName, clientServer.URL)) + r := eskip.MustParse(fmt.Sprintf(`* -> authorizeWithRegoPolicy("%s", "%s") -> "%s"`, ti.bundleName, ti.contextExtensions, clientServer.URL)) proxy := proxytest.New(fr, r...) @@ -168,6 +249,17 @@ func TestAuthorizeRequestFilter(t *testing.T) { } } +func TestCreateFilterArguments(t *testing.T) { + opaRegistry := openpolicyagent.NewOpenPolicyAgentRegistry() + ftSpec := NewAuthorizeWithRegoPolicySpec(opaRegistry, openpolicyagent.WithConfigTemplate([]byte(""))) + + _, err := ftSpec.CreateFilter([]interface{}{}) + assert.ErrorIs(t, err, filters.ErrInvalidFilterParameters) + + _, err = ftSpec.CreateFilter([]interface{}{"a bundle", "extra: value", "superfluous argument"}) + assert.ErrorIs(t, err, filters.ErrInvalidFilterParameters) +} + func isHeadersPresent(t *testing.T, expectedHeaders http.Header, headers http.Header) bool { for headerName, expectedValues := range expectedHeaders { actualValues, headerFound := headers[headerName] diff --git a/filters/openpolicyagent/openpolicyagent.go b/filters/openpolicyagent/openpolicyagent.go index 1148124e57..56aa581131 100644 --- a/filters/openpolicyagent/openpolicyagent.go +++ b/filters/openpolicyagent/openpolicyagent.go @@ -36,6 +36,7 @@ import ( const ( defaultReuseDuration = 30 * time.Second defaultShutdownGracePeriod = 30 * time.Second + DefaultCleanIdlePeriod = 10 * time.Second ) type OpenPolicyAgentRegistry struct { @@ -53,6 +54,7 @@ type OpenPolicyAgentRegistry struct { closed bool quit chan struct{} reuseDuration time.Duration + cleanInterval time.Duration } type OpenPolicyAgentFilter interface { @@ -66,9 +68,17 @@ func WithReuseDuration(duration time.Duration) func(*OpenPolicyAgentRegistry) er } } +func WithCleanInterval(interval time.Duration) func(*OpenPolicyAgentRegistry) error { + return func(cfg *OpenPolicyAgentRegistry) error { + cfg.cleanInterval = interval + return nil + } +} + func NewOpenPolicyAgentRegistry(opts ...func(*OpenPolicyAgentRegistry) error) *OpenPolicyAgentRegistry { registry := &OpenPolicyAgentRegistry{ reuseDuration: defaultReuseDuration, + cleanInterval: DefaultCleanIdlePeriod, instances: make(map[string]*OpenPolicyAgentInstance), lastused: make(map[*OpenPolicyAgentInstance]time.Time), quit: make(chan struct{}), @@ -127,7 +137,7 @@ func WithEnvoyMetadataFile(file string) func(*OpenPolicyAgentInstanceConfig) err err = WithEnvoyMetadataBytes(content)(cfg) if err != nil { - return fmt.Errorf("cannot parse '%v': %w", file, err) + return fmt.Errorf("cannot parse '%q': %w", file, err) } return nil @@ -199,7 +209,7 @@ func (registry *OpenPolicyAgentRegistry) cleanUnusedInstances(t time.Time) { } func (registry *OpenPolicyAgentRegistry) startCleanerDaemon() { - ticker := time.NewTicker(10 * time.Second) + ticker := time.NewTicker(registry.cleanInterval) defer ticker.Stop() for { @@ -239,16 +249,13 @@ func (registry *OpenPolicyAgentRegistry) NewOpenPolicyAgentInstance(bundleName s if instance, ok := registry.instances[bundleName]; ok { delete(registry.lastused, instance) - return instance, nil } instance, err := registry.newOpenPolicyAgentInstance(bundleName, config, filterName) - if err != nil { return nil, err } - registry.instances[bundleName] = instance return instance, nil @@ -276,7 +283,6 @@ func (registry *OpenPolicyAgentRegistry) newOpenPolicyAgentInstance(bundleName s } engine, err := New(inmem.New(), configBytes, config, filterName, bundleName) - if err != nil { return nil, err } @@ -296,11 +302,10 @@ func (registry *OpenPolicyAgentRegistry) newOpenPolicyAgentInstance(bundleName s } type OpenPolicyAgentInstance struct { - manager *plugins.Manager - instanceConfig OpenPolicyAgentInstanceConfig - opaConfig *config.Config - bundleName string - + manager *plugins.Manager + instanceConfig OpenPolicyAgentInstanceConfig + opaConfig *config.Config + bundleName string preparedQuery *rego.PreparedEvalQuery preparedQueryDoOnce *sync.Once interQueryBuiltinCache iCache.InterQueryCache @@ -308,13 +313,11 @@ type OpenPolicyAgentInstance struct { } func envVariablesMap() map[string]string { - var rawEnvVariables = os.Environ() - - var envVariables = make(map[string]string) + rawEnvVariables := os.Environ() + envVariables := make(map[string]string) for _, item := range rawEnvVariables { tokens := strings.SplitN(item, "=", 2) - envVariables[tokens[0]] = tokens[1] } @@ -332,7 +335,6 @@ func interpolateConfigTemplate(configTemplate []byte, bundleName string) ([]byte binding["Env"] = envVariablesMap() err := tpl.ExecuteTemplate(&buf, "opa-config", binding) - if err != nil { return nil, err } @@ -468,24 +470,43 @@ func (opa *OpenPolicyAgentInstance) MetricsKey(key string) string { return key + "." + opa.bundleName } -// Implementation of the envoyauth.EvalContext interface +// ParsedQuery is an implementation of the envoyauth.EvalContext interface func (opa *OpenPolicyAgentInstance) ParsedQuery() ast.Body { return opa.EnvoyPluginConfig().ParsedQuery } -func (opa *OpenPolicyAgentInstance) Store() storage.Store { return opa.manager.Store } -func (opa *OpenPolicyAgentInstance) Compiler() *ast.Compiler { return opa.manager.GetCompiler() } -func (opa *OpenPolicyAgentInstance) Runtime() *ast.Term { return opa.manager.Info } -func (opa *OpenPolicyAgentInstance) Logger() logging.Logger { return opa.manager.Logger() } +// Store is an implementation of the envoyauth.EvalContext interface +func (opa *OpenPolicyAgentInstance) Store() storage.Store { return opa.manager.Store } + +// Compiler is an implementation of the envoyauth.EvalContext interface +func (opa *OpenPolicyAgentInstance) Compiler() *ast.Compiler { return opa.manager.GetCompiler() } + +// Runtime is an implementation of the envoyauth.EvalContext interface +func (opa *OpenPolicyAgentInstance) Runtime() *ast.Term { return opa.manager.Info } + +// Logger is an implementation of the envoyauth.EvalContext interface +func (opa *OpenPolicyAgentInstance) Logger() logging.Logger { return opa.manager.Logger() } + +// PreparedQueryDoOnce is an implementation of the envoyauth.EvalContext interface func (opa *OpenPolicyAgentInstance) PreparedQueryDoOnce() *sync.Once { return opa.preparedQueryDoOnce } + +// InterQueryBuiltinCache is an implementation of the envoyauth.EvalContext interface func (opa *OpenPolicyAgentInstance) InterQueryBuiltinCache() iCache.InterQueryCache { return opa.interQueryBuiltinCache } + +// PreparedQuery is an implementation of the envoyauth.EvalContext interface func (opa *OpenPolicyAgentInstance) PreparedQuery() *rego.PreparedEvalQuery { return opa.preparedQuery } + +// SetPreparedQuery is an implementation of the envoyauth.EvalContext interface func (opa *OpenPolicyAgentInstance) SetPreparedQuery(q *rego.PreparedEvalQuery) { opa.preparedQuery = q } + +// Config is an implementation of the envoyauth.EvalContext interface func (opa *OpenPolicyAgentInstance) Config() *config.Config { return opa.opaConfig } + +// DistributedTracing is an implementation of the envoyauth.EvalContext interface func (opa *OpenPolicyAgentInstance) DistributedTracing() opatracing.Options { return opatracing.NewOptions(opa) } diff --git a/filters/openpolicyagent/openpolicyagent_test.go b/filters/openpolicyagent/openpolicyagent_test.go index a20db25bf3..4cbf9f5f2e 100644 --- a/filters/openpolicyagent/openpolicyagent_test.go +++ b/filters/openpolicyagent/openpolicyagent_test.go @@ -1,18 +1,44 @@ package openpolicyagent import ( + "context" + "encoding/json" "fmt" + "io" + "net/http" "os" + "strconv" "testing" "time" ext_authz_v3_core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" + authv3 "github.com/envoyproxy/go-control-plane/envoy/service/auth/v3" _struct "github.com/golang/protobuf/ptypes/struct" + "github.com/open-policy-agent/opa-envoy-plugin/envoyauth" opasdktest "github.com/open-policy-agent/opa/sdk/test" + "github.com/opentracing/opentracing-go" "github.com/stretchr/testify/assert" + "github.com/zalando/skipper/filters" + "github.com/zalando/skipper/filters/filtertest" + "github.com/zalando/skipper/filters/openpolicyagent/internal/envoy" + "github.com/zalando/skipper/metrics/metricstest" + "github.com/zalando/skipper/routing" + "github.com/zalando/skipper/tracing/tracingtest" "google.golang.org/protobuf/encoding/protojson" ) +type MockOpenPolicyAgentFilter struct { + opa *OpenPolicyAgentInstance +} + +func (f *MockOpenPolicyAgentFilter) OpenPolicyAgent() *OpenPolicyAgentInstance { + return f.opa +} + +func (f *MockOpenPolicyAgentFilter) Request(filters.FilterContext) {} + +func (f *MockOpenPolicyAgentFilter) Response(filters.FilterContext) {} + func TestInterpolateTemplate(t *testing.T) { os.Setenv("CONTROL_PLANE_TOKEN", "testtoken") interpolatedConfig, err := interpolateConfigTemplate([]byte(` @@ -63,12 +89,19 @@ func TestLoadEnvoyMetadata(t *testing.T) { assert.Equal(t, expected, cfg.envoyMetadata) } -func TestRegistry(t *testing.T) { +func mockControlPlane() (*opasdktest.Server, []byte) { opaControlPlane := opasdktest.MustNewServer( opasdktest.MockBundle("/bundles/test", map[string]string{ "main.rego": ` package envoy.authz - + + default allow = false + `, + }), + opasdktest.MockBundle("/bundles/anotherbundlename", map[string]string{ + "main.rego": ` + package envoy.authz + default allow = false `, }), @@ -93,43 +126,211 @@ func TestRegistry(t *testing.T) { } }`, opaControlPlane.URL())) - registry := NewOpenPolicyAgentRegistry(WithReuseDuration(2 * time.Second)) + return opaControlPlane, config +} + +func TestRegistry(t *testing.T) { + _, config := mockControlPlane() + + registry := NewOpenPolicyAgentRegistry(WithReuseDuration(1*time.Second), WithCleanInterval(1*time.Second)) cfg, err := NewOpenPolicyAgentConfig(WithConfigTemplate(config)) assert.NoError(t, err) inst1, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") - assert.NoError(t, err) registry.markUnused(map[*OpenPolicyAgentInstance]struct{}{}) inst2, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") - assert.NoError(t, err) - assert.Equal(t, inst1, inst2, "same instance is reused after release") inst3, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") - assert.NoError(t, err) - assert.Equal(t, inst2, inst3, "same instance is reused multiple times") registry.markUnused(map[*OpenPolicyAgentInstance]struct{}{}) //Allow clean up - time.Sleep(15 * time.Second) - - inst4, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + time.Sleep(2 * time.Second) + inst_different_bundle, err := registry.NewOpenPolicyAgentInstance("anotherbundlename", *cfg, "testfilter") assert.NoError(t, err) + inst4, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + assert.NoError(t, err) assert.NotEqual(t, inst1, inst4, "after cleanup a new instance should be created") + //Trigger cleanup via post processor + registry.Do([]*routing.Route{ + { + Filters: []*routing.RouteFilter{{Filter: &MockOpenPolicyAgentFilter{opa: inst_different_bundle}}}, + }, + }) + + // Allow clean up + time.Sleep(2 * time.Second) + + inst5, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + assert.NoError(t, err) + assert.NotEqual(t, inst4, inst5, "after cleanup a new instance should be created") + registry.Close() _, err = registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") - assert.Error(t, err, "should not work after close") } + +func TestStartup(t *testing.T) { + _, config := mockControlPlane() + + registry := NewOpenPolicyAgentRegistry(WithReuseDuration(1*time.Second), WithCleanInterval(1*time.Second)) + + cfg, err := NewOpenPolicyAgentConfig(WithConfigTemplate(config)) + assert.NoError(t, err) + + inst1, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + assert.NoError(t, err) + + target := envoy.PluginConfig{Path: "/envoy/authz/allow", DryRun: false} + target.ParseQuery() + assert.Equal(t, target, inst1.EnvoyPluginConfig()) +} + +func TestTracing(t *testing.T) { + _, config := mockControlPlane() + + registry := NewOpenPolicyAgentRegistry(WithReuseDuration(1*time.Second), WithCleanInterval(1*time.Second)) + + cfg, err := NewOpenPolicyAgentConfig(WithConfigTemplate(config)) + assert.NoError(t, err) + + inst, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + assert.NoError(t, err) + + tracer := &tracingtest.Tracer{} + parent := tracer.StartSpan("start_span") + ctx := opentracing.ContextWithSpan(context.Background(), parent) + span, _ := inst.StartSpanFromContext(ctx) + span.Finish() + parent.Finish() + + recspan, ok := tracer.FindSpan("open-policy-agent") + assert.True(t, ok, "No span was created for open policy agent") + assert.Equal(t, map[string]interface{}{"opa.bundle_name": "test", "opa.label.id": inst.manager.Labels()["id"], "opa.label.version": inst.manager.Labels()["version"]}, recspan.Tags) +} + +func TestEval(t *testing.T) { + _, config := mockControlPlane() + + registry := NewOpenPolicyAgentRegistry(WithReuseDuration(1*time.Second), WithCleanInterval(1*time.Second)) + + cfg, err := NewOpenPolicyAgentConfig(WithConfigTemplate(config)) + assert.NoError(t, err) + + inst, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + assert.NoError(t, err) + + tracer := &tracingtest.Tracer{} + span := tracer.StartSpan("open-policy-agent") + ctx := opentracing.ContextWithSpan(context.Background(), span) + + result, err := inst.Eval(ctx, &authv3.CheckRequest{}) + assert.NoError(t, err) + + allowed, err := result.IsAllowed() + assert.NoError(t, err) + assert.False(t, allowed) + + span.Finish() + testspan, ok := tracer.FindSpan("open-policy-agent") + assert.True(t, ok) + assert.Equal(t, result.DecisionID, testspan.Tags["opa.decision_id"]) +} + +func TestResponses(t *testing.T) { + _, config := mockControlPlane() + + registry := NewOpenPolicyAgentRegistry(WithReuseDuration(1*time.Second), WithCleanInterval(1*time.Second)) + + cfg, err := NewOpenPolicyAgentConfig(WithConfigTemplate(config)) + assert.NoError(t, err) + + inst, err := registry.NewOpenPolicyAgentInstance("test", *cfg, "testfilter") + assert.NoError(t, err) + + tracer := &tracingtest.Tracer{} + span := tracer.StartSpan("open-policy-agent") + metrics := &metricstest.MockMetrics{} + + fc := &filtertest.Context{FMetrics: metrics} + + inst.ServeInvalidDecisionError(fc, span, &envoyauth.EvalResult{}, fmt.Errorf("something happened")) + assert.True(t, fc.FServed) + assert.Equal(t, fc.FResponse.StatusCode, http.StatusInternalServerError) + metrics.WithCounters(func(counters map[string]int64) { + assert.Equal(t, int64(1), counters["decision.err.test"]) + }) + span.Finish() + testspan, ok := tracer.FindSpan("open-policy-agent") + assert.True(t, ok, "span not found") + assert.Contains(t, testspan.Tags, "error") + + fc = &filtertest.Context{FMetrics: metrics} + inst.ServeInvalidDecisionError(fc, span, nil, fmt.Errorf("something happened")) + assert.True(t, fc.FServed) + assert.Equal(t, fc.FResponse.StatusCode, http.StatusInternalServerError) + metrics.WithCounters(func(counters map[string]int64) { + assert.Equal(t, int64(2), counters["decision.err.test"]) + }) + + fc = &filtertest.Context{FMetrics: metrics} + inst.ServeResponse(fc, span, &envoyauth.EvalResult{ + Decision: map[string]interface{}{ + "http_status": json.Number(strconv.Itoa(http.StatusOK)), + "headers": map[string]interface{}{ + "someheader": "somevalue", + }, + "body": "Welcome!", + }, + }) + assert.True(t, fc.FServed) + assert.Equal(t, fc.FResponse.StatusCode, http.StatusOK) + assert.Equal(t, fc.FResponse.Header, http.Header{ + "Someheader": {"somevalue"}, + }) + body, err := io.ReadAll(fc.FResponse.Body) + assert.NoError(t, err) + assert.Equal(t, string(body), "Welcome!") + + fc = &filtertest.Context{FMetrics: metrics} + inst.ServeResponse(fc, span, &envoyauth.EvalResult{ + Decision: map[string]interface{}{ + "headers": "invalid header type", + "body": "Welcome!", + }, + }) + assert.True(t, fc.FServed) + assert.Equal(t, fc.FResponse.StatusCode, http.StatusInternalServerError) + + fc = &filtertest.Context{FMetrics: metrics} + inst.ServeResponse(fc, span, &envoyauth.EvalResult{ + Decision: map[string]interface{}{ + "body": map[string]interface{}{ + "invalid": "body type", + }, + }, + }) + assert.True(t, fc.FServed) + assert.Equal(t, fc.FResponse.StatusCode, http.StatusInternalServerError) + + fc = &filtertest.Context{FMetrics: metrics} + inst.ServeResponse(fc, span, &envoyauth.EvalResult{ + Decision: map[string]interface{}{ + "http_status": "invalid status code", + }, + }) + assert.True(t, fc.FServed) + assert.Equal(t, fc.FResponse.StatusCode, http.StatusInternalServerError) +} diff --git a/filters/openpolicyagent/response.go b/filters/openpolicyagent/response.go index e24f58b1e6..df613b1716 100644 --- a/filters/openpolicyagent/response.go +++ b/filters/openpolicyagent/response.go @@ -11,12 +11,12 @@ import ( "github.com/zalando/skipper/filters" ) -func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.FilterContext, span opentracing.Span, result *envoyauth.EvalResult, err error) { - resp := http.Response{} +func (opa *OpenPolicyAgentInstance) ServeInvalidDecisionError(fc filters.FilterContext, span opentracing.Span, result *envoyauth.EvalResult, err error) { + opa.HandleInvalidDecisionError(fc, span, result, err, true) +} +func (opa *OpenPolicyAgentInstance) HandleInvalidDecisionError(fc filters.FilterContext, span opentracing.Span, result *envoyauth.EvalResult, err error, serve bool) { fc.Metrics().IncCounter(opa.MetricsKey("decision.err")) - - resp.StatusCode = http.StatusInternalServerError span.SetTag("error", true) if result != nil { @@ -42,7 +42,12 @@ func (opa *OpenPolicyAgentInstance) RejectInvalidDecisionError(fc filters.Filter }).Info("Rejecting request because of an invalid decision") } - fc.Serve(&resp) + if serve { + resp := http.Response{} + resp.StatusCode = http.StatusInternalServerError + + fc.Serve(&resp) + } } func (opa *OpenPolicyAgentInstance) ServeResponse(fc filters.FilterContext, span opentracing.Span, result *envoyauth.EvalResult) { @@ -51,22 +56,20 @@ func (opa *OpenPolicyAgentInstance) ServeResponse(fc filters.FilterContext, span var err error resp.StatusCode, err = result.GetResponseHTTPStatus() if err != nil { - opa.RejectInvalidDecisionError(fc, span, result, err) + opa.ServeInvalidDecisionError(fc, span, result, err) return } resp.Header, err = result.GetResponseHTTPHeaders() if err != nil { - opa.RejectInvalidDecisionError(fc, span, result, err) + opa.ServeInvalidDecisionError(fc, span, result, err) return } - hasbody := result.HasResponseBody() - - if hasbody { + if result.HasResponseBody() { body, err := result.GetResponseBody() if err != nil { - opa.RejectInvalidDecisionError(fc, span, result, err) + opa.ServeInvalidDecisionError(fc, span, result, err) return } diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go index c21c54b67c..c83c8ccaca 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go @@ -62,7 +62,6 @@ func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) { } opa, err := s.registry.NewOpenPolicyAgentInstance(bundleName, *opaConfig, s.Name()) - if err != nil { return nil, err } @@ -89,21 +88,13 @@ func (f *serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { start := time.Now() result, err := f.opa.Eval(ctx, authzreq) fc.Metrics().MeasureSince(f.opa.MetricsKey("eval_time"), start) - if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) - return - } + f.opa.ServeInvalidDecisionError(fc, span, result, err) - if f.opa.EnvoyPluginConfig().DryRun { return } - if err != nil { - f.opa.RejectInvalidDecisionError(fc, span, result, err) - } else { - f.opa.ServeResponse(fc, span, result) - } + f.opa.ServeResponse(fc, span, result) } func (f *serveResponseWithRegoPolicyFilter) Response(fc filters.FilterContext) {} diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go index 1806c2fd3d..98342568f4 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go +++ b/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go @@ -46,6 +46,15 @@ func TestAuthorizeRequestFilter(t *testing.T) { expectedStatus: http.StatusForbidden, expectedHeaders: make(http.Header), }, + { + msg: "Misconfigured Rego Query", + bundleName: "somebundle.tar.gz", + regoQuery: "envoy/authz/invalid_path", + requestPath: "allow", + expectedStatus: http.StatusInternalServerError, + expectedBody: "", + expectedHeaders: make(http.Header), + }, { msg: "Allow With Structured Rules", bundleName: "somebundle.tar.gz", @@ -197,3 +206,23 @@ func TestAuthorizeRequestFilter(t *testing.T) { }) } } + +func TestCreateFilterArguments(t *testing.T) { + opaRegistry := openpolicyagent.NewOpenPolicyAgentRegistry() + ftSpec := NewServeResponseWithRegoPolicySpec(opaRegistry, openpolicyagent.WithConfigTemplate([]byte(""))) + + _, err := ftSpec.CreateFilter([]interface{}{}) + assert.ErrorIs(t, err, filters.ErrInvalidFilterParameters) + + _, err = ftSpec.CreateFilter([]interface{}{42}) + assert.ErrorIs(t, err, filters.ErrInvalidFilterParameters) + + _, err = ftSpec.CreateFilter([]interface{}{"a bundle", 42}) + assert.ErrorIs(t, err, filters.ErrInvalidFilterParameters) + + _, err = ftSpec.CreateFilter([]interface{}{"a bundle", "invalid; context extensions"}) + assert.Error(t, err) + + _, err = ftSpec.CreateFilter([]interface{}{"a bundle", "extra: value", "superfluous argument"}) + assert.ErrorIs(t, err, filters.ErrInvalidFilterParameters) +} diff --git a/filters/openpolicyagent/tracing.go b/filters/openpolicyagent/tracing.go index e8a98f895f..d0b6b60244 100644 --- a/filters/openpolicyagent/tracing.go +++ b/filters/openpolicyagent/tracing.go @@ -34,7 +34,7 @@ func (tr *transport) RoundTrip(req *http.Request) (*http.Response, error) { parentSpan := opentracing.SpanFromContext(ctx) if parentSpan != nil { - span := opentracing.StartSpan("http.send", opentracing.ChildOf(parentSpan.Context())) + span := parentSpan.Tracer().StartSpan("http.send", opentracing.ChildOf(parentSpan.Context())) defer span.Finish() req = req.WithContext(opentracing.ContextWithSpan(ctx, span)) diff --git a/filters/openpolicyagent/tracing_test.go b/filters/openpolicyagent/tracing_test.go new file mode 100644 index 0000000000..aad4f77ad4 --- /dev/null +++ b/filters/openpolicyagent/tracing_test.go @@ -0,0 +1,41 @@ +package openpolicyagent + +import ( + "context" + "net/http" + "testing" + + opatracing "github.com/open-policy-agent/opa/tracing" + "github.com/opentracing/opentracing-go" + "github.com/stretchr/testify/assert" + "github.com/zalando/skipper/tracing/tracingtest" +) + +type MockTransport struct { +} + +func (t *MockTransport) RoundTrip(*http.Request) (*http.Response, error) { + return &http.Response{}, nil +} + +func TestTracingFactory(t *testing.T) { + f := &tracingFactory{} + + tr := f.NewTransport(&MockTransport{}, opatracing.Options{&OpenPolicyAgentInstance{}}) + + tracer := &tracingtest.Tracer{} + span := tracer.StartSpan("open-policy-agent") + ctx := opentracing.ContextWithSpan(context.Background(), span) + + req := &http.Request{ + Header: map[string][]string{}, + } + req = req.WithContext(ctx) + + _, err := tr.RoundTrip(req) + assert.NoError(t, err) + + span.Finish() + _, ok := tracer.FindSpan("http.send") + assert.True(t, ok, "No http.send span was created") +} diff --git a/skipper.go b/skipper.go index 845b9f6592..d8ccf44dec 100644 --- a/skipper.go +++ b/skipper.go @@ -896,9 +896,10 @@ type Options struct { // filters. LuaSources []string - EnableOpenPolicyAgent bool - OpenPolicyAgentConfigTemplate string - OpenPolicyAgentEnvoyMetadata string + EnableOpenPolicyAgent bool + OpenPolicyAgentConfigTemplate string + OpenPolicyAgentEnvoyMetadata string + OpenPolicyAgentCleanerInterval time.Duration } func (o *Options) KubernetesDataClientOptions() kubernetes.Options { @@ -1762,7 +1763,7 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { var opaRegistry *openpolicyagent.OpenPolicyAgentRegistry if o.EnableOpenPolicyAgent { - opaRegistry = openpolicyagent.NewOpenPolicyAgentRegistry() + opaRegistry = openpolicyagent.NewOpenPolicyAgentRegistry(openpolicyagent.WithCleanInterval(o.OpenPolicyAgentCleanerInterval)) defer opaRegistry.Close() opts := make([]func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error, 0) From 6be33e55c04ecd31850e4b837b6f13bc7475329d Mon Sep 17 00:00:00 2001 From: Magnus Jungsbluth Date: Mon, 14 Aug 2023 10:51:45 +0200 Subject: [PATCH 24/24] Rename filters Signed-off-by: Magnus Jungsbluth --- docs/operation/operation.md | 16 ++++++++-------- docs/reference/filters.md | 12 ++++++------ docs/tutorials/auth.md | 6 +++--- filters/filters.go | 4 ++-- .../opaauthorizerequest.go} | 16 ++++++++-------- .../opaauthorizerequest_test.go} | 8 ++++---- .../opaserveresponse.go} | 16 ++++++++-------- .../opaserveresponse_test.go} | 8 ++++---- skipper.go | 8 ++++---- 9 files changed, 47 insertions(+), 47 deletions(-) rename filters/openpolicyagent/{authorizewithregopolicy/authorizewithregopolicy.go => opaauthorizerequest/opaauthorizerequest.go} (85%) rename filters/openpolicyagent/{authorizewithregopolicy/authorizewithregopolicy_test.go => opaauthorizerequest/opaauthorizerequest_test.go} (95%) rename filters/openpolicyagent/{serveresponsewithregopolicy/serveresponsewithregopolicy.go => opaserveresponse/opaserveresponse.go} (77%) rename filters/openpolicyagent/{serveresponsewithregopolicy/serveresponsewithregopolicy_test.go => opaserveresponse/opaserveresponse_test.go} (94%) diff --git a/docs/operation/operation.md b/docs/operation/operation.md index a5d8a5b77c..e40feee974 100644 --- a/docs/operation/operation.md +++ b/docs/operation/operation.md @@ -493,17 +493,17 @@ See more details about rate limiting at [Rate limiting](../reference/filters.md# If Open Policy Agent filters are enabled, the following counters show up in the `/metrics` endpoint. The bundle-name is the first parameter of the filter so that for example increased error codes can be attributed to a specific source bundle / system. -- `skipper.authorizeWithRegoPolicy.custom.decision.allow.` -- `skipper.authorizeWithRegoPolicy.custom.decision.deny.` -- `skipper.authorizeWithRegoPolicy.custom.decision.err.` -- `skipper.serveResponseWithRegoPolicy.custom.decision.allow.` -- `skipper.serveResponseWithRegoPolicy.custom.decision.deny.` -- `skipper.serveResponseWithRegoPolicy.custom.decision.err.` +- `skipper.opaAuthorizeRequest.custom.decision.allow.` +- `skipper.opaAuthorizeRequest.custom.decision.deny.` +- `skipper.opaAuthorizeRequest.custom.decision.err.` +- `skipper.opaServeResponse.custom.decision.allow.` +- `skipper.opaServeResponse.custom.decision.deny.` +- `skipper.opaServeResponse.custom.decision.err.` The following timer metrics are exposed per used bundle-name: -- `skipper.authorizeWithRegoPolicy.custom.eval_time.` -- `skipper.serveResponseWithRegoPolicy.custom.eval_time.` +- `skipper.opaAuthorizeRequest.custom.eval_time.` +- `skipper.opaServeResponse.custom.eval_time.` ## OpenTracing diff --git a/docs/reference/filters.md b/docs/reference/filters.md index 50fdb35ef0..3ef8cfa4a8 100644 --- a/docs/reference/filters.md +++ b/docs/reference/filters.md @@ -1723,19 +1723,19 @@ As of now there is no negative/deny rule possible. The first matching path is ev To get started with [Open Policy Agent](https://www.openpolicyagent.org/), also have a look at the [tutorial](../tutorials/auth.md#open-policy-agent). This section is only a reference for the implemented filters. -#### authorizeWithRegoPolicy +#### opaAuthorizeRequest The canonical use case that is also implemented with [Envoy External Authorization](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter): Use the http request to evaluate if Skipper should deny the request (with customizable response) or let the request pass to the downstream service Example: ``` -authorizeWithRegoPolicy("my-app-id") +opaAuthorizeRequest("my-app-id") ``` Example (passing context): ``` -authorizeWithRegoPolicy("my-app-id", "com.mydomain.xxx.myprop: myvalue") +opaAuthorizeRequest("my-app-id", "com.mydomain.xxx.myprop: myvalue") ``` *Data Flows* @@ -1798,7 +1798,7 @@ Headers both to the upstream and the downstream service can be manipulated the s This allows both to add and remove unwanted headers in allow/deny cases. -#### serveResponseWithRegoPolicy +#### opaServeResponse Always serves the response even if the policy allows the request and can customize the response completely. Can be used to re-implement legacy authorization services by already using data in Open Policy Agent but implementing an old REST API. This can also be useful to support Single Page Applications to return the calling users' permissions. @@ -1807,12 +1807,12 @@ Always serves the response even if the policy allows the request and can customi Example: ``` -serveResponseWithRegoPolicy("my-app-id") +opaServeResponse("my-app-id") ``` Example (passing context): ``` -serveResponseWithRegoPolicy("my-app-id", "com.mydomain.xxx.myprop: myvalue") +opaServeResponse("my-app-id", "com.mydomain.xxx.myprop: myvalue") ``` *Data Flows* diff --git a/docs/tutorials/auth.md b/docs/tutorials/auth.md index cd00d9f05e..35fd393dfa 100644 --- a/docs/tutorials/auth.md +++ b/docs/tutorials/auth.md @@ -455,8 +455,8 @@ Generally there are two ways to pass context to a policy: This context can be passed as second argument to filters: -`authorizeWithRegoPolicy("my-app-id", "com.mycompany.myprop: myvalue")` -or `authorizeWithRegoPolicy("my-app-id", "{'com.mycompany.myprop': 'my value'}")` +`opaAuthorizeRequest("my-app-id", "com.mycompany.myprop: myvalue")` +or `opaAuthorizeRequest("my-app-id", "{'com.mycompany.myprop': 'my value'}")` The second argument is parsed as YAML, cannot be nested and values need to be strings. @@ -492,7 +492,7 @@ Start Skipper with ``` skipper -enable-open-policy-agent -open-policy-agent-config-template opaconfig.yaml \ - -inline-routes 'notfound: * -> authorizeWithRegoPolicy("") -> inlineContent("

Authorized Hello

") -> ' + -inline-routes 'notfound: * -> opaAuthorizeRequest("") -> inlineContent("

Authorized Hello

") -> ' ``` You can test the policy with diff --git a/filters/filters.go b/filters/filters.go index ffb4ed1a7c..33d89ad731 100644 --- a/filters/filters.go +++ b/filters/filters.go @@ -341,8 +341,8 @@ const ( EndpointCreatedName = "endpointCreated" ConsistentHashKeyName = "consistentHashKey" ConsistentHashBalanceFactorName = "consistentHashBalanceFactor" - AuthorizeWithRegoPolicyName = "authorizeWithRegoPolicy" - ServeResponseWithRegoPolicyName = "serveResponseWithRegoPolicy" + OpaAuthorizeRequestName = "opaAuthorizeRequest" + OpaServeResponseName = "opaServeResponse" // Undocumented filters HealthCheckName = "healthcheck" diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go b/filters/openpolicyagent/opaauthorizerequest/opaauthorizerequest.go similarity index 85% rename from filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go rename to filters/openpolicyagent/opaauthorizerequest/opaauthorizerequest.go index 1909019cb4..b3747d5d5b 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy.go +++ b/filters/openpolicyagent/opaauthorizerequest/opaauthorizerequest.go @@ -1,4 +1,4 @@ -package authorizewithregopolicy +package opaauthorizerequest import ( "net/http" @@ -16,7 +16,7 @@ type spec struct { opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error } -func NewAuthorizeWithRegoPolicySpec(registry *openpolicyagent.OpenPolicyAgentRegistry, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) filters.Spec { +func NewOpaAuthorizeRequestSpec(registry *openpolicyagent.OpenPolicyAgentRegistry, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) filters.Spec { return &spec{ registry: registry, opts: opts, @@ -24,7 +24,7 @@ func NewAuthorizeWithRegoPolicySpec(registry *openpolicyagent.OpenPolicyAgentReg } func (s *spec) Name() string { - return filters.AuthorizeWithRegoPolicyName + return filters.OpaAuthorizeRequestName } func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) { @@ -68,20 +68,20 @@ func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) { return nil, err } - return &authorizeWithRegoPolicyFilter{ + return &opaAuthorizeRequestFilter{ opa: opa, registry: s.registry, envoyContextExtensions: envoyContextExtensions, }, nil } -type authorizeWithRegoPolicyFilter struct { +type opaAuthorizeRequestFilter struct { opa *openpolicyagent.OpenPolicyAgentInstance registry *openpolicyagent.OpenPolicyAgentRegistry envoyContextExtensions map[string]string } -func (f *authorizeWithRegoPolicyFilter) Request(fc filters.FilterContext) { +func (f *opaAuthorizeRequestFilter) Request(fc filters.FilterContext) { req := fc.Request() span, ctx := f.opa.StartSpanFromFilterContext(fc) defer span.Finish() @@ -144,8 +144,8 @@ func addRequestHeaders(fc filters.FilterContext, headers http.Header) { } } -func (*authorizeWithRegoPolicyFilter) Response(filters.FilterContext) {} +func (*opaAuthorizeRequestFilter) Response(filters.FilterContext) {} -func (f *authorizeWithRegoPolicyFilter) OpenPolicyAgent() *openpolicyagent.OpenPolicyAgentInstance { +func (f *opaAuthorizeRequestFilter) OpenPolicyAgent() *openpolicyagent.OpenPolicyAgentInstance { return f.opa } diff --git a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go b/filters/openpolicyagent/opaauthorizerequest/opaauthorizerequest_test.go similarity index 95% rename from filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go rename to filters/openpolicyagent/opaauthorizerequest/opaauthorizerequest_test.go index ea56d6e975..41c3169259 100644 --- a/filters/openpolicyagent/authorizewithregopolicy/authorizewithregopolicy_test.go +++ b/filters/openpolicyagent/opaauthorizerequest/opaauthorizerequest_test.go @@ -1,4 +1,4 @@ -package authorizewithregopolicy +package opaauthorizerequest import ( "fmt" @@ -218,10 +218,10 @@ func TestAuthorizeRequestFilter(t *testing.T) { }`, opaControlPlane.URL(), ti.regoQuery)) opaFactory := openpolicyagent.NewOpenPolicyAgentRegistry() - ftSpec := NewAuthorizeWithRegoPolicySpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) + ftSpec := NewOpaAuthorizeRequestSpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) fr.Register(ftSpec) - r := eskip.MustParse(fmt.Sprintf(`* -> authorizeWithRegoPolicy("%s", "%s") -> "%s"`, ti.bundleName, ti.contextExtensions, clientServer.URL)) + r := eskip.MustParse(fmt.Sprintf(`* -> opaAuthorizeRequest("%s", "%s") -> "%s"`, ti.bundleName, ti.contextExtensions, clientServer.URL)) proxy := proxytest.New(fr, r...) @@ -251,7 +251,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { func TestCreateFilterArguments(t *testing.T) { opaRegistry := openpolicyagent.NewOpenPolicyAgentRegistry() - ftSpec := NewAuthorizeWithRegoPolicySpec(opaRegistry, openpolicyagent.WithConfigTemplate([]byte(""))) + ftSpec := NewOpaAuthorizeRequestSpec(opaRegistry, openpolicyagent.WithConfigTemplate([]byte(""))) _, err := ftSpec.CreateFilter([]interface{}{}) assert.ErrorIs(t, err, filters.ErrInvalidFilterParameters) diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go b/filters/openpolicyagent/opaserveresponse/opaserveresponse.go similarity index 77% rename from filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go rename to filters/openpolicyagent/opaserveresponse/opaserveresponse.go index c83c8ccaca..6134fa0d74 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy.go +++ b/filters/openpolicyagent/opaserveresponse/opaserveresponse.go @@ -1,4 +1,4 @@ -package serveresponsewithregopolicy +package opaserveresponse import ( "time" @@ -15,7 +15,7 @@ type spec struct { opts []func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error } -func NewServeResponseWithRegoPolicySpec(registry *openpolicyagent.OpenPolicyAgentRegistry, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) filters.Spec { +func NewOpaServeResponseSpec(registry *openpolicyagent.OpenPolicyAgentRegistry, opts ...func(*openpolicyagent.OpenPolicyAgentInstanceConfig) error) filters.Spec { return &spec{ registry: registry, opts: opts, @@ -23,7 +23,7 @@ func NewServeResponseWithRegoPolicySpec(registry *openpolicyagent.OpenPolicyAgen } func (s *spec) Name() string { - return filters.ServeResponseWithRegoPolicyName + return filters.OpaServeResponseName } func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) { @@ -66,20 +66,20 @@ func (s *spec) CreateFilter(args []interface{}) (filters.Filter, error) { return nil, err } - return &serveResponseWithRegoPolicyFilter{ + return &opaServeResponseFilter{ opa: opa, registry: s.registry, envoyContextExtensions: envoyContextExtensions, }, nil } -type serveResponseWithRegoPolicyFilter struct { +type opaServeResponseFilter struct { opa *openpolicyagent.OpenPolicyAgentInstance registry *openpolicyagent.OpenPolicyAgentRegistry envoyContextExtensions map[string]string } -func (f *serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { +func (f *opaServeResponseFilter) Request(fc filters.FilterContext) { span, ctx := f.opa.StartSpanFromFilterContext(fc) defer span.Finish() @@ -97,8 +97,8 @@ func (f *serveResponseWithRegoPolicyFilter) Request(fc filters.FilterContext) { f.opa.ServeResponse(fc, span, result) } -func (f *serveResponseWithRegoPolicyFilter) Response(fc filters.FilterContext) {} +func (f *opaServeResponseFilter) Response(fc filters.FilterContext) {} -func (f *serveResponseWithRegoPolicyFilter) OpenPolicyAgent() *openpolicyagent.OpenPolicyAgentInstance { +func (f *opaServeResponseFilter) OpenPolicyAgent() *openpolicyagent.OpenPolicyAgentInstance { return f.opa } diff --git a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go b/filters/openpolicyagent/opaserveresponse/opaserveresponse_test.go similarity index 94% rename from filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go rename to filters/openpolicyagent/opaserveresponse/opaserveresponse_test.go index 98342568f4..63494faf43 100644 --- a/filters/openpolicyagent/serveresponsewithregopolicy/serveresponsewithregopolicy_test.go +++ b/filters/openpolicyagent/opaserveresponse/opaserveresponse_test.go @@ -1,4 +1,4 @@ -package serveresponsewithregopolicy +package opaserveresponse import ( "fmt" @@ -165,7 +165,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { }`, opaControlPlane.URL(), ti.regoQuery)) opaFactory := openpolicyagent.NewOpenPolicyAgentRegistry() - ftSpec := NewServeResponseWithRegoPolicySpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) + ftSpec := NewOpaServeResponseSpec(opaFactory, openpolicyagent.WithConfigTemplate(config)) filterArgs := []interface{}{ti.bundleName} if ti.contextExtensions != "" { @@ -177,7 +177,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { fr.Register(ftSpec) - r := eskip.MustParse(fmt.Sprintf(`* -> serveResponseWithRegoPolicy("%s", "%s") -> "%s"`, ti.bundleName, ti.contextExtensions, clientServer.URL)) + r := eskip.MustParse(fmt.Sprintf(`* -> opaServeResponse("%s", "%s") -> "%s"`, ti.bundleName, ti.contextExtensions, clientServer.URL)) proxy := proxytest.New(fr, r...) reqURL, err := url.Parse(proxy.URL) @@ -209,7 +209,7 @@ func TestAuthorizeRequestFilter(t *testing.T) { func TestCreateFilterArguments(t *testing.T) { opaRegistry := openpolicyagent.NewOpenPolicyAgentRegistry() - ftSpec := NewServeResponseWithRegoPolicySpec(opaRegistry, openpolicyagent.WithConfigTemplate([]byte(""))) + ftSpec := NewOpaServeResponseSpec(opaRegistry, openpolicyagent.WithConfigTemplate([]byte(""))) _, err := ftSpec.CreateFilter([]interface{}{}) assert.ErrorIs(t, err, filters.ErrInvalidFilterParameters) diff --git a/skipper.go b/skipper.go index d8ccf44dec..1f4af8f91a 100644 --- a/skipper.go +++ b/skipper.go @@ -37,8 +37,8 @@ import ( "github.com/zalando/skipper/filters/fadein" logfilter "github.com/zalando/skipper/filters/log" "github.com/zalando/skipper/filters/openpolicyagent" - "github.com/zalando/skipper/filters/openpolicyagent/authorizewithregopolicy" - "github.com/zalando/skipper/filters/openpolicyagent/serveresponsewithregopolicy" + "github.com/zalando/skipper/filters/openpolicyagent/opaauthorizerequest" + "github.com/zalando/skipper/filters/openpolicyagent/opaserveresponse" ratelimitfilters "github.com/zalando/skipper/filters/ratelimit" "github.com/zalando/skipper/filters/shedder" teefilters "github.com/zalando/skipper/filters/tee" @@ -1773,8 +1773,8 @@ func run(o Options, sig chan os.Signal, idleConnsCH chan struct{}) error { } o.CustomFilters = append(o.CustomFilters, - authorizewithregopolicy.NewAuthorizeWithRegoPolicySpec(opaRegistry, opts...), - serveresponsewithregopolicy.NewServeResponseWithRegoPolicySpec(opaRegistry, opts...), + opaauthorizerequest.NewOpaAuthorizeRequestSpec(opaRegistry, opts...), + opaserveresponse.NewOpaServeResponseSpec(opaRegistry, opts...), ) }