diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b35014a4..383ae8e3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -105,7 +105,19 @@ jobs: run: | sudo ls /etc/cni/net.d sudo rm /etc/cni/net.d/87-podman-bridge.conflist + - name: Verify Rego file presence + run: ls -l ${{ github.workspace }}/docs/sample-rego-policies/example.rego + - name: Set Rego file path + run: echo "REGO_FILE_PATH=${{ github.workspace }}/docs/sample-rego-policies/example.rego" >> $GITHUB_ENV + - name: Start finch-daemon with opa Authz + run: sudo bin/finch-daemon --debug --experimental --rego-file ${{ github.workspace }}/docs/sample-rego-policies/example.rego --skip-rego-perm-check --socket-owner $UID --socket-addr /run/finch.sock --pidfile /run/finch.pid & + - name: Run opa e2e tests + run: sudo -E make test-e2e-opa + - name: Clean up Daemon socket + run: sudo rm /run/finch.sock && sudo rm /run/finch.pid - name: Start finch-daemon run: sudo bin/finch-daemon --debug --socket-owner $UID & - name: Run e2e test run: sudo make test-e2e + - name: Clean up Daemon socket + run: sudo rm /var/run/finch.sock && sudo rm /run/finch.pid diff --git a/Makefile b/Makefile index 1622edeb..a567ebed 100644 --- a/Makefile +++ b/Makefile @@ -114,6 +114,15 @@ test-e2e: linux TEST_E2E=1 \ $(GINKGO) $(GFLAGS) ./e2e/... +.PHONY: test-e2e-opa +test-e2e-opa: linux + DOCKER_HOST="unix:///run/finch.sock" \ + DOCKER_API_VERSION="v1.41" \ + MIDDLEWARE_E2E=1 \ + TEST_E2E=0 \ + DAEMON_ROOT="$(BIN)/finch-daemon" \ + $(GINKGO) $(GFLAGS) ./e2e/... + .PHONY: licenses licenses: PATH=$(BIN):$(PATH) go-licenses report --template="scripts/third-party-license.tpl" --ignore github.com/runfinch ./... > THIRD_PARTY_LICENSES @@ -126,4 +135,4 @@ coverage: linux .PHONY: release release: linux @echo "$@" - @$(FINCH_DAEMON_PROJECT_ROOT)/scripts/create-releases.sh $(RELEASE_TAG) + @$(FINCH_DAEMON_PROJECT_ROOT)/scripts/create-releases.sh $(RELEASE_TAG) \ No newline at end of file diff --git a/README.md b/README.md index deb95ecb..93612850 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,32 @@ Getting started with Finch Daemon on Linux only requires a few steps: 5. Test any changes with `make test-unit` and `sudo make test-e2e` +## Experimental Features + +Finch Daemon includes experimental features that can be enabled using the `--experimental` flag. These features are under development and may change in future releases. + +### Using Experimental Features + +To enable experimental features, use the `--experimental` flag when starting the daemon: + +```bash +sudo bin/finch-daemon --debug --socket-owner $UID --experimental +``` + +### Current Experimental Features + +#### OPA Authorization Middleware + +The OPA (Open Policy Agent) middleware allows you to define authorization policies for API requests using Rego policy language. This feature requires both the `--experimental` flag and the `--rego-file` flag to be set. + +Example usage: +```bash +sudo bin/finch-daemon --debug --socket-owner $UID --experimental --rego-file /path/to/policy.rego +``` + +For detailed documentation on the OPA middleware, see [opa-middleware.md](docs/opa-middleware.md). + + ## Creating a systemd service If you want finch-daemon to be managed as a systemd service, for benefits like automatic restart if it gets killed, you can configure it as a systemd service on Linux by diff --git a/api/router/router.go b/api/router/router.go index 65066929..b5e703e1 100644 --- a/api/router/router.go +++ b/api/router/router.go @@ -5,6 +5,7 @@ package router import ( "context" + "errors" "fmt" "net/http" "os" @@ -15,6 +16,7 @@ import ( "github.com/moby/moby/api/server/httputils" "github.com/moby/moby/api/types/versions" + "github.com/open-policy-agent/opa/v1/rego" "github.com/runfinch/finch-daemon/api/handlers/builder" "github.com/runfinch/finch-daemon/api/handlers/container" "github.com/runfinch/finch-daemon/api/handlers/distribution" @@ -30,6 +32,14 @@ import ( "github.com/runfinch/finch-daemon/version" ) +var errRego = errors.New("error in rego policy file") +var errInput = errors.New("error in HTTP request") + +type inputRegoRequest struct { + Method string + Path string +} + // Options defines the router options to be passed into the handlers. type Options struct { Config *config.Config @@ -41,6 +51,7 @@ type Options struct { VolumeService volume.Service ExecService exec.Service DistributionService distribution.Service + RegoFilePath string // NerdctlWrapper wraps the interactions with nerdctl to build NerdctlWrapper *backend.NerdctlWrapper @@ -48,12 +59,20 @@ type Options struct { // New creates a new router and registers the handlers to it. Returns a handler object // The struct definitions of the HTTP responses come from https://github.com/moby/moby/tree/master/api/types. -func New(opts *Options) http.Handler { +func New(opts *Options) (http.Handler, error) { r := mux.NewRouter() r.Use(VersionMiddleware) - vr := types.VersionedRouter{Router: r} logger := flog.NewLogrus() + + if opts.RegoFilePath != "" { + regoMiddleware, err := CreateRegoMiddleware(opts.RegoFilePath, logger) + if err != nil { + return nil, err + } + r.Use(regoMiddleware) + } + vr := types.VersionedRouter{Router: r} system.RegisterHandlers(vr, opts.SystemService, opts.Config, opts.NerdctlWrapper, logger) image.RegisterHandlers(vr, opts.ImageService, opts.Config, logger) container.RegisterHandlers(vr, opts.ContainerService, opts.Config, logger) @@ -62,7 +81,7 @@ func New(opts *Options) http.Handler { volume.RegisterHandlers(vr, opts.VolumeService, opts.Config, logger) exec.RegisterHandlers(vr, opts.ExecService, opts.Config, logger) distribution.RegisterHandlers(vr, opts.DistributionService, opts.Config, logger) - return ghandlers.LoggingHandler(os.Stderr, r) + return ghandlers.LoggingHandler(os.Stderr, r), nil } // VersionMiddleware checks for the requested version of the api and makes sure it falls within the bounds @@ -90,3 +109,53 @@ func VersionMiddleware(next http.Handler) http.Handler { next.ServeHTTP(w, newReq) }) } + +// CreateRegoMiddleware dynamically parses the rego file at the path specified in options +// and return a function that allows or denies the request based on the policy. +// Will return a nil function and an error if the given file path is blank or invalid. +func CreateRegoMiddleware(regoFilePath string, logger *flog.Logrus) (func(next http.Handler) http.Handler, error) { + if regoFilePath == "" { + return nil, errRego + } + + query := "data.finch.authz.allow" + nr := rego.New( + rego.Load([]string{regoFilePath}, nil), + rego.Query(query), + ) + + preppedQuery, err := nr.PrepareForEval(context.Background()) + if err != nil { + return nil, err + } + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + input := inputRegoRequest{ + Method: r.Method, + Path: r.URL.Path, + } + + logger.Debugf("OPA input being evaluated: Method=%s, Path=%s", input.Method, input.Path) + + rs, err := preppedQuery.Eval(r.Context(), rego.EvalInput(input)) + if err != nil { + logger.Errorf("OPA policy evaluation failed: %v", err) + response.SendErrorResponse(w, http.StatusInternalServerError, errInput) + return + } + + logger.Debugf("OPA evaluation results: %+v", rs) + + if !rs.Allowed() { + logger.Infof("OPA request denied: Method=%s, Path=%s", r.Method, r.URL.Path) + response.SendErrorResponse(w, http.StatusForbidden, + fmt.Errorf("method %s not allowed for path %s", r.Method, r.URL.Path)) + return + } + logger.Debugf("OPA request allowed: Method=%s, Path=%s", r.Method, r.URL.Path) + newReq := r.WithContext(r.Context()) + next.ServeHTTP(w, newReq) + }) + }, nil +} diff --git a/api/router/router_test.go b/api/router/router_test.go index 4b9a0115..2b69ed2b 100644 --- a/api/router/router_test.go +++ b/api/router/router_test.go @@ -8,6 +8,8 @@ import ( "fmt" "net/http" "net/http/httptest" + "os" + "path/filepath" "testing" "github.com/containerd/nerdctl/v2/pkg/config" @@ -51,8 +53,9 @@ var _ = Describe("version middleware test", func() { BuilderService: nil, VolumeService: nil, NerdctlWrapper: nil, + RegoFilePath: "", } - h = New(opts) + h, _ = New(opts) rr = httptest.NewRecorder() expected = types.VersionInfo{ Platform: struct { @@ -126,3 +129,69 @@ var _ = Describe("version middleware test", func() { Expect(v).Should(Equal(expected)) }) }) + +// Unit tests for the rego handler. +var _ = Describe("rego middleware test", func() { + var ( + opts *Options + rr *httptest.ResponseRecorder + expected types.VersionInfo + sysSvc *mocks_system.MockService + regoFilePath string + ) + + BeforeEach(func() { + mockCtrl := gomock.NewController(GinkgoT()) + defer mockCtrl.Finish() + + tempDirPath := GinkgoT().TempDir() + regoFilePath = filepath.Join(tempDirPath, "authz.rego") + os.Create(regoFilePath) + + c := config.Config{} + sysSvc = mocks_system.NewMockService(mockCtrl) + opts = &Options{ + Config: &c, + SystemService: sysSvc, + } + rr = httptest.NewRecorder() + expected = types.VersionInfo{} + sysSvc.EXPECT().GetVersion(gomock.Any()).Return(&expected, nil).AnyTimes() + }) + It("should return a 200 error for calls by default", func() { + h, err := New(opts) + Expect(err).Should(BeNil()) + + req, _ := http.NewRequest(http.MethodGet, "/version", nil) + h.ServeHTTP(rr, req) + + Expect(rr).Should(HaveHTTPStatus(http.StatusOK)) + }) + + It("should return a 400 error for disallowed calls", func() { + regoPolicy := `package finch.authz +import rego.v1 + +default allow = false` + + os.WriteFile(regoFilePath, []byte(regoPolicy), 0644) + opts.RegoFilePath = regoFilePath + h, err := New(opts) + Expect(err).Should(BeNil()) + + req, _ := http.NewRequest(http.MethodGet, "/version", nil) + h.ServeHTTP(rr, req) + + Expect(rr).Should(HaveHTTPStatus(http.StatusForbidden)) + }) + + It("should return an error for poorly formed rego files", func() { + regoPolicy := `poorly formed rego file` + + os.WriteFile(regoFilePath, []byte(regoPolicy), 0644) + opts.RegoFilePath = regoFilePath + _, err := New(opts) + + Expect(err).Should(Not(BeNil())) + }) +}) diff --git a/cmd/finch-daemon/main.go b/cmd/finch-daemon/main.go index 314a83f8..ed2f8129 100644 --- a/cmd/finch-daemon/main.go +++ b/cmd/finch-daemon/main.go @@ -43,12 +43,15 @@ const ( ) type DaemonOptions struct { - debug bool - socketAddr string - socketOwner int - debugAddress string - configPath string - pidFile string + debug bool + socketAddr string + socketOwner int + debugAddress string + configPath string + pidFile string + regoFilePath string + enableExperimental bool + skipRegoPermCheck bool } var options = new(DaemonOptions) @@ -67,6 +70,10 @@ func main() { rootCmd.Flags().StringVar(&options.debugAddress, "debug-addr", "", "") rootCmd.Flags().StringVar(&options.configPath, "config-file", defaultConfigPath, "Daemon Config Path") rootCmd.Flags().StringVar(&options.pidFile, "pidfile", defaultPidFile, "pid file location") + rootCmd.Flags().StringVar(&options.regoFilePath, "rego-file", "", "Rego Policy Path (requires --experimental flag)") + rootCmd.Flags().BoolVar(&options.skipRegoPermCheck, "skip-rego-perm-check", false, "skip the rego file permission check (allows permissions more permissive than 0600)") + rootCmd.Flags().BoolVar(&options.enableExperimental, "experimental", false, "enable experimental features") + if err := rootCmd.Execute(); err != nil { log.Printf("got error: %v", err) log.Fatal(err) @@ -215,8 +222,27 @@ func newRouter(options *DaemonOptions, logger *flog.Logrus) (http.Handler, error return nil, err } - opts := createRouterOptions(conf, clientWrapper, ncWrapper, logger) - return router.New(opts), nil + var regoFilePath string + + if options.regoFilePath != "" { + if !options.enableExperimental { + return nil, fmt.Errorf("rego file provided without experimental flag - OPA middleware is an experimental feature, please enable it with '--experimental' flag") + } + regoFilePath, err = checkRegoFileValidity(options, logger) + if err != nil { + return nil, err + } + } else if options.enableExperimental { + // Only experimental flag set + logger.Info("experimental flag passed, but no experimental features enabled") + } + + opts := createRouterOptions(conf, clientWrapper, ncWrapper, logger, regoFilePath) + newRouter, err := router.New(opts) + if err != nil { + return nil, err + } + return newRouter, nil } func handleSignal(socket string, server *http.Server, logger *flog.Logrus) { diff --git a/cmd/finch-daemon/router_utils.go b/cmd/finch-daemon/router_utils.go index 0261f4c4..839eccef 100644 --- a/cmd/finch-daemon/router_utils.go +++ b/cmd/finch-daemon/router_utils.go @@ -7,6 +7,8 @@ import ( "errors" "fmt" "os" + "path/filepath" + "strings" containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/pkg/namespaces" @@ -96,6 +98,7 @@ func createRouterOptions( clientWrapper *backend.ContainerdClientWrapper, ncWrapper *backend.NerdctlWrapper, logger *flog.Logrus, + regoFilePath string, ) *router.Options { fs := afero.NewOsFs() tarCreator := archive.NewTarCreator(ecc.NewExecCmdCreator(), logger) @@ -112,5 +115,41 @@ func createRouterOptions( ExecService: exec.NewService(clientWrapper, logger), DistributionService: distribution.NewService(clientWrapper, ncWrapper, logger), NerdctlWrapper: ncWrapper, + RegoFilePath: regoFilePath, } } + +// checkRegoFileValidity validates and prepares the Rego policy file for use. +// It verifies that the file exists, has the right extension (.rego), and has appropriate permissions. +func checkRegoFileValidity(options *DaemonOptions, logger *flog.Logrus) (string, error) { + if options.regoFilePath == "" { + return "", fmt.Errorf("rego file path not provided, please provide the policy file path using the --rego-file flag") + } + + if _, err := os.Stat(options.regoFilePath); os.IsNotExist(err) { + return "", fmt.Errorf("provided Rego file path does not exist: %s", options.regoFilePath) + } + + // Check if the file has a valid extension (.rego) + fileExt := strings.ToLower(filepath.Ext(options.regoFilePath)) + + if fileExt != ".rego" { + return "", fmt.Errorf("invalid file extension for Rego file. Only .rego files are supported") + } + + if !options.skipRegoPermCheck { + fileInfo, err := os.Stat(options.regoFilePath) + if err != nil { + return "", fmt.Errorf("error checking rego file permissions: %v", err) + } + + if fileInfo.Mode().Perm()&0177 != 0 { + return "", fmt.Errorf("rego file permissions %o are too permissive (maximum allowable permissions: 0600)", fileInfo.Mode().Perm()) + } + logger.Debugf("rego file permissions check passed: %o", fileInfo.Mode().Perm()) + } else { + logger.Warnf("skipping rego file permission check - file may have permissions more permissive than 0600") + } + + return options.regoFilePath, nil +} diff --git a/cmd/finch-daemon/router_utils_test.go b/cmd/finch-daemon/router_utils_test.go index c5ee9537..b52f9069 100644 --- a/cmd/finch-daemon/router_utils_test.go +++ b/cmd/finch-daemon/router_utils_test.go @@ -5,9 +5,11 @@ package main import ( "os" + "path/filepath" "testing" "github.com/containerd/nerdctl/v2/pkg/config" + "github.com/runfinch/finch-daemon/pkg/flog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -71,3 +73,72 @@ namespace = "test_namespace" assert.Equal(t, "test_address", cfg.Address) assert.Equal(t, "test_namespace", cfg.Namespace) } + +func TestCheckRegoFileValidity(t *testing.T) { + logger := flog.NewLogrus() + tests := []struct { + name string + setupFunc func(t *testing.T) (string, func()) + skipPermCheck bool + expectedError string + }{ + { + name: "valid rego file", + setupFunc: func(t *testing.T) (string, func()) { + // Create a temporary directory that will be automatically cleaned up + tmpDir := t.TempDir() + + // Create a file with .rego extension and proper content + regoPath := filepath.Join(tmpDir, "test.rego") + regoContent := `package finch.authz + +import future.keywords.if +import rego.v1 + +default allow = false +` + err := os.WriteFile(regoPath, []byte(regoContent), 0600) + require.NoError(t, err) + + return regoPath, func() {} + }, + expectedError: "", + }, + { + name: "non-existent file", + setupFunc: func(t *testing.T) (string, func()) { + return filepath.Join(os.TempDir(), "nonexistent.rego"), func() {} + }, + expectedError: "provided Rego file path does not exist", + }, + { + name: "wrong extension", + setupFunc: func(t *testing.T) (string, func()) { + tmpFile, err := os.CreateTemp("", "test.txt") + require.NoError(t, err) + return tmpFile.Name(), func() { os.Remove(tmpFile.Name()) } + }, + expectedError: "invalid file extension", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filePath, cleanup := tt.setupFunc(t) + defer cleanup() + + options := &DaemonOptions{ + regoFilePath: filePath, + } + path, err := checkRegoFileValidity(options, logger) + + if tt.expectedError != "" { + assert.ErrorContains(t, err, tt.expectedError) + assert.Empty(t, path) + } else { + assert.NoError(t, err) + assert.Equal(t, filePath, path) + } + }) + } +} diff --git a/docs/opa-middleware.md b/docs/opa-middleware.md new file mode 100644 index 00000000..f6caf6ee --- /dev/null +++ b/docs/opa-middleware.md @@ -0,0 +1,167 @@ +# OPA Authorization Middleware (Experimental) + +> ⚠️ **Experimental Feature**: The OPA authorization middleware is being introduced as an experimental feature. + +This guide provides instructions for setting up [OPA](https://github.com/open-policy-agent/opa) authorization policies with the finch-daemon. These policies allow users to allowlist or deny certain resources based on policy rules. + +## Experimental Status + +This feature is being released as experimental because: +- Integration patterns and best practices are still being established +- Performance characteristics are being evaluated + +As an experimental feature: +- Breaking changes may occur in any release +- Long-term backward compatibility is not guaranteed +- Documentation and examples may evolve substantially +- Production use is not recommended at this stage + +## What Is OPA Authz implementation +Open Policy Agent (OPA) is an open-source, general-purpose policy engine that enables unified, context-aware policy enforcement across the entire stack. OPA provides a high-level declarative language, Rego, for specifying policy as code and simple APIs to offload policy decision-making from your software. + +In the current implementation, users can use OPA Rego policies to filter API requests at the Daemon level. It's important to note that the current implementation only supports allowlisting of requests. This means you can specify which requests should be allowed, and all others will be denied by default. + +## Setting up a policy + +Use the [sample rego](../docs/sample-rego-policies/example.rego) policy template to build your policy rules. + +The package name must be `finch.authz`, the daemon middleware will look for the result of the `allow` key on each API call to determine wether to allow/deny the request. +An approved request will go through without any events, a rejected request will fail with status code 403 + +Example: + +The following policy blocks all API requests made to the daemon. +``` +package finch.authz + +default allow = false + +``` +`allow` can be modified based on the business requirements for example we can prevent users from creating new containers by preventing them from accessing the create API + +``` +allow if { + not (input.Method == "POST" and input.Path == "/v1.43/containers/create") +} +``` +Use the [Rego playground](https://play.openpolicyagent.org/) to fine tune your rego policies + +## Enable OPA Middleware + +Once you are ready with your policy document, use the `--experimental` flag to enable experimental features including OPA middleware. The daemon will then look for the policy document provided by the `--rego-file` flag. + +Note: Since OPA middleware is an experimental feature, the `--experimental` flag is required when using `--rego-file`. + +The daemon enforces strict permissions (0600 or more restrictive) on the Rego policy file to prevent unauthorized modifications. You can bypass this check using the `--skip-rego-perm-check` flag. + +Examples: + +Standard secure usage: +```bash +sudo bin/finch-daemon --debug --socket-owner $UID --socket-addr /run/finch-test.sock --pidfile /run/finch-test.pid --experimental --rego-file /path/to/policy.rego +``` + +With permission check bypassed: +```bash +sudo bin/finch-daemon --debug --socket-owner $UID --socket-addr /run/finch-test.sock --pidfile /run/finch-test.pid --experimental --rego-file /path/to/policy.rego --skip-rego-perm-check +``` + +Note: If you enable experimental features with `--experimental` but don't provide a `--rego-file`, the daemon will run without OPA policy evaluation. + + +# Best practices for secure rego policies + +## Comprehensive API Path Protection + +When writing Rego policies, use pattern matching for API paths to prevent unauthorized access. Simple string matching can be bypassed by adding prefixes to API paths. + +Consider this potentially vulnerable policy that tries to restrict access to a specific container: +``` +# INCORRECT: Can be bypassed +allow if { + not (input.Path == "/v1.43/containers/sensitive-container/json") +} +``` +This policy can be bypassed in multiple ways: +1. Using container ID instead of name: `/v1.43/containers/abc123.../json` +2. Adding path prefixes: `/custom/v1.43/containers/sensitive-container/json` + +Follow the path matching best practices below to properly secure your resources. + +## Path Matching Best Practices + +``` +package finch.authz + +import future.keywords.if +import rego.v1 + +# Use pattern matching for comprehensive path protection +is_container_api if { + glob.match("/*/containers/*", [], input.Path) +} + +is_container_create if { + input.Method == "POST" + glob.match("/*/containers/create", [], input.Path) +} + +# Protect against path variations +allow if { + not is_container_api # Blocks all container-related paths + not is_container_create # Specifically blocks container creation +} +``` +Use these [example policies](https://github.com/open-policy-agent/opa-docker-authz/blob/2c7eb5c729fca70a3e5cda6f15c2d9cc121b9481/example.rego) to build your opa policy + +Remember that only `Method` and `Path` is the only values that +gets passed to the opa middleware. + + +### Common Security Pitfalls + +- **Incomplete Path Matching**: Always use pattern matching functions like glob.match() instead of exact string matching to catch path variations. +- **Missing HTTP Methods**: Consider all HTTP methods that could access a resource (GET, POST, PUT, DELETE). +- **Alternative API Endpoints**: Be aware that some operations can be performed through multiple endpoints. + +### Monitoring and Alerting +The finch-daemon's inability to start due to policy issues could impact system operations. Implement System Service Monitoring in order to be on top of any such failures. + +### Security Recommendations +- Policy Testing + - Test policies in a non-production environment + - Use the [rego playground](https://play.openpolicyagent.org/) to test policies +- Logging and Audit + - Enable comprehensive logging of policy decisions + - Monitor for unexpected denials + + +### Critical Security Considerations: Rego Policy File Protection + +### Rego File Permissions +By default, the daemon requires the Rego policy file to have permissions no more permissive than 0600 (readable and writable only by the owner). This restriction helps prevent unauthorized modifications to the policy file. + +The `--skip-rego-perm-check` flag can be used to bypass this permission check. However, using this flag comes with significant security risks: +- More permissive file permissions could allow unauthorized users to modify the policy +- Changes to the policy file could go unnoticed +- Security controls could be weakened without proper oversight + +It is strongly recommended to: +- Avoid using `--skip-rego-perm-check` in production environments +- Always use proper file permissions (0600 or more restrictive) +- Implement additional monitoring if the flag must be used + +The Rego policy file is a critical security control. +Any user with sudo privileges can: + +- Modify the policy file to weaken security controls +- Replace the policy with a more permissive version +- Disable policy enforcement entirely + +#### Recomended Security Controls + +- Access Controls + - Restrict sudo access to specific commands +- Monitoring + - Monitor policy file changes + - Monitor daemon service status diff --git a/docs/sample-rego-policies/example.rego b/docs/sample-rego-policies/example.rego new file mode 100644 index 00000000..37f48f9e --- /dev/null +++ b/docs/sample-rego-policies/example.rego @@ -0,0 +1,43 @@ +# This is an experimental preview policy example. +# As this feature is under active development: +# - Breaking changes may occur without notice +# - Production use is not recommended + +package finch.authz + +import future.keywords.if +import rego.v1 + +default allow = false + +allow if { + not is_container_create + not is_networks_api + not is_swarm_api + not is_plugins +} + +is_container_create if { + input.Method == "POST" + glob.match("/**/containers/create", ["/"], input.Path) +} + +is_networks_api if { + input.Method == "GET" + glob.match("/**/networks", ["/"], input.Path) +} + +is_swarm_api if { + input.Method == "GET" + glob.match("/**/swarm", ["/"], input.Path) +} + +is_plugins if { + input.Method == "GET" + glob.match("/**/plugins", ["/"], input.Path) +} + +is_forbidden_container if { + input.Method == "GET" + glob.match("/**/container/1f576a797a486438548377124f6cb7770a5cb7c8ff6a11c069cb4128d3f59462/json", ["/"], input.Path) +} diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 0d82caec..b3918bed 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -19,23 +19,36 @@ import ( "github.com/runfinch/finch-daemon/e2e/util" ) -// Subject defines which CLI the tests are run against, defaults to \"nerdctl\" in the user's PATH. -var Subject = flag.String("subject", "nerdctl", `which CLI the tests are run against, defaults to "nerdctl" in the user's PATH.`) -var SubjectPrefix = flag.String("daemon-context-subject-prefix", "", `A string which prefixes the command the tests are run against, defaults to "". This string will be split by spaces.`) -var PrefixedSubjectEnv = flag.String("daemon-context-subject-env", "", `Environment to add when running a prefixed subject, in the form of a string like "EXAMPLE=foo EXAMPLE2=bar"`) +const ( + defaultNamespace = "finch" + testE2EEnv = "TEST_E2E" + middlewareE2EEnv = "MIDDLEWARE_E2E" + opaTestDescription = "Finch Daemon OPA E2E Tests" + e2eTestDescription = "Finch Daemon Functional test" +) -func TestRun(t *testing.T) { - if os.Getenv("TEST_E2E") != "1" { - t.Skip("E2E tests skipped. Set TEST_E2E=1 to run these tests") - } +var ( + Subject = flag.String("subject", "nerdctl", `which CLI the tests are run against, defaults to "nerdctl" in the user's PATH.`) + SubjectPrefix = flag.String("daemon-context-subject-prefix", "", `A string which prefixes the command the tests are run against, defaults to "". This string will be split by spaces.`) + PrefixedSubjectEnv = flag.String("daemon-context-subject-env", "", `Environment to add when running a prefixed subject, in the form of a string like "EXAMPLE=foo EXAMPLE2=bar"`) +) - if err := parseTestFlags(); err != nil { - log.Println("failed to parse go test flags", err) - os.Exit(1) +func TestRun(t *testing.T) { + switch { + case os.Getenv(middlewareE2EEnv) == "1": + runOPATests(t) + case os.Getenv(testE2EEnv) == "1": + runE2ETests(t) + default: + t.Skip("E2E tests skipped. Set TEST_E2E=1 to run regular E2E tests or MIDDLEWARE_E2E=1 to run OPA middleware tests") } +} - opt, _ := option.New([]string{*Subject, "--namespace", "finch"}) +func createTestOption() (*option.Option, error) { + return option.New([]string{*Subject, "--namespace", defaultNamespace}) +} +func setupTestSuite(opt *option.Option) { ginkgo.SynchronizedBeforeSuite(func() []byte { tests.SetupLocalRegistry(opt) return nil @@ -43,66 +56,123 @@ func TestRun(t *testing.T) { ginkgo.SynchronizedAfterSuite(func() { tests.CleanupLocalRegistry(opt) - // clean up everything after the local registry is cleaned up command.RemoveAll(opt) }, func() {}) +} - var pOpt = option.New - if *SubjectPrefix != "" { - var modifiers []option.Modifier - if *PrefixedSubjectEnv != "" { - modifiers = append(modifiers, option.Env(strings.Split(*PrefixedSubjectEnv, " "))) - } - pOpt = util.WrappedOption(strings.Split(*SubjectPrefix, " "), modifiers...) +func runOPATests(t *testing.T) { + if err := parseTestFlags(); err != nil { + log.Fatal("failed to parse go test flags:", err) + } + + opt, err := createTestOption() + if err != nil { + log.Fatal("failed to create test option:", err) + } + + setupTestSuite(opt) + + ginkgo.Describe(opaTestDescription, func() { + tests.OpaMiddlewareTest(opt) + }) + + runTests(t, opaTestDescription) +} + +func runE2ETests(t *testing.T) { + if err := parseTestFlags(); err != nil { + log.Fatal("failed to parse go test flags:", err) + } + + opt, err := createTestOption() + if err != nil { + log.Fatal("failed to create test option:", err) } - const description = "Finch Daemon Functional test" - ginkgo.Describe(description, func() { - // functional test for container APIs - tests.ContainerCreate(opt, pOpt) - tests.ContainerStart(opt) - tests.ContainerStop(opt) - tests.ContainerRestart(opt) - tests.ContainerRemove(opt) - tests.ContainerList(opt) - tests.ContainerRename(opt) - tests.ContainerStats(opt) - tests.ContainerAttach(opt) - tests.ContainerLogs(opt) - tests.ContainerKill(opt) - tests.ContainerInspect(opt) - tests.ContainerWait(opt) - tests.ContainerPause(opt) - tests.ContainerUnpause(opt) - - // functional test for volume APIs - tests.VolumeList(opt) - tests.VolumeInspect(opt) - tests.VolumeRemove(opt) - - // functional test for network APIs - tests.NetworkCreate(opt, pOpt) - tests.NetworkRemove(opt) - tests.NetworkList(opt) - tests.NetworkInspect(opt) - - // functional test for image APIs - tests.ImageRemove(opt) - tests.ImagePush(opt) - tests.ImagePull(opt) - - // functional test for system api - tests.SystemVersion(opt) - tests.SystemEvents(opt) - - // functional test for distribution api - tests.DistributionInspect(opt) + setupTestSuite(opt) + + pOpt := createPrefixedOption() + + ginkgo.Describe(e2eTestDescription, func() { + runContainerTests(opt, pOpt) + runVolumeTests(opt) + runNetworkTests(opt, pOpt) + runImageTests(opt) + runSystemTests(opt) + runDistributionTests(opt) }) + runTests(t, e2eTestDescription) +} + +func createPrefixedOption() func([]string, ...option.Modifier) (*option.Option, error) { + if *SubjectPrefix == "" { + return option.New + } + + var modifiers []option.Modifier + if *PrefixedSubjectEnv != "" { + modifiers = append(modifiers, option.Env(strings.Split(*PrefixedSubjectEnv, " "))) + } + return util.WrappedOption(strings.Split(*SubjectPrefix, " "), modifiers...) +} + +func runTests(t *testing.T, description string) { gomega.RegisterFailHandler(ginkgo.Fail) ginkgo.RunSpecs(t, description) } +// functional test for container APIs. +func runContainerTests(opt *option.Option, pOpt func([]string, ...option.Modifier) (*option.Option, error)) { + tests.ContainerCreate(opt, pOpt) + tests.ContainerStart(opt) + tests.ContainerStop(opt) + tests.ContainerRestart(opt) + tests.ContainerRemove(opt) + tests.ContainerList(opt) + tests.ContainerRename(opt) + tests.ContainerStats(opt) + tests.ContainerAttach(opt) + tests.ContainerLogs(opt) + tests.ContainerKill(opt) + tests.ContainerInspect(opt) + tests.ContainerWait(opt) + tests.ContainerPause(opt) +} + +// functional test for volume APIs. +func runVolumeTests(opt *option.Option) { + tests.VolumeList(opt) + tests.VolumeInspect(opt) + tests.VolumeRemove(opt) +} + +// functional test for network APIs. +func runNetworkTests(opt *option.Option, pOpt func([]string, ...option.Modifier) (*option.Option, error)) { + tests.NetworkCreate(opt, pOpt) + tests.NetworkRemove(opt) + tests.NetworkList(opt) + tests.NetworkInspect(opt) +} + +// functional test for image APIs. +func runImageTests(opt *option.Option) { + tests.ImageRemove(opt) + tests.ImagePush(opt) + tests.ImagePull(opt) +} + +// . +func runSystemTests(opt *option.Option) { + tests.SystemVersion(opt) + tests.SystemEvents(opt) +} + +// functional test for distribution api. +func runDistributionTests(opt *option.Option) { + tests.DistributionInspect(opt) +} + // parseTestFlags parses go test flags because pflag package ignores flags with '-test.' prefix // Related issues: // https://github.com/spf13/pflag/issues/63 diff --git a/e2e/tests/opa_middleware.go b/e2e/tests/opa_middleware.go new file mode 100644 index 00000000..24f5001c --- /dev/null +++ b/e2e/tests/opa_middleware.go @@ -0,0 +1,155 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package tests + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "os" + "os/exec" + "path/filepath" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/runfinch/common-tests/command" + "github.com/runfinch/common-tests/option" + + "github.com/runfinch/finch-daemon/api/types" + "github.com/runfinch/finch-daemon/e2e/client" +) + +// OpaMiddlewareTest tests the OPA functionality. +func OpaMiddlewareTest(opt *option.Option) { + Describe("test opa middleware functionality", func() { + var ( + uClient *http.Client + version string + wantContainerName string + containerCreateOptions types.ContainerCreateRequest + createUrl string + unimplementedUnspecifiedUrl string + unimplementedSpecifiedUrl string + ) + BeforeEach(func() { + // create a custom client to use http over unix sockets + uClient = client.NewClient(GetDockerHostUrl()) + // get the docker api version that will be tested + version = GetDockerApiVersion() + wantContainerName = fmt.Sprintf("/%s", testContainerName) + // set default container containerCreateOptions + containerCreateOptions = types.ContainerCreateRequest{} + containerCreateOptions.Image = defaultImage + createUrl = client.ConvertToFinchUrl(version, "/containers/create") + unimplementedUnspecifiedUrl = client.ConvertToFinchUrl(version, "/secrets") + unimplementedSpecifiedUrl = client.ConvertToFinchUrl(version, "/swarm") + }) + AfterEach(func() { + command.RemoveAll(opt) + }) + It("should allow GET version API request", func() { + res, err := uClient.Get(client.ConvertToFinchUrl("", "/version")) + Expect(err).ShouldNot(HaveOccurred()) + jd := json.NewDecoder(res.Body) + var v types.VersionInfo + err = jd.Decode(&v) + Expect(err).ShouldNot(HaveOccurred()) + Expect(v.Version).ShouldNot(BeNil()) + Expect(v.ApiVersion).Should(Equal("1.43")) + fmt.Println(version) + }) + + It("shold allow GET containers API request", func() { + id := command.StdoutStr(opt, "run", "-d", "--name", testContainerName, defaultImage, "sleep", "infinity") + want := []types.ContainerListItem{ + { + Id: id[:12], + Names: []string{wantContainerName}, + }, + } + + res, err := uClient.Get(client.ConvertToFinchUrl(version, "/containers/json")) + Expect(err).Should(BeNil()) + Expect(res.StatusCode).Should(Equal(http.StatusOK)) + var got []types.ContainerListItem + err = json.NewDecoder(res.Body).Decode(&got) + Expect(err).Should(BeNil()) + Expect(len(got)).Should(Equal(2)) + got = filterContainerList(got) + Expect(got).Should(ContainElements(want)) + }) + + It("shold disallow POST containers/create API request", func() { + containerCreateOptions.Cmd = []string{"echo", "hello world"} + + reqBody, err := json.Marshal(containerCreateOptions) + Expect(err).Should(BeNil()) + + fmt.Println("createUrl = ", createUrl) + res, _ := uClient.Post(createUrl, "application/json", bytes.NewReader(reqBody)) + + Expect(res.StatusCode).Should(Equal(http.StatusForbidden)) + }) + + It("should fail unimplemented API calls, fail via daemon", func() { + fmt.Println("incompatibleUrl = ", unimplementedUnspecifiedUrl) + res, _ := uClient.Get(unimplementedUnspecifiedUrl) + + Expect(res.StatusCode).Should(Equal(http.StatusNotFound)) + }) + + It("should fail non implemented API calls,even if specified in the rego file", func() { + fmt.Println("incompatibleUrl = ", unimplementedSpecifiedUrl) + res, _ := uClient.Get(unimplementedSpecifiedUrl) + + Expect(res.StatusCode).Should(Equal(http.StatusNotFound)) + }) + + // Add this test to OpaMiddlewareTest function + It("should handle rego file permissions correctly", func() { + // Create a temporary rego file with overly permissive permissions + tmpDir := GinkgoT().TempDir() + + regoPath := filepath.Join(tmpDir, "test.rego") + regoContent := []byte(`package finch.authz + default allow = false`) + + var err error + err = os.WriteFile(regoPath, regoContent, 0644) + Expect(err).NotTo(HaveOccurred()) + + // Try to start daemon with overly permissive file + cmd := exec.Command(GetFinchDaemonExe(), //nolint:gosec // G204: This is a test file with controlled inputs + "--socket-addr", "/run/test.sock", + "--pidfile", "/run/test.pid", + "--rego-file", regoPath, + "--experimental") + err = cmd.Run() + + // Should fail due to permissions + Expect(err).To(HaveOccurred()) + + // For the second test with skip-check: + cmd = exec.Command(GetFinchDaemonExe(), //nolint:gosec // G204: This is a test file with controlled inputs + "--socket-addr", "/run/test.sock", + "--pidfile", "/run/test.pid", + "--rego-file", regoPath, + "--experimental", + "--skip-rego-perm-check") + + // Start the process in background + err = cmd.Start() + Expect(err).NotTo(HaveOccurred()) + + // Give it a moment to initialize + time.Sleep(1 * time.Second) + + // Kill the process + err = cmd.Process.Kill() + Expect(err).NotTo(HaveOccurred()) + }) + }) +} diff --git a/e2e/tests/tests.go b/e2e/tests/tests.go index 932827a0..c26f0d67 100644 --- a/e2e/tests/tests.go +++ b/e2e/tests/tests.go @@ -226,3 +226,11 @@ func GetFinchExe() string { } return finchexe } + +func GetFinchDaemonExe() string { + daemonPath := os.Getenv("DAEMON_ROOT") + if daemonPath == "" { + daemonPath = "./bin/finch-daemon" // fallback + } + return daemonPath +} diff --git a/go.mod b/go.mod index 8c19f2e9..70108905 100644 --- a/go.mod +++ b/go.mod @@ -48,6 +48,29 @@ require ( google.golang.org/protobuf v1.36.6 ) +require ( + github.com/OneOfOne/xxhash v1.2.8 // indirect + github.com/agnivade/levenshtein v1.2.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect + github.com/tchap/go-patricia/v2 v2.3.2 // 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.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/sdk v1.35.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) + require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect @@ -115,6 +138,7 @@ require ( github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multihash v0.2.3 // indirect github.com/multiformats/go-varint v0.0.7 // indirect + github.com/open-policy-agent/opa v1.1.0 github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626 // indirect github.com/opencontainers/selinux v1.12.0 // indirect github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect @@ -132,7 +156,6 @@ require ( github.com/vbatts/tar-split v0.11.6 // indirect github.com/yuchanns/srslog v1.1.0 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.opentelemetry.io/otel v1.35.0 // indirect go.opentelemetry.io/otel/metric v1.35.0 // indirect @@ -144,13 +167,12 @@ require ( golang.org/x/sync v0.14.0 // indirect golang.org/x/term v0.32.0 // indirect golang.org/x/text v0.25.0 // indirect - golang.org/x/time v0.8.0 // indirect + golang.org/x/time v0.9.0 // indirect golang.org/x/tools v0.31.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect google.golang.org/grpc v1.72.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect tags.cncf.io/container-device-interface v1.0.1 // indirect tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect ) diff --git a/go.sum b/go.sum index 1c38955f..61e6d23c 100644 --- a/go.sum +++ b/go.sum @@ -12,9 +12,23 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= +github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= +github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= +github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +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/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.16.0 h1:+BiEnHL6Z7lXnlGUsXQPPAE7+kenAd4ES8MQ5min0Ok= github.com/cilium/ebpf v0.16.0/go.mod h1:L7u2Blt2jMM/vLAVgjxluxtBKlz3/GWjB0dMOEngfwE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -84,6 +98,12 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v4 v4.5.1 h1:7DCIXrQjo1LKmM96YD+hLVJ2EEsyyoWxJfpdd56HLps= +github.com/dgraph-io/badger/v4 v4.5.1/go.mod h1:qn3Be0j3TfV4kPbVoK0arXCD1/nr1ftth6sbL5jxdoA= +github.com/dgraph-io/ristretto/v2 v2.1.0 h1:59LjpOJLNDULHh8MC4UaegN52lC4JnO2dITsie/Pa8I= +github.com/dgraph-io/ristretto/v2 v2.1.0/go.mod h1:uejeqfYXpUomfse0+lO+13ATz4TypQYLJZzBSAemuB4= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= +github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c= @@ -98,6 +118,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 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.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 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= @@ -108,12 +130,18 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluent/fluent-logger-golang v1.10.0 h1:JcLj8u3WclQv2juHGKTSzBRM5vIZjEqbrmvn/n+m1W0= github.com/fluent/fluent-logger-golang v1.10.0/go.mod h1:UNyv8FAGmQcYJRtk+yfxhWqWUwsabTipgjXvBDR8kTs= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI= +github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/getlantern/httptest v0.0.0-20161025015934-4b40f4c7e590 h1:OhyiFx+yBN30O3IHrIq+9LAEhy6o7fin21wUQxF8NiE= github.com/getlantern/httptest v0.0.0-20161025015934-4b40f4c7e590/go.mod h1:rE/jidqqHHG9sjSxC24Gd5YCfZ1AT91C2wjJ28TAOfA= github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848 h1:2MhMMVBTnaHrst6HyWFDhwQCaJ05PZuOv1bE2gN8WFY= github.com/getlantern/mockconn v0.0.0-20200818071412-cb30d065a848/go.mod h1:+F5GJ7qGpQ03DBtcOEyQpM30ix4BLswdaojecFtsdy8= +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-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -127,6 +155,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +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.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -150,6 +180,8 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/flatbuffers v24.12.23+incompatible h1:ubBKR94NR4pXUCY/MUsRVzd9umNW7ht7EG9hHfS9FX8= +github.com/google/flatbuffers v24.12.23+incompatible/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= @@ -170,6 +202,8 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= 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= @@ -199,6 +233,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= @@ -253,10 +289,14 @@ github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7B github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/open-policy-agent/opa v1.1.0 h1:HMz2evdEMTyNqtdLjmu3Vyx06BmhNYAx67Yz3Ll9q2s= +github.com/open-policy-agent/opa v1.1.0/go.mod h1:T1pASQ1/vwfTa+e2fYcfpLCvWgYtqtiUv+IuA/dLPQs= 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.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -282,9 +322,17 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rootless-containers/bypass4netns v0.4.2 h1:JUZcpX7VLRfDkLxBPC6fyNalJGv9MjnjECOilZIvKRc= @@ -326,6 +374,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= +github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww= github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/urfave/cli v1.19.1/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= @@ -344,6 +394,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +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/yuchanns/srslog v1.1.0 h1:CEm97Xxxd8XpJThE0gc/XsqUGgPufh5u5MUjC27/KOk= github.com/yuchanns/srslog v1.1.0/go.mod h1:HsLjdv3XV02C3kgBW2bTyW6i88OQE+VYJZIxrPKPPak= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -357,6 +409,10 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRND go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= @@ -365,10 +421,12 @@ go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5J go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -474,8 +532,8 @@ golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -499,6 +557,9 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=