From dc133cbe3d8fa1b47c7198361aaef1444a0338a3 Mon Sep 17 00:00:00 2001 From: Moritz Sanft <58110325+msanft@users.noreply.github.com> Date: Tue, 12 May 2026 16:18:07 +0200 Subject: [PATCH 1/3] nodeinstaller/kataconfig: allow insecure attestation via kernel cmdline --- nodeinstaller/internal/kataconfig/config.go | 5 +++++ .../runtime-go/expected-configuration-qemu-insecure-gpu.toml | 2 +- .../runtime-go/expected-configuration-qemu-insecure.toml | 2 +- .../runtime-rs/expected-configuration-qemu-insecure-gpu.toml | 2 +- .../runtime-rs/expected-configuration-qemu-insecure.toml | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/nodeinstaller/internal/kataconfig/config.go b/nodeinstaller/internal/kataconfig/config.go index 80c66ed37f..ae6ca5cd33 100644 --- a/nodeinstaller/internal/kataconfig/config.go +++ b/nodeinstaller/internal/kataconfig/config.go @@ -6,6 +6,7 @@ package kataconfig import ( "fmt" "path/filepath" + "strings" "github.com/edgelesssys/contrast/internal/platforms" "github.com/pelletier/go-toml/v2" @@ -73,6 +74,10 @@ func KataRuntimeConfig( // TODO(katexochen): Remove after https://github.com/kata-containers/kata-containers/pull/12472 is merged. config.Hypervisor["qemu"]["disable_image_nvdimm"] = true + if platforms.IsInsecure(platform) { + qemuExtraKernelParams = strings.TrimSpace(qemuExtraKernelParams + " contrast.allow_insecure_attestation=1") + } + // Replace the kernel params entirely (and don't append) since that's // also what we do when calculating the launch measurement. config.Hypervisor["qemu"]["kernel_params"] = qemuExtraKernelParams diff --git a/nodeinstaller/internal/kataconfig/testdata/runtime-go/expected-configuration-qemu-insecure-gpu.toml b/nodeinstaller/internal/kataconfig/testdata/runtime-go/expected-configuration-qemu-insecure-gpu.toml index b098f474cd..f5e6e94c12 100644 --- a/nodeinstaller/internal/kataconfig/testdata/runtime-go/expected-configuration-qemu-insecure-gpu.toml +++ b/nodeinstaller/internal/kataconfig/testdata/runtime-go/expected-configuration-qemu-insecure-gpu.toml @@ -44,7 +44,7 @@ image = '/share/kata-containers.img' indep_iothreads = 0 initrd = '/share/kata-initrd.zst' kernel = '/share/kata-kernel' -kernel_params = '' +kernel_params = 'contrast.allow_insecure_attestation=1' kernel_verity_params = '' machine_accelerators = '' machine_type = 'q35' diff --git a/nodeinstaller/internal/kataconfig/testdata/runtime-go/expected-configuration-qemu-insecure.toml b/nodeinstaller/internal/kataconfig/testdata/runtime-go/expected-configuration-qemu-insecure.toml index 54373ea971..4e2bfc1cc6 100644 --- a/nodeinstaller/internal/kataconfig/testdata/runtime-go/expected-configuration-qemu-insecure.toml +++ b/nodeinstaller/internal/kataconfig/testdata/runtime-go/expected-configuration-qemu-insecure.toml @@ -43,7 +43,7 @@ image = '/share/kata-containers.img' indep_iothreads = 0 initrd = '/share/kata-initrd.zst' kernel = '/share/kata-kernel' -kernel_params = '' +kernel_params = 'contrast.allow_insecure_attestation=1' kernel_verity_params = '' machine_accelerators = '' machine_type = 'q35' diff --git a/nodeinstaller/internal/kataconfig/testdata/runtime-rs/expected-configuration-qemu-insecure-gpu.toml b/nodeinstaller/internal/kataconfig/testdata/runtime-rs/expected-configuration-qemu-insecure-gpu.toml index 3e32921ff8..aa74880d6d 100644 --- a/nodeinstaller/internal/kataconfig/testdata/runtime-rs/expected-configuration-qemu-insecure-gpu.toml +++ b/nodeinstaller/internal/kataconfig/testdata/runtime-rs/expected-configuration-qemu-insecure-gpu.toml @@ -41,7 +41,7 @@ guest_memory_dump_path = '' image = '/share/kata-containers.img' initrd = '/share/kata-initrd.zst' kernel = '/share/kata-kernel' -kernel_params = '' +kernel_params = 'contrast.allow_insecure_attestation=1' kernel_verity_params = '' machine_accelerators = '' machine_type = 'q35' diff --git a/nodeinstaller/internal/kataconfig/testdata/runtime-rs/expected-configuration-qemu-insecure.toml b/nodeinstaller/internal/kataconfig/testdata/runtime-rs/expected-configuration-qemu-insecure.toml index 57b1749288..07ca8d7b90 100644 --- a/nodeinstaller/internal/kataconfig/testdata/runtime-rs/expected-configuration-qemu-insecure.toml +++ b/nodeinstaller/internal/kataconfig/testdata/runtime-rs/expected-configuration-qemu-insecure.toml @@ -41,7 +41,7 @@ guest_memory_dump_path = '' image = '/share/kata-containers.img' initrd = '/share/kata-initrd.zst' kernel = '/share/kata-kernel' -kernel_params = '' +kernel_params = 'contrast.allow_insecure_attestation=1' kernel_verity_params = '' machine_accelerators = '' machine_type = 'q35' From 71f0d317ea42b1313b843533803b1b1433d13402 Mon Sep 17 00:00:00 2001 From: Moritz Sanft <58110325+msanft@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:56:06 +0200 Subject: [PATCH 2/3] initdata-processor: support insecure platforms --- initdata-processor/main.go | 139 +++++++++++++++--- initdata-processor/main_test.go | 47 ++++++ initdata-processor/validator/validator.go | 6 +- .../by-name/initdata-processor/package.nix | 2 + packages/nixos/kata.nix | 7 +- 5 files changed, 178 insertions(+), 23 deletions(-) create mode 100644 initdata-processor/main_test.go diff --git a/initdata-processor/main.go b/initdata-processor/main.go index 7ffeafb44c..3659ad1b41 100644 --- a/initdata-processor/main.go +++ b/initdata-processor/main.go @@ -5,15 +5,25 @@ package main import ( "bytes" + "context" + "errors" "fmt" "io" "io/fs" "log" + "net" + "net/http" "os" + "os/signal" "path/filepath" + "slices" + "strings" + "syscall" + "time" "github.com/edgelesssys/contrast/initdata-processor/policy" "github.com/edgelesssys/contrast/initdata-processor/validator" + "github.com/edgelesssys/contrast/internal/attestation/insecure" "github.com/edgelesssys/contrast/internal/initdata" ) @@ -22,7 +32,10 @@ const ( insecureConfigPath = "/run/insecure-cfg" ) -var version = "0.0.0-dev" +var ( + version = "0.0.0-dev" + kernelCmdlinePath = "/proc/cmdline" +) // We always exit with status code 0 so that the Kata agent can start and propagate errors to // the runtime. @@ -30,6 +43,9 @@ func main() { log.Printf("Contrast initdata-processor %s", version) log.Print("Report issues at https://github.com/edgelesssys/contrast/issues") + var hostdata []byte + var insecurePlatform bool + // Handle initdata. if err := os.MkdirAll(measuredConfigPath, 0o755); err != nil { failf("Could not create directory %q: %v", measuredConfigPath, err) @@ -45,7 +61,8 @@ func main() { failf("%s is not an initdata device: %v", device, err) return } - if err := handleInitdata(doc); err != nil { + hostdata, insecurePlatform, err = handleInitdata(doc) + if err != nil { failf("handling initdata: %v", err) return } @@ -59,46 +76,128 @@ func main() { device, err = checkDeviceAvailability("imagepuller") if err != nil { log.Println("No imagepuller auth config found, only unauthenticated pulls will be available") - return - } - doc, err = initdata.FromDevice(device, "imgpullr") - if err != nil { - failf("%s is not an imagepuller config device: %v", device, err) - return + } else { + doc, err = initdata.FromDevice(device, "imgpullr") + if err != nil { + failf("%s is not an imagepuller config device: %v", device, err) + return + } + if err := handleImagepullerAuthConfig(doc); err != nil { + failf("handling imagepuller auth config: %v", err) + return + } + log.Printf("Processed imagepuller auth config from %q ", device) } - if err := handleImagepullerAuthConfig(doc); err != nil { - failf("handling imagepuller auth config: %v", err) - return + + // Signal systemd that initdata processing is complete. + sdNotifyReady() + + // On insecure platforms, serve the hostdata digest via HTTP so that + // the insecure aTLS issuer (running inside containers) can fetch it. + if insecurePlatform { + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + log.Printf("Starting insecure hostdata server on %s", insecure.HostdataAddr) + if err := serveHostdata(ctx, hostdata); err != nil { + log.Printf("Hostdata server error: %v", err) + } } - log.Printf("Processed imagepuller auth config from %q ", device) } -func handleInitdata(doc initdata.Raw) error { +func handleInitdata(doc initdata.Raw) (hostdata []byte, insecurePlatform bool, retErr error) { digest, err := doc.Digest() if err != nil { - return fmt.Errorf("initdata validation failed: %w", err) + return nil, false, fmt.Errorf("computing initdata digest: %w", err) } - validator, err := validator.New() + + allowInsecure, err := allowInsecureAttestation() if err != nil { - return fmt.Errorf("creating validator: %w", err) + return nil, false, err } - if err := validator.ValidateDigest(digest); err != nil { - return fmt.Errorf("validating initdata digest: %w", err) + + v, verr := validator.New() + if errors.Is(verr, validator.ErrNoPlatform) { + if !allowInsecure { + return nil, false, fmt.Errorf("no TEE platform detected and insecure attestation is not allowed") + } + log.Print("WARNING: No TEE platform detected, skipping initdata digest validation. This is expected on insecure platforms.") + } else if verr != nil { + return nil, false, fmt.Errorf("creating validator: %w", verr) + } else if err := v.ValidateDigest(digest); err != nil { + return nil, false, fmt.Errorf("validating initdata digest: %w", err) } + data, err := doc.Parse() if err != nil { - return fmt.Errorf("parsing initdata: %w", err) + return nil, false, fmt.Errorf("parsing initdata: %w", err) } for name, content := range data.Data { name = filepath.Clean(name) path := filepath.Join(measuredConfigPath, name) if err := os.WriteFile(path, []byte(content), 0o644); err != nil { - return fmt.Errorf("writing file %q: %w", path, err) + return nil, false, fmt.Errorf("writing file %q: %w", path, err) } } + return digest, allowInsecure, nil +} + +func allowInsecureAttestation() (bool, error) { + cmdline, err := os.ReadFile(kernelCmdlinePath) + if err != nil { + return false, fmt.Errorf("reading kernel command line: %w", err) + } + if slices.Contains(strings.Fields(string(cmdline)), "contrast.allow_insecure_attestation=1") { + return true, nil + } + return false, nil +} + +// serveHostdata starts an HTTP server that serves the hostdata digest. +func serveHostdata(ctx context.Context, hostdata []byte) error { + mux := http.NewServeMux() + mux.HandleFunc("GET /hostdata", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/octet-stream") + if _, err := w.Write(hostdata); err != nil { + log.Printf("hostdata write error: %v", err) + } + }) + server := &http.Server{ + Addr: insecure.HostdataAddr, + Handler: mux, + ReadHeaderTimeout: 5 * time.Second, + } + go func() { + <-ctx.Done() + shutdownCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second) + defer cancel() + if err := server.Shutdown(shutdownCtx); err != nil { + log.Printf("hostdata server shutdown error: %v", err) + } + }() + if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } return nil } +// sdNotifyReady signals systemd that the service is ready. +func sdNotifyReady() { + addr := os.Getenv("NOTIFY_SOCKET") + if addr == "" { + return + } + conn, err := (&net.Dialer{}).DialContext(context.Background(), "unixgram", addr) + if err != nil { + log.Printf("sd_notify: dial: %v", err) + return + } + defer conn.Close() + if _, err := conn.Write([]byte("READY=1")); err != nil { + log.Printf("sd_notify: write: %v", err) + } +} + func handleImagepullerAuthConfig(doc initdata.Raw) error { configLocation := "/run/insecure-cfg/imagepuller.toml" return os.WriteFile(configLocation, doc, 0o644) diff --git a/initdata-processor/main_test.go b/initdata-processor/main_test.go new file mode 100644 index 0000000000..e5945f427d --- /dev/null +++ b/initdata-processor/main_test.go @@ -0,0 +1,47 @@ +// Copyright 2026 Edgeless Systems GmbH +// SPDX-License-Identifier: BUSL-1.1 + +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAllowInsecureAttestation(t *testing.T) { + oldKernelCmdlinePath := kernelCmdlinePath + t.Cleanup(func() { kernelCmdlinePath = oldKernelCmdlinePath }) + + testCases := map[string]struct { + cmdline string + want bool + }{ + "absent": { + cmdline: "console=hvc0", + want: false, + }, + "disabled": { + cmdline: "console=hvc0 contrast.allow_insecure_attestation=0", + want: false, + }, + "enabled": { + cmdline: "console=hvc0 contrast.allow_insecure_attestation=1 quiet", + want: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + kernelCmdlinePath = filepath.Join(t.TempDir(), "cmdline") + require.NoError(t, os.WriteFile(kernelCmdlinePath, []byte(tc.cmdline), 0o644)) + + got, err := allowInsecureAttestation() + require.NoError(t, err) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/initdata-processor/validator/validator.go b/initdata-processor/validator/validator.go index c78ec25c4a..b09c46d5bb 100644 --- a/initdata-processor/validator/validator.go +++ b/initdata-processor/validator/validator.go @@ -29,7 +29,7 @@ func New() (*Validator, error) { if terr == nil { return &Validator{&tdxDigestGetter{tqp}}, nil } - return nil, fmt.Errorf("%w:\nTDX:%w\nSNP:%w", errBadPlatform, terr, serr) + return nil, fmt.Errorf("%w:\nTDX:%w\nSNP:%w", ErrNoPlatform, terr, serr) } // ValidateDigest compares the given digest with either MRCONFIGID or HOSTDATA, and returns an error if they don't match. @@ -85,7 +85,9 @@ func getTDXQuoteProvider() (tdxclient.QuoteProvider, error) { } var ( - errBadPlatform = errors.New("no digest getter available for current platform") + // ErrNoPlatform is returned by New when no TEE platform is available for digest validation. + ErrNoPlatform = errors.New("no digest getter available for current platform") + errUnexpectedDigestSize = errors.New("unexpected digest size") errDigestMismatch = errors.New("digests don't match") ) diff --git a/packages/by-name/initdata-processor/package.nix b/packages/by-name/initdata-processor/package.nix index 4e16b56c8e..49389c0fae 100644 --- a/packages/by-name/initdata-processor/package.nix +++ b/packages/by-name/initdata-processor/package.nix @@ -48,6 +48,8 @@ buildGoModule (finalAttrs: { (path.append root "go.sum") (path.append root "initdata-processor/go.mod") (path.append root "initdata-processor/go.sum") + (fileset.fileFilter (file: hasSuffix ".go" file.name) (path.append root "internal/attestation")) + (fileset.fileFilter (file: hasSuffix ".go" file.name) (path.append root "internal/oid")) (fileset.fileFilter (file: hasSuffix ".go" file.name) (path.append root "internal/initdata")) (fileset.fileFilter (file: hasSuffix ".go" file.name) (path.append root "initdata-processor")) ]; diff --git a/packages/nixos/kata.nix b/packages/nixos/kata.nix index 98cde03b88..3a85eeab50 100644 --- a/packages/nixos/kata.nix +++ b/packages/nixos/kata.nix @@ -57,7 +57,12 @@ in wants = [ "initdata.target" ]; serviceConfig = { - Type = "oneshot"; + # notify: the process signals READY=1 when initdata processing is + # complete, allowing initdata.target to be reached. On insecure + # platforms the process stays alive to serve hostdata via HTTP; + # on CC platforms it exits after signaling. + Type = "notify"; + NotifyAccess = "main"; RemainAfterExit = "yes"; ExecStart = lib.getExe pkgs.contrastPkgs.initdata-processor; StandardOutput = "journal+console"; From f13a5d25addf81f00fa4e491dd52f2db22eb5366 Mon Sep 17 00:00:00 2001 From: Moritz Sanft <58110325+msanft@users.noreply.github.com> Date: Tue, 26 May 2026 09:37:55 +0200 Subject: [PATCH 3/3] deps: update golang.org/x/net --- packages/by-name/fifo/package.nix | 2 +- tools/fifo/go.mod | 9 ++++----- tools/fifo/go.sum | 28 ++++++++++++++-------------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/packages/by-name/fifo/package.nix b/packages/by-name/fifo/package.nix index 4144f4408a..23e4e3df86 100644 --- a/packages/by-name/fifo/package.nix +++ b/packages/by-name/fifo/package.nix @@ -10,7 +10,7 @@ buildGoModule { src = ../../../tools/fifo; proxyVendor = true; - vendorHash = "sha256-R4ObM2ZSD/lFQQNY1fKuRZ8zoNy2j8t6RJnJOf4v/7E="; + vendorHash = "sha256-TBXaMzHkH76g2zOwAzEsVxo5u+1QyNJBnsQEivsgYRg="; env.CGO_ENABLED = 0; diff --git a/tools/fifo/go.mod b/tools/fifo/go.mod index 503c285f91..00db3fe9ea 100644 --- a/tools/fifo/go.mod +++ b/tools/fifo/go.mod @@ -44,13 +44,12 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.51.0 // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/term v0.40.0 // indirect - golang.org/x/text v0.34.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.42.0 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/tools/fifo/go.sum b/tools/fifo/go.sum index 4fad71ceb6..38bfdc4135 100644 --- a/tools/fifo/go.sum +++ b/tools/fifo/go.sum @@ -102,24 +102,24 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= -golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= -golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=