From cdc7776861a39a53dafe3b299fce3d5c4e72a023 Mon Sep 17 00:00:00 2001 From: Mateusz Morusiewicz <11313015+Ruteri@users.noreply.github.com> Date: Thu, 5 Jun 2025 14:32:19 +0200 Subject: [PATCH 1/3] Upgrades internal from constellation to 5b0073ffe87a3bfb5592e1686ac4a16d3ba553ca --- go.mod | 3 +- go.sum | 4 + .../api/attestationconfigapi/fetcher_test.go | 4 +- internal/api/attestationconfigapi/version.go | 4 +- internal/api/versionsapi/cli/add.go | 96 +- internal/api/versionsapi/fetcher_test.go | 4 +- internal/api/versionsapi/version.go | 9 +- internal/api/versionsapi/version_test.go | 125 +++ internal/atls/atls.go | 2 + internal/atls/atls_test.go | 8 +- internal/attestation/attestation.go | 4 - .../attestation/aws/nitrotpm/issuer_test.go | 5 +- .../aws/nitrotpm/validator_test.go | 5 +- internal/attestation/aws/snp/issuer_test.go | 1 - .../attestation/aws/snp/testdata/report.txt | 2 +- .../attestation/aws/snp/testdata/testdata.go | 2 +- .../attestation/aws/snp/testdata/vlek.pem | 56 +- .../attestation/aws/snp/validator_test.go | 4 +- internal/attestation/azure/snp/issuer_test.go | 3 +- internal/attestation/azure/snp/validator.go | 10 +- .../attestation/azure/snp/validator_test.go | 5 +- internal/attestation/azure/tdx/issuer.go | 1 - internal/attestation/azure/tdx/issuer_test.go | 4 +- .../azure/trustedlaunch/trustedlaunch_test.go | 6 +- internal/attestation/gcp/es/issuer_test.go | 3 +- internal/attestation/gcp/es/validator_test.go | 5 +- .../measurements/fetchmeasurements_test.go | 3 +- .../measurement-generator/generate.go | 1 - .../attestation/measurements/measurements.go | 9 +- .../measurements/measurements_enterprise.go | 16 +- .../measurements/measurements_test.go | 3 +- .../attestation/measurements/overrides.go | 2 +- internal/attestation/tdx/validator.go | 3 +- internal/attestation/variant/variant.go | 26 +- internal/attestation/vtpm/attestation.go | 1 - internal/attestation/vtpm/attestation_test.go | 9 +- internal/cloud/aws/aws_test.go | 9 +- internal/cloud/azure/BUILD.bazel | 2 - internal/cloud/azure/azure_test.go | 21 +- internal/cloud/azure/imds_test.go | 3 +- internal/cloud/azure/iptables_linux.go | 66 -- internal/cloud/gcp/gcp_test.go | 15 +- internal/cloud/openstack/imds.go | 15 + internal/cloud/openstack/imds_test.go | 7 +- internal/cloud/openstack/openstack.go | 6 +- internal/cloud/openstack/openstack_test.go | 12 +- internal/config/config.go | 4 + internal/config/config_doc.go | 21 +- internal/config/config_test.go | 65 +- internal/config/image_enterprise.go | 2 +- internal/config/validation.go | 15 +- internal/config/validation_test.go | 1 - internal/constants/constants.go | 4 + internal/crypto/BUILD.bazel | 5 +- internal/crypto/crypto.go | 15 + internal/crypto/crypto_test.go | 43 +- internal/cryptsetup/BUILD.bazel | 26 - internal/cryptsetup/cryptsetup.go | 358 ------- internal/cryptsetup/cryptsetup_cgo.go | 82 -- internal/cryptsetup/cryptsetup_cross.go | 39 - internal/file/file.go | 4 +- internal/grpc/atlscredentials/BUILD.bazel | 28 - .../grpc/atlscredentials/atlscredentials.go | 68 -- .../atlscredentials/atlscredentials_test.go | 128 --- internal/grpc/dialer/BUILD.bazel | 32 - internal/grpc/dialer/dialer.go | 79 -- internal/grpc/dialer/dialer_test.go | 115 --- internal/grpc/grpclog/BUILD.bazel | 24 - internal/grpc/grpclog/grpclog.go | 54 - internal/grpc/grpclog/grpclog_test.go | 114 --- internal/grpc/retry/BUILD.bazel | 24 - internal/grpc/retry/retry.go | 92 -- internal/grpc/retry/retry_test.go | 110 -- internal/grpc/testdialer/BUILD.bazel | 9 - internal/grpc/testdialer/testdialer.go | 48 - internal/imagefetcher/BUILD.bazel | 41 - internal/imagefetcher/imagefetcher.go | 210 ---- internal/imagefetcher/imagefetcher_test.go | 304 ------ internal/imagefetcher/raw.go | 143 --- internal/imagefetcher/raw_test.go | 204 ---- internal/installer/BUILD.bazel | 30 - internal/installer/installer.go | 351 ------- internal/installer/installer_test.go | 774 -------------- internal/kms/README.md | 104 -- internal/kms/config/BUILD.bazel | 8 - internal/kms/config/config.go | 32 - internal/kms/kms/BUILD.bazel | 8 - internal/kms/kms/aws/BUILD.bazel | 15 - internal/kms/kms/aws/aws.go | 58 -- internal/kms/kms/azure/BUILD.bazel | 14 - internal/kms/kms/azure/azure.go | 60 -- internal/kms/kms/cluster/BUILD.bazel | 22 - internal/kms/kms/cluster/cluster.go | 52 - internal/kms/kms/cluster/cluster_test.go | 114 --- internal/kms/kms/gcp/BUILD.bazel | 14 - internal/kms/kms/gcp/gcp.go | 71 -- internal/kms/kms/internal/BUILD.bazel | 32 - internal/kms/kms/internal/internal.go | 83 -- internal/kms/kms/internal/internal_test.go | 148 --- internal/kms/kms/kms.go | 29 - internal/kms/setup/BUILD.bazel | 31 - internal/kms/setup/setup.go | 133 --- internal/kms/setup/setup_test.go | 38 - internal/kms/storage/BUILD.bazel | 8 - internal/kms/storage/awss3/BUILD.bazel | 30 - internal/kms/storage/awss3/awss3.go | 123 --- internal/kms/storage/awss3/awss3_test.go | 176 ---- internal/kms/storage/azureblob/BUILD.bazel | 35 - internal/kms/storage/azureblob/azureblob.go | 105 -- .../kms/storage/azureblob/azureblob_test.go | 172 ---- internal/kms/storage/gcs/BUILD.bazel | 26 - internal/kms/storage/gcs/gcs.go | 132 --- internal/kms/storage/gcs/gcs_test.go | 226 ---- internal/kms/storage/memfs/BUILD.bazel | 21 - internal/kms/storage/memfs/memfs.go | 44 - internal/kms/storage/memfs/memfs_test.go | 54 - internal/kms/storage/storage.go | 19 - internal/kms/test/BUILD.bazel | 29 - internal/kms/test/aws_test.go | 122 --- internal/kms/test/azure_test.go | 107 -- internal/kms/test/gcp_test.go | 75 -- internal/kms/test/integration_test.go | 116 --- internal/kms/uri/BUILD.bazel | 20 - internal/kms/uri/uri.go | 529 ---------- internal/kms/uri/uri_test.go | 113 -- internal/kubernetes/BUILD.bazel | 43 - internal/kubernetes/configmaps.go | 63 -- internal/kubernetes/configmaps_test.go | 55 - internal/kubernetes/kubectl/BUILD.bazel | 34 - internal/kubernetes/kubectl/kubectl.go | 256 ----- internal/kubernetes/kubectl/kubectl_test.go | 46 - internal/kubernetes/kubernetes.go | 12 - internal/kubernetes/marshal.go | 155 --- internal/kubernetes/marshal_test.go | 366 ------- internal/kubernetes/secrets.go | 24 - internal/kubernetes/secrets_test.go | 55 - internal/license/BUILD.bazel | 34 - internal/license/checker_enterprise.go | 98 -- internal/license/checker_enterprise_test.go | 97 -- internal/license/checker_oss.go | 28 - internal/license/file.go | 27 - internal/license/file_test.go | 62 -- internal/license/integration/BUILD.bazel | 16 - .../integration/license_integration_test.go | 53 - internal/license/license.go | 24 - internal/mpimage/BUILD.bazel | 24 - internal/mpimage/mpimage.go | 8 - internal/mpimage/uri.go | 80 -- internal/mpimage/uri_test.go | 83 -- internal/nodestate/BUILD.bazel | 27 - internal/nodestate/nodestate.go | 38 - internal/nodestate/nodestate_test.go | 112 -- internal/osimage/BUILD.bazel | 12 - internal/osimage/archive/BUILD.bazel | 16 - internal/osimage/archive/archive.go | 93 -- internal/osimage/imageinfo/BUILD.bazel | 16 - internal/osimage/imageinfo/imageinfo.go | 101 -- .../osimage/measurementsuploader/BUILD.bazel | 17 - .../measurementsuploader.go | 121 --- internal/osimage/nop/BUILD.bazel | 12 - internal/osimage/nop/nop.go | 33 - internal/osimage/osimage.go | 26 - internal/osimage/secureboot/BUILD.bazel | 25 - internal/osimage/secureboot/secureboot.go | 285 ------ .../osimage/secureboot/secureboot_test.go | 182 ---- internal/osimage/secureboot/zlibdict.go | 961 ------------------ internal/osimage/uplosi/BUILD.bazel | 15 - internal/osimage/uplosi/uplosi.conf.in | 26 - internal/osimage/uplosi/uplosiupload.go | 287 ------ internal/retry/BUILD.bazel | 21 - internal/retry/retry.go | 69 -- internal/retry/retry_test.go | 113 -- internal/sigstore/rekor_integration_test.go | 5 +- internal/staticupload/staticupload.go | 3 +- internal/staticupload/staticupload_test.go | 19 +- internal/versions/components/components.pb.go | 76 +- internal/versions/components/components.proto | 2 +- internal/versions/versions.go | 151 ++- 178 files changed, 570 insertions(+), 11655 deletions(-) delete mode 100644 internal/cryptsetup/BUILD.bazel delete mode 100644 internal/cryptsetup/cryptsetup.go delete mode 100644 internal/cryptsetup/cryptsetup_cgo.go delete mode 100644 internal/cryptsetup/cryptsetup_cross.go delete mode 100644 internal/grpc/atlscredentials/BUILD.bazel delete mode 100644 internal/grpc/atlscredentials/atlscredentials.go delete mode 100644 internal/grpc/atlscredentials/atlscredentials_test.go delete mode 100644 internal/grpc/dialer/BUILD.bazel delete mode 100644 internal/grpc/dialer/dialer.go delete mode 100644 internal/grpc/dialer/dialer_test.go delete mode 100644 internal/grpc/grpclog/BUILD.bazel delete mode 100644 internal/grpc/grpclog/grpclog.go delete mode 100644 internal/grpc/grpclog/grpclog_test.go delete mode 100644 internal/grpc/retry/BUILD.bazel delete mode 100644 internal/grpc/retry/retry.go delete mode 100644 internal/grpc/retry/retry_test.go delete mode 100644 internal/grpc/testdialer/BUILD.bazel delete mode 100644 internal/grpc/testdialer/testdialer.go delete mode 100644 internal/imagefetcher/BUILD.bazel delete mode 100644 internal/imagefetcher/imagefetcher.go delete mode 100644 internal/imagefetcher/imagefetcher_test.go delete mode 100644 internal/imagefetcher/raw.go delete mode 100644 internal/imagefetcher/raw_test.go delete mode 100644 internal/installer/BUILD.bazel delete mode 100644 internal/installer/installer.go delete mode 100644 internal/installer/installer_test.go delete mode 100644 internal/kms/README.md delete mode 100644 internal/kms/config/BUILD.bazel delete mode 100644 internal/kms/config/config.go delete mode 100644 internal/kms/kms/BUILD.bazel delete mode 100644 internal/kms/kms/aws/BUILD.bazel delete mode 100644 internal/kms/kms/aws/aws.go delete mode 100644 internal/kms/kms/azure/BUILD.bazel delete mode 100644 internal/kms/kms/azure/azure.go delete mode 100644 internal/kms/kms/cluster/BUILD.bazel delete mode 100644 internal/kms/kms/cluster/cluster.go delete mode 100644 internal/kms/kms/cluster/cluster_test.go delete mode 100644 internal/kms/kms/gcp/BUILD.bazel delete mode 100644 internal/kms/kms/gcp/gcp.go delete mode 100644 internal/kms/kms/internal/BUILD.bazel delete mode 100644 internal/kms/kms/internal/internal.go delete mode 100644 internal/kms/kms/internal/internal_test.go delete mode 100644 internal/kms/kms/kms.go delete mode 100644 internal/kms/setup/BUILD.bazel delete mode 100644 internal/kms/setup/setup.go delete mode 100644 internal/kms/setup/setup_test.go delete mode 100644 internal/kms/storage/BUILD.bazel delete mode 100644 internal/kms/storage/awss3/BUILD.bazel delete mode 100644 internal/kms/storage/awss3/awss3.go delete mode 100644 internal/kms/storage/awss3/awss3_test.go delete mode 100644 internal/kms/storage/azureblob/BUILD.bazel delete mode 100644 internal/kms/storage/azureblob/azureblob.go delete mode 100644 internal/kms/storage/azureblob/azureblob_test.go delete mode 100644 internal/kms/storage/gcs/BUILD.bazel delete mode 100644 internal/kms/storage/gcs/gcs.go delete mode 100644 internal/kms/storage/gcs/gcs_test.go delete mode 100644 internal/kms/storage/memfs/BUILD.bazel delete mode 100644 internal/kms/storage/memfs/memfs.go delete mode 100644 internal/kms/storage/memfs/memfs_test.go delete mode 100644 internal/kms/storage/storage.go delete mode 100644 internal/kms/test/BUILD.bazel delete mode 100644 internal/kms/test/aws_test.go delete mode 100644 internal/kms/test/azure_test.go delete mode 100644 internal/kms/test/gcp_test.go delete mode 100644 internal/kms/test/integration_test.go delete mode 100644 internal/kms/uri/BUILD.bazel delete mode 100644 internal/kms/uri/uri.go delete mode 100644 internal/kms/uri/uri_test.go delete mode 100644 internal/kubernetes/BUILD.bazel delete mode 100644 internal/kubernetes/configmaps.go delete mode 100644 internal/kubernetes/configmaps_test.go delete mode 100644 internal/kubernetes/kubectl/BUILD.bazel delete mode 100644 internal/kubernetes/kubectl/kubectl.go delete mode 100644 internal/kubernetes/kubectl/kubectl_test.go delete mode 100644 internal/kubernetes/kubernetes.go delete mode 100644 internal/kubernetes/marshal.go delete mode 100644 internal/kubernetes/marshal_test.go delete mode 100644 internal/kubernetes/secrets.go delete mode 100644 internal/kubernetes/secrets_test.go delete mode 100644 internal/license/BUILD.bazel delete mode 100644 internal/license/checker_enterprise.go delete mode 100644 internal/license/checker_enterprise_test.go delete mode 100644 internal/license/checker_oss.go delete mode 100644 internal/license/file.go delete mode 100644 internal/license/file_test.go delete mode 100644 internal/license/integration/BUILD.bazel delete mode 100644 internal/license/integration/license_integration_test.go delete mode 100644 internal/license/license.go delete mode 100644 internal/mpimage/BUILD.bazel delete mode 100644 internal/mpimage/mpimage.go delete mode 100644 internal/mpimage/uri.go delete mode 100644 internal/mpimage/uri_test.go delete mode 100644 internal/nodestate/BUILD.bazel delete mode 100644 internal/nodestate/nodestate.go delete mode 100644 internal/nodestate/nodestate_test.go delete mode 100644 internal/osimage/BUILD.bazel delete mode 100644 internal/osimage/archive/BUILD.bazel delete mode 100644 internal/osimage/archive/archive.go delete mode 100644 internal/osimage/imageinfo/BUILD.bazel delete mode 100644 internal/osimage/imageinfo/imageinfo.go delete mode 100644 internal/osimage/measurementsuploader/BUILD.bazel delete mode 100644 internal/osimage/measurementsuploader/measurementsuploader.go delete mode 100644 internal/osimage/nop/BUILD.bazel delete mode 100644 internal/osimage/nop/nop.go delete mode 100644 internal/osimage/osimage.go delete mode 100644 internal/osimage/secureboot/BUILD.bazel delete mode 100644 internal/osimage/secureboot/secureboot.go delete mode 100644 internal/osimage/secureboot/secureboot_test.go delete mode 100644 internal/osimage/secureboot/zlibdict.go delete mode 100644 internal/osimage/uplosi/BUILD.bazel delete mode 100644 internal/osimage/uplosi/uplosi.conf.in delete mode 100644 internal/osimage/uplosi/uplosiupload.go delete mode 100644 internal/retry/BUILD.bazel delete mode 100644 internal/retry/retry.go delete mode 100644 internal/retry/retry_test.go diff --git a/go.mod b/go.mod index fad36a6..0dc23b5 100644 --- a/go.mod +++ b/go.mod @@ -179,6 +179,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect @@ -225,7 +226,7 @@ require ( golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.27.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect diff --git a/go.sum b/go.sum index 72c0261..825821c 100644 --- a/go.sum +++ b/go.sum @@ -345,6 +345,8 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= +github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -584,6 +586,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/internal/api/attestationconfigapi/fetcher_test.go b/internal/api/attestationconfigapi/fetcher_test.go index bc89f31..496dbd7 100644 --- a/internal/api/attestationconfigapi/fetcher_test.go +++ b/internal/api/attestationconfigapi/fetcher_test.go @@ -7,7 +7,6 @@ package attestationconfigapi import ( "bytes" - "context" "encoding/json" "errors" "fmt" @@ -18,7 +17,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" "github.com/flashbots/cvm-reverse-proxy/internal/constants" - "github.com/stretchr/testify/assert" ) @@ -104,7 +102,7 @@ func TestFetchLatestSEVSNPVersion(t *testing.T) { }, } fetcher := newFetcherWithClientAndVerifier(client, stubVerifier{}, constants.CDNRepositoryURL) - res, err := fetcher.FetchLatestVersion(context.Background(), tc.attestation) + res, err := fetcher.FetchLatestVersion(t.Context(), tc.attestation) assert := assert.New(t) if tc.wantErr { assert.Error(err) diff --git a/internal/api/attestationconfigapi/version.go b/internal/api/attestationconfigapi/version.go index dd72be9..9ce6e20 100644 --- a/internal/api/attestationconfigapi/version.go +++ b/internal/api/attestationconfigapi/version.go @@ -111,8 +111,8 @@ func (i *List) SortReverse() { } // AddVersion adds new to i's list and sorts the element in descending order. -func (i *List) AddVersion(new string) { - i.List = append(i.List, new) +func (i *List) AddVersion(ver string) { + i.List = append(i.List, ver) i.List = variant.RemoveDuplicate(i.List) i.SortReverse() diff --git a/internal/api/versionsapi/cli/add.go b/internal/api/versionsapi/cli/add.go index 2c737ad..c1a1ae9 100644 --- a/internal/api/versionsapi/cli/add.go +++ b/internal/api/versionsapi/cli/add.go @@ -15,9 +15,7 @@ import ( apiclient "github.com/flashbots/cvm-reverse-proxy/internal/api/client" "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" "github.com/flashbots/cvm-reverse-proxy/internal/logger" - "github.com/spf13/cobra" - "golang.org/x/mod/semver" ) func newAddCmd() *cobra.Command { @@ -54,19 +52,8 @@ func runAdd(cmd *cobra.Command, _ []string) (retErr error) { return err } log := logger.NewTextLogger(flags.logLevel) - log.Debug("Using flags", "dryRun", flags.dryRun, "kind", flags.kind, "latest", flags.latest, "ref", flags.ref, - "release", flags.release, "stream", flags.stream, "version", flags.version) - - log.Debug("Validating flags") - if err := flags.validate(log); err != nil { - return err - } - - log.Debug("Creating version struct") - ver, err := versionsapi.NewVersion(flags.ref, flags.stream, flags.version, flags.kind) - if err != nil { - return fmt.Errorf("creating version: %w", err) - } + log.Debug("Using flags", "dryRun", flags.dryRun, "kind", flags.version.Kind(), "latest", flags.latest, "ref", flags.version.Ref(), + "stream", flags.version.Stream(), "version", flags.version.Version()) log.Debug("Creating versions API client") client, clientClose, err := versionsapi.NewClient(cmd.Context(), flags.region, flags.bucket, flags.distributionID, flags.dryRun, log) @@ -81,27 +68,27 @@ func runAdd(cmd *cobra.Command, _ []string) (retErr error) { }() log.Info("Adding version") - if err := ensureVersion(cmd.Context(), client, flags.kind, ver, versionsapi.GranularityMajor, log); err != nil { + if err := ensureVersion(cmd.Context(), client, flags.version, versionsapi.GranularityMajor, log); err != nil { return err } - if err := ensureVersion(cmd.Context(), client, flags.kind, ver, versionsapi.GranularityMinor, log); err != nil { + if err := ensureVersion(cmd.Context(), client, flags.version, versionsapi.GranularityMinor, log); err != nil { return err } if flags.latest { - if err := updateLatest(cmd.Context(), client, flags.kind, ver, log); err != nil { + if err := updateLatest(cmd.Context(), client, flags.version, log); err != nil { return fmt.Errorf("setting latest version: %w", err) } } - log.Info(fmt.Sprintf("List major->minor URL: %s", ver.ListURL(versionsapi.GranularityMajor))) - log.Info(fmt.Sprintf("List minor->patch URL: %s", ver.ListURL(versionsapi.GranularityMinor))) + log.Info(fmt.Sprintf("List major->minor URL: %s", flags.version.ListURL(versionsapi.GranularityMajor))) + log.Info(fmt.Sprintf("List minor->patch URL: %s", flags.version.ListURL(versionsapi.GranularityMinor))) return nil } -func ensureVersion(ctx context.Context, client *versionsapi.Client, kind versionsapi.VersionKind, ver versionsapi.Version, gran versionsapi.Granularity, +func ensureVersion(ctx context.Context, client *versionsapi.Client, ver versionsapi.Version, gran versionsapi.Granularity, log *slog.Logger, ) error { verListReq := versionsapi.List{ @@ -109,7 +96,7 @@ func ensureVersion(ctx context.Context, client *versionsapi.Client, kind version Stream: ver.Stream(), Granularity: gran, Base: ver.WithGranularity(gran), - Kind: kind, + Kind: ver.Kind(), } verList, err := client.FetchVersionList(ctx, verListReq) var notFoundErr *apiclient.NotFoundError @@ -141,11 +128,11 @@ func ensureVersion(ctx context.Context, client *versionsapi.Client, kind version return nil } -func updateLatest(ctx context.Context, client *versionsapi.Client, kind versionsapi.VersionKind, ver versionsapi.Version, log *slog.Logger) error { +func updateLatest(ctx context.Context, client *versionsapi.Client, ver versionsapi.Version, log *slog.Logger) error { latest := versionsapi.Latest{ Ref: ver.Ref(), Stream: ver.Stream(), - Kind: kind, + Kind: ver.Kind(), } latest, err := client.FetchVersionLatest(ctx, latest) var notFoundErr *apiclient.NotFoundError @@ -165,7 +152,7 @@ func updateLatest(ctx context.Context, client *versionsapi.Client, kind versions Ref: ver.Ref(), Stream: ver.Stream(), Version: ver.Version(), - Kind: kind, + Kind: ver.Kind(), } if err := client.UpdateVersionLatest(ctx, latest); err != nil { return fmt.Errorf("updating latest version: %w", err) @@ -175,60 +162,20 @@ func updateLatest(ctx context.Context, client *versionsapi.Client, kind versions } type addFlags struct { - version string - stream string - ref string - release bool + version versionsapi.Version latest bool dryRun bool region string bucket string distributionID string - kind versionsapi.VersionKind logLevel slog.Level } -func (f *addFlags) validate(log *slog.Logger) error { - if !semver.IsValid(f.version) { - return fmt.Errorf("version %q is not a valid semantic version", f.version) - } - if semver.Canonical(f.version) != f.version { - return fmt.Errorf("version %q is not a canonical semantic version", f.version) - } - - if f.ref == "" && !f.release { - return fmt.Errorf("either --ref or --release must be set") - } - - if f.kind == versionsapi.VersionKindUnknown { - return fmt.Errorf("unknown version kind %q", f.kind) - } - - if f.release { - log.Debug(fmt.Sprintf("Setting ref to %q, as release flag is set", versionsapi.ReleaseRef)) - f.ref = versionsapi.ReleaseRef - } else { - log.Debug("Setting latest to true, as release flag is not set") - f.latest = true // always set latest for non-release versions - } - - if err := versionsapi.ValidateRef(f.ref); err != nil { - return fmt.Errorf("invalid ref %w", err) - } - - if err := versionsapi.ValidateStream(f.ref, f.stream); err != nil { - return fmt.Errorf("invalid stream %w", err) - } - - return nil -} - func parseAddFlags(cmd *cobra.Command) (addFlags, error) { ref, err := cmd.Flags().GetString("ref") if err != nil { return addFlags{}, err } - ref = versionsapi.CanonicalizeRef(ref) stream, err := cmd.Flags().GetString("stream") if err != nil { return addFlags{}, err @@ -275,17 +222,24 @@ func parseAddFlags(cmd *cobra.Command) (addFlags, error) { return addFlags{}, err } + if release { + ref = versionsapi.ReleaseRef + } else { + latest = true // always set latest for non-release versions + } + + ver, err := versionsapi.NewVersion(ref, stream, version, kind) + if err != nil { + return addFlags{}, fmt.Errorf("creating version: %w", err) + } + return addFlags{ - version: version, - stream: stream, - ref: versionsapi.CanonicalizeRef(ref), - release: release, + version: ver, latest: latest, dryRun: dryRun, region: region, bucket: bucket, distributionID: distributionID, logLevel: logLevel, - kind: kind, }, nil } diff --git a/internal/api/versionsapi/fetcher_test.go b/internal/api/versionsapi/fetcher_test.go index 3a3625a..e2a6ca7 100644 --- a/internal/api/versionsapi/fetcher_test.go +++ b/internal/api/versionsapi/fetcher_test.go @@ -8,14 +8,12 @@ package versionsapi import ( "bytes" - "context" "encoding/json" "io" "net/http" "testing" "github.com/flashbots/cvm-reverse-proxy/internal/constants" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" @@ -193,7 +191,7 @@ func TestFetchVersionList(t *testing.T) { fetcher := Fetcher{client, constants.CDNRepositoryURL} - list, err := fetcher.FetchVersionList(context.Background(), tc.list) + list, err := fetcher.FetchVersionList(t.Context(), tc.list) if tc.wantErr { assert.Error(err) diff --git a/internal/api/versionsapi/version.go b/internal/api/versionsapi/version.go index 3613198..fcea2bb 100644 --- a/internal/api/versionsapi/version.go +++ b/internal/api/versionsapi/version.go @@ -41,7 +41,7 @@ type Version struct { // NewVersion creates a new Version object and validates it. func NewVersion(ref, stream, version string, kind VersionKind) (Version, error) { ver := Version{ - ref: ref, + ref: CanonicalizeRef(ref), stream: stream, version: version, kind: kind, @@ -62,7 +62,7 @@ func NewVersionFromShortPath(shortPath string, kind VersionKind) (Version, error } ver := Version{ - ref: ref, + ref: ref, // Canonicalized by parseShortPath. stream: stream, version: version, kind: kind, @@ -331,7 +331,7 @@ func CanonicalizeRef(ref string) string { canRef := notAZ09Regexp.ReplaceAllString(ref, "-") if canRef == ReleaseRef { - return "" // No ref should be cannonicalized to the release ref. + return "" // No ref should be canonicalized to the release ref. } return canRef @@ -401,7 +401,7 @@ func MeasurementURL(version Version) (measurementURL, signatureURL *url.URL, err } var ( - shortPathRegex = regexp.MustCompile(`^ref/([a-zA-Z0-9-]+)/stream/([a-zA-Z0-9-]+)/([a-zA-Z0-9.-]+)$`) + shortPathRegex = regexp.MustCompile(`^ref/([^/]+)/stream/([a-zA-Z0-9-]+)/([a-zA-Z0-9.-]+)$`) shortPathReleaseRegex = regexp.MustCompile(`^stream/([a-zA-Z0-9-]+)/([a-zA-Z0-9.-]+)$`) ) @@ -422,6 +422,7 @@ func parseShortPath(shortPath string) (ref, stream, version string, err error) { if shortPathRegex.MatchString(shortPath) { matches := shortPathRegex.FindStringSubmatch(shortPath) ref := matches[1] + ref = CanonicalizeRef(ref) if err := ValidateRef(ref); err != nil { return "", "", "", err } diff --git a/internal/api/versionsapi/version_test.go b/internal/api/versionsapi/version_test.go index 3fddec1..8beec1c 100644 --- a/internal/api/versionsapi/version_test.go +++ b/internal/api/versionsapi/version_test.go @@ -16,6 +16,111 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/constants" ) +func TestNewVersion(t *testing.T) { + testCases := map[string]struct { + ref string + stream string + version string + kind VersionKind + wantVer Version + wantErr bool + }{ + "stable release image": { + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, + wantVer: Version{ + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindImage, + }, + }, + "release debug image": { + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, + wantVer: Version{ + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, + }, + }, + "stable release cli": { + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindCLI, + wantVer: Version{ + ref: ReleaseRef, + stream: "stable", + version: "v9.9.9", + kind: VersionKindCLI, + }, + }, + "release debug cli": { + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindCLI, + wantVer: Version{ + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindCLI, + }, + }, + "unknown kind": { + ref: ReleaseRef, + stream: "debug", + version: "v9.9.9", + kind: VersionKindUnknown, + wantErr: true, + }, + "non-release ref as input": { + ref: "working-branch", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, + wantVer: Version{ + ref: "working-branch", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, + }, + }, + "non-canonical ref as input": { + ref: "testing-1.23", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, + wantVer: Version{ + ref: "testing-1-23", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + ver, err := NewVersion(tc.ref, tc.stream, tc.version, tc.kind) + if tc.wantErr { + assert.Error(err) + return + } + assert.NoError(err) + assert.Equal(tc.wantVer, ver) + }) + } +} + func TestNewVersionFromShortPath(t *testing.T) { testCases := map[string]struct { path string @@ -78,6 +183,26 @@ func TestNewVersionFromShortPath(t *testing.T) { kind: VersionKindCLI, wantErr: true, }, + "non-release ref as input": { + path: "ref/working-branch/stream/debug/v9.9.9", + kind: VersionKindImage, + wantVer: Version{ + ref: "working-branch", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, + }, + }, + "non-canonical ref as input": { + path: "ref/testing-1.23/stream/debug/v9.9.9", + kind: VersionKindImage, + wantVer: Version{ + ref: "testing-1-23", + stream: "debug", + version: "v9.9.9", + kind: VersionKindImage, + }, + }, } for name, tc := range testCases { diff --git a/internal/atls/atls.go b/internal/atls/atls.go index 572f1b9..bafa15f 100644 --- a/internal/atls/atls.go +++ b/internal/atls/atls.go @@ -70,6 +70,7 @@ func CreateAttestationClientTLSConfig(issuer Issuer, validators []Validator) (*t InsecureSkipVerify: true, // disable default verification because we use our own verify func ServerName: base64.StdEncoding.EncodeToString(clientNonce), // abuse ServerName as a channel to transmit the nonce MinVersion: tls.VersionTLS12, + NextProtos: []string{"http/1.1", "h2"}, // grpc-go requires us to advertise HTTP/2 (h2) over ALPN }, nil } @@ -114,6 +115,7 @@ func getATLSConfigForClientFunc(issuer Issuer, validators []Validator) (func(*tl VerifyPeerCertificate: serverConn.verify, GetCertificate: serverConn.getCertificate, MinVersion: tls.VersionTLS12, + NextProtos: []string{"http/1.1", "h2"}, // grpc-go requires us to advertise HTTP/2 (h2) over ALPN } // enable mutual aTLS if any validators are set diff --git a/internal/atls/atls_test.go b/internal/atls/atls_test.go index 6f412d6..97557e2 100644 --- a/internal/atls/atls_test.go +++ b/internal/atls/atls_test.go @@ -7,7 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-only package atls import ( - "context" "encoding/asn1" "errors" "io" @@ -16,7 +15,6 @@ import ( "testing" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" @@ -163,7 +161,7 @@ func TestTLSConfig(t *testing.T) { server.StartTLS() defer server.Close() - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, server.URL, http.NoBody) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, server.URL, http.NoBody) require.NoError(err) resp, err := client.Do(req) if tc.wantErr { @@ -222,7 +220,7 @@ func TestClientConnectionConcurrency(t *testing.T) { var reqs []*http.Request for _, url := range urls { - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, http.NoBody) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, http.NoBody) require.NoError(err) reqs = append(reqs, req) } @@ -296,7 +294,7 @@ func TestServerConnectionConcurrency(t *testing.T) { var reqs []*http.Request for _, url := range urls { - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, http.NoBody) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, url, http.NoBody) require.NoError(err) reqs = append(reqs, req) } diff --git a/internal/attestation/attestation.go b/internal/attestation/attestation.go index 581198f..3b409d9 100644 --- a/internal/attestation/attestation.go +++ b/internal/attestation/attestation.go @@ -45,7 +45,6 @@ const ( // Logger is a logger used to print warnings and infos during attestation validation. type Logger interface { - Debug(msg string, args ...any) Info(msg string, args ...any) Warn(msg string, args ...any) } @@ -53,9 +52,6 @@ type Logger interface { // NOPLogger is a no-op implementation of [Logger]. type NOPLogger struct{} -// Debug is a no-op. -func (NOPLogger) Debug(string, ...interface{}) {} - // Info is a no-op. func (NOPLogger) Info(string, ...interface{}) {} diff --git a/internal/attestation/aws/nitrotpm/issuer_test.go b/internal/attestation/aws/nitrotpm/issuer_test.go index d7e160e..4c17036 100644 --- a/internal/attestation/aws/nitrotpm/issuer_test.go +++ b/internal/attestation/aws/nitrotpm/issuer_test.go @@ -12,10 +12,9 @@ import ( "os" "testing" - "github.com/flashbots/cvm-reverse-proxy/internal/attestation/simulator" - "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/smithy-go/middleware" + "github.com/flashbots/cvm-reverse-proxy/internal/attestation/simulator" tpmclient "github.com/google/go-tpm-tools/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -101,7 +100,7 @@ func TestGetInstanceInfo(t *testing.T) { instanceInfoFunc := getInstanceInfo(&tc.client) assert.NotNil(instanceInfoFunc) - info, err := instanceInfoFunc(context.Background(), tpm, nil) + info, err := instanceInfoFunc(t.Context(), tpm, nil) if tc.wantErr { assert.Error(err) assert.Nil(info) diff --git a/internal/attestation/aws/nitrotpm/validator_test.go b/internal/attestation/aws/nitrotpm/validator_test.go index 6803951..8f541a7 100644 --- a/internal/attestation/aws/nitrotpm/validator_test.go +++ b/internal/attestation/aws/nitrotpm/validator_test.go @@ -12,11 +12,10 @@ import ( "errors" "testing" - "github.com/flashbots/cvm-reverse-proxy/internal/attestation/vtpm" - "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/flashbots/cvm-reverse-proxy/internal/attestation/vtpm" "github.com/google/go-tpm-tools/proto/attest" "github.com/stretchr/testify/assert" ) @@ -43,7 +42,7 @@ func TestGeTrustedKey(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) out, err := getTrustedKey( - context.Background(), + t.Context(), vtpm.AttestationDocument{ Attestation: &attest.Attestation{ AkPub: tc.akPub, diff --git a/internal/attestation/aws/snp/issuer_test.go b/internal/attestation/aws/snp/issuer_test.go index 1f4be4b..d0f6643 100644 --- a/internal/attestation/aws/snp/issuer_test.go +++ b/internal/attestation/aws/snp/issuer_test.go @@ -11,7 +11,6 @@ import ( "testing" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/simulator" - tpmclient "github.com/google/go-tpm-tools/client" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/attestation/aws/snp/testdata/report.txt b/internal/attestation/aws/snp/testdata/report.txt index a5ed00a..e413ca3 100644 --- a/internal/attestation/aws/snp/testdata/report.txt +++ b/internal/attestation/aws/snp/testdata/report.txt @@ -1 +1 @@ -AgAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAAAAAAW1QMAAAAAAAAABAAAAAAAAAADJjVhPI4zH6KeCWNxkQ/mofaTg92gLJRhQApwtm2Ho9pd2GMAJSK+Q6/DTywjOYm9bkAeNR0Q18yADW9d/PAZ8amfwHRwvrA7EzD0SJUgr48CiBh/+2YccfNcqwm+oZzZYtTy0J9aFsPDi32mvtJEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACKGpDGe5jVOEMlqVJrJXkLPExDcn4NNEkTnWyr1bpRyf//////////////////////////////////////////AwAAAAAAFdEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAFdMUNwEAEjcBAAMAAAAAABXTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABdtzdhdRVgTlqcv/jK6JowumHvC2VvXiZ9Zpf150hAG4Y4Zc/ypAlPoORo0uIcxPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATMdpGsLZn99jRGCI3eaEGkcNMvDnJVcVSviZtDTMpNjydn/F1cE2AKOFqCZQ4HnZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= +AwAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAEAAAAAAAY3CcAAAAAAAAABAAAAAAAAACHq3yvUQ4bNSDcPM62TuRBKOEJdvsNP8XidGmdiq9QYVSvTB3goCa0n9+GHprHVVFVGzU00cYTaaOwj1uu0NsvWlzbrY9UDOSrygEg+uyG9i73EopKxxGt001Gi1lXyCqi8k6PER2tw2ibeHuI7QcVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARowRHpiQyfxJKbRS+DVfQGWxQvKf1S21qaW2zACl7rf//////////////////////////////////////////BAAAAAAAGNkZAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAGNsdNwEAHTcBAAQAAAAAABjbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATEij8MQ3cc95xvjozFQCY/3yYhrUJa6qN5kOaH0eHbuMzQ0iOgY3m6riTBYsQlksAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAt0MH84001UcDpwNKn6LJSVfidlQxQ2nAM6WGsDjMvA4Z8WcYJeQhgpcDL7YJ+dbpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= diff --git a/internal/attestation/aws/snp/testdata/testdata.go b/internal/attestation/aws/snp/testdata/testdata.go index 61d14f1..a07df98 100644 --- a/internal/attestation/aws/snp/testdata/testdata.go +++ b/internal/attestation/aws/snp/testdata/testdata.go @@ -15,7 +15,7 @@ import _ "embed" var SNPReport string // AKDigest holds the AK digest embedded in SNPReport.REPORT_DATA. -const AKDigest = "032635613c8e331fa29e096371910fe6a1f69383dda02c9461400a70b66d87a3da5dd863002522be43afc34f2c233989bd6e401e351d10d7cc800d6f5dfcf019" +const AKDigest = "87ab7caf510e1b3520dc3cceb64ee44128e10976fb0d3fc5e274699d8aaf506154af4c1de0a026b49fdf861e9ac75551551b3534d1c61369a3b08f5baed0db2f" // VLEK for SNPReport. // diff --git a/internal/attestation/aws/snp/testdata/vlek.pem b/internal/attestation/aws/snp/testdata/vlek.pem index 96a1db2..3f8be03 100644 --- a/internal/attestation/aws/snp/testdata/vlek.pem +++ b/internal/attestation/aws/snp/testdata/vlek.pem @@ -1,30 +1,30 @@ -----BEGIN CERTIFICATE----- -MIIFLTCCAtygAwIBAgIBADBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQCAgUA -oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATCjAwIBATCBgDEUMBIG -A1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVTMRQwEgYDVQQHDAtTYW50YSBD -bGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFkdmFuY2VkIE1pY3JvIERldmlj -ZXMxFzAVBgNVBAMMDlNFVi1WTEVLLU1pbGFuMB4XDTI0MDUwNTIxNDUyNloXDTI1 -MDUwNTIxNDUyNlowejEUMBIGA1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVT -MRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFk -dmFuY2VkIE1pY3JvIERldmljZXMxETAPBgNVBAMMCFNFVi1WTEVLMHYwEAYHKoZI -zj0CAQYFK4EEACIDYgAEHCsA6v0QwdgijkHV1KnV+1wMqjVaITbdleQV40cnL6ZT -Pq3IsXeFGI9tq2a2EoDksTTqeo5a1ZDq2BiNA2cue0PlZhHkv2MK1cNPMDGAOddc -k7VNaqrRLUo84kn6tRXpo4HyMIHvMBAGCSsGAQQBnHgBAQQDAgEAMBQGCSsGAQQB -nHgBAgQHFgVNaWxhbjARBgorBgEEAZx4AQMBBAMCAQMwEQYKKwYBBAGceAEDAgQD -AgEAMBEGCisGAQQBnHgBAwQEAwIBADARBgorBgEEAZx4AQMFBAMCAQAwEQYKKwYB -BAGceAEDBgQDAgEAMBEGCisGAQQBnHgBAwcEAwIBADARBgorBgEEAZx4AQMDBAMC -ARUwEgYKKwYBBAGceAEDCAQEAgIA0TAsBgkrBgEEAZx4AQUEHxYdQ049Y2MtZXUt -d2VzdC0xLmFtYXpvbmF3cy5jb20wRgYJKoZIhvcNAQEKMDmgDzANBglghkgBZQME -AgIFAKEcMBoGCSqGSIb3DQEBCDANBglghkgBZQMEAgIFAKIDAgEwowMCAQEDggIB -ACeJ78s9Nrdz+WtvsNAecT7+ztE8jpxLZdgacsPtf3xU/JfcQHhVUuy/Lp5rIQ7B -h1HalTrmuY7goRO1kTp/lobXyntWkit0d5nR6iNjzp/uHr8+qEym2WbYX1Jesang -BQX06XxXTmEphrHElTrp8BovYIsPejdY2nNUYV6fhrdTXEh+qLDGQmwjK12FG+hu -4AS+rev2V7H9uE1XKXsM4TTqvI1hT3E2ocN4KjfUBi7yL/BF97kXfdqZH48pPD4y -i7TbZ7S89UikrAv0ZtgGyXY8yR094YVjfbnUvyYTyh4fgV8a8Mxsb4yhPoOOxkUI -8tNBhM4LkTPkR/4+Y2Dg6maglZJ5Hb2WWWNkd0CZchZC80T7HIgHztINMnHULiYi -sNRtKeUAqUNtwy0d2YehX+v9HzueTfKtvxIy2oBfT1LCykvTQTibE3aCvFMkEiw8 -4CunpWfPAoZEzzJUTxLQ6PkdE4MVRTTuuOAVHTrtkIUOB6tlkgMzijqAdwzTDdIj -NGQxTm0Vd2h+zvZl2HnSCi6PMoZml5RwZHiZXKRC90bPn0Vk1XlYW1wMEFHTWQqo -tFH44eWyGIoTwSqcqATR/HklCoUP0wMe2sSsMemJMPwAXWW4fZxmee72OR4p6c+w -TGzR0J5WFdJ0g2Ix+NobBydNaJnQz4H5Y+/gZFUCRrWh +MIIFIzCCAtegAwIBAgIBADBBBgkqhkiG9w0BAQowNKAPMA0GCWCGSAFlAwQCAgUA +oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATAwgYAxFDASBgNVBAsM +C0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEgQ2xhcmEx +CzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZpY2VzMRcw +FQYDVQQDDA5TRVYtVkxFSy1NaWxhbjAeFw0yNDEyMTAyMjMwMTZaFw0yNTEyMTAy +MjMwMTZaMHoxFDASBgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIG +A1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNl +ZCBNaWNybyBEZXZpY2VzMREwDwYDVQQDDAhTRVYtVkxFSzB2MBAGByqGSM49AgEG +BSuBBAAiA2IABJRw6hwLZt7KX95uPePz/3Gt/z9mm/32f0JpE2twW8w6DQ1xOPnW +YRLJeMSZNpaYW/NRpNf0vfy5IDQt44didvu+37x2aqyaneFiBh5jTxSg/2dCZ+bi +4eZw/p0Us7bubqOB8jCB7zAQBgkrBgEEAZx4AQEEAwIBADAUBgkrBgEEAZx4AQIE +BxYFTWlsYW4wEQYKKwYBBAGceAEDAQQDAgEEMBEGCisGAQQBnHgBAwIEAwIBADAR +BgorBgEEAZx4AQMEBAMCAQAwEQYKKwYBBAGceAEDBQQDAgEAMBEGCisGAQQBnHgB +AwYEAwIBADARBgorBgEEAZx4AQMHBAMCAQAwEQYKKwYBBAGceAEDAwQDAgEYMBIG +CisGAQQBnHgBAwgEBAICANkwLAYJKwYBBAGceAEFBB8WHUNOPWNjLWV1LXdlc3Qt +MS5hbWF6b25hd3MuY29tMEEGCSqGSIb3DQEBCjA0oA8wDQYJYIZIAWUDBAICBQCh +HDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMAOCAgEAar1tA7vYelxK +uj+r7APOEPcAAoF7RWZs6ixDlXHuFVj2rfxqmxt8nqjedEKBfUGPCEsbAV+Z/bj9 +GqN+q5Bn1yk6RL/VqxTxTVhpa0G33R87UjE+S+42k6ENgddbl4hxws5g83Sn9All +/XjNPHmciWjmix4PJs5tZv+YaJ15BSBkJfrTRo+rX3UDKeqUHNoX+Cx6D7ECF/6k +ToFlHBEBqHKa2EzhNMK2UXm/vm0ATSaNHuDEGBvzbXflPmHZi1RZqQ7q9VMenFDx +JwAgnUrltcuLjKMID7c2yj+Emk/CBEEFoAJRfSoSvMdhqrNaRlbEqEnQ95C/XNPn +Mqtk5Ao/UVV5fRXYSt5oGKTBGhqTwv+Xqyei+/IgpcJyGPFbHVX9UPteP4RnSLiq +uJ3oRIvyEw+u6bkMNBBAjh4C+Jp2BVrLs1aC0h9fjfVEofWTb/NioJRigKTNfbao +sTy6tX8qoUSxtp/bIqK1jg1Y7eIDIMCgqnm0N+hJT7CnkwyCBUkOHmsExzQcthmg +y0J1J7bTA507rY5ZglNSRLCXqAfORVxIBwTaOXrJV2lMLScTUdnhFrVPFUAl7uCj +rKta1iGye+fieoYncdHLIVyIJGsTC+AbhPIAR2Zh847Sxw1SVOobTPc0wUIoKrOU +xR32EkufsNGLb8TiEsgpa2ulbw8xi6U= -----END CERTIFICATE----- diff --git a/internal/attestation/aws/snp/validator_test.go b/internal/attestation/aws/snp/validator_test.go index 55b69e9..6963fc3 100644 --- a/internal/attestation/aws/snp/validator_test.go +++ b/internal/attestation/aws/snp/validator_test.go @@ -8,7 +8,6 @@ package snp import ( "bytes" - "context" "crypto" "crypto/x509" "encoding/base64" @@ -26,7 +25,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/attestation/vtpm" "github.com/flashbots/cvm-reverse-proxy/internal/config" "github.com/flashbots/cvm-reverse-proxy/internal/logger" - "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/proto/sevsnp" spb "github.com/google/go-sev-guest/proto/sevsnp" @@ -68,7 +66,7 @@ func TestGetTrustedKey(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) out, err := validator().getTrustedKey( - context.Background(), + t.Context(), vtpm.AttestationDocument{ Attestation: &attest.Attestation{ AkPub: tc.akPub, diff --git a/internal/attestation/azure/snp/issuer_test.go b/internal/attestation/azure/snp/issuer_test.go index 22df4ec..86972e9 100644 --- a/internal/attestation/azure/snp/issuer_test.go +++ b/internal/attestation/azure/snp/issuer_test.go @@ -14,7 +14,6 @@ import ( "testing" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/snp" - "github.com/edgelesssys/go-azguestattestation/maa" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -83,7 +82,7 @@ func TestGetSNPAttestation(t *testing.T) { data := []byte("data") - attestationJSON, err := issuer.getInstanceInfo(context.Background(), nil, data) + attestationJSON, err := issuer.getInstanceInfo(t.Context(), nil, data) if tc.wantErr { assert.Error(err) return diff --git a/internal/attestation/azure/snp/validator.go b/internal/attestation/azure/snp/validator.go index dd6752f..8e9795c 100644 --- a/internal/attestation/azure/snp/validator.go +++ b/internal/attestation/azure/snp/validator.go @@ -22,7 +22,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/vtpm" "github.com/flashbots/cvm-reverse-proxy/internal/config" - "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" spb "github.com/google/go-sev-guest/proto/sevsnp" @@ -223,6 +222,15 @@ func (v *Validator) checkIDKeyDigest(ctx context.Context, report *spb.Attestatio return nil } +// nopAttestationLogger is a no-op implementation of AttestationLogger. +type nopAttestationLogger struct{} + +// Infof is a no-op. +func (nopAttestationLogger) Info(string, ...interface{}) {} + +// Warnf is a no-op. +func (nopAttestationLogger) Warn(string, ...interface{}) {} + type maaValidator interface { validateToken(ctx context.Context, maaURL string, token string, extraData []byte) error } diff --git a/internal/attestation/azure/snp/validator_test.go b/internal/attestation/azure/snp/validator_test.go index 0493dcb..70a5446 100644 --- a/internal/attestation/azure/snp/validator_test.go +++ b/internal/attestation/azure/snp/validator_test.go @@ -25,7 +25,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/attestation/vtpm" "github.com/flashbots/cvm-reverse-proxy/internal/config" "github.com/flashbots/cvm-reverse-proxy/internal/logger" - "github.com/google/go-sev-guest/abi" "github.com/google/go-sev-guest/kds" spb "github.com/google/go-sev-guest/proto/sevsnp" @@ -183,7 +182,7 @@ func TestCheckIDKeyDigest(t *testing.T) { report := reportWithIDKeyDigest(tc.idKeyDigest) validator := newTestValidator(cfg, tc.validateMaaTokenErr) - err := validator.checkIDKeyDigest(context.Background(), report, "", nil) + err := validator.checkIDKeyDigest(t.Context(), report, "", nil) if tc.wantErr { require.Error(err) } else { @@ -651,7 +650,7 @@ func TestTrustedKeyFromSNP(t *testing.T) { attestationValidator: tc.validator, } - key, err := validator.getTrustedKey(context.Background(), attDoc, nil) + key, err := validator.getTrustedKey(t.Context(), attDoc, nil) if tc.wantErr { assert.Error(err) if tc.assertion != nil { diff --git a/internal/attestation/azure/tdx/issuer.go b/internal/attestation/azure/tdx/issuer.go index 7265e42..dde8957 100644 --- a/internal/attestation/azure/tdx/issuer.go +++ b/internal/attestation/azure/tdx/issuer.go @@ -20,7 +20,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/attestation/azure" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/vtpm" - "github.com/google/go-tpm/legacy/tpm2" ) diff --git a/internal/attestation/azure/tdx/issuer_test.go b/internal/attestation/azure/tdx/issuer_test.go index dd835cf..8003766 100644 --- a/internal/attestation/azure/tdx/issuer_test.go +++ b/internal/attestation/azure/tdx/issuer_test.go @@ -8,7 +8,6 @@ package tdx import ( "bytes" - "context" "encoding/binary" "encoding/json" "io" @@ -16,7 +15,6 @@ import ( "testing" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/azure/tdx/testdata" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -136,7 +134,7 @@ func TestIMDSGetQuote(t *testing.T) { client: tc.client, } - _, err := quoteGetter.getQuote(context.Background(), []byte("test")) + _, err := quoteGetter.getQuote(t.Context(), []byte("test")) if tc.wantErr { assert.Error(err) } else { diff --git a/internal/attestation/azure/trustedlaunch/trustedlaunch_test.go b/internal/attestation/azure/trustedlaunch/trustedlaunch_test.go index 6a0f3d2..c8bf49e 100644 --- a/internal/attestation/azure/trustedlaunch/trustedlaunch_test.go +++ b/internal/attestation/azure/trustedlaunch/trustedlaunch_test.go @@ -8,7 +8,6 @@ package trustedlaunch import ( "bytes" - "context" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -25,7 +24,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/config" "github.com/flashbots/cvm-reverse-proxy/internal/crypto" "github.com/flashbots/cvm-reverse-proxy/internal/logger" - tpmclient "github.com/google/go-tpm-tools/client" "github.com/google/go-tpm-tools/proto/attest" "github.com/google/go-tpm/legacy/tpm2" @@ -193,7 +191,7 @@ func TestGetAttestationCert(t *testing.T) { issuer := NewIssuer(logger.NewTest(t)) issuer.hClient = newTestClient(tc.crlServer) - certs, err := issuer.getAttestationCert(context.Background(), tpm, nil) + certs, err := issuer.getAttestationCert(t.Context(), tpm, nil) if tc.wantIssueErr { assert.Error(err) return @@ -214,7 +212,7 @@ func TestGetAttestationCert(t *testing.T) { roots.AddCert(cert) validator.roots = roots - key, err := validator.verifyAttestationKey(context.Background(), attDoc, nil) + key, err := validator.verifyAttestationKey(t.Context(), attDoc, nil) if tc.wantValidateErr { assert.Error(err) return diff --git a/internal/attestation/gcp/es/issuer_test.go b/internal/attestation/gcp/es/issuer_test.go index 6549f77..3d9ad4c 100644 --- a/internal/attestation/gcp/es/issuer_test.go +++ b/internal/attestation/gcp/es/issuer_test.go @@ -14,7 +14,6 @@ import ( "testing" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/gcp" - "github.com/google/go-tpm-tools/proto/attest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -68,7 +67,7 @@ func TestGetGCEInstanceInfo(t *testing.T) { require := require.New(t) var tpm io.ReadWriteCloser - out, err := gcp.GCEInstanceInfo(tc.client)(context.Background(), tpm, nil) + out, err := gcp.GCEInstanceInfo(tc.client)(t.Context(), tpm, nil) if tc.wantErr { assert.Error(err) } else { diff --git a/internal/attestation/gcp/es/validator_test.go b/internal/attestation/gcp/es/validator_test.go index d001f1f..2545387 100644 --- a/internal/attestation/gcp/es/validator_test.go +++ b/internal/attestation/gcp/es/validator_test.go @@ -13,11 +13,10 @@ import ( "errors" "testing" + "cloud.google.com/go/compute/apiv1/computepb" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/gcp" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/vtpm" - - "cloud.google.com/go/compute/apiv1/computepb" "github.com/google/go-tpm-tools/proto/attest" "github.com/googleapis/gax-go/v2" "github.com/stretchr/testify/assert" @@ -154,7 +153,7 @@ Y+t5OxL3kL15VzY1Ob0d5cMCAwEAAQ== getTrustedKey, err := gcp.TrustedKeyGetter(variant.GCPSEVES{}, tc.getClient) require.NoError(t, err) - out, err := getTrustedKey(context.Background(), attDoc, nil) + out, err := getTrustedKey(t.Context(), attDoc, nil) if tc.wantErr { assert.Error(err) diff --git a/internal/attestation/measurements/fetchmeasurements_test.go b/internal/attestation/measurements/fetchmeasurements_test.go index ad1fd8f..58cbbc5 100644 --- a/internal/attestation/measurements/fetchmeasurements_test.go +++ b/internal/attestation/measurements/fetchmeasurements_test.go @@ -17,7 +17,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" "github.com/flashbots/cvm-reverse-proxy/internal/sigstore" - "github.com/stretchr/testify/assert" ) @@ -142,7 +141,7 @@ func TestFetchMeasurements(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) sut := NewVerifyFetcher(tc.cosign, tc.rekor, client) - m, err := sut.FetchAndVerifyMeasurements(context.Background(), "v999.999.999", cloudprovider.GCP, variant.GCPSEVES{}, tc.noVerify) + m, err := sut.FetchAndVerifyMeasurements(t.Context(), "v999.999.999", cloudprovider.GCP, variant.GCPSEVES{}, tc.noVerify) if tc.wantErr { assert.Error(err) if tc.asRekorErr { diff --git a/internal/attestation/measurements/measurement-generator/generate.go b/internal/attestation/measurements/measurement-generator/generate.go index 8328abe..7704254 100644 --- a/internal/attestation/measurements/measurement-generator/generate.go +++ b/internal/attestation/measurements/measurement-generator/generate.go @@ -30,7 +30,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/constants" "github.com/flashbots/cvm-reverse-proxy/internal/sigstore" "github.com/flashbots/cvm-reverse-proxy/internal/sigstore/keyselect" - "golang.org/x/tools/go/ast/astutil" ) diff --git a/internal/attestation/measurements/measurements.go b/internal/attestation/measurements/measurements.go index 6262c16..2964ece 100644 --- a/internal/attestation/measurements/measurements.go +++ b/internal/attestation/measurements/measurements.go @@ -30,7 +30,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" - "github.com/google/go-tpm/tpmutil" "github.com/siderolabs/talos/pkg/machinery/config/encoder" "gopkg.in/yaml.v3" @@ -481,17 +480,17 @@ func (m *Measurement) unmarshal(eM encodedMeasurement) error { // WithAllBytes returns a measurement value where all bytes are set to b. Takes a dynamic length as input. // Expected are either 32 bytes (PCRMeasurementLength) or 48 bytes (TDXMeasurementLength). // Over inputs are possible in this function, but potentially rejected elsewhere. -func WithAllBytes(b byte, validationOpt MeasurementValidationOption, len int) Measurement { +func WithAllBytes(b byte, validationOpt MeasurementValidationOption, length int) Measurement { return Measurement{ - Expected: bytes.Repeat([]byte{b}, len), + Expected: bytes.Repeat([]byte{b}, length), ValidationOpt: validationOpt, } } // PlaceHolderMeasurement returns a measurement with placeholder values for Expected. -func PlaceHolderMeasurement(len int) Measurement { +func PlaceHolderMeasurement(length int) Measurement { return Measurement{ - Expected: bytes.Repeat([]byte{0x12, 0x34}, len/2), + Expected: bytes.Repeat([]byte{0x12, 0x34}, length/2), ValidationOpt: Enforce, } } diff --git a/internal/attestation/measurements/measurements_enterprise.go b/internal/attestation/measurements/measurements_enterprise.go index db47d32..ae1ffe8 100644 --- a/internal/attestation/measurements/measurements_enterprise.go +++ b/internal/attestation/measurements/measurements_enterprise.go @@ -19,14 +19,14 @@ package measurements // revive:disable:var-naming var ( - aws_AWSNitroTPM = M{0: {Expected: []byte{0x73, 0x7f, 0x76, 0x7a, 0x12, 0xf5, 0x4e, 0x70, 0xee, 0xcb, 0xc8, 0x68, 0x40, 0x11, 0x32, 0x3a, 0xe2, 0xfe, 0x2d, 0xd9, 0xf9, 0x07, 0x85, 0x57, 0x79, 0x69, 0xd7, 0xa2, 0x01, 0x3e, 0x8c, 0x12}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x9c, 0x56, 0xa6, 0x86, 0x76, 0x02, 0x13, 0x2b, 0xbd, 0xd4, 0x84, 0xd8, 0x89, 0xa5, 0x58, 0x65, 0x0b, 0xc4, 0x9d, 0x14, 0x05, 0x32, 0x0a, 0xa1, 0x02, 0x3d, 0x91, 0x6e, 0xf2, 0x6b, 0xb9, 0xef}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0xde, 0x68, 0xf8, 0xff, 0xbe, 0x2c, 0x18, 0x21, 0xed, 0x0b, 0x93, 0x2c, 0xb7, 0xb6, 0x74, 0xda, 0x52, 0x97, 0x37, 0x41, 0x22, 0xe6, 0xe3, 0xf2, 0xe0, 0x4e, 0x9c, 0x94, 0x60, 0xcf, 0xd8, 0x76}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x76, 0xc5, 0x22, 0x1e, 0xd9, 0x79, 0x35, 0xdc, 0xad, 0xa9, 0x78, 0x09, 0xa4, 0x55, 0x89, 0x6b, 0xd1, 0x81, 0xb1, 0x75, 0x41, 0xc6, 0x26, 0x4e, 0x88, 0xc0, 0x3e, 0xcb, 0x03, 0x02, 0x2a, 0x25}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} - aws_AWSSEVSNP = M{0: {Expected: []byte{0x7b, 0x06, 0x8c, 0x0c, 0x3a, 0xc2, 0x9a, 0xfe, 0x26, 0x41, 0x34, 0x53, 0x6b, 0x9b, 0xe2, 0x6f, 0x1d, 0x4c, 0xcd, 0x57, 0x5b, 0x88, 0xd3, 0xc3, 0xce, 0xab, 0xf3, 0x6a, 0xc9, 0x9c, 0x02, 0x78}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0xcb, 0xab, 0x69, 0x86, 0xc0, 0xfc, 0xc4, 0x96, 0x88, 0x95, 0x14, 0xf8, 0x3a, 0xd5, 0x5f, 0x8f, 0x49, 0x8f, 0xe8, 0xfb, 0xda, 0x39, 0x3b, 0x4d, 0xb4, 0xf4, 0xc7, 0xab, 0xd1, 0xc2, 0x54, 0x94}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x0b, 0xd3, 0xc9, 0xaa, 0x5e, 0x89, 0x5a, 0x09, 0x1f, 0x06, 0x9d, 0xdf, 0xde, 0x6a, 0x29, 0xc4, 0x7f, 0xad, 0xe9, 0x15, 0x57, 0x65, 0xed, 0x5e, 0x3b, 0x68, 0xcc, 0x64, 0xf0, 0xa2, 0x15, 0x9d}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x91, 0xae, 0xae, 0xcf, 0x1f, 0x6a, 0xa7, 0x33, 0xb4, 0x2c, 0x7c, 0x9e, 0x5f, 0xcc, 0x54, 0xb4, 0x55, 0x2f, 0x0e, 0xec, 0x22, 0x08, 0xf5, 0xdd, 0x37, 0x30, 0xae, 0x69, 0xc9, 0x6e, 0xb1, 0xbb}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} - azure_AzureSEVSNP = M{1: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x1f, 0x4f, 0x46, 0xdb, 0x17, 0xed, 0x0f, 0x0b, 0xcd, 0x0c, 0xdf, 0xfb, 0xb8, 0xdf, 0xce, 0xb6, 0x21, 0xf2, 0x83, 0x9f, 0x7d, 0x2e, 0x4f, 0xb7, 0x11, 0xfc, 0x3c, 0xc8, 0xd8, 0xfd, 0xa5, 0xfb}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x24, 0x3a, 0xa1, 0x4a, 0xa1, 0xbb, 0x08, 0x6e, 0x54, 0x8b, 0x11, 0x86, 0x03, 0x4c, 0x02, 0x8f, 0xd8, 0xb8, 0xc8, 0xe7, 0x93, 0xf1, 0x7c, 0x1b, 0x12, 0x60, 0x4a, 0x67, 0xda, 0x3f, 0x64, 0xbd}, ValidationOpt: Enforce}, 11: {Expected: []byte{0xbb, 0x54, 0xa1, 0x51, 0x9b, 0xc5, 0x2e, 0xc9, 0x69, 0x54, 0x70, 0x04, 0x51, 0xa6, 0xd3, 0xdc, 0xd3, 0x19, 0x2b, 0xf1, 0xc8, 0x41, 0xe4, 0xa2, 0x74, 0x46, 0xc8, 0x4e, 0xba, 0x55, 0xf9, 0x6f}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} - azure_AzureTDX = M{1: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0xf8, 0x3e, 0xd1, 0x1a, 0x5d, 0x7b, 0x17, 0xff, 0xed, 0x82, 0xd9, 0x4f, 0xec, 0x20, 0xd6, 0xa8, 0x88, 0xfe, 0xde, 0x66, 0x5c, 0x0d, 0xfd, 0xa8, 0x64, 0x65, 0x53, 0x14, 0x1d, 0xfc, 0xb6, 0x9d}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x81, 0x77, 0x77, 0x39, 0x0e, 0x6e, 0x23, 0xa6, 0x0e, 0xe7, 0x1f, 0xf4, 0x7e, 0xc5, 0xaa, 0x71, 0x89, 0xeb, 0x3a, 0xad, 0x95, 0xb6, 0xc6, 0x2d, 0x32, 0x3f, 0x50, 0x30, 0x79, 0x10, 0x84, 0x30}, ValidationOpt: Enforce}, 11: {Expected: []byte{0xfe, 0x87, 0x47, 0x6a, 0xed, 0xc8, 0x87, 0x84, 0x51, 0x00, 0x66, 0x96, 0x7b, 0x3f, 0x03, 0x7b, 0x51, 0xa9, 0x52, 0x69, 0x9b, 0xe7, 0xed, 0xfe, 0xa4, 0xdf, 0x5f, 0x25, 0xed, 0xa1, 0xf0, 0x24}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} + aws_AWSNitroTPM = M{0: {Expected: []byte{0x73, 0x7f, 0x76, 0x7a, 0x12, 0xf5, 0x4e, 0x70, 0xee, 0xcb, 0xc8, 0x68, 0x40, 0x11, 0x32, 0x3a, 0xe2, 0xfe, 0x2d, 0xd9, 0xf9, 0x07, 0x85, 0x57, 0x79, 0x69, 0xd7, 0xa2, 0x01, 0x3e, 0x8c, 0x12}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x73, 0xa3, 0x8e, 0x0d, 0x77, 0x89, 0x63, 0x9a, 0xe4, 0xc8, 0xa8, 0x66, 0x45, 0xa8, 0x11, 0xd3, 0x14, 0xa0, 0xa1, 0xf9, 0x62, 0x00, 0x18, 0xf9, 0x68, 0xb5, 0xec, 0x57, 0x04, 0xc3, 0xd7, 0x0b}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0xf5, 0x67, 0x80, 0x1a, 0x50, 0x03, 0xd9, 0xef, 0x20, 0xd8, 0x7d, 0x2f, 0xc7, 0x6b, 0xeb, 0xb0, 0xd6, 0x28, 0x48, 0xb1, 0xbc, 0xed, 0x0c, 0xc5, 0x0f, 0x84, 0xae, 0xdb, 0x57, 0x37, 0x75, 0xf0}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x56, 0xa9, 0x8c, 0x3c, 0x70, 0x72, 0xfc, 0x9c, 0x4e, 0xb1, 0x1e, 0xe4, 0xbb, 0x65, 0x2e, 0xa8, 0xdd, 0x71, 0xbb, 0x54, 0xc8, 0x13, 0xc9, 0xe5, 0x42, 0x39, 0xa2, 0x0b, 0x0c, 0xc2, 0x9d, 0xc0}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} + aws_AWSSEVSNP = M{0: {Expected: []byte{0xd6, 0xdf, 0x85, 0x53, 0x58, 0xf5, 0xb1, 0x0f, 0x06, 0xf0, 0xfa, 0xb3, 0xf4, 0x08, 0xad, 0x26, 0xcd, 0x16, 0x5a, 0x29, 0x49, 0xba, 0xd6, 0x9e, 0x2c, 0xc7, 0x56, 0x92, 0x52, 0x9e, 0x66, 0x2a}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x98, 0x4a, 0x55, 0x3b, 0xb5, 0x19, 0xa9, 0x87, 0x4c, 0x76, 0x0c, 0xd0, 0xed, 0x3c, 0x00, 0x4c, 0xcf, 0x35, 0xdc, 0x5c, 0x45, 0x35, 0x46, 0x36, 0x34, 0x84, 0xfa, 0xa6, 0xa6, 0x5a, 0xb2, 0xac}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0xcf, 0xbf, 0x4d, 0xaf, 0x0c, 0xf1, 0xd0, 0xf6, 0x6b, 0x62, 0xe6, 0xda, 0x12, 0xd3, 0x96, 0xc3, 0x20, 0x35, 0x2d, 0x87, 0x26, 0x09, 0x87, 0xeb, 0x5c, 0x79, 0x1a, 0xb9, 0x1d, 0xa9, 0xaf, 0x02}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x34, 0xb2, 0xd3, 0x62, 0x2b, 0xbc, 0xb0, 0x6f, 0x92, 0x4d, 0xb6, 0xd6, 0x2f, 0x73, 0x0b, 0x79, 0x28, 0x33, 0x3b, 0x43, 0xb7, 0xf0, 0x1b, 0xe6, 0x49, 0x31, 0x7b, 0x81, 0x4e, 0x09, 0x43, 0xa7}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} + azure_AzureSEVSNP = M{1: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x52, 0x2c, 0xc5, 0x99, 0x86, 0x9c, 0xce, 0xa4, 0xa2, 0x98, 0x9c, 0x11, 0x93, 0xaf, 0xa6, 0x38, 0x96, 0xde, 0x5a, 0xe5, 0xa8, 0x04, 0x62, 0x52, 0x86, 0x6a, 0xf9, 0x85, 0xe3, 0x0b, 0x0e, 0xf6}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0xb5, 0xbf, 0x47, 0x5a, 0x0a, 0x34, 0xe3, 0xdc, 0xd2, 0x89, 0x90, 0xdb, 0x6d, 0x0f, 0xc2, 0xe0, 0x0a, 0x93, 0x4e, 0x05, 0xfa, 0x3e, 0xe5, 0x8b, 0x5c, 0x20, 0x24, 0xab, 0xb8, 0x7a, 0x09, 0xa8}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x68, 0x73, 0xa7, 0xcf, 0xc2, 0x4d, 0xe1, 0x9d, 0x55, 0x5b, 0x4e, 0x4c, 0xc7, 0x48, 0x9b, 0xb6, 0xc9, 0x4d, 0xc4, 0xda, 0xa1, 0xcb, 0x88, 0x31, 0xb8, 0x8e, 0x0f, 0x20, 0x27, 0x7c, 0xc2, 0xa9}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} + azure_AzureTDX = M{1: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0xeb, 0x2f, 0xda, 0x44, 0x76, 0x10, 0xc8, 0x8a, 0xfb, 0x7a, 0xdb, 0x62, 0x31, 0x9b, 0xf5, 0x43, 0xac, 0x01, 0xdf, 0xf1, 0xc8, 0xe3, 0xbd, 0xc2, 0xaa, 0x83, 0x24, 0x08, 0xc5, 0xf2, 0x66, 0xca}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0xd2, 0x45, 0xd8, 0x15, 0xdd, 0xbc, 0xb4, 0x26, 0x2a, 0x53, 0xb0, 0x14, 0x2d, 0x72, 0x91, 0xcc, 0x88, 0x6b, 0xe1, 0x53, 0x09, 0xb3, 0x7f, 0xab, 0x04, 0x74, 0x68, 0x0d, 0x26, 0x73, 0xe4, 0xaf}, ValidationOpt: Enforce}, 11: {Expected: []byte{0xa8, 0x88, 0x9e, 0xea, 0x7f, 0xf1, 0x9e, 0x8b, 0x74, 0x9c, 0x3f, 0x28, 0x7e, 0xd3, 0xa6, 0xe7, 0x23, 0x2e, 0x02, 0xfa, 0x01, 0xfd, 0x06, 0x1a, 0x56, 0xc9, 0xe7, 0xae, 0xfd, 0x43, 0x60, 0x3b}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} azure_AzureTrustedLaunch M - gcp_GCPSEVES = M{1: {Expected: []byte{0x36, 0x95, 0xdc, 0xc5, 0x5e, 0x3a, 0xa3, 0x40, 0x27, 0xc2, 0x77, 0x93, 0xc8, 0x5c, 0x72, 0x3c, 0x69, 0x7d, 0x70, 0x8c, 0x42, 0xd1, 0xf7, 0x3b, 0xd6, 0xfa, 0x4f, 0x26, 0x60, 0x8a, 0x5b, 0x24}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0xe9, 0x1d, 0x35, 0x65, 0xeb, 0x5d, 0xb6, 0x5e, 0x76, 0x0d, 0x7e, 0x5e, 0x91, 0x39, 0xad, 0x80, 0x98, 0x6f, 0x39, 0xb7, 0x42, 0x7f, 0x03, 0x1d, 0xe1, 0xa5, 0xcd, 0xe5, 0x35, 0x52, 0xb4, 0x8f}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0xca, 0xa7, 0x9a, 0x6c, 0x58, 0x2c, 0x9d, 0x72, 0x82, 0x73, 0x56, 0xee, 0x3b, 0xef, 0xae, 0x7e, 0x6c, 0x7e, 0x4c, 0x3d, 0x25, 0xd3, 0x67, 0x87, 0xdd, 0x19, 0x1b, 0xf0, 0x25, 0xf5, 0xee, 0x31}, ValidationOpt: Enforce}, 11: {Expected: []byte{0xf8, 0x50, 0x12, 0x5f, 0x26, 0xda, 0xfc, 0x19, 0xca, 0xfb, 0x0e, 0xff, 0x4c, 0xa9, 0x7b, 0x11, 0xec, 0xa4, 0x85, 0x3a, 0xee, 0xb3, 0x65, 0xbe, 0xd0, 0xbb, 0x33, 0xeb, 0xbb, 0xf1, 0xbc, 0x40}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} - gcp_GCPSEVSNP = M{1: {Expected: []byte{0x36, 0x95, 0xdc, 0xc5, 0x5e, 0x3a, 0xa3, 0x40, 0x27, 0xc2, 0x77, 0x93, 0xc8, 0x5c, 0x72, 0x3c, 0x69, 0x7d, 0x70, 0x8c, 0x42, 0xd1, 0xf7, 0x3b, 0xd6, 0xfa, 0x4f, 0x26, 0x60, 0x8a, 0x5b, 0x24}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x51, 0xd1, 0x60, 0xb2, 0x6e, 0x9a, 0xb9, 0xce, 0x9f, 0x62, 0x04, 0xf5, 0x42, 0x64, 0x76, 0xd0, 0x2b, 0x8d, 0x00, 0x8b, 0x0a, 0x1e, 0x28, 0x38, 0x87, 0x55, 0x75, 0x44, 0x90, 0xb2, 0xbc, 0x19}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0xbe, 0x3e, 0x53, 0x2f, 0x8b, 0xb0, 0x23, 0x35, 0x30, 0xeb, 0x8b, 0xf3, 0x5f, 0x25, 0xa7, 0x10, 0xd4, 0xea, 0x6d, 0x4a, 0xaa, 0xd3, 0x7b, 0x25, 0xe0, 0x56, 0x8e, 0xc0, 0xbf, 0xb4, 0xbe, 0x5e}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x16, 0xc0, 0x65, 0x84, 0x5f, 0x7f, 0x81, 0x87, 0xb5, 0x4d, 0x52, 0x6a, 0x87, 0x34, 0xf0, 0xcb, 0xa8, 0x00, 0xea, 0x08, 0x0e, 0x7f, 0x96, 0xdd, 0x76, 0xe9, 0x60, 0x43, 0xf3, 0x01, 0x72, 0x96}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} - openstack_QEMUVTPM = M{4: {Expected: []byte{0xf0, 0x23, 0x76, 0x9e, 0x24, 0xae, 0x35, 0x4f, 0x93, 0x7c, 0x5c, 0xfe, 0xc4, 0x96, 0xe8, 0xa7, 0xf7, 0x1b, 0x85, 0x7a, 0xfc, 0x9c, 0xe5, 0x8d, 0x52, 0xf9, 0x47, 0x42, 0xcc, 0xa7, 0x72, 0x98}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x6f, 0x8b, 0x4d, 0x24, 0x0e, 0xe7, 0x13, 0x07, 0x27, 0xbf, 0xbe, 0xc4, 0x32, 0xa6, 0xad, 0xf8, 0x1c, 0x40, 0x7a, 0xa9, 0x9d, 0xf4, 0xfd, 0xfb, 0x8d, 0x6f, 0xf1, 0x83, 0x68, 0x0c, 0x4a, 0x0e}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x0d, 0x59, 0x65, 0x0f, 0x7c, 0x0c, 0xaf, 0xc4, 0xc3, 0x7e, 0xd7, 0xa0, 0xb8, 0x92, 0x7a, 0xb3, 0xda, 0xd1, 0xa6, 0xb2, 0x6f, 0x02, 0xfb, 0x45, 0xc8, 0x5e, 0x68, 0x13, 0x04, 0x39, 0x1d, 0x9c}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} + gcp_GCPSEVES = M{1: {Expected: []byte{0x36, 0x95, 0xdc, 0xc5, 0x5e, 0x3a, 0xa3, 0x40, 0x27, 0xc2, 0x77, 0x93, 0xc8, 0x5c, 0x72, 0x3c, 0x69, 0x7d, 0x70, 0x8c, 0x42, 0xd1, 0xf7, 0x3b, 0xd6, 0xfa, 0x4f, 0x26, 0x60, 0x8a, 0x5b, 0x24}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x6a, 0xb9, 0xc8, 0xd3, 0x7d, 0x79, 0x49, 0xe8, 0x01, 0xa3, 0xe3, 0xf3, 0x18, 0xd3, 0x34, 0x62, 0xfb, 0xd4, 0x96, 0xf9, 0x61, 0xde, 0xd5, 0xc5, 0xe6, 0xfb, 0xa8, 0x83, 0xe6, 0x36, 0xb2, 0x1c}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x72, 0x5c, 0x17, 0x8b, 0x8b, 0x62, 0x45, 0xa7, 0x69, 0xe4, 0x3b, 0xa3, 0xa5, 0x7b, 0xee, 0xb9, 0xcf, 0x68, 0x54, 0xdd, 0x7e, 0x9f, 0xe4, 0x48, 0xb7, 0xc5, 0xb0, 0x4d, 0xda, 0xc8, 0xdb, 0x87}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x78, 0x74, 0x49, 0x06, 0xa1, 0x9b, 0xa0, 0x15, 0x94, 0x58, 0xa5, 0x1c, 0x46, 0xcd, 0xa6, 0xe2, 0x39, 0x39, 0x44, 0x20, 0x54, 0xd5, 0xf3, 0xa4, 0x06, 0x7e, 0xa9, 0x8d, 0xe9, 0xd0, 0x04, 0x94}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} + gcp_GCPSEVSNP = M{1: {Expected: []byte{0x36, 0x95, 0xdc, 0xc5, 0x5e, 0x3a, 0xa3, 0x40, 0x27, 0xc2, 0x77, 0x93, 0xc8, 0x5c, 0x72, 0x3c, 0x69, 0x7d, 0x70, 0x8c, 0x42, 0xd1, 0xf7, 0x3b, 0xd6, 0xfa, 0x4f, 0x26, 0x60, 0x8a, 0x5b, 0x24}, ValidationOpt: WarnOnly}, 2: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 3: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 4: {Expected: []byte{0x04, 0xc0, 0x75, 0xd0, 0x9b, 0x7e, 0x4e, 0xcf, 0xb5, 0x16, 0x89, 0x6b, 0x26, 0x50, 0x9e, 0x33, 0xd1, 0xc1, 0x64, 0x10, 0x82, 0xd2, 0x4c, 0x95, 0xb1, 0x8d, 0x56, 0xec, 0x0c, 0x3d, 0x83, 0x25}, ValidationOpt: Enforce}, 6: {Expected: []byte{0x3d, 0x45, 0x8c, 0xfe, 0x55, 0xcc, 0x03, 0xea, 0x1f, 0x44, 0x3f, 0x15, 0x62, 0xbe, 0xec, 0x8d, 0xf5, 0x1c, 0x75, 0xe1, 0x4a, 0x9f, 0xcf, 0x9a, 0x72, 0x34, 0xa1, 0x3f, 0x19, 0x8e, 0x79, 0x69}, ValidationOpt: WarnOnly}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0xc3, 0x94, 0x1e, 0x53, 0xda, 0x29, 0x96, 0x2e, 0xfc, 0x9f, 0xf4, 0x4e, 0x9d, 0xde, 0x62, 0x4c, 0xdf, 0xa2, 0x71, 0xed, 0x15, 0x49, 0x19, 0xed, 0xe8, 0xec, 0xf2, 0xbc, 0x67, 0x80, 0x8b, 0x62}, ValidationOpt: Enforce}, 11: {Expected: []byte{0xbd, 0xd1, 0x4b, 0x35, 0xe0, 0xe6, 0x03, 0xea, 0xb0, 0xaa, 0x55, 0x5b, 0x4b, 0x94, 0xdc, 0x1f, 0x53, 0x46, 0x47, 0x58, 0xc8, 0x8b, 0xad, 0x00, 0x7f, 0x0f, 0x06, 0x94, 0xa5, 0xa8, 0xfa, 0x6a}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} + openstack_QEMUVTPM = M{4: {Expected: []byte{0x5d, 0x50, 0x7b, 0xa0, 0x21, 0xd3, 0xbe, 0xba, 0x8f, 0x3b, 0x32, 0xff, 0xa9, 0x44, 0xa3, 0xdb, 0x16, 0x39, 0x91, 0xdb, 0xad, 0x0f, 0xd7, 0xe7, 0xf0, 0x50, 0x19, 0xa7, 0x49, 0xc0, 0x32, 0xbd}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x33, 0x6f, 0x51, 0x2a, 0x64, 0x17, 0x0c, 0x97, 0xe9, 0x0f, 0x5c, 0xa3, 0xdf, 0x81, 0x6c, 0x81, 0xb7, 0xd2, 0x2f, 0xec, 0xc6, 0x97, 0x15, 0x54, 0x51, 0xff, 0x6b, 0x27, 0x9d, 0x45, 0x80, 0xc6}, ValidationOpt: Enforce}, 11: {Expected: []byte{0xa8, 0x42, 0xd2, 0x06, 0x43, 0x6c, 0x79, 0x7c, 0x72, 0x44, 0x7f, 0xa1, 0x01, 0x87, 0xa3, 0x74, 0x61, 0xbb, 0x32, 0xd3, 0x92, 0x52, 0xb1, 0x2b, 0x24, 0xe9, 0xaf, 0x0f, 0x28, 0x7b, 0x03, 0xd2}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 14: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: WarnOnly}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} qemu_QEMUTDX M - qemu_QEMUVTPM = M{4: {Expected: []byte{0xad, 0xe9, 0xee, 0x15, 0xc4, 0xdc, 0x47, 0x0f, 0x75, 0x4f, 0xdd, 0x24, 0xa0, 0x1d, 0x3e, 0x27, 0x7b, 0x57, 0xc0, 0x6d, 0x90, 0xcd, 0x05, 0xbe, 0x79, 0x3e, 0xe1, 0x1a, 0x22, 0xbe, 0x8d, 0x2b}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x6c, 0x28, 0xbd, 0xa4, 0x3d, 0x02, 0xad, 0x3d, 0xef, 0x02, 0x3f, 0x6e, 0xdb, 0x8d, 0xdf, 0xc8, 0xb2, 0x06, 0x23, 0xa0, 0xe5, 0x96, 0x5d, 0x50, 0xfc, 0xff, 0x29, 0xe2, 0x82, 0xbf, 0xcd, 0xc2}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x0a, 0x5a, 0x99, 0x07, 0x21, 0x16, 0xf6, 0xbb, 0xc6, 0x9d, 0x66, 0xfa, 0x09, 0x9a, 0x48, 0x58, 0x28, 0xd4, 0xd2, 0x99, 0x5e, 0x55, 0x60, 0x82, 0x73, 0x54, 0xc3, 0x46, 0xc3, 0x26, 0xb9, 0xf9}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} + qemu_QEMUVTPM = M{4: {Expected: []byte{0x72, 0x44, 0xa4, 0x36, 0x64, 0x9a, 0x60, 0xf7, 0xbc, 0x3a, 0xb6, 0x88, 0xe0, 0xd4, 0xbf, 0x81, 0xe5, 0xc7, 0xc9, 0x88, 0xf8, 0x16, 0x26, 0x09, 0x64, 0xb5, 0x08, 0xea, 0xef, 0x17, 0xf0, 0x1e}, ValidationOpt: Enforce}, 8: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 9: {Expected: []byte{0x2a, 0x00, 0x6f, 0x8c, 0x3d, 0xa3, 0xa2, 0x80, 0x42, 0x2c, 0x7c, 0xbb, 0x4f, 0x1e, 0x47, 0x08, 0x8d, 0x91, 0x97, 0xec, 0xf9, 0x39, 0x0f, 0xd8, 0x96, 0x5b, 0x50, 0x12, 0xed, 0xf6, 0x1e, 0x14}, ValidationOpt: Enforce}, 11: {Expected: []byte{0x0d, 0x4e, 0xfa, 0xf6, 0xa3, 0xf4, 0x74, 0xa2, 0xc9, 0x10, 0xda, 0xae, 0xea, 0x8e, 0x98, 0x7c, 0x1f, 0x61, 0x38, 0x95, 0xa4, 0x11, 0x24, 0x3f, 0x16, 0x20, 0x4c, 0x75, 0x24, 0xc2, 0x35, 0x10}, ValidationOpt: Enforce}, 12: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 13: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}, 15: {Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ValidationOpt: Enforce}} ) diff --git a/internal/attestation/measurements/measurements_test.go b/internal/attestation/measurements/measurements_test.go index 7bb03c2..8665304 100644 --- a/internal/attestation/measurements/measurements_test.go +++ b/internal/attestation/measurements/measurements_test.go @@ -8,7 +8,6 @@ package measurements import ( "bytes" - "context" "encoding/json" "io" "net/http" @@ -458,7 +457,7 @@ func TestMeasurementsFetchAndVerify(t *testing.T) { require.NoError(err) hash, err := m.fetchAndVerify( - context.Background(), client, verifier, + t.Context(), client, verifier, measurementsURL, signatureURL, tc.imageVersion, tc.csp, diff --git a/internal/attestation/measurements/overrides.go b/internal/attestation/measurements/overrides.go index f176e2e..d883564 100644 --- a/internal/attestation/measurements/overrides.go +++ b/internal/attestation/measurements/overrides.go @@ -78,7 +78,7 @@ var measurementOverridesForAttestationVariant = map[string]measurementOverride{ }, variant.AWSSEVSNP{}.String(): { ValueOverrides: []valueOverride{ - {Index: 0, Value: []byte{0x7b, 0x06, 0x8c, 0x0c, 0x3a, 0xc2, 0x9a, 0xfe, 0x26, 0x41, 0x34, 0x53, 0x6b, 0x9b, 0xe2, 0x6f, 0x1d, 0x4c, 0xcd, 0x57, 0x5b, 0x88, 0xd3, 0xc3, 0xce, 0xab, 0xf3, 0x6a, 0xc9, 0x9c, 0x02, 0x78}}, + {Index: 0, Value: []byte{0xd6, 0xdf, 0x85, 0x53, 0x58, 0xf5, 0xb1, 0x0f, 0x06, 0xf0, 0xfa, 0xb3, 0xf4, 0x08, 0xad, 0x26, 0xcd, 0x16, 0x5a, 0x29, 0x49, 0xba, 0xd6, 0x9e, 0x2c, 0xc7, 0x56, 0x92, 0x52, 0x9e, 0x66, 0x2a}}, }, }, } diff --git a/internal/attestation/tdx/validator.go b/internal/attestation/tdx/validator.go index ed0566a..319085a 100644 --- a/internal/attestation/tdx/validator.go +++ b/internal/attestation/tdx/validator.go @@ -16,7 +16,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/attestation/measurements" "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" "github.com/flashbots/cvm-reverse-proxy/internal/config" - "github.com/edgelesssys/go-tdx-qpl/verification" "github.com/edgelesssys/go-tdx-qpl/verification/types" ) @@ -53,7 +52,7 @@ func (v *Validator) Validate(ctx context.Context, attDocRaw []byte, nonce []byte v.log.Info("Validating attestation document") defer func() { if err != nil { - v.log.Warn(fmt.Sprintf("Failed to validate TDX attestation document: %s", err)) + v.log.Warn(fmt.Sprintf("Failed to validate attestation document: %s", err)) } }() diff --git a/internal/attestation/variant/variant.go b/internal/attestation/variant/variant.go index 3692751..7c05050 100644 --- a/internal/attestation/variant/variant.go +++ b/internal/attestation/variant/variant.go @@ -101,28 +101,12 @@ type Variant interface { } func FromOID(oid asn1.ObjectIdentifier) (Variant, error) { - switch oid.String() { - case Dummy{}.OID().String(): - return Dummy{}, nil - case AWSSEVSNP{}.OID().String(): - return AWSSEVSNP{}, nil - case AWSNitroTPM{}.OID().String(): - return AWSNitroTPM{}, nil - case GCPSEVES{}.OID().String(): - return GCPSEVES{}, nil - case GCPSEVSNP{}.OID().String(): - return GCPSEVSNP{}, nil - case AzureSEVSNP{}.OID().String(): - return AzureSEVSNP{}, nil - case AzureTrustedLaunch{}.OID().String(): - return AzureTrustedLaunch{}, nil - case AzureTDX{}.OID().String(): - return AzureTDX{}, nil - case QEMUVTPM{}.OID().String(): - return QEMUVTPM{}, nil - case QEMUTDX{}.OID().String(): - return QEMUTDX{}, nil + for _, v := range []Variant{Dummy{},AWSSEVSNP{}, AWSNitroTPM{}, GCPSEVES{}, GCPSEVSNP{}, AzureSEVSNP{}, AzureTrustedLaunch{}, AzureTDX{}, QEMUVTPM{}, QEMUTDX{}} { + if v.OID().Equal(oid) { + return v, nil + } } + return nil, fmt.Errorf("unknown OID: %q", oid) } diff --git a/internal/attestation/vtpm/attestation.go b/internal/attestation/vtpm/attestation.go index 3cab272..d373920 100644 --- a/internal/attestation/vtpm/attestation.go +++ b/internal/attestation/vtpm/attestation.go @@ -224,7 +224,6 @@ func (v *Validator) Validate(ctx context.Context, attDocRaw []byte, nonce []byte if err := json.Unmarshal(attDocRaw, &attDoc); err != nil { return nil, fmt.Errorf("unmarshaling TPM attestation document: %w", err) } - v.log.Debug(fmt.Sprintf("Attestation document: %s", string(attDocRaw))) extraData := attestation.MakeExtraData(attDoc.UserData, nonce) diff --git a/internal/attestation/vtpm/attestation_test.go b/internal/attestation/vtpm/attestation_test.go index 7634e6c..9975e8e 100644 --- a/internal/attestation/vtpm/attestation_test.go +++ b/internal/attestation/vtpm/attestation_test.go @@ -90,7 +90,7 @@ func TestValidate(t *testing.T) { nonce := []byte{1, 2, 3, 4} challenge := []byte("Constellation") - ctx := context.Background() + ctx := t.Context() attDocRaw, err := issuer.Issue(ctx, challenge, nonce) require.NoError(err) @@ -347,7 +347,7 @@ func TestFailIssuer(t *testing.T) { tc.issuer.log = logger.NewTest(t) - _, err := tc.issuer.Issue(context.Background(), tc.userData, tc.nonce) + _, err := tc.issuer.Issue(t.Context(), tc.userData, tc.nonce) assert.Error(err) }) } @@ -478,7 +478,6 @@ func TestGetSelectedMeasurements(t *testing.T) { type testAttestationLogger struct { infos []string - debugs []string warnings []string } @@ -486,10 +485,6 @@ func (w *testAttestationLogger) Info(format string, args ...any) { w.infos = append(w.infos, fmt.Sprintf(format, args...)) } -func (w *testAttestationLogger) Debug(format string, args ...any) { - w.debugs = append(w.debugs, fmt.Sprintf(format, args...)) -} - func (w *testAttestationLogger) Warn(format string, args ...any) { w.warnings = append(w.warnings, fmt.Sprintf(format, args...)) } diff --git a/internal/cloud/aws/aws_test.go b/internal/cloud/aws/aws_test.go index 6d3be00..a97ef66 100644 --- a/internal/cloud/aws/aws_test.go +++ b/internal/cloud/aws/aws_test.go @@ -19,11 +19,10 @@ import ( elbTypes "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2/types" rgtTypes "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi/types" + "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" "github.com/flashbots/cvm-reverse-proxy/internal/cloud" "github.com/flashbots/cvm-reverse-proxy/internal/cloud/metadata" "github.com/flashbots/cvm-reverse-proxy/internal/role" - - "github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi" "github.com/stretchr/testify/assert" ) @@ -186,7 +185,7 @@ func TestSelf(t *testing.T) { ec2: tc.ec2API, } - self, err := m.Self(context.Background()) + self, err := m.Self(t.Context()) if tc.wantErr { assert.Error(err) return @@ -432,7 +431,7 @@ func TestList(t *testing.T) { ec2: tc.ec2, } - list, err := m.List(context.Background()) + list, err := m.List(t.Context()) if tc.wantErr { assert.Error(err) return @@ -695,7 +694,7 @@ func TestGetLoadBalancerEndpoint(t *testing.T) { ec2: successfulEC2, } - gotHost, gotPort, err := m.GetLoadBalancerEndpoint(context.Background()) + gotHost, gotPort, err := m.GetLoadBalancerEndpoint(t.Context()) if tc.wantErr { assert.Error(err) return diff --git a/internal/cloud/azure/BUILD.bazel b/internal/cloud/azure/BUILD.bazel index b0248bc..6026ada 100644 --- a/internal/cloud/azure/BUILD.bazel +++ b/internal/cloud/azure/BUILD.bazel @@ -25,11 +25,9 @@ go_library( ] + select({ "@io_bazel_rules_go//go/platform:android": [ "@io_k8s_kubernetes//pkg/util/iptables", - "@io_k8s_utils//exec", ], "@io_bazel_rules_go//go/platform:linux": [ "@io_k8s_kubernetes//pkg/util/iptables", - "@io_k8s_utils//exec", ], "//conditions:default": [], }), diff --git a/internal/cloud/azure/azure_test.go b/internal/cloud/azure/azure_test.go index 98ae781..509f385 100644 --- a/internal/cloud/azure/azure_test.go +++ b/internal/cloud/azure/azure_test.go @@ -11,14 +11,13 @@ import ( "errors" "testing" - "github.com/flashbots/cvm-reverse-proxy/internal/cloud" - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/metadata" - "github.com/flashbots/cvm-reverse-proxy/internal/role" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v6" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v6" + "github.com/flashbots/cvm-reverse-proxy/internal/cloud" + "github.com/flashbots/cvm-reverse-proxy/internal/cloud/metadata" + "github.com/flashbots/cvm-reverse-proxy/internal/role" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" @@ -151,7 +150,7 @@ func TestGetInstance(t *testing.T) { scaleSetsVMAPI: tc.scaleSetsVMAPI, netIfacAPI: tc.networkInterfacesAPI, } - instance, err := metadata.getInstance(context.Background(), tc.providerID) + instance, err := metadata.getInstance(t.Context(), tc.providerID) if tc.wantErr { assert.Error(err) return @@ -187,7 +186,7 @@ func TestUID(t *testing.T) { cloud := &Cloud{ imds: tc.imdsAPI, } - uid, err := cloud.UID(context.Background()) + uid, err := cloud.UID(t.Context()) if tc.wantErr { assert.Error(err) return @@ -223,7 +222,7 @@ func TestInitSecretHash(t *testing.T) { cloud := &Cloud{ imds: tc.imdsAPI, } - initSecretHash, err := cloud.InitSecretHash(context.Background()) + initSecretHash, err := cloud.InitSecretHash(t.Context()) if tc.wantErr { assert.Error(err) return @@ -411,7 +410,7 @@ func TestList(t *testing.T) { scaleSetsAPI: tc.scaleSetsAPI, scaleSetsVMAPI: tc.scaleSetsVMAPI, } - instances, err := azureMetadata.List(context.Background()) + instances, err := azureMetadata.List(t.Context()) if tc.wantErr { assert.Error(err) @@ -474,7 +473,7 @@ func TestGetNetworkSecurityGroupName(t *testing.T) { metadata := Cloud{ secGroupAPI: tc.securityGroupsAPI, } - name, err := metadata.getNetworkSecurityGroupName(context.Background(), "resource-group", "uid") + name, err := metadata.getNetworkSecurityGroupName(t.Context(), "resource-group", "uid") if tc.wantErr { assert.Error(err) return @@ -548,7 +547,7 @@ func TestGetSubnetworkCIDR(t *testing.T) { imds: tc.imdsAPI, virtNetAPI: tc.virtualNetworksAPI, } - subnetworkCIDR, err := metadata.getSubnetworkCIDR(context.Background()) + subnetworkCIDR, err := metadata.getSubnetworkCIDR(t.Context()) if tc.wantErr { assert.Error(err) return @@ -709,7 +708,7 @@ func TestGetLoadBalancerEndpoint(t *testing.T) { loadBalancerAPI: tc.loadBalancerAPI, pubIPAPI: tc.publicIPAddressesAPI, } - gotHost, gotPort, err := metadata.GetLoadBalancerEndpoint(context.Background()) + gotHost, gotPort, err := metadata.GetLoadBalancerEndpoint(t.Context()) if tc.wantErr { assert.Error(err) return diff --git a/internal/cloud/azure/imds_test.go b/internal/cloud/azure/imds_test.go index a8f50cd..c6d4184 100644 --- a/internal/cloud/azure/imds_test.go +++ b/internal/cloud/azure/imds_test.go @@ -17,7 +17,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/cloud" "github.com/flashbots/cvm-reverse-proxy/internal/role" - "github.com/stretchr/testify/assert" "google.golang.org/grpc/test/bufconn" ) @@ -215,7 +214,7 @@ func TestIMDSClient(t *testing.T) { } iClient := IMDSClient{client: &hClient} - ctx := context.Background() + ctx := t.Context() id, err := iClient.providerID(ctx) if tc.wantProviderIDErr { diff --git a/internal/cloud/azure/iptables_linux.go b/internal/cloud/azure/iptables_linux.go index 29112bd..e05e31a 100644 --- a/internal/cloud/azure/iptables_linux.go +++ b/internal/cloud/azure/iptables_linux.go @@ -9,70 +9,4 @@ SPDX-License-Identifier: AGPL-3.0-only package azure import ( - "context" - "fmt" - "log/slog" - - "github.com/flashbots/cvm-reverse-proxy/internal/role" - - "k8s.io/kubernetes/pkg/util/iptables" - "k8s.io/utils/exec" ) - -// PrepareControlPlaneNode sets up iptables for the control plane node only -// if an internal load balancer is used. -// -// This is needed since during `kubeadm init` the API server must talk to the -// kubeAPIEndpoint, which is the load balancer IP address. During that time, the -// only healthy VM is the VM itself. Therefore, traffic is sent to the load balancer -// and the 5-tuple is (VM IP, , LB IP, 6443, TCP). -// Now the load balancer does not re-write the source IP address only the destination (DNAT). -// Therefore the 5-tuple is (VM IP, , VM IP, 6443, TCP). -// Now the VM responds to the SYN packet with a SYN-ACK packet, but the outgoing -// connection waits on a response from the load balancer and not the VM therefore -// dropping the packet. -// -// OpenShift also uses the same mechanism to redirect traffic to the API server: -// https://github.com/openshift/machine-config-operator/blob/e453bd20bac0e48afa74e9a27665abaf454d93cd/templates/master/00-master/azure/files/opt-libexec-openshift-azure-routes-sh.yaml -func (c *Cloud) PrepareControlPlaneNode(ctx context.Context, log *slog.Logger) error { - selfMetadata, err := c.Self(ctx) - if err != nil { - return fmt.Errorf("failed to get self metadata: %w", err) - } - - // skipping iptables setup for worker nodes - if selfMetadata.Role != role.ControlPlane { - log.Info("not a control plane node, skipping iptables setup") - return nil - } - - // skipping iptables setup if no internal LB exists e.g. - // for public LB architectures - loadbalancerIP, err := c.getLoadBalancerPrivateIP(ctx) - if err != nil { - log.With(slog.Any("error", err)).Warn("skipping iptables setup, failed to get load balancer private IP") - return nil - } - - log.Info(fmt.Sprintf("Setting up iptables for control plane node with load balancer IP %s", loadbalancerIP)) - iptablesExec := iptables.New(exec.New(), iptables.ProtocolIPv4) - - const chainName = "azure-lb-nat" - if _, err := iptablesExec.EnsureChain(iptables.TableNAT, chainName); err != nil { - return fmt.Errorf("failed to create iptables chain: %w", err) - } - - if _, err := iptablesExec.EnsureRule(iptables.Append, iptables.TableNAT, "PREROUTING", "-j", chainName); err != nil { - return fmt.Errorf("failed to add rule to iptables chain: %w", err) - } - - if _, err := iptablesExec.EnsureRule(iptables.Append, iptables.TableNAT, "OUTPUT", "-j", chainName); err != nil { - return fmt.Errorf("failed to add rule to iptables chain: %w", err) - } - - if _, err := iptablesExec.EnsureRule(iptables.Append, iptables.TableNAT, chainName, "--dst", loadbalancerIP, "-p", "tcp", "--dport", "6443", "-j", "REDIRECT"); err != nil { - return fmt.Errorf("failed to add rule to iptables chain: %w", err) - } - - return nil -} diff --git a/internal/cloud/gcp/gcp_test.go b/internal/cloud/gcp/gcp_test.go index 26e63b6..1be29ba 100644 --- a/internal/cloud/gcp/gcp_test.go +++ b/internal/cloud/gcp/gcp_test.go @@ -11,11 +11,10 @@ import ( "errors" "testing" + "cloud.google.com/go/compute/apiv1/computepb" "github.com/flashbots/cvm-reverse-proxy/internal/cloud" "github.com/flashbots/cvm-reverse-proxy/internal/cloud/metadata" "github.com/flashbots/cvm-reverse-proxy/internal/role" - - "cloud.google.com/go/compute/apiv1/computepb" gax "github.com/googleapis/gax-go/v2" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -173,7 +172,7 @@ func TestGetInstance(t *testing.T) { instanceAPI: &tc.instanceAPI, subnetAPI: &tc.subnetAPI, } - instance, err := cloud.getInstance(context.Background(), tc.projectID, tc.zone, tc.instanceName) + instance, err := cloud.getInstance(t.Context(), tc.projectID, tc.zone, tc.instanceName) if tc.wantErr { assert.Error(err) @@ -475,7 +474,7 @@ func TestGetLoadbalancerEndpoint(t *testing.T) { regionalForwardingRulesAPI: &tc.regionalForwardingRulesAPI, } - gotHost, gotPort, err := cloud.GetLoadBalancerEndpoint(context.Background()) + gotHost, gotPort, err := cloud.GetLoadBalancerEndpoint(t.Context()) if tc.wantErr { assert.Error(err) return @@ -811,7 +810,7 @@ func TestList(t *testing.T) { zoneAPI: &tc.zoneAPI, } - instances, err := cloud.List(context.Background()) + instances, err := cloud.List(t.Context()) if tc.wantErr { assert.Error(err) return @@ -916,7 +915,7 @@ func TestZones(t *testing.T) { assert.Empty(cloud.zoneCache) - gotZones, err := cloud.zones(context.Background(), "someProject", "someregion-west3") + gotZones, err := cloud.zones(t.Context(), "someProject", "someregion-west3") if tc.wantErr { assert.Error(err) return @@ -1067,7 +1066,7 @@ func TestUID(t *testing.T) { instanceAPI: &tc.instanceAPI, } - uid, err := cloud.UID(context.Background()) + uid, err := cloud.UID(t.Context()) if tc.wantErr { assert.Error(err) return @@ -1171,7 +1170,7 @@ func TestInitSecretHash(t *testing.T) { instanceAPI: &tc.instanceAPI, } - initSecretHash, err := cloud.InitSecretHash(context.Background()) + initSecretHash, err := cloud.InitSecretHash(t.Context()) if tc.wantErr { assert.Error(err) return diff --git a/internal/cloud/openstack/imds.go b/internal/cloud/openstack/imds.go index 9d1ca36..9d289e6 100644 --- a/internal/cloud/openstack/imds.go +++ b/internal/cloud/openstack/imds.go @@ -172,6 +172,20 @@ func (c *imdsClient) userDomainName(ctx context.Context) (string, error) { return c.userDataCache.UserDomainName, nil } +func (c *imdsClient) regionName(ctx context.Context) (string, error) { + if c.timeForUpdate(c.cacheTime) || c.userDataCache.RegionName == "" { + if err := c.update(ctx); err != nil { + return "", err + } + } + + if c.userDataCache.RegionName == "" { + return "", errors.New("unable to get user domain name") + } + + return c.userDataCache.RegionName, nil +} + func (c *imdsClient) username(ctx context.Context) (string, error) { if c.timeForUpdate(c.cacheTime) || c.userDataCache.Username == "" { if err := c.update(ctx); err != nil { @@ -295,6 +309,7 @@ type metadataTags struct { type userDataResponse struct { AuthURL string `json:"openstack-auth-url,omitempty"` UserDomainName string `json:"openstack-user-domain-name,omitempty"` + RegionName string `json:"openstack-region-name,omitempty"` Username string `json:"openstack-username,omitempty"` Password string `json:"openstack-password,omitempty"` LoadBalancerEndpoint string `json:"openstack-load-balancer-endpoint,omitempty"` diff --git a/internal/cloud/openstack/imds_test.go b/internal/cloud/openstack/imds_test.go index 3fe2d67..b4db4d8 100644 --- a/internal/cloud/openstack/imds_test.go +++ b/internal/cloud/openstack/imds_test.go @@ -18,7 +18,6 @@ import ( "time" "github.com/flashbots/cvm-reverse-proxy/internal/role" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -177,7 +176,7 @@ func TestProviderID(t *testing.T) { cacheTime: tc.cacheTime, } - result, err := tu.method(imds, context.Background()) + result, err := tu.method(imds, t.Context()) if tc.wantErr { assert.Error(err) @@ -265,7 +264,7 @@ func TestRole(t *testing.T) { cacheTime: tc.cacheTime, } - result, err := imds.role(context.Background()) + result, err := imds.role(t.Context()) if tc.wantErr { assert.Error(err) @@ -337,7 +336,7 @@ func TestVPCIP(t *testing.T) { vpcIPCacheTime: tc.cacheTime, } - result, err := imds.vpcIP(context.Background()) + result, err := imds.vpcIP(t.Context()) if tc.wantErr { assert.Error(err) diff --git a/internal/cloud/openstack/openstack.go b/internal/cloud/openstack/openstack.go index e9d435f..f2a4707 100644 --- a/internal/cloud/openstack/openstack.go +++ b/internal/cloud/openstack/openstack.go @@ -17,7 +17,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/cloud/metadata" "github.com/flashbots/cvm-reverse-proxy/internal/constants" "github.com/flashbots/cvm-reverse-proxy/internal/role" - "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/subnets" @@ -55,6 +54,10 @@ func New(ctx context.Context) (*MetadataClient, error) { if err != nil { return nil, fmt.Errorf("getting user domain name: %w", err) } + regionName, err := imds.regionName(ctx) + if err != nil { + return nil, fmt.Errorf("getting region name: %w", err) + } clientOpts := &clientconfig.ClientOpts{ AuthType: clientconfig.AuthV3Password, @@ -64,6 +67,7 @@ func New(ctx context.Context) (*MetadataClient, error) { Username: username, Password: password, }, + RegionName: regionName, } serversClient, err := clientconfig.NewServiceClient(ctx, "compute", clientOpts) diff --git a/internal/cloud/openstack/openstack_test.go b/internal/cloud/openstack/openstack_test.go index ade71f8..580f5e2 100644 --- a/internal/cloud/openstack/openstack_test.go +++ b/internal/cloud/openstack/openstack_test.go @@ -7,14 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only package openstack import ( - "context" "errors" "fmt" "testing" "github.com/flashbots/cvm-reverse-proxy/internal/cloud/metadata" "github.com/flashbots/cvm-reverse-proxy/internal/role" - "github.com/gophercloud/gophercloud/v2" "github.com/gophercloud/gophercloud/v2/openstack/compute/v2/servers" "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/networks" @@ -89,7 +87,7 @@ func TestSelf(t *testing.T) { c := &MetadataClient{imds: tc.imds} - got, err := c.Self(context.Background()) + got, err := c.Self(t.Context()) if tc.wantErr { assert.Error(err) @@ -385,7 +383,7 @@ func TestList(t *testing.T) { c := &MetadataClient{imds: tc.imds, api: tc.api} - got, err := c.List(context.Background()) + got, err := c.List(t.Context()) if tc.wantErr { assert.Error(err) @@ -419,7 +417,7 @@ func TestUID(t *testing.T) { c := &MetadataClient{imds: tc.imds} - got, err := c.UID(context.Background()) + got, err := c.UID(t.Context()) if tc.wantErr { assert.Error(err) @@ -453,7 +451,7 @@ func TestInitSecretHash(t *testing.T) { c := &MetadataClient{imds: tc.imds} - got, err := c.InitSecretHash(context.Background()) + got, err := c.InitSecretHash(t.Context()) if tc.wantErr { assert.Error(err) @@ -487,7 +485,7 @@ func TestGetLoadBalancerEndpoint(t *testing.T) { c := &MetadataClient{imds: tc.imds} - got, _, err := c.GetLoadBalancerEndpoint(context.Background()) + got, _, err := c.GetLoadBalancerEndpoint(t.Context()) if tc.wantErr { assert.Error(err) diff --git a/internal/config/config.go b/internal/config/config.go index 61c4217..1601406 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -188,6 +188,9 @@ type GCPConfig struct { // Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization ServiceAccountKeyPath string `yaml:"serviceAccountKeyPath" validate:"required"` // description: | + // GCP service account mail address. This is being attached to the VMs for authorization. + IAMServiceAccountVM string `yaml:"IAMServiceAccountVM"` + // description: | // Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage DeployCSIDriver *bool `yaml:"deployCSIDriver" validate:"required"` // description: | @@ -349,6 +352,7 @@ func Default() *Config { Region: "", Zone: "", ServiceAccountKeyPath: "", + IAMServiceAccountVM: "", DeployCSIDriver: toPtr(true), UseMarketplaceImage: toPtr(false), }, diff --git a/internal/config/config_doc.go b/internal/config/config_doc.go index d26af16..b87db6b 100644 --- a/internal/config/config_doc.go +++ b/internal/config/config_doc.go @@ -241,7 +241,7 @@ func init() { FieldName: "gcp", }, } - GCPConfigDoc.Fields = make([]encoder.Doc, 6) + GCPConfigDoc.Fields = make([]encoder.Doc, 7) GCPConfigDoc.Fields[0].Name = "project" GCPConfigDoc.Fields[0].Type = "string" GCPConfigDoc.Fields[0].Note = "" @@ -262,16 +262,21 @@ func init() { GCPConfigDoc.Fields[3].Note = "" GCPConfigDoc.Fields[3].Description = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization" GCPConfigDoc.Fields[3].Comments[encoder.LineComment] = "Path of service account key file. For required service account roles, see https://docs.edgeless.systems/constellation/getting-started/install#authorization" - GCPConfigDoc.Fields[4].Name = "deployCSIDriver" - GCPConfigDoc.Fields[4].Type = "bool" + GCPConfigDoc.Fields[4].Name = "IAMServiceAccountVM" + GCPConfigDoc.Fields[4].Type = "string" GCPConfigDoc.Fields[4].Note = "" - GCPConfigDoc.Fields[4].Description = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" - GCPConfigDoc.Fields[4].Comments[encoder.LineComment] = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" - GCPConfigDoc.Fields[5].Name = "useMarketplaceImage" + GCPConfigDoc.Fields[4].Description = "GCP service account mail address. This is being attached to the VMs for authorization." + GCPConfigDoc.Fields[4].Comments[encoder.LineComment] = "GCP service account mail address. This is being attached to the VMs for authorization." + GCPConfigDoc.Fields[5].Name = "deployCSIDriver" GCPConfigDoc.Fields[5].Type = "bool" GCPConfigDoc.Fields[5].Note = "" - GCPConfigDoc.Fields[5].Description = "Use the specified GCP Marketplace image offering." - GCPConfigDoc.Fields[5].Comments[encoder.LineComment] = "Use the specified GCP Marketplace image offering." + GCPConfigDoc.Fields[5].Description = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" + GCPConfigDoc.Fields[5].Comments[encoder.LineComment] = "Deploy Persistent Disk CSI driver with on-node encryption. For details see: https://docs.edgeless.systems/constellation/architecture/encrypted-storage" + GCPConfigDoc.Fields[6].Name = "useMarketplaceImage" + GCPConfigDoc.Fields[6].Type = "bool" + GCPConfigDoc.Fields[6].Note = "" + GCPConfigDoc.Fields[6].Description = "Use the specified GCP Marketplace image offering." + GCPConfigDoc.Fields[6].Comments[encoder.LineComment] = "Use the specified GCP Marketplace image offering." OpenStackConfigDoc.Type = "OpenStackConfig" OpenStackConfigDoc.Comments[encoder.LineComment] = "OpenStackConfig holds config information for OpenStack based Constellation deployments." diff --git a/internal/config/config_test.go b/internal/config/config_test.go index edd411b..1b7d969 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -30,7 +30,6 @@ import ( "github.com/flashbots/cvm-reverse-proxy/internal/file" "github.com/flashbots/cvm-reverse-proxy/internal/semver" "github.com/flashbots/cvm-reverse-proxy/internal/versions" - gosemver "golang.org/x/mod/semver" ) @@ -465,6 +464,7 @@ func TestValidate(t *testing.T) { gcp.Project = "test-project" gcp.Zone = "test-zone" gcp.ServiceAccountKeyPath = "test-key-path" + gcp.IAMServiceAccountVM = "example@example.com" cnf.Provider = ProviderConfig{} cnf.Provider.GCP = gcp cnf.Attestation.GCPSEVSNP.Measurements = measurements.M{ @@ -689,67 +689,80 @@ func TestValidInstanceTypeForProvider(t *testing.T) { testCases := map[string]struct { variant variant.Variant instanceTypes []string + providerConfig ProviderConfig expectedResult bool }{ "empty all": { variant: variant.Dummy{}, instanceTypes: []string{}, expectedResult: false, + providerConfig: ProviderConfig{}, }, "empty aws": { variant: variant.AWSSEVSNP{}, instanceTypes: []string{}, expectedResult: false, + providerConfig: ProviderConfig{}, }, "empty azure only CVMs": { variant: variant.AzureSEVSNP{}, instanceTypes: []string{}, expectedResult: false, + providerConfig: ProviderConfig{}, }, "empty azure with non-CVMs": { variant: variant.AzureTrustedLaunch{}, instanceTypes: []string{}, expectedResult: false, + providerConfig: ProviderConfig{}, }, "empty gcp": { variant: variant.GCPSEVES{}, instanceTypes: []string{}, expectedResult: false, + providerConfig: ProviderConfig{}, }, "azure only CVMs (SNP)": { variant: variant.AzureSEVSNP{}, instanceTypes: instancetypes.AzureSNPInstanceTypes, expectedResult: true, + providerConfig: ProviderConfig{}, }, "azure only CVMs (TDX)": { variant: variant.AzureTDX{}, instanceTypes: instancetypes.AzureTDXInstanceTypes, expectedResult: true, + providerConfig: ProviderConfig{}, }, "azure trusted launch VMs": { variant: variant.AzureTrustedLaunch{}, instanceTypes: instancetypes.AzureTrustedLaunchInstanceTypes, expectedResult: true, + providerConfig: ProviderConfig{}, }, "gcp": { variant: variant.GCPSEVES{}, instanceTypes: instancetypes.GCPInstanceTypes, expectedResult: true, + providerConfig: ProviderConfig{}, }, "gcp sev-snp": { variant: variant.GCPSEVSNP{}, instanceTypes: instancetypes.GCPInstanceTypes, expectedResult: true, + providerConfig: ProviderConfig{}, }, "put gcp when azure is set": { variant: variant.AzureSEVSNP{}, instanceTypes: instancetypes.GCPInstanceTypes, expectedResult: false, + providerConfig: ProviderConfig{}, }, "put azure when gcp is set": { variant: variant.GCPSEVES{}, instanceTypes: instancetypes.AzureSNPInstanceTypes, expectedResult: false, + providerConfig: ProviderConfig{}, }, // Testing every possible instance type for AWS is not feasible, so we just test a few based on known supported / unsupported families // Also serves as a test for checkIfInstanceInValidAWSFamilys @@ -757,31 +770,79 @@ func TestValidInstanceTypeForProvider(t *testing.T) { variant: variant.AWSSEVSNP{}, instanceTypes: []string{"c5.xlarge", "c5a.2xlarge", "c5a.16xlarge", "u-12tb1.112xlarge"}, expectedResult: false, // False because 2 two of the instances are not valid + providerConfig: ProviderConfig{}, }, "aws one valid instance one with too little vCPUs": { variant: variant.AWSSEVSNP{}, instanceTypes: []string{"c5.medium"}, expectedResult: false, + providerConfig: ProviderConfig{}, }, "aws graviton sub-family unsupported": { variant: variant.AWSSEVSNP{}, instanceTypes: []string{"m6g.xlarge", "r6g.2xlarge", "x2gd.xlarge", "g5g.8xlarge"}, expectedResult: false, + providerConfig: ProviderConfig{}, }, "aws combined two valid instances as one string": { variant: variant.AWSSEVSNP{}, instanceTypes: []string{"c5.xlarge, c5a.2xlarge"}, expectedResult: false, + providerConfig: ProviderConfig{}, }, "aws only CVMs": { variant: variant.AWSSEVSNP{}, instanceTypes: []string{"c6a.xlarge", "m6a.xlarge", "r6a.xlarge"}, expectedResult: true, + providerConfig: ProviderConfig{}, }, "aws nitroTPM VMs": { variant: variant.AWSNitroTPM{}, instanceTypes: []string{"c5.xlarge", "c5a.2xlarge", "c5a.16xlarge", "u-12tb1.112xlarge"}, expectedResult: true, + providerConfig: ProviderConfig{}, + }, + "stackit valid flavors": { + variant: variant.QEMUVTPM{}, + instanceTypes: []string{ + "m1a.2cd", + "m1a.4cd", + "m1a.8cd", + "m1a.16cd", + "m1a.30cd", + }, + expectedResult: true, + providerConfig: ProviderConfig{OpenStack: &OpenStackConfig{Cloud: "stackit"}}, + }, + "stackit not valid flavors": { + variant: variant.QEMUVTPM{}, + instanceTypes: []string{ + // removed the c which indicates a confidential flavor + "m1a.2d", + "m1a.4d", + "m1a.8d", + "m1a.16d", + "m1a.30d", + }, + expectedResult: false, + providerConfig: ProviderConfig{OpenStack: &OpenStackConfig{Cloud: "stackit"}}, + }, + "openstack cloud named test": { + variant: variant.QEMUVTPM{}, + instanceTypes: []string{ + "foo.bar", + "foo.bar1", + }, + expectedResult: true, + providerConfig: ProviderConfig{OpenStack: &OpenStackConfig{Cloud: "test"}}, + }, + "Qemutdx valid instance type": { + variant: variant.QEMUTDX{}, + instanceTypes: []string{ + "foo.bar", + }, + expectedResult: true, + providerConfig: ProviderConfig{QEMU: &QEMUConfig{}}, }, } for name, tc := range testCases { @@ -789,7 +850,7 @@ func TestValidInstanceTypeForProvider(t *testing.T) { assert := assert.New(t) for _, instanceType := range tc.instanceTypes { assert.Equal( - tc.expectedResult, validInstanceTypeForProvider(instanceType, tc.variant), + tc.expectedResult, validInstanceTypeForProvider(instanceType, tc.variant, tc.providerConfig), instanceType, ) } diff --git a/internal/config/image_enterprise.go b/internal/config/image_enterprise.go index 9142b62..5bcebcc 100644 --- a/internal/config/image_enterprise.go +++ b/internal/config/image_enterprise.go @@ -10,5 +10,5 @@ package config const ( // defaultImage is the default image to use. - defaultImage = "ref/main/stream/nightly/v2.18.0-pre.0.20240903162608-eab9aca26fca" + defaultImage = "ref/main/stream/nightly/v2.24.0-pre.0.20250515163144-ba9dcb87ed0a" ) diff --git a/internal/config/validation.go b/internal/config/validation.go index da37984..0382366 100644 --- a/internal/config/validation.go +++ b/internal/config/validation.go @@ -520,7 +520,7 @@ func (c *Config) translateMoreThanOneProviderError(ut ut.Translator, fe validato return t } -func validInstanceTypeForProvider(insType string, attestation variant.Variant) bool { +func validInstanceTypeForProvider(insType string, attestation variant.Variant, provider ProviderConfig) bool { switch attestation { case variant.AWSSEVSNP{}, variant.AWSNitroTPM{}: return isSupportedAWSInstanceType(insType, attestation.Equal(variant.AWSNitroTPM{})) @@ -549,6 +549,17 @@ func validInstanceTypeForProvider(insType string, attestation variant.Variant) b } } case variant.QEMUVTPM{}, variant.QEMUTDX{}: + // only allow confidential instances on stackit cloud using QEMU vTPM + if provider.OpenStack != nil { + if cloud := provider.OpenStack.Cloud; strings.ToLower(cloud) == "stackit" { + for _, instanceType := range instancetypes.STACKITInstanceTypes { + if insType == instanceType { + return true + } + } + return false + } + } return true } return false @@ -789,7 +800,7 @@ func (c *Config) validateNodeGroupZoneField(fl validator.FieldLevel) bool { } func (c *Config) validateInstanceType(fl validator.FieldLevel) bool { - return validInstanceTypeForProvider(fl.Field().String(), c.GetAttestationConfig().GetVariant()) + return validInstanceTypeForProvider(fl.Field().String(), c.GetAttestationConfig().GetVariant(), c.Provider) } func (c *Config) validateStateDiskTypeField(fl validator.FieldLevel) bool { diff --git a/internal/config/validation_test.go b/internal/config/validation_test.go index acb5ee5..ca9ebe1 100644 --- a/internal/config/validation_test.go +++ b/internal/config/validation_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/flashbots/cvm-reverse-proxy/internal/semver" - "github.com/stretchr/testify/assert" ) diff --git a/internal/constants/constants.go b/internal/constants/constants.go index c95c54e..1ae62e8 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -42,6 +42,10 @@ const ( DefaultWorkerGroupName = "worker_default" // CLIDebugLogFile is the name of the debug log file for constellation init/constellation apply. CLIDebugLogFile = "constellation-debug.log" + // SSHCAKeySuffix is the suffix used together with the DEKPrefix to derive an SSH CA key for emergency ssh access. + SSHCAKeySuffix = "ca_emergency_ssh" + // SSHCAKeyPath is the path to the emergency SSH CA key on the node. + SSHCAKeyPath = "/run/ssh/ssh_ca.pub" // // Ports. diff --git a/internal/crypto/BUILD.bazel b/internal/crypto/BUILD.bazel index 9fe7cfe..81f9491 100644 --- a/internal/crypto/BUILD.bazel +++ b/internal/crypto/BUILD.bazel @@ -6,7 +6,10 @@ go_library( srcs = ["crypto.go"], importpath = "cvm-reverse-proxy/internal/crypto", visibility = ["//:__subpackages__"], - deps = ["@org_golang_x_crypto//hkdf"], + deps = [ + "@org_golang_x_crypto//hkdf", + "@org_golang_x_crypto//ssh", + ], ) go_test( diff --git a/internal/crypto/crypto.go b/internal/crypto/crypto.go index 081e25d..0a88ec2 100644 --- a/internal/crypto/crypto.go +++ b/internal/crypto/crypto.go @@ -9,6 +9,7 @@ package crypto import ( "bytes" + "crypto/ed25519" "crypto/rand" "crypto/sha256" "crypto/x509" @@ -18,6 +19,7 @@ import ( "math/big" "golang.org/x/crypto/hkdf" + "golang.org/x/crypto/ssh" ) const ( @@ -62,6 +64,19 @@ func GenerateRandomBytes(length int) ([]byte, error) { return nonce, nil } +// GenerateEmergencySSHCAKey creates a CA that is used to sign keys for emergency ssh access. +func GenerateEmergencySSHCAKey(seed []byte) (ssh.Signer, error) { + _, priv, err := ed25519.GenerateKey(bytes.NewReader(seed)) + if err != nil { + return nil, err + } + ca, err := ssh.NewSignerFromSigner(priv) + if err != nil { + return nil, err + } + return ca, nil +} + // PemToX509Cert takes a list of PEM-encoded certificates, parses the first one and returns it // as an x.509 certificate. func PemToX509Cert(raw []byte) (*x509.Certificate, error) { diff --git a/internal/crypto/crypto_test.go b/internal/crypto/crypto_test.go index 8c4a6bf..0b487d8 100644 --- a/internal/crypto/crypto_test.go +++ b/internal/crypto/crypto_test.go @@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only package crypto import ( + "crypto/ed25519" "crypto/x509" "testing" "github.com/flashbots/cvm-reverse-proxy/internal/crypto/testvector" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" @@ -122,6 +122,47 @@ func TestGenerateRandomBytes(t *testing.T) { assert.Len(n3, 16) } +func TestGenerateEmergencySSHCAKey(t *testing.T) { + nullKey := make([]byte, ed25519.SeedSize) + + testCases := map[string]struct { + key []byte + wantErr bool + }{ + "key length = 0": { + key: make([]byte, 0), + wantErr: true, + }, + "valid key": { + key: nullKey, + }, + "nil input": { + key: nil, + wantErr: true, + }, + "long key": { + key: make([]byte, 256), + }, + "key too short": { + key: make([]byte, ed25519.SeedSize-1), + wantErr: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + _, err := GenerateEmergencySSHCAKey(tc.key) + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + } + }) + } +} + func TestPemToX509Cert(t *testing.T) { testCases := map[string]struct { pemCert []byte diff --git a/internal/cryptsetup/BUILD.bazel b/internal/cryptsetup/BUILD.bazel deleted file mode 100644 index 6dc33a6..0000000 --- a/internal/cryptsetup/BUILD.bazel +++ /dev/null @@ -1,26 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "cryptsetup", - srcs = [ - "cryptsetup.go", - "cryptsetup_cgo.go", - "cryptsetup_cross.go", - ], - # keep - cdeps = [ - "@//nix/cc:cryptsetup", - ], - cgo = True, - importpath = "cvm-reverse-proxy/internal/cryptsetup", - visibility = ["//:__subpackages__"], - deps = select({ - "@io_bazel_rules_go//go/platform:android": [ - "@com_github_martinjungblut_go_cryptsetup//:go-cryptsetup", - ], - "@io_bazel_rules_go//go/platform:linux": [ - "@com_github_martinjungblut_go_cryptsetup//:go-cryptsetup", - ], - "//conditions:default": [], - }), -) diff --git a/internal/cryptsetup/cryptsetup.go b/internal/cryptsetup/cryptsetup.go deleted file mode 100644 index a61fb83..0000000 --- a/internal/cryptsetup/cryptsetup.go +++ /dev/null @@ -1,358 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package cryptsetup provides a wrapper around libcryptsetup. -The package is used to manage encrypted disks for Constellation. - -Since libcryptsetup is not thread safe, this package uses a global lock to prevent concurrent use. -There should only be one instance using this package per process. -*/ -package cryptsetup - -import ( - "encoding/json" - "errors" - "fmt" - "strings" - "sync" - "time" -) - -const ( - // ConstellationStateDiskTokenID is the ID of Constellation's state disk token. - ConstellationStateDiskTokenID = 0 - // SetDiskInitialized is a flag to set the Constellation state disk token to initialized. - SetDiskInitialized = true - // SetDiskNotInitialized is a flag to set the Constellation state disk token to not initialized. - SetDiskNotInitialized = false - - // FormatIntegrity is a flag to enable dm-integrity for a crypt device when formatting. - FormatIntegrity = true - // FormatNoIntegrity is a flag to disable dm-integrity for a crypt device when formatting. - FormatNoIntegrity = false - tmpDevicePrefix = "tmp-cryptsetup-" - mappedDevicePath = "/dev/mapper/" -) - -// packageLock is needed to block concurrent use of package functions, since libcryptsetup is not thread safe. -// See: https://gitlab.com/cryptsetup/cryptsetup/-/issues/710 -// -// https://stackoverflow.com/questions/30553386/cryptsetup-backend-safe-with-multithreading -var ( - packageLock = sync.Mutex{} - errDeviceNotOpen = errors.New("crypt device not open") - errDeviceAlreadyOpen = errors.New("crypt device already open") -) - -// CryptSetup manages encrypted devices. -type CryptSetup struct { - nameInit func(name string) (cryptDevice, error) - pathInit func(path string) (cryptDevice, error) - device cryptDevice -} - -// New creates a new CryptSetup. -// Before first use, call Init() or InitByName() to open a crypt device. -func New() *CryptSetup { - return &CryptSetup{ - nameInit: initByName, - pathInit: initByDevicePath, - } -} - -// Init opens a crypt device by device path. -func (c *CryptSetup) Init(devicePath string) (free func(), err error) { - packageLock.Lock() - defer packageLock.Unlock() - if c.device != nil { - return nil, errDeviceAlreadyOpen - } - device, err := c.pathInit(devicePath) - if err != nil { - return nil, fmt.Errorf("init cryptsetup by device path %q: %w", devicePath, err) - } - c.device = device - return c.Free, nil -} - -// InitByName opens an active crypt device using its mapped name. -func (c *CryptSetup) InitByName(name string) (free func(), err error) { - packageLock.Lock() - defer packageLock.Unlock() - if c.device != nil { - return nil, errDeviceAlreadyOpen - } - device, err := c.nameInit(name) - if err != nil { - return nil, fmt.Errorf("init cryptsetup by name %q: %w", name, err) - } - c.device = device - return c.Free, nil -} - -// Free frees resources from a previously opened crypt device. -func (c *CryptSetup) Free() { - packageLock.Lock() - defer packageLock.Unlock() - if c.device != nil { - c.device.Free() - c.device = nil - } -} - -// ActivateByPassphrase actives a crypt device using a passphrase. -func (c *CryptSetup) ActivateByPassphrase(deviceName string, keyslot int, passphrase string, flags int) error { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen - } - if err := c.device.ActivateByPassphrase(deviceName, keyslot, passphrase, flags); err != nil { - return fmt.Errorf("activating crypt device %q using passphrase: %w", deviceName, err) - } - return nil -} - -// ActivateByVolumeKey activates a crypt device using a volume key. -// Set volumeKey to empty string to use the internal key. -func (c *CryptSetup) ActivateByVolumeKey(deviceName, volumeKey string, volumeKeySize, flags int) error { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen - } - if err := c.device.ActivateByVolumeKey(deviceName, volumeKey, volumeKeySize, flags); err != nil { - return fmt.Errorf("activating crypt device %q using volume key: %w", deviceName, err) - } - return nil -} - -// Deactivate deactivates a crypt device, removing the mapped device. -func (c *CryptSetup) Deactivate(deviceName string) error { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen - } - if err := c.device.Deactivate(deviceName); err != nil { - return fmt.Errorf("deactivating crypt device %q: %w", deviceName, err) - } - return nil -} - -// Format formats a disk as a LUKS2 crypt device. -// Optionally set integrity to true to enable dm-integrity for the device. -func (c *CryptSetup) Format(integrity bool) error { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen - } - if err := format(c.device, integrity); err != nil { - return fmt.Errorf("formatting crypt device %q: %w", c.device.GetDeviceName(), err) - } - return nil -} - -// GetDeviceName gets the path to the underlying device. -func (c *CryptSetup) GetDeviceName() string { - return c.device.GetDeviceName() -} - -// GetUUID gets the device's LUKS2 UUID. -// The UUID is returned in lowercase. -func (c *CryptSetup) GetUUID() (string, error) { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return "", errDeviceNotOpen - } - uuid := c.device.GetUUID() - if uuid == "" { - return "", fmt.Errorf("unable to get UUID for device %q", c.device.GetDeviceName()) - } - return strings.ToLower(uuid), nil -} - -// KeyslotAddByVolumeKey adds a key slot to a device, allowing later activations using the chosen passphrase. -// Set volumeKey to empty string to use the internal key. -func (c *CryptSetup) KeyslotAddByVolumeKey(keyslot int, volumeKey string, passphrase string) error { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen - } - if err := c.device.KeyslotAddByVolumeKey(keyslot, volumeKey, passphrase); err != nil { - return fmt.Errorf("adding keyslot to device %q: %w", c.device.GetDeviceName(), err) - } - return nil -} - -// KeyslotChangeByPassphrase changes the passphrase for a keyslot. -func (c *CryptSetup) KeyslotChangeByPassphrase(currentKeyslot, newKeyslot int, currentPassphrase, newPassphrase string) error { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen - } - if err := c.device.KeyslotChangeByPassphrase(currentKeyslot, newKeyslot, currentPassphrase, newPassphrase); err != nil { - return fmt.Errorf("updating passphrase for device %q: %w", c.device.GetDeviceName(), err) - } - return nil -} - -// LoadLUKS2 loads the device as LUKS2 crypt device. -func (c *CryptSetup) LoadLUKS2() error { - if err := loadLUKS2(c.device); err != nil { - return fmt.Errorf("loading LUKS2 crypt device %q: %w", c.device.GetDeviceName(), err) - } - return nil -} - -// Resize resizes a device to the given size. -// name must be equal to the mapped device name. -// Set newSize to 0 to use the maximum available size. -func (c *CryptSetup) Resize(name string, newSize uint64) error { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen - } - if err := c.device.Resize(name, newSize); err != nil { - return fmt.Errorf("resizing crypt device %q: %w", c.device.GetDeviceName(), err) - } - return nil -} - -// TokenJSONGet gets the JSON data for a token. -func (c *CryptSetup) TokenJSONGet(token int) (string, error) { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return "", errDeviceNotOpen - } - json, err := c.device.TokenJSONGet(token) - if err != nil { - return "", fmt.Errorf("getting JSON data for token %d: %w", token, err) - } - return json, nil -} - -// TokenJSONSet sets the JSON data for a token. -// The JSON data must be a valid LUKS2 token. -// Required fields are: -// - type [string] the token type (tokens with luks2- prefix are reserved) -// - keyslots [array] the array of keyslot objects names that are assigned to the token -// -// Returns the allocated token ID on success. -func (c *CryptSetup) TokenJSONSet(token int, json string) (int, error) { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return -1, errDeviceNotOpen - } - tokenID, err := c.device.TokenJSONSet(token, json) - if err != nil { - return -1, fmt.Errorf("setting JSON data for token %d: %w", token, err) - } - return tokenID, nil -} - -// SetConstellationStateDiskToken sets the Constellation state disk token. -func (c *CryptSetup) SetConstellationStateDiskToken(diskIsInitialized bool) error { - token := constellationLUKS2Token{ - Type: "constellation-state-disk", - Keyslots: []string{}, - DiskIsInitialized: diskIsInitialized, - } - json, err := json.Marshal(token) - if err != nil { - return fmt.Errorf("marshaling token: %w", err) - } - if _, err := c.device.TokenJSONSet(ConstellationStateDiskTokenID, string(json)); err != nil { - return fmt.Errorf("setting token: %w", err) - } - return nil -} - -// ConstellationStateDiskTokenIsInitialized returns true if the Constellation state disk token is set to initialized. -func (c *CryptSetup) ConstellationStateDiskTokenIsInitialized() bool { - stateDiskToken, err := c.device.TokenJSONGet(ConstellationStateDiskTokenID) - if err != nil { - return false - } - var token constellationLUKS2Token - if err := json.Unmarshal([]byte(stateDiskToken), &token); err != nil { - return false - } - return token.DiskIsInitialized -} - -// Wipe overwrites the device with zeros to initialize integrity checksums. -func (c *CryptSetup) Wipe( - name string, blockWipeSize int, flags int, logCallback func(size, offset uint64), logFrequency time.Duration, -) (err error) { - packageLock.Lock() - defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen - } - - // Active temporary device to perform wipe on - tmpDevice := tmpDevicePrefix + name - if err := c.device.ActivateByVolumeKey(tmpDevice, "", 0, wipeFlags); err != nil { - return fmt.Errorf("trying to activate temporary dm-crypt volume: %w", err) - } - defer func() { - if deactivateErr := c.device.Deactivate(tmpDevice); deactivateErr != nil { - err = errors.Join(err, fmt.Errorf("deactivating temporary device %q: %w", tmpDevice, deactivateErr)) - } - }() - - // Set up non-blocking progress callback. - ticker := time.NewTicker(logFrequency) - firstReq := make(chan struct{}, 1) - firstReq <- struct{}{} - defer ticker.Stop() - - progressCallback := func(size, offset uint64) int { - select { - case <-firstReq: - logCallback(size, offset) - case <-ticker.C: - logCallback(size, offset) - default: - } - return 0 - } - - if err := c.device.Wipe(mappedDevicePath+tmpDevice, wipePattern, 0, 0, blockWipeSize, flags, progressCallback); err != nil { - return fmt.Errorf("wiping disk of device %q: %w", c.device.GetDeviceName(), err) - } - return nil -} - -type constellationLUKS2Token struct { - Type string `json:"type"` - Keyslots []string `json:"keyslots"` - DiskIsInitialized bool `json:"diskIsInitialized"` -} - -type cryptDevice interface { - ActivateByPassphrase(deviceName string, keyslot int, passphrase string, flags int) error - ActivateByVolumeKey(deviceName, volumeKey string, volumeKeySize, flags int) error - Deactivate(deviceName string) error - GetDeviceName() string - GetUUID() string - Free() bool - KeyslotAddByVolumeKey(keyslot int, volumeKey string, passphrase string) error - KeyslotChangeByPassphrase(currentKeyslot, newKeyslot int, currentPassphrase, newPassphrase string) error - Resize(name string, newSize uint64) error - TokenJSONGet(token int) (string, error) - TokenJSONSet(token int, json string) (int, error) - Wipe(devicePath string, pattern int, offset, length uint64, wipeBlockSize int, flags int, progress func(size, offset uint64) int) error -} diff --git a/internal/cryptsetup/cryptsetup_cgo.go b/internal/cryptsetup/cryptsetup_cgo.go deleted file mode 100644 index 555e07d..0000000 --- a/internal/cryptsetup/cryptsetup_cgo.go +++ /dev/null @@ -1,82 +0,0 @@ -//go:build linux && cgo - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ -package cryptsetup - -// #include -import "C" - -import ( - "errors" - - "github.com/martinjungblut/go-cryptsetup" -) - -const ( - // ReadWriteQueueBypass is a flag to disable the write and read workqueues for a crypt device. - ReadWriteQueueBypass = C.CRYPT_ACTIVATE_NO_WRITE_WORKQUEUE | C.CRYPT_ACTIVATE_NO_READ_WORKQUEUE - wipeFlags = cryptsetup.CRYPT_ACTIVATE_PRIVATE | cryptsetup.CRYPT_ACTIVATE_NO_JOURNAL - wipePattern = cryptsetup.CRYPT_WIPE_ZERO -) - -var errInvalidType = errors.New("device is not a *cryptsetup.Device") - -func format(device cryptDevice, integrity bool) error { - switch d := device.(type) { - case cgoFormatter: - luks2Params := cryptsetup.LUKS2{ - SectorSize: 4096, - PBKDFType: &cryptsetup.PbkdfType{ - // Use low memory recommendation from https://datatracker.ietf.org/doc/html/rfc9106#section-7 - Type: "argon2id", - TimeMs: 2000, - Iterations: 3, - ParallelThreads: 4, - MaxMemoryKb: 65536, // ~64MiB - }, - } - genericParams := cryptsetup.GenericParams{ - Cipher: "aes", - CipherMode: "xts-plain64", - VolumeKeySize: 64, // 32*2 bytes for aes-xts-plain64 encryption - } - - if integrity { - luks2Params.Integrity = "hmac(sha256)" - genericParams.VolumeKeySize += 32 // 32 bytes for hmac(sha256) integrity - } - - return d.Format(luks2Params, genericParams) - default: - return errInvalidType - } -} - -func initByDevicePath(devicePath string) (cryptDevice, error) { - return cryptsetup.Init(devicePath) -} - -func initByName(name string) (cryptDevice, error) { - return cryptsetup.InitByName(name) -} - -func loadLUKS2(device cryptDevice) error { - switch d := device.(type) { - case cgoLoader: - return d.Load(cryptsetup.LUKS2{}) - default: - return errInvalidType - } -} - -type cgoFormatter interface { - Format(deviceType cryptsetup.DeviceType, genericParams cryptsetup.GenericParams) error -} - -type cgoLoader interface { - Load(deviceType cryptsetup.DeviceType) error -} diff --git a/internal/cryptsetup/cryptsetup_cross.go b/internal/cryptsetup/cryptsetup_cross.go deleted file mode 100644 index df1a307..0000000 --- a/internal/cryptsetup/cryptsetup_cross.go +++ /dev/null @@ -1,39 +0,0 @@ -//go:build !linux || !cgo - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ -package cryptsetup - -import ( - "errors" -) - -const ( - // ReadWriteQueueBypass is a flag to disable the write and read workqueues for a crypt device. - ReadWriteQueueBypass = cryptActivateNoReadWorkqueue | cryptActivateNoWriteWorkqueue - cryptActivateNoReadWorkqueue = 0x1000000 - cryptActivateNoWriteWorkqueue = 0x2000000 - wipeFlags = 0x10 | 0x1000 - wipePattern = 0 -) - -var errCGONotSupported = errors.New("using cryptsetup requires building with CGO") - -func format(_ cryptDevice, _ bool) error { - return errCGONotSupported -} - -func initByDevicePath(_ string) (cryptDevice, error) { - return nil, errCGONotSupported -} - -func initByName(_ string) (cryptDevice, error) { - return nil, errCGONotSupported -} - -func loadLUKS2(_ cryptDevice) error { - return errCGONotSupported -} diff --git a/internal/file/file.go b/internal/file/file.go index 84e0104..4fa0d1e 100644 --- a/internal/file/file.go +++ b/internal/file/file.go @@ -233,8 +233,8 @@ func (h *Handler) CopyFile(src, dst string, opts ...Option) error { } // RenameFile renames a file, overwriting any existing file at the destination. -func (h *Handler) RenameFile(old, new string) error { - return h.fs.Rename(old, new) +func (h *Handler) RenameFile(a, b string) error { + return h.fs.Rename(a, b) } // IsEmpty returns true if the given directory is empty. diff --git a/internal/grpc/atlscredentials/BUILD.bazel b/internal/grpc/atlscredentials/BUILD.bazel deleted file mode 100644 index 02c05df..0000000 --- a/internal/grpc/atlscredentials/BUILD.bazel +++ /dev/null @@ -1,28 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "atlscredentials", - srcs = ["atlscredentials.go"], - importpath = "cvm-reverse-proxy/internal/grpc/atlscredentials", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/atls", - "@org_golang_google_grpc//credentials", - ], -) - -go_test( - name = "atlscredentials_test", - srcs = ["atlscredentials_test.go"], - embed = [":atlscredentials"], - deps = [ - "//bootstrapper/initproto", - "//internal/atls", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@org_golang_google_grpc//:grpc", - "@org_golang_google_grpc//test/bufconn", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/grpc/atlscredentials/atlscredentials.go b/internal/grpc/atlscredentials/atlscredentials.go deleted file mode 100644 index ac997fc..0000000 --- a/internal/grpc/atlscredentials/atlscredentials.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package atlscredentials handles creation of TLS credentials for attested TLS (ATLS). -package atlscredentials - -import ( - "context" - "errors" - "net" - - "github.com/flashbots/cvm-reverse-proxy/internal/atls" - - "google.golang.org/grpc/credentials" -) - -// Credentials for attested TLS (ATLS). -type Credentials struct { - issuer atls.Issuer - validators []atls.Validator -} - -// New creates new ATLS Credentials. -func New(issuer atls.Issuer, validators []atls.Validator) *Credentials { - return &Credentials{ - issuer: issuer, - validators: validators, - } -} - -// ClientHandshake performs the client handshake. -func (c *Credentials) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { - clientCfg, err := atls.CreateAttestationClientTLSConfig(c.issuer, c.validators) - if err != nil { - return nil, nil, err - } - - return credentials.NewTLS(clientCfg).ClientHandshake(ctx, authority, rawConn) -} - -// ServerHandshake performs the server handshake. -func (c *Credentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) { - serverCfg, err := atls.CreateAttestationServerTLSConfig(c.issuer, c.validators) - if err != nil { - return nil, nil, err - } - - return credentials.NewTLS(serverCfg).ServerHandshake(rawConn) -} - -// Info provides information about the protocol. -func (c *Credentials) Info() credentials.ProtocolInfo { - return credentials.NewTLS(nil).Info() -} - -// Clone the credentials object. -func (c *Credentials) Clone() credentials.TransportCredentials { - cloned := *c - return &cloned -} - -// OverrideServerName is not supported and will fail. -func (c *Credentials) OverrideServerName(_ string) error { - return errors.New("cannot override server name") -} diff --git a/internal/grpc/atlscredentials/atlscredentials_test.go b/internal/grpc/atlscredentials/atlscredentials_test.go deleted file mode 100644 index 85bfa67..0000000 --- a/internal/grpc/atlscredentials/atlscredentials_test.go +++ /dev/null @@ -1,128 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package atlscredentials - -import ( - "bytes" - "context" - "encoding/asn1" - "encoding/json" - "errors" - "net" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/atls" - - "github.com/edgelesssys/constellation/v2/bootstrapper/initproto" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/goleak" - "google.golang.org/grpc" - "google.golang.org/grpc/test/bufconn" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1")) -} - -func TestATLSCredentials(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - oid := fakeOID{1, 3, 9900, 1} - - // - // Create servers - // - - serverCreds := New(fakeIssuer{fakeOID: oid}, nil) - - const serverCount = 15 - var listeners []*bufconn.Listener - for i := 0; i < serverCount; i++ { - api := &fakeAPI{} - server := grpc.NewServer(grpc.Creds(serverCreds)) - initproto.RegisterAPIServer(server, api) - - listener := bufconn.Listen(1024) - listeners = append(listeners, listener) - - defer server.GracefulStop() - go server.Serve(listener) - } - - // - // Dial concurrently - // - - clientCreds := New(nil, []atls.Validator{fakeValidator{fakeOID: oid}}) - - errChan := make(chan error, serverCount) - for _, listener := range listeners { - lis := listener - go func() { - var err error - defer func() { errChan <- err }() - conn, err := grpc.NewClient("192.0.2.1", grpc.WithContextDialer(func(_ context.Context, _ string) (net.Conn, error) { - return lis.Dial() - }), grpc.WithTransportCredentials(clientCreds)) - require.NoError(err) - defer conn.Close() - - client := initproto.NewAPIClient(conn) - _, err = client.Init(context.Background(), &initproto.InitRequest{}) - }() - } - - for i := 0; i < serverCount; i++ { - assert.NoError(<-errChan) - } -} - -type fakeIssuer struct { - fakeOID -} - -func (fakeIssuer) Issue(_ context.Context, userData []byte, nonce []byte) ([]byte, error) { - return json.Marshal(fakeDoc{UserData: userData, Nonce: nonce}) -} - -type fakeValidator struct { - fakeOID - err error -} - -func (v fakeValidator) Validate(_ context.Context, attDoc []byte, nonce []byte) ([]byte, error) { - var doc fakeDoc - if err := json.Unmarshal(attDoc, &doc); err != nil { - return nil, err - } - if !bytes.Equal(doc.Nonce, nonce) { - return nil, errors.New("invalid nonce") - } - return doc.UserData, v.err -} - -type fakeOID asn1.ObjectIdentifier - -func (o fakeOID) OID() asn1.ObjectIdentifier { - return asn1.ObjectIdentifier(o) -} - -type fakeDoc struct { - UserData []byte - Nonce []byte -} - -type fakeAPI struct { - initproto.UnimplementedAPIServer -} - -func (f *fakeAPI) Init(_ *initproto.InitRequest, stream initproto.API_InitServer) error { - _ = stream.Send(&initproto.InitResponse{}) - return nil -} diff --git a/internal/grpc/dialer/BUILD.bazel b/internal/grpc/dialer/BUILD.bazel deleted file mode 100644 index 86cbb49..0000000 --- a/internal/grpc/dialer/BUILD.bazel +++ /dev/null @@ -1,32 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "dialer", - srcs = ["dialer.go"], - importpath = "cvm-reverse-proxy/internal/grpc/dialer", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/atls", - "//internal/grpc/atlscredentials", - "@org_golang_google_grpc//:grpc", - "@org_golang_google_grpc//credentials/insecure", - ], -) - -go_test( - name = "dialer_test", - srcs = ["dialer_test.go"], - embed = [":dialer"], - deps = [ - "//internal/atls", - "//internal/attestation/variant", - "//internal/grpc/atlscredentials", - "//internal/grpc/testdialer", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@org_golang_google_grpc//:grpc", - "@org_golang_google_grpc//interop/grpc_testing", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/grpc/dialer/dialer.go b/internal/grpc/dialer/dialer.go deleted file mode 100644 index 60a6851..0000000 --- a/internal/grpc/dialer/dialer.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package dialer provides a grpc dialer that can be used to create grpc client connections with different levels of ATLS encryption / verification. -package dialer - -import ( - "context" - "net" - - "github.com/flashbots/cvm-reverse-proxy/internal/atls" - "github.com/flashbots/cvm-reverse-proxy/internal/grpc/atlscredentials" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -// Dialer can open grpc client connections with different levels of ATLS encryption / verification. -type Dialer struct { - issuer atls.Issuer - validator atls.Validator - netDialer NetDialer -} - -// New creates a new Dialer. -func New(issuer atls.Issuer, validator atls.Validator, netDialer NetDialer) *Dialer { - return &Dialer{ - issuer: issuer, - validator: validator, - netDialer: netDialer, - } -} - -// Dial creates a new grpc client connection to the given target using the atls validator. -func (d *Dialer) Dial(target string) (*grpc.ClientConn, error) { - var validators []atls.Validator - if d.validator != nil { - validators = append(validators, d.validator) - } - credentials := atlscredentials.New(d.issuer, validators) - - return grpc.NewClient(target, - d.grpcWithDialer(), - grpc.WithTransportCredentials(credentials), - ) -} - -// DialInsecure creates a new grpc client connection to the given target without using encryption or verification. -// Only use this method when using another kind of encryption / verification (VPN, etc). -func (d *Dialer) DialInsecure(target string) (*grpc.ClientConn, error) { - return grpc.NewClient(target, - d.grpcWithDialer(), - grpc.WithTransportCredentials(insecure.NewCredentials()), - ) -} - -// DialNoVerify creates a new grpc client connection to the given target without verifying the server's attestation. -func (d *Dialer) DialNoVerify(target string) (*grpc.ClientConn, error) { - credentials := atlscredentials.New(nil, nil) - - return grpc.NewClient(target, - d.grpcWithDialer(), - grpc.WithTransportCredentials(credentials), - ) -} - -func (d *Dialer) grpcWithDialer() grpc.DialOption { - return grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) { - return d.netDialer.DialContext(ctx, "tcp", addr) - }) -} - -// NetDialer implements the net Dialer interface. -type NetDialer interface { - DialContext(ctx context.Context, network, address string) (net.Conn, error) -} diff --git a/internal/grpc/dialer/dialer_test.go b/internal/grpc/dialer/dialer_test.go deleted file mode 100644 index d5fada6..0000000 --- a/internal/grpc/dialer/dialer_test.go +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package dialer - -import ( - "context" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/atls" - "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" - "github.com/flashbots/cvm-reverse-proxy/internal/grpc/atlscredentials" - "github.com/flashbots/cvm-reverse-proxy/internal/grpc/testdialer" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/goleak" - "google.golang.org/grpc" - "google.golang.org/grpc/interop/grpc_testing" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1")) -} - -func TestDial(t *testing.T) { - testCases := map[string]struct { - tls bool - dialFn func(dialer *Dialer, target string) (*grpc.ClientConn, error) - wantErr bool - }{ - "Dial with tls on server works": { - tls: true, - dialFn: func(dialer *Dialer, target string) (*grpc.ClientConn, error) { - return dialer.Dial(target) - }, - }, - "Dial without tls on server fails": { - dialFn: func(dialer *Dialer, target string) (*grpc.ClientConn, error) { - return dialer.Dial(target) - }, - wantErr: true, - }, - "DialNoVerify with tls on server works": { - tls: true, - dialFn: func(dialer *Dialer, target string) (*grpc.ClientConn, error) { - return dialer.DialNoVerify(target) - }, - }, - "DialNoVerify without tls on server fails": { - dialFn: func(dialer *Dialer, target string) (*grpc.ClientConn, error) { - return dialer.DialNoVerify(target) - }, - wantErr: true, - }, - "DialInsecure without tls on server works": { - dialFn: func(dialer *Dialer, target string) (*grpc.ClientConn, error) { - return dialer.DialInsecure(target) - }, - }, - "DialInsecure with tls on server fails": { - tls: true, - dialFn: func(dialer *Dialer, target string) (*grpc.ClientConn, error) { - return dialer.DialInsecure(target) - }, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - netDialer := testdialer.NewBufconnDialer() - dialer := New(nil, atls.NewFakeValidator(variant.Dummy{}), netDialer) - server := newServer(variant.Dummy{}, tc.tls) - api := &testAPI{} - grpc_testing.RegisterTestServiceServer(server, api) - go server.Serve(netDialer.GetListener("192.0.2.1:1234")) - defer server.Stop() - conn, err := tc.dialFn(dialer, "192.0.2.1:1234") - require.NoError(err) - defer conn.Close() - - client := grpc_testing.NewTestServiceClient(conn) - _, err = client.EmptyCall(context.Background(), &grpc_testing.Empty{}) - - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - }) - } -} - -func newServer(oid variant.Getter, tls bool) *grpc.Server { - if tls { - creds := atlscredentials.New(atls.NewFakeIssuer(oid), nil) - return grpc.NewServer(grpc.Creds(creds)) - } - return grpc.NewServer() -} - -type testAPI struct { - grpc_testing.UnimplementedTestServiceServer -} - -func (s *testAPI) EmptyCall(_ context.Context, _ *grpc_testing.Empty) (*grpc_testing.Empty, error) { - return &grpc_testing.Empty{}, nil -} diff --git a/internal/grpc/grpclog/BUILD.bazel b/internal/grpc/grpclog/BUILD.bazel deleted file mode 100644 index dcf1efb..0000000 --- a/internal/grpc/grpclog/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "grpclog", - srcs = ["grpclog.go"], - importpath = "cvm-reverse-proxy/internal/grpc/grpclog", - visibility = ["//:__subpackages__"], - deps = [ - "@org_golang_google_grpc//connectivity", - "@org_golang_google_grpc//peer", - ], -) - -go_test( - name = "grpclog_test", - srcs = ["grpclog_test.go"], - embed = [":grpclog"], - deps = [ - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@org_golang_google_grpc//connectivity", - ], -) diff --git a/internal/grpc/grpclog/grpclog.go b/internal/grpc/grpclog/grpclog.go deleted file mode 100644 index be4d27f..0000000 --- a/internal/grpc/grpclog/grpclog.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// grpclog provides a logging utilities for gRPC. -package grpclog - -import ( - "context" - "fmt" - "sync" - - "google.golang.org/grpc/connectivity" - "google.golang.org/grpc/peer" -) - -// PeerAddrFromContext returns a peer's address from context, or "unknown" if not found. -func PeerAddrFromContext(ctx context.Context) string { - p, ok := peer.FromContext(ctx) - if !ok { - return "unknown" - } - return p.Addr.String() -} - -// LogStateChangesUntilReady logs the state changes of a gRPC connection. -func LogStateChangesUntilReady(ctx context.Context, conn getStater, log debugLog, wg *sync.WaitGroup, isReadyCallback func()) { - wg.Add(1) - go func() { - defer wg.Done() - state := conn.GetState() - log.Debug(fmt.Sprintf("Connection state started as %q", state)) - for ; state != connectivity.Ready && conn.WaitForStateChange(ctx, state); state = conn.GetState() { - log.Debug(fmt.Sprintf("Connection state changed to %q", state)) - } - if state == connectivity.Ready { - log.Debug("Connection ready") - isReadyCallback() - } else { - log.Debug(fmt.Sprintf("Connection state ended with %q", state)) - } - }() -} - -type getStater interface { - GetState() connectivity.State - WaitForStateChange(context.Context, connectivity.State) bool -} - -type debugLog interface { - Debug(msg string, args ...any) -} diff --git a/internal/grpc/grpclog/grpclog_test.go b/internal/grpc/grpclog/grpclog_test.go deleted file mode 100644 index eb91252..0000000 --- a/internal/grpc/grpclog/grpclog_test.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// grpclog provides a logging utilities for gRPC. -package grpclog - -import ( - "context" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/connectivity" -) - -func TestLogStateChanges(t *testing.T) { - testCases := map[string]struct { - name string - conn getStater - assert func(t *testing.T, lg *spyLog, isReadyCallbackCalled bool) - }{ - "state: connecting, ready": { - conn: &stubConn{ - states: []connectivity.State{ - connectivity.Connecting, - connectivity.Ready, - connectivity.Ready, - }, - }, - assert: func(t *testing.T, lg *spyLog, isReadyCallbackCalled bool) { - require.Len(t, lg.msgs, 3) - assert.Equal(t, "Connection state started as \"CONNECTING\"", lg.msgs[0]) - assert.Equal(t, "Connection state changed to \"CONNECTING\"", lg.msgs[1]) - assert.Equal(t, "Connection ready", lg.msgs[2]) - assert.True(t, isReadyCallbackCalled) - }, - }, - "state: ready": { - conn: &stubConn{ - states: []connectivity.State{ - connectivity.Ready, - connectivity.Idle, - }, - stopWaitForChange: false, - }, - assert: func(t *testing.T, lg *spyLog, isReadyCallbackCalled bool) { - require.Len(t, lg.msgs, 2) - assert.Equal(t, "Connection state started as \"READY\"", lg.msgs[0]) - assert.Equal(t, "Connection ready", lg.msgs[1]) - assert.True(t, isReadyCallbackCalled) - }, - }, - "no WaitForStateChange (e.g. when context is canceled)": { - conn: &stubConn{ - states: []connectivity.State{ - connectivity.Connecting, - connectivity.Idle, - }, - stopWaitForChange: true, - }, - assert: func(t *testing.T, lg *spyLog, isReadyCallbackCalled bool) { - require.Len(t, lg.msgs, 2) - assert.Equal(t, "Connection state started as \"CONNECTING\"", lg.msgs[0]) - assert.Equal(t, "Connection state ended with \"CONNECTING\"", lg.msgs[1]) - assert.False(t, isReadyCallbackCalled) - }, - }, - } - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - logger := &spyLog{} - - var wg sync.WaitGroup - isReadyCallbackCalled := false - LogStateChangesUntilReady(context.Background(), tc.conn, logger, &wg, func() { isReadyCallbackCalled = true }) - wg.Wait() - tc.assert(t, logger, isReadyCallbackCalled) - }) - } -} - -type spyLog struct { - msgs []string -} - -func (f *spyLog) Debug(msg string, _ ...any) { - f.msgs = append(f.msgs, msg) -} - -type stubConn struct { - states []connectivity.State - idx int - stopWaitForChange bool -} - -func (f *stubConn) GetState() connectivity.State { - if f.idx > len(f.states)-1 { - return f.states[len(f.states)-1] - } - res := f.states[f.idx] - f.idx++ - return res -} - -func (f *stubConn) WaitForStateChange(context.Context, connectivity.State) bool { - if f.stopWaitForChange { - return false - } - return f.idx < len(f.states) -} diff --git a/internal/grpc/retry/BUILD.bazel b/internal/grpc/retry/BUILD.bazel deleted file mode 100644 index 85e34ac..0000000 --- a/internal/grpc/retry/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "retry", - srcs = ["retry.go"], - importpath = "cvm-reverse-proxy/internal/grpc/retry", - visibility = ["//:__subpackages__"], - deps = [ - "@org_golang_google_grpc//codes", - "@org_golang_google_grpc//status", - ], -) - -go_test( - name = "retry_test", - srcs = ["retry_test.go"], - embed = [":retry"], - deps = [ - "@com_github_stretchr_testify//assert", - "@org_golang_google_grpc//codes", - "@org_golang_google_grpc//status", - ], -) diff --git a/internal/grpc/retry/retry.go b/internal/grpc/retry/retry.go deleted file mode 100644 index b7457fc..0000000 --- a/internal/grpc/retry/retry.go +++ /dev/null @@ -1,92 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package retry provides functions to check if a gRPC error is retryable. -package retry - -import ( - "errors" - "strings" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -const ( - authEOFErr = `connection error: desc = "transport: authentication handshake failed: EOF"` - authReadTCPErr = `connection error: desc = "transport: authentication handshake failed: read tcp` - authHandshakeErr = `connection error: desc = "transport: authentication handshake failed` - authHandshakeDeadlineExceededErr = `connection error: desc = "transport: authentication handshake failed: context deadline exceeded` -) - -// grpcErr is the error type that is returned by the grpc client. -// taken from google.golang.org/grpc/status.FromError. -type grpcErr interface { - GRPCStatus() *status.Status - Error() string -} - -// ServiceIsUnavailable checks if the error is a grpc status with code Unavailable. -// In the special case of an authentication handshake failure, false is returned to prevent further retries. -// Since the GCP proxy loadbalancer may error with an authentication handshake failure if no available backends are ready, -// the special handshake errors caused by the GCP LB (e.g. "read tcp", "EOF") are retried. -func ServiceIsUnavailable(err error) bool { - var targetErr grpcErr - if !errors.As(err, &targetErr) { - return false - } - - statusErr, ok := status.FromError(targetErr) - if !ok { - return false - } - - if statusErr.Code() != codes.Unavailable { - return false - } - - // retry if GCP proxy LB isn't available - if strings.HasPrefix(statusErr.Message(), authEOFErr) { - return true - } - - // retry if GCP proxy LB isn't fully available yet - if strings.HasPrefix(statusErr.Message(), authReadTCPErr) { - return true - } - - // retry if the handshake deadline was exceeded - if strings.HasPrefix(statusErr.Message(), authHandshakeDeadlineExceededErr) { - return true - } - - return !strings.HasPrefix(statusErr.Message(), authHandshakeErr) -} - -// LoadbalancerIsNotReady checks if the error was caused by a GCP LB not being ready yet. -func LoadbalancerIsNotReady(err error) bool { - var targetErr grpcErr - if !errors.As(err, &targetErr) { - return false - } - - statusErr, ok := status.FromError(targetErr) - if !ok { - return false - } - - if statusErr.Code() != codes.Unavailable { - return false - } - - // retry if the handshake deadline was exceeded - if strings.HasPrefix(statusErr.Message(), authHandshakeDeadlineExceededErr) { - return true - } - - // retry if GCP proxy LB isn't fully available yet - return strings.HasPrefix(statusErr.Message(), authReadTCPErr) -} diff --git a/internal/grpc/retry/retry_test.go b/internal/grpc/retry/retry_test.go deleted file mode 100644 index 5e51e4b..0000000 --- a/internal/grpc/retry/retry_test.go +++ /dev/null @@ -1,110 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package retry - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/assert" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func TestServiceIsUnavailable(t *testing.T) { - testCases := map[string]struct { - err error - wantUnavailable bool - }{ - "nil": {}, - "not status error": { - err: errors.New("error"), - }, - "not unavailable": { - err: status.Error(codes.Internal, "error"), - }, - "unavailable error with authentication handshake failure": { - err: status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: bad certificate"`), - }, - "normal unavailable error": { - err: status.Error(codes.Unavailable, "error"), - wantUnavailable: true, - }, - "handshake EOF error": { - err: status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: EOF"`), - wantUnavailable: true, - }, - "handshake read tcp error": { - err: status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: read tcp error"`), - wantUnavailable: true, - }, - "handshake deadline exceeded error": { - err: status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: context deadline exceeded"`), - wantUnavailable: true, - }, - "wrapped error": { - err: fmt.Errorf("some wrapping: %w", status.Error(codes.Unavailable, "error")), - wantUnavailable: true, - }, - "code unknown": { - err: status.Error(codes.Unknown, "unknown"), - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - assert.Equal(tc.wantUnavailable, ServiceIsUnavailable(tc.err)) - }) - } -} - -func TestLoadbalancerIsNotReady(t *testing.T) { - testCases := map[string]struct { - err error - wantNotReady bool - }{ - "nil": {}, - "not status error": { - err: errors.New("error"), - }, - "not unavailable": { - err: status.Error(codes.Internal, "error"), - }, - "unavailable error with authentication handshake failure": { - err: status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: bad certificate"`), - }, - "handshake EOF error": { - err: status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: EOF"`), - }, - "handshake read tcp error": { - err: status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: read tcp error"`), - wantNotReady: true, - }, - "handshake deadline exceeded error": { - err: status.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: context deadline exceeded"`), - wantNotReady: true, - }, - "normal unavailable error": { - err: status.Error(codes.Unavailable, "error"), - }, - "wrapped error": { - err: fmt.Errorf("some wrapping: %w", status.Error(codes.Unavailable, "error")), - }, - "code unknown": { - err: status.Error(codes.Unknown, "unknown"), - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - assert.Equal(tc.wantNotReady, LoadbalancerIsNotReady(tc.err)) - }) - } -} diff --git a/internal/grpc/testdialer/BUILD.bazel b/internal/grpc/testdialer/BUILD.bazel deleted file mode 100644 index 1c0032e..0000000 --- a/internal/grpc/testdialer/BUILD.bazel +++ /dev/null @@ -1,9 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "testdialer", - srcs = ["testdialer.go"], - importpath = "cvm-reverse-proxy/internal/grpc/testdialer", - visibility = ["//:__subpackages__"], - deps = ["@org_golang_google_grpc//test/bufconn"], -) diff --git a/internal/grpc/testdialer/testdialer.go b/internal/grpc/testdialer/testdialer.go deleted file mode 100644 index e677190..0000000 --- a/internal/grpc/testdialer/testdialer.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package testdialer provides a fake dialer for testing. -package testdialer - -import ( - "context" - "fmt" - "net" - "sync" - - "google.golang.org/grpc/test/bufconn" -) - -// BufconnDialer is a fake dialer based on gRPC bufconn package. -type BufconnDialer struct { - mut sync.Mutex - listeners map[string]*bufconn.Listener -} - -// NewBufconnDialer creates a new bufconn dialer for testing. -func NewBufconnDialer() *BufconnDialer { - return &BufconnDialer{listeners: make(map[string]*bufconn.Listener)} -} - -// DialContext implements the Dialer interface. -func (b *BufconnDialer) DialContext(ctx context.Context, _, address string) (net.Conn, error) { - b.mut.Lock() - listener, ok := b.listeners[address] - b.mut.Unlock() - if !ok { - return nil, fmt.Errorf("could not connect to server on %v", address) - } - return listener.DialContext(ctx) -} - -// GetListener returns a fake listener that is coupled with this dialer. -func (b *BufconnDialer) GetListener(endpoint string) net.Listener { - listener := bufconn.Listen(1024) - b.mut.Lock() - b.listeners[endpoint] = listener - b.mut.Unlock() - return listener -} diff --git a/internal/imagefetcher/BUILD.bazel b/internal/imagefetcher/BUILD.bazel deleted file mode 100644 index 4b1b001..0000000 --- a/internal/imagefetcher/BUILD.bazel +++ /dev/null @@ -1,41 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "imagefetcher", - srcs = [ - "imagefetcher.go", - "raw.go", - ], - importpath = "cvm-reverse-proxy/internal/imagefetcher", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/api/fetcher", - "//internal/api/versionsapi", - "//internal/attestation/variant", - "//internal/cloud/cloudprovider", - "//internal/mpimage", - "//internal/semver", - "@com_github_schollz_progressbar_v3//:progressbar", - "@com_github_spf13_afero//:afero", - ], -) - -go_test( - name = "imagefetcher_test", - srcs = [ - "imagefetcher_test.go", - "raw_test.go", - ], - embed = [":imagefetcher"], - deps = [ - "//internal/api/versionsapi", - "//internal/attestation/variant", - "//internal/cloud/cloudprovider", - "//internal/file", - "@com_github_spf13_afero//:afero", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/imagefetcher/imagefetcher.go b/internal/imagefetcher/imagefetcher.go deleted file mode 100644 index ccd3b78..0000000 --- a/internal/imagefetcher/imagefetcher.go +++ /dev/null @@ -1,210 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package imagefetcher provides helping wrappers around a versionsapi fetcher. - -It also enables local image overrides and download of raw images. -*/ -package imagefetcher - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io/fs" - "regexp" - "strings" - - "github.com/flashbots/cvm-reverse-proxy/internal/api/fetcher" - "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" - "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" - "github.com/flashbots/cvm-reverse-proxy/internal/mpimage" - "github.com/flashbots/cvm-reverse-proxy/internal/semver" - - "github.com/spf13/afero" -) - -// Fetcher fetches image references using a lookup table. -type Fetcher struct { - fetcher versionsAPIImageInfoFetcher - fs *afero.Afero -} - -// New returns a new image fetcher. -func New() *Fetcher { - return &Fetcher{ - fetcher: versionsapi.NewFetcher(), - fs: &afero.Afero{Fs: afero.NewOsFs()}, - } -} - -// FetchReference fetches the image reference for a given image version uid, CSP and image variant. -func (f *Fetcher) FetchReference(ctx context.Context, - provider cloudprovider.Provider, attestationVariant variant.Variant, - image, region string, useMarketplaceImage bool, -) (string, error) { - ver, err := versionsapi.NewVersionFromShortPath(image, versionsapi.VersionKindImage) - if err != nil { - return "", fmt.Errorf("parsing config image short path: %w", err) - } - - imgInfoReq := versionsapi.ImageInfo{ - Ref: ver.Ref(), - Stream: ver.Stream(), - Version: ver.Version(), - } - - url, err := imgInfoReq.URL() - if err != nil { - return "", err - } - - imgInfo, err := getFromFile(f.fs, imgInfoReq) - if err != nil && errors.Is(err, fs.ErrNotExist) { - imgInfo, err = f.fetcher.FetchImageInfo(ctx, imgInfoReq) - } - - var notFoundErr *fetcher.NotFoundError - switch { - case errors.As(err, ¬FoundErr): - overridePath := imageInfoFilename(imgInfoReq) - return "", fmt.Errorf("image info file not found locally at %q or remotely at %s", overridePath, url) - case err != nil: - return "", err - } - - if err := imgInfo.Validate(); err != nil { - return "", fmt.Errorf("validating image info file: %w", err) - } - - if useMarketplaceImage { - return buildMarketplaceImage(marketplaceImagePayload{ - ver: ver, - provider: provider, - attestationVariant: attestationVariant, - imgInfo: imgInfo, - filters: filters(provider, region), - }) - } - - return getReferenceFromImageInfo(provider, attestationVariant.String(), imgInfo, filters(provider, region)...) -} - -// marketplaceImagePayload is a helper struct to pass around the required information to build a marketplace image URI. -type marketplaceImagePayload struct { - ver versionsapi.Version - provider cloudprovider.Provider - attestationVariant variant.Variant - imgInfo versionsapi.ImageInfo - filters []filter -} - -// buildMarketplaceImage returns a marketplace image URI for the given CSP and version. -func buildMarketplaceImage(payload marketplaceImagePayload) (string, error) { - sv, err := semver.New(payload.ver.Version()) - if err != nil { - return "", fmt.Errorf("parsing image version: %w", err) - } - - if sv.Prerelease() != "" && payload.provider != cloudprovider.OpenStack { - return "", fmt.Errorf("marketplace images are not supported for prerelease versions") - } - - switch payload.provider { - case cloudprovider.Azure: - // For Azure, multiple fields of information are required to use marketplace images, - // so we pack them in a custom URI. - return mpimage.NewAzureMarketplaceImage(sv).URI(), nil - case cloudprovider.GCP: - // For GCP, we just need to replace the GCP project name (constellation-images) to the public project that - // hosts the marketplace images (mpi-edgeless-systems-public). - imageRef, err := getReferenceFromImageInfo(payload.provider, payload.attestationVariant.String(), payload.imgInfo, payload.filters...) - if err != nil { - return "", fmt.Errorf("getting image reference: %w", err) - } - return strings.Replace(imageRef, "constellation-images", "mpi-edgeless-systems-public", 1), nil - case cloudprovider.AWS: - // For AWS, we use the AMI alias, which just needs the version and infers the rest transparently. - return fmt.Sprintf("resolve:ssm:/aws/service/marketplace/prod-77ylkenlkgufs/%s", payload.imgInfo.Version), nil - case cloudprovider.OpenStack: - // For OpenStack / STACKIT, we use the image reference directly. - return getReferenceFromImageInfo(payload.provider, payload.attestationVariant.String(), payload.imgInfo, payload.filters...) - default: - return "", fmt.Errorf("marketplace images are not supported for csp %s", payload.provider.String()) - } -} - -func filters(provider cloudprovider.Provider, region string) []filter { - var filters []filter - switch provider { - case cloudprovider.AWS: - filters = append(filters, func(i versionsapi.ImageInfoEntry) bool { - return i.Region == region - }) - } - return filters -} - -func getFromFile(fs *afero.Afero, imgInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) { - fileName := imageInfoFilename(imgInfo) - - raw, err := fs.ReadFile(fileName) - if err != nil { - return versionsapi.ImageInfo{}, err - } - - var newInfo versionsapi.ImageInfo - if err := json.Unmarshal(raw, &newInfo); err != nil { - return versionsapi.ImageInfo{}, fmt.Errorf("decoding image info file: %w", err) - } - - return newInfo, nil -} - -var filenameReplaceRegexp = regexp.MustCompile(`([^a-zA-Z0-9.-])`) - -func imageInfoFilename(imgInfo versionsapi.ImageInfo) string { - path := imgInfo.JSONPath() - return filenameReplaceRegexp.ReplaceAllString(path, "_") -} - -// getReferenceFromImageInfo returns the image reference for a given CSP and image variant. -func getReferenceFromImageInfo(provider cloudprovider.Provider, - attestationVariant string, imgInfo versionsapi.ImageInfo, - filt ...filter, -) (string, error) { - var correctVariant versionsapi.ImageInfoEntry - var found bool -variantLoop: - for _, variant := range imgInfo.List { - gotCSP := cloudprovider.FromString(variant.CSP) - if gotCSP != provider || variant.AttestationVariant != attestationVariant { - continue - } - for _, f := range filt { - if !f(variant) { - continue variantLoop - } - } - correctVariant = variant - found = true - break - } - if !found { - return "", fmt.Errorf("image not available in image info for CSP %q, variant %q and other filters", provider.String(), attestationVariant) - } - - return correctVariant.Reference, nil -} - -type versionsAPIImageInfoFetcher interface { - FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) -} - -type filter func(versionsapi.ImageInfoEntry) bool diff --git a/internal/imagefetcher/imagefetcher_test.go b/internal/imagefetcher/imagefetcher_test.go deleted file mode 100644 index 83b2fec..0000000 --- a/internal/imagefetcher/imagefetcher_test.go +++ /dev/null @@ -1,304 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package imagefetcher - -import ( - "context" - "encoding/json" - "errors" - "net/http" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" - "github.com/flashbots/cvm-reverse-proxy/internal/attestation/variant" - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" - "github.com/flashbots/cvm-reverse-proxy/internal/file" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1")) -} - -func TestGetReference(t *testing.T) { - testCases := map[string]struct { - info versionsapi.ImageInfo - provider cloudprovider.Provider - variant string - filter filter - wantReference string - wantErr bool - }{ - "reference exists with filter": { - info: versionsapi.ImageInfo{ - List: []versionsapi.ImageInfoEntry{ - {CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someReference"}, - {CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someOtherReference", Region: "someRegion"}, - }, - }, - provider: cloudprovider.AWS, - variant: "aws-nitro-tpm", - filter: func(entry versionsapi.ImageInfoEntry) bool { - return entry.Region == "someRegion" - }, - wantReference: "someOtherReference", - }, - "reference exists aws": { - info: versionsapi.ImageInfo{ - List: []versionsapi.ImageInfoEntry{ - {CSP: "aws", AttestationVariant: "aws-nitro-tpm", Reference: "someReference"}, - }, - }, - provider: cloudprovider.AWS, - variant: "aws-nitro-tpm", - wantReference: "someReference", - }, - "reference exists azure": { - info: versionsapi.ImageInfo{ - List: []versionsapi.ImageInfoEntry{ - {CSP: "azure", AttestationVariant: "azure-sev-snp", Reference: "someReference"}, - }, - }, - provider: cloudprovider.Azure, - variant: "azure-sev-snp", - wantReference: "someReference", - }, - "reference exists gcp": { - info: versionsapi.ImageInfo{ - List: []versionsapi.ImageInfoEntry{ - {CSP: "gcp", AttestationVariant: "gcp-sev-es", Reference: "someReference"}, - }, - }, - provider: cloudprovider.GCP, - variant: "gcp-sev-es", - wantReference: "someReference", - }, - "reference exists openstack": { - info: versionsapi.ImageInfo{ - List: []versionsapi.ImageInfoEntry{ - {CSP: "openstack", AttestationVariant: "qemu-vtpm", Reference: "someReference"}, - }, - }, - provider: cloudprovider.OpenStack, - variant: "qemu-vtpm", - wantReference: "someReference", - }, - "reference exists qemu": { - info: versionsapi.ImageInfo{ - List: []versionsapi.ImageInfoEntry{ - {CSP: "qemu", AttestationVariant: "qemu-vtpm", Reference: "someReference"}, - }, - }, - provider: cloudprovider.QEMU, - variant: "qemu-vtpm", - wantReference: "someReference", - }, - "csp does not exist": { - info: versionsapi.ImageInfo{List: []versionsapi.ImageInfoEntry{}}, - provider: cloudprovider.Unknown, - variant: "someVariant", - wantErr: true, - }, - "variant does not exist": { - info: versionsapi.ImageInfo{ - List: []versionsapi.ImageInfoEntry{ - {CSP: "aws", AttestationVariant: "dummy", Reference: "someReference"}, - }, - }, - provider: cloudprovider.AWS, - variant: "aws-nitro-tpm", - wantErr: true, - }, - "info is empty": { - info: versionsapi.ImageInfo{}, - provider: cloudprovider.AWS, - variant: "someVariant", - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - var filters []filter - if tc.filter != nil { - filters = []filter{tc.filter} - } - reference, err := getReferenceFromImageInfo(tc.provider, tc.variant, tc.info, filters...) - - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - assert.Equal(tc.wantReference, reference) - }) - } -} - -func TestFetchReference(t *testing.T) { - img := "ref/abc/stream/nightly/v1.2.3" - newImgInfo := func() versionsapi.ImageInfo { - return versionsapi.ImageInfo{ - Ref: "abc", - Stream: "nightly", - Version: "v1.2.3", - List: []versionsapi.ImageInfoEntry{ - { - CSP: "qemu", - AttestationVariant: "dummy", - Reference: "someReference", - }, - }, - } - } - imgInfoPath := imageInfoFilename(newImgInfo()) - - testCases := map[string]struct { - provider cloudprovider.Provider - image string - imageInfoFetcher versionsAPIImageInfoFetcher - localFile []byte - wantReference string - wantErr bool - }{ - "reference fetched remotely": { - provider: cloudprovider.QEMU, - image: img, - imageInfoFetcher: &stubVersionsAPIImageFetcher{ - fetchImageInfoInfo: newImgInfo(), - }, - wantReference: "someReference", - }, - "reference fetched remotely fails": { - provider: cloudprovider.QEMU, - image: img, - imageInfoFetcher: &stubVersionsAPIImageFetcher{ - fetchImageInfoErr: errors.New("failed"), - }, - wantErr: true, - }, - "reference fetched locally": { - provider: cloudprovider.QEMU, - image: img, - localFile: func() []byte { - info := newImgInfo() - info.List[0].Reference = "localOverrideReference" - file, err := json.Marshal(info) - require.NoError(t, err) - return file - }(), - wantReference: "localOverrideReference", - }, - "local file first": { - provider: cloudprovider.QEMU, - image: img, - imageInfoFetcher: &stubVersionsAPIImageFetcher{ - fetchImageInfoInfo: newImgInfo(), - }, - localFile: func() []byte { - info := newImgInfo() - info.List[0].Reference = "localOverrideReference" - file, err := json.Marshal(info) - require.NoError(t, err) - return file - }(), - wantReference: "localOverrideReference", - }, - "local file is invalid": { - provider: cloudprovider.QEMU, - image: img, - localFile: []byte("invalid"), - wantErr: true, - }, - "local file has invalid image info": { - provider: cloudprovider.QEMU, - image: img, - localFile: func() []byte { - info := newImgInfo() - info.Ref = "" - file, err := json.Marshal(info) - require.NoError(t, err) - return file - }(), - wantErr: true, - }, - "image version does not exist": { - provider: cloudprovider.QEMU, - image: "nonExistingImageName", - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - fs := afero.NewMemMapFs() - af := &afero.Afero{Fs: fs} - if tc.localFile != nil { - fh := file.NewHandler(af) - require.NoError(fh.Write(imgInfoPath, tc.localFile)) - } - - fetcher := &Fetcher{ - fetcher: tc.imageInfoFetcher, - fs: af, - } - - reference, err := fetcher.FetchReference(context.Background(), tc.provider, variant.Dummy{}, - tc.image, "someRegion", false) - - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - assert.Equal(tc.wantReference, reference) - }) - } -} - -type stubVersionsAPIImageFetcher struct { - fetchImageInfoInfo versionsapi.ImageInfo - fetchImageInfoErr error -} - -func (f *stubVersionsAPIImageFetcher) FetchImageInfo(_ context.Context, _ versionsapi.ImageInfo) ( - versionsapi.ImageInfo, error, -) { - return f.fetchImageInfoInfo, f.fetchImageInfoErr -} - -func must(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Fatal(err) - } -} - -// roundTripFunc . -type roundTripFunc func(req *http.Request) *http.Response - -// RoundTrip . -func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -// newTestClient returns *http.Client with Transport replaced to avoid making real calls. -func newTestClient(fn roundTripFunc) *http.Client { - return &http.Client{ - Transport: fn, - } -} diff --git a/internal/imagefetcher/raw.go b/internal/imagefetcher/raw.go deleted file mode 100644 index 593b0d9..0000000 --- a/internal/imagefetcher/raw.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package imagefetcher - -import ( - "context" - "errors" - "fmt" - "io" - "io/fs" - "net/http" - "net/url" - "os" - "path/filepath" - - "github.com/schollz/progressbar/v3" - "github.com/spf13/afero" -) - -// Downloader downloads raw images. -type Downloader struct { - httpc httpc - fs *afero.Afero -} - -// NewDownloader creates a new Downloader. -func NewDownloader() *Downloader { - return &Downloader{ - httpc: http.DefaultClient, - fs: &afero.Afero{Fs: afero.NewOsFs()}, - } -} - -// Download downloads the raw image from source. -func (d *Downloader) Download(ctx context.Context, errWriter io.Writer, showBar bool, source, imageName string) (string, error) { - url, err := url.Parse(source) - if err != nil { - return "", fmt.Errorf("parsing image source URL: %w", err) - } - imageName = filepath.Base(imageName) - var partfile, destination string - switch url.Scheme { - case "http", "https": - cwd, err := os.Getwd() - if err != nil { - return "", fmt.Errorf("getting current working directory: %w", err) - } - partfile = filepath.Join(cwd, imageName+".raw.part") - destination = filepath.Join(cwd, imageName+".raw") - case "file": - return url.Path, nil - default: - return "", fmt.Errorf("unsupported image source URL scheme: %s", url.Scheme) - } - if !d.shouldDownload(destination) { - return destination, nil - } - if err := d.downloadWithProgress(ctx, errWriter, showBar, source, partfile); err != nil { - return "", err - } - return destination, d.fs.Rename(partfile, destination) -} - -// shouldDownload checks if the image should be downloaded. -func (d *Downloader) shouldDownload(destination string) bool { - _, err := d.fs.Stat(destination) - return errors.Is(err, fs.ErrNotExist) -} - -// downloadWithProgress downloads the raw image from source to the destination. -func (d *Downloader) downloadWithProgress(ctx context.Context, errWriter io.Writer, showBar bool, source, destination string) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, source, nil) - if err != nil { - return fmt.Errorf("creating request: %w", err) - } - - resp, err := d.httpc.Do(req) - if err != nil { - return fmt.Errorf("doing request: %w", err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("downloading from %q: %s", source, resp.Status) - } - - f, err := d.fs.OpenFile(destination, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return err - } - defer f.Close() - - var bar io.WriteCloser - if showBar { - bar = prepareBar(errWriter, resp.ContentLength) - } else { - bar = &nopWriteCloser{} - } - defer bar.Close() - - _, err = io.Copy(io.MultiWriter(f, bar), resp.Body) - if err != nil { - return err - } - - return nil -} - -func prepareBar(writer io.Writer, total int64) io.WriteCloser { - return progressbar.NewOptions64( - total, - progressbar.OptionSetWriter(writer), - progressbar.OptionShowBytes(true), - progressbar.OptionSetPredictTime(true), - progressbar.OptionFullWidth(), - progressbar.OptionSetTheme(progressbar.Theme{ - Saucer: "=", - SaucerHead: ">", - SaucerPadding: " ", - BarStart: "[", - BarEnd: "]", - }), - progressbar.OptionClearOnFinish(), - progressbar.OptionOnCompletion(func() { fmt.Fprintf(writer, "Done.\n\n") }), - ) -} - -type nopWriteCloser struct{} - -func (*nopWriteCloser) Write(p []byte) (int, error) { - return len(p), nil -} - -func (*nopWriteCloser) Close() error { - return nil -} - -type httpc interface { - Do(req *http.Request) (*http.Response, error) -} diff --git a/internal/imagefetcher/raw_test.go b/internal/imagefetcher/raw_test.go deleted file mode 100644 index e2bbd8b..0000000 --- a/internal/imagefetcher/raw_test.go +++ /dev/null @@ -1,204 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package imagefetcher - -import ( - "bytes" - "context" - "io" - "net/http" - "os" - "path" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" -) - -func TestShouldDownload(t *testing.T) { - testCases := map[string]struct { - partfile, destination string - wantDownload bool - }{ - "no files exist yet": { - wantDownload: true, - }, - "partial download": { - partfile: "some data", - wantDownload: true, - }, - "download succeeded": { - destination: "all of the data", - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - downloader := &Downloader{ - fs: newDownloaderStubFs(t, "someVersion", tc.partfile, tc.destination), - } - gotDownload := downloader.shouldDownload("someVersion.raw") - assert.Equal(tc.wantDownload, gotDownload) - }) - } -} - -func TestDownloadWithProgress(t *testing.T) { - rawImage := "raw image" - client := newTestClient(func(req *http.Request) *http.Response { - if req.URL.String() == "https://cdn.example.com/image.raw" { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(rawImage)), - Header: make(http.Header), - } - } - return &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewBufferString("Not found.")), - Header: make(http.Header), - } - }) - - testCases := map[string]struct { - source string - wantErr bool - }{ - "correct file requested": { - source: "https://cdn.example.com/image.raw", - }, - "incorrect file requested": { - source: "https://cdn.example.com/incorrect.raw", - wantErr: true, - }, - "invalid scheme": { - source: "xyz://", - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - fs := newDownloaderStubFs(t, "someVersion", "", "") - downloader := &Downloader{ - httpc: client, - fs: fs, - } - var outBuffer bytes.Buffer - err := downloader.downloadWithProgress(context.Background(), &outBuffer, false, tc.source, "someVersion.raw") - if tc.wantErr { - assert.Error(err) - return - } - assert.NoError(err) - out, err := fs.ReadFile("someVersion.raw") - assert.NoError(err) - assert.Equal(rawImage, string(out)) - }) - } -} - -func TestDownload(t *testing.T) { - rawImage := "raw image" - cwd, err := os.Getwd() - assert.NoError(t, err) - wantDestination := path.Join(cwd, "someVersion.raw") - client := newTestClient(func(req *http.Request) *http.Response { - if req.URL.String() == "https://cdn.example.com/image.raw" { - return &http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(rawImage)), - Header: make(http.Header), - } - } - return &http.Response{ - StatusCode: http.StatusNotFound, - Body: io.NopCloser(bytes.NewBufferString("Not found.")), - Header: make(http.Header), - } - }) - - testCases := map[string]struct { - source string - destination string - overrideFile string - wantErr bool - }{ - "correct file requested": { - source: "https://cdn.example.com/image.raw", - }, - "file url": { - source: "file:///override.raw", - overrideFile: "override image", - }, - "file exists": { - source: "https://cdn.example.com/image.raw", - destination: "already exists", - }, - "incorrect file requested": { - source: "https://cdn.example.com/incorrect.raw", - wantErr: true, - }, - "invalid scheme": { - source: "xyz://", - wantErr: true, - }, - "invalid URL": { - source: "\x00", - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - fs := newDownloaderStubFs(t, cwd+"/someVersion", "", tc.destination) - if tc.overrideFile != "" { - must(t, fs.WriteFile("/override.raw", []byte(tc.overrideFile), os.ModePerm)) - } - downloader := &Downloader{ - httpc: client, - fs: fs, - } - var outBuffer bytes.Buffer - gotDestination, err := downloader.Download(context.Background(), &outBuffer, false, tc.source, "someVersion") - if tc.wantErr { - assert.Error(err) - return - } - assert.NoError(err) - if tc.overrideFile == "" { - assert.Equal(wantDestination, gotDestination) - } else { - assert.Equal("/override.raw", gotDestination) - } - out, err := fs.ReadFile(gotDestination) - assert.NoError(err) - switch { - case tc.overrideFile != "": - assert.Equal(tc.overrideFile, string(out)) - case tc.destination != "": - assert.Equal(tc.destination, string(out)) - default: - assert.Equal(rawImage, string(out)) - } - }) - } -} - -func newDownloaderStubFs(t *testing.T, version, partfile, destination string) *afero.Afero { - fs := afero.NewMemMapFs() - if partfile != "" { - must(t, afero.WriteFile(fs, version+".raw.part", []byte(partfile), os.ModePerm)) - } - if destination != "" { - must(t, afero.WriteFile(fs, version+".raw", []byte(destination), os.ModePerm)) - } - return &afero.Afero{Fs: fs} -} diff --git a/internal/installer/BUILD.bazel b/internal/installer/BUILD.bazel deleted file mode 100644 index 9b88951..0000000 --- a/internal/installer/BUILD.bazel +++ /dev/null @@ -1,30 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "installer", - srcs = ["installer.go"], - importpath = "cvm-reverse-proxy/internal/installer", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/retry", - "//internal/versions/components", - "@com_github_spf13_afero//:afero", - "@com_github_vincent_petithory_dataurl//:dataurl", - "@io_k8s_utils//clock", - ], -) - -go_test( - name = "installer_test", - srcs = ["installer_test.go"], - embed = [":installer"], - deps = [ - "//internal/versions/components", - "@com_github_spf13_afero//:afero", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@io_k8s_utils//clock/testing", - "@org_golang_google_grpc//test/bufconn", - ], -) diff --git a/internal/installer/installer.go b/internal/installer/installer.go deleted file mode 100644 index 3db5cd3..0000000 --- a/internal/installer/installer.go +++ /dev/null @@ -1,351 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package installer provides functionality to install binary components of supported kubernetes versions. -package installer - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "crypto/sha256" - "errors" - "fmt" - "io" - "io/fs" - "net/http" - "net/url" - "os" - "path" - "slices" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/retry" - "github.com/flashbots/cvm-reverse-proxy/internal/versions/components" - - "github.com/spf13/afero" - "github.com/vincent-petithory/dataurl" - "k8s.io/utils/clock" -) - -const ( - // determines the period after which retryDownloadToTempDir will retry a download. - downloadInterval = 10 * time.Millisecond - executablePerm = 0o544 -) - -// OsInstaller installs binary components of supported kubernetes versions. -type OsInstaller struct { - fs *afero.Afero - hClient httpClient - // clock is needed for testing purposes - clock clock.WithTicker - // retriable is the function used to check if an error is retriable. Needed for testing. - retriable func(error) bool -} - -// NewOSInstaller creates a new osInstaller. -func NewOSInstaller() *OsInstaller { - return &OsInstaller{ - fs: &afero.Afero{Fs: afero.NewOsFs()}, - hClient: &http.Client{}, - clock: clock.RealClock{}, - retriable: isRetriable, - } -} - -// Install downloads a resource from a URL, applies any given text transformations and extracts the resulting file if required. -// The resulting file(s) are copied to the destination. It also verifies the sha256 hash of the downloaded file. -func (i *OsInstaller) Install(ctx context.Context, kubernetesComponent *components.Component) error { - tempPath, err := i.retryDownloadToTempDir(ctx, kubernetesComponent.Url) - if err != nil { - return err - } - - file, err := i.fs.OpenFile(tempPath, os.O_RDONLY, 0) - if err != nil { - return fmt.Errorf("opening file %q: %w", tempPath, err) - } - sha := sha256.New() - if _, err := io.Copy(sha, file); err != nil { - return fmt.Errorf("reading file %q: %w", tempPath, err) - } - calculatedHash := fmt.Sprintf("sha256:%x", sha.Sum(nil)) - if len(kubernetesComponent.Hash) > 0 && calculatedHash != kubernetesComponent.Hash { - return fmt.Errorf("hash of file %q %s does not match expected hash %s", tempPath, calculatedHash, kubernetesComponent.Hash) - } - - defer func() { - _ = i.fs.Remove(tempPath) - }() - if kubernetesComponent.Extract { - err = i.extractArchive(tempPath, kubernetesComponent.InstallPath, executablePerm) - } else { - err = i.copy(tempPath, kubernetesComponent.InstallPath, executablePerm) - } - if err != nil { - return fmt.Errorf("installing from %q: copying to destination %q: %w", kubernetesComponent.Url, kubernetesComponent.InstallPath, err) - } - - return nil -} - -// extractArchive extracts tar gz archives to a prefixed destination. -func (i *OsInstaller) extractArchive(archivePath, prefix string, perm fs.FileMode) error { - archiveFile, err := i.fs.Open(archivePath) - if err != nil { - return fmt.Errorf("opening archive file: %w", err) - } - defer archiveFile.Close() - gzReader, err := gzip.NewReader(archiveFile) - if err != nil { - return fmt.Errorf("reading archive file as gzip: %w", err) - } - defer gzReader.Close() - if err := i.fs.MkdirAll(prefix, fs.ModePerm); err != nil { - return fmt.Errorf("creating prefix folder: %w", err) - } - tarReader := tar.NewReader(gzReader) - - for { - header, err := tarReader.Next() - if err == io.EOF { - return nil - } - if err != nil { - return fmt.Errorf("parsing tar header: %w", err) - } - if err := verifyTarPath(header.Name); err != nil { - return fmt.Errorf("verifying tar path %q: %w", header.Name, err) - } - switch header.Typeflag { - case tar.TypeDir: - if len(header.Name) == 0 { - return errors.New("cannot create dir for empty path") - } - prefixedPath := path.Join(prefix, header.Name) - if err := i.fs.Mkdir(prefixedPath, fs.FileMode(header.Mode)&perm); err != nil && !errors.Is(err, os.ErrExist) { - return fmt.Errorf("creating folder %q: %w", prefixedPath, err) - } - case tar.TypeReg: - if len(header.Name) == 0 { - return errors.New("cannot create file for empty path") - } - prefixedPath := path.Join(prefix, header.Name) - out, err := i.fs.OpenFile(prefixedPath, os.O_WRONLY|os.O_CREATE, fs.FileMode(header.Mode)) - if err != nil { - return fmt.Errorf("creating file %q for writing: %w", prefixedPath, err) - } - defer out.Close() - if _, err := io.Copy(out, tarReader); err != nil { - return fmt.Errorf("writing extracted file contents: %w", err) - } - case tar.TypeSymlink: - if err := verifyTarPath(header.Linkname); err != nil { - return fmt.Errorf("invalid tar path %q: %w", header.Linkname, err) - } - if len(header.Name) == 0 { - return errors.New("cannot symlink file for empty oldname") - } - if len(header.Linkname) == 0 { - return errors.New("cannot symlink file for empty newname") - } - if symlinker, ok := i.fs.Fs.(afero.Symlinker); ok { - if err := symlinker.SymlinkIfPossible(path.Join(prefix, header.Name), path.Join(prefix, header.Linkname)); err != nil { - return fmt.Errorf("creating symlink: %w", err) - } - } else { - return errors.New("fs does not support symlinks") - } - default: - return fmt.Errorf("unsupported tar record: %v", header.Typeflag) - } - } -} - -func (i *OsInstaller) retryDownloadToTempDir(ctx context.Context, url string) (fileName string, someError error) { - doer := downloadDoer{ - url: url, - downloader: i, - } - - // Retries are canceled as soon as the context is canceled. - // We need to call NewIntervalRetrier with a clock argument so that the tests can fake the clock by changing the osInstaller clock. - retrier := retry.NewIntervalRetrier(&doer, downloadInterval, i.retriable, i.clock) - if err := retrier.Do(ctx); err != nil { - return "", fmt.Errorf("retrying downloadToTempDir: %w", err) - } - - return doer.path, nil -} - -// retriableHTTPStatusCodes are status codes that might flip to 200 if retried. -// This arguably depends on the web server implementation, but below list is -// a reasonable selection, cf. https://stackoverflow.com/a/74627395. -var retriableHTTPStatusCodes = []int{ - http.StatusRequestTimeout, - http.StatusTooEarly, - http.StatusTooManyRequests, - http.StatusBadGateway, - http.StatusServiceUnavailable, - http.StatusGatewayTimeout, -} - -// downloadHTTP downloads the given URL with the embedded HTTP client and writes the content to out. -func (i *OsInstaller) downloadHTTP(ctx context.Context, url string, out io.Writer) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return fmt.Errorf("request to download %q: %w", url, err) - } - resp, err := i.hClient.Do(req) - if err != nil { - // A failure at this point might be transient, such as network connectivity. - return fmt.Errorf("request to download %q: %w", url, &retriableError{err: err}) - } - if resp.StatusCode != http.StatusOK { - // The HTTP request went through, but the result is not what we - // expected. Wrap the error return in case we think the request could - // be retried. - err = fmt.Errorf("request to download %q failed with status code: %v", url, resp.Status) - if slices.Contains(retriableHTTPStatusCodes, resp.StatusCode) { - err = &retriableError{err: err} - } - return err - } - defer resp.Body.Close() - - if _, err = io.Copy(out, resp.Body); err != nil { - return fmt.Errorf("downloading %q: %w", url, &retriableError{err: err}) - } - return nil -} - -// unpackData parses the given data URL and writes the content to out. -func (i *OsInstaller) unpackData(url string, out io.Writer) error { - dataURL, err := dataurl.DecodeString(url) - if err != nil { - return fmt.Errorf("parsing data URL: %w", err) - } - buf := bytes.NewBuffer(dataURL.Data) - if _, err = io.Copy(out, buf); err != nil { - return fmt.Errorf("writing content of data URL %q: %w", url, err) - } - return nil -} - -// downloadToTempDir downloads a file from the given URL to a temporary location and returns the path to the downloaded file. -func (i *OsInstaller) downloadToTempDir(ctx context.Context, u string) (string, error) { - url, err := url.Parse(u) - if err != nil { - return "", fmt.Errorf("parsing component URL: %w", err) - } - - out, err := afero.TempFile(i.fs, "", "") - if err != nil { - return "", fmt.Errorf("creating destination temp file: %w", err) - } - - if url.Scheme == "data" { - err = i.unpackData(u, out) - } else { - err = i.downloadHTTP(ctx, u, out) - } - out.Close() - if err != nil { - removeErr := i.fs.Remove(out.Name()) - return "", errors.Join(err, removeErr) - } - return out.Name(), nil -} - -// copy copies a file from oldname to newname. -func (i *OsInstaller) copy(oldname, newname string, perm fs.FileMode) (err error) { - old, openOldErr := i.fs.OpenFile(oldname, os.O_RDONLY, fs.ModePerm) - if openOldErr != nil { - return fmt.Errorf("copying %q to %q: cannot open source file for reading: %w", oldname, newname, openOldErr) - } - defer func() { _ = old.Close() }() - // create destination path if not exists - if err := i.fs.MkdirAll(path.Dir(newname), fs.ModePerm); err != nil { - return fmt.Errorf("copying %q to %q: unable to create destination folder: %w", oldname, newname, err) - } - newFile, openNewErr := i.fs.OpenFile(newname, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, perm) - if openNewErr != nil { - return fmt.Errorf("copying %q to %q: cannot open destination file for writing: %w", oldname, newname, openNewErr) - } - defer func() { - _ = newFile.Close() - if err != nil { - _ = i.fs.Remove(newname) - } - }() - if _, err := io.Copy(newFile, old); err != nil { - return fmt.Errorf("copying %q to %q: copying file contents: %w", oldname, newname, err) - } - - return nil -} - -type downloadDoer struct { - url string - downloader downloader - path string -} - -type downloader interface { - downloadToTempDir(ctx context.Context, url string) (string, error) -} - -func (d *downloadDoer) Do(ctx context.Context) error { - path, err := d.downloader.downloadToTempDir(ctx, d.url) - d.path = path - return err -} - -// retriableError is an error that can be retried. -type retriableError struct{ err error } - -func (e *retriableError) Error() string { - return fmt.Sprintf("retriable error: %s", e.err.Error()) -} - -func (e *retriableError) Unwrap() error { return e.err } - -// isRetriable returns true if the action resulting in this error can be retried. -func isRetriable(err error) bool { - retriableError := &retriableError{} - return errors.As(err, &retriableError) -} - -// verifyTarPath checks if a tar path is valid (must not contain ".." as path element). -func verifyTarPath(pat string) error { - n := len(pat) - r := 0 - for r < n { - switch { - case os.IsPathSeparator(pat[r]): - // empty path element - r++ - case pat[r] == '.' && (r+1 == n || os.IsPathSeparator(pat[r+1])): - // . element - r++ - case pat[r] == '.' && pat[r+1] == '.' && (r+2 == n || os.IsPathSeparator(pat[r+2])): - // .. element - return errors.New("path contains \"..\"") - default: - // skip to next path element - for r < n && !os.IsPathSeparator(pat[r]) { - r++ - } - } - } - return nil -} - -type httpClient interface { - Do(req *http.Request) (*http.Response, error) -} diff --git a/internal/installer/installer_test.go b/internal/installer/installer_test.go deleted file mode 100644 index 787dd7b..0000000 --- a/internal/installer/installer_test.go +++ /dev/null @@ -1,774 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package installer - -import ( - "archive/tar" - "bufio" - "bytes" - "compress/gzip" - "context" - "errors" - "io" - "io/fs" - "net" - "net/http" - "net/http/httptest" - "path" - "sync" - "testing" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/versions/components" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/test/bufconn" - testclock "k8s.io/utils/clock/testing" -) - -func TestInstall(t *testing.T) { - serverURL := "http://server/path" - testCases := map[string]struct { - server httpBufconnServer - component *components.Component - hash string - destination string - extract bool - wantErr bool - wantFiles map[string][]byte - }{ - "download works": { - server: newHTTPBufconnServerWithBody([]byte("file-contents")), - component: &components.Component{ - Url: serverURL, - Hash: "sha256:f03779b36bece74893fd6533a67549675e21573eb0e288d87158738f9c24594e", - InstallPath: "/destination", - }, - wantFiles: map[string][]byte{"/destination": []byte("file-contents")}, - }, - "download with extract works": { - server: newHTTPBufconnServerWithBody(createTarGz([]byte("file-contents"), "/destination")), - component: &components.Component{ - Url: serverURL, - Hash: "sha256:a52a1664ca0a6ec9790384e3d058852ab8b3a8f389a9113d150fdc6ab308d949", - InstallPath: "/prefix", - Extract: true, - }, - wantFiles: map[string][]byte{"/prefix/destination": []byte("file-contents")}, - }, - "hash validation fails": { - server: newHTTPBufconnServerWithBody([]byte("file-contents")), - component: &components.Component{ - Url: serverURL, - Hash: "sha256:abc", - InstallPath: "/destination", - }, - wantErr: true, - }, - "hash is not mandatory": { - server: newHTTPBufconnServerWithBody([]byte("file-contents")), - component: &components.Component{ - Url: serverURL, - Hash: "", - InstallPath: "/destination", - }, - wantFiles: map[string][]byte{"/destination": []byte("file-contents")}, - }, - "download fails": { - server: newHTTPBufconnServer(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(500) }), - component: &components.Component{ - Url: serverURL, - Hash: "sha256:abc", - InstallPath: "/destination", - }, - wantErr: true, - }, - "dataurl works": { - server: newHTTPBufconnServer(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(500) }), - component: &components.Component{ - Url: "data:text/plain,file-contents", - Hash: "", - InstallPath: "/destination", - }, - wantFiles: map[string][]byte{"/destination": []byte("file-contents")}, - }, - "broken dataurl fails": { - server: newHTTPBufconnServer(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(500) }), - component: &components.Component{ - Url: "data:file-contents", - Hash: "", - InstallPath: "/destination", - }, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - defer tc.server.Close() - - hClient := http.Client{ - Transport: &http.Transport{ - DialContext: tc.server.DialContext, - Dial: tc.server.Dial, - DialTLSContext: tc.server.DialContext, - DialTLS: tc.server.Dial, - }, - } - - // This test was written before retriability was added to Install. It makes sense to test Install as if it wouldn't retry requests. - inst := OsInstaller{ - fs: &afero.Afero{Fs: afero.NewMemMapFs()}, - hClient: &hClient, - clock: testclock.NewFakeClock(time.Time{}), - retriable: func(_ error) bool { return false }, - } - - err := inst.Install(context.Background(), tc.component) - if tc.wantErr { - assert.Error(err) - return - } - - require.NoError(err) - for path, wantContents := range tc.wantFiles { - contents, err := inst.fs.ReadFile(path) - assert.NoError(err) - assert.Equal(wantContents, contents) - } - }) - } -} - -func TestExtractArchive(t *testing.T) { - tarGzTestFile := createTarGz([]byte("file-contents"), "/destination") - tarGzTestWithFolder := createTarGzWithFolder([]byte("file-contents"), "/folder/destination", nil) - - testCases := map[string]struct { - source string - destination string - contents []byte - readonly bool - wantErr bool - wantFiles map[string][]byte - }{ - "extract works": { - source: "in.tar.gz", - destination: "/prefix", - contents: tarGzTestFile, - wantFiles: map[string][]byte{ - "/prefix/destination": []byte("file-contents"), - }, - }, - "extract with folder works": { - source: "in.tar.gz", - destination: "/prefix", - contents: tarGzTestWithFolder, - wantFiles: map[string][]byte{ - "/prefix/folder/destination": []byte("file-contents"), - }, - }, - "source missing": { - source: "in.tar.gz", - destination: "/prefix", - wantErr: true, - }, - "non-gzip file contents": { - source: "in.tar.gz", - contents: []byte("invalid bytes"), - destination: "/prefix", - wantErr: true, - }, - "non-tar file contents": { - source: "in.tar.gz", - contents: createGz([]byte("file-contents")), - destination: "/prefix", - wantErr: true, - }, - "mkdir prefix dir fails on RO fs": { - source: "in.tar.gz", - contents: tarGzTestFile, - destination: "/prefix", - readonly: true, - wantErr: true, - }, - "mkdir tar dir fails on RO fs": { - source: "in.tar.gz", - contents: tarGzTestWithFolder, - destination: "/", - readonly: true, - wantErr: true, - }, - "writing tar file fails on RO fs": { - source: "in.tar.gz", - contents: tarGzTestFile, - destination: "/", - readonly: true, - wantErr: true, - }, - "symlink can be detected (but is unsupported on memmapfs)": { - source: "in.tar.gz", - contents: createTarGzWithSymlink("source", "dest"), - destination: "/prefix", - wantErr: true, - }, - "unsupported tar header type is detected": { - source: "in.tar.gz", - contents: createTarGzWithFifo("/destination"), - destination: "/prefix", - wantErr: true, - }, - "path traversal is detected": { - source: "in.tar.gz", - contents: createTarGz([]byte{}, "../destination"), - wantErr: true, - }, - "path traversal in symlink is detected": { - source: "in.tar.gz", - contents: createTarGzWithSymlink("/source", "../destination"), - wantErr: true, - }, - "empty file name is detected": { - source: "in.tar.gz", - contents: createTarGz([]byte{}, ""), - wantErr: true, - }, - "empty folder name is detected": { - source: "in.tar.gz", - contents: createTarGzWithFolder([]byte{}, "source", stringPtr("")), - wantErr: true, - }, - "empty symlink source is detected": { - source: "in.tar.gz", - contents: createTarGzWithSymlink("", "/target"), - wantErr: true, - }, - "empty symlink target is detected": { - source: "in.tar.gz", - contents: createTarGzWithSymlink("/source", ""), - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - afs := afero.NewMemMapFs() - if len(tc.source) > 0 && len(tc.contents) > 0 { - require.NoError(afero.WriteFile(afs, tc.source, tc.contents, fs.ModePerm)) - } - - if tc.readonly { - afs = afero.NewReadOnlyFs(afs) - } - - inst := OsInstaller{ - fs: &afero.Afero{Fs: afs}, - } - err := inst.extractArchive(tc.source, tc.destination, fs.ModePerm) - if tc.wantErr { - assert.Error(err) - return - } - - require.NoError(err) - for path, wantContents := range tc.wantFiles { - contents, err := inst.fs.ReadFile(path) - assert.NoError(err) - assert.Equal(wantContents, contents) - } - }) - } -} - -func TestRetryDownloadToTempDir(t *testing.T) { - testCases := map[string]struct { - responses []int - cancelCtx bool - wantErr bool - wantFile []byte - }{ - "Succeed on third try": { - responses: []int{500, 500, 200}, - wantFile: []byte("file-content"), - }, - "Cancel after second try": { - responses: []int{500, 500}, - cancelCtx: true, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - // control the server's responses through stateCh - stateCh := make(chan int) - server := newHTTPBufconnServerWithState(stateCh, tc.wantFile) - defer server.Close() - - hClient := http.Client{ - Transport: &http.Transport{ - DialContext: server.DialContext, - Dial: server.Dial, - DialTLSContext: server.DialContext, - DialTLS: server.Dial, - }, - } - - afs := afero.NewMemMapFs() - - // control download retries through FakeClock clock - clock := testclock.NewFakeClock(time.Now()) - inst := OsInstaller{ - fs: &afero.Afero{Fs: afs}, - hClient: &hClient, - clock: clock, - retriable: func(error) bool { return true }, - } - - // abort retryDownloadToTempDir in some test cases by using the context - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - wg := sync.WaitGroup{} - var downloadErr error - var path string - wg.Add(1) - go func() { - defer wg.Done() - path, downloadErr = inst.retryDownloadToTempDir(ctx, "http://server/path") - }() - - // control the server's responses through stateCh. - for _, resp := range tc.responses { - stateCh <- resp - clock.Step(downloadInterval) - } - if tc.cancelCtx { - cancel() - } - - wg.Wait() - - if tc.wantErr { - assert.Error(downloadErr) - return - } - - require.NoError(downloadErr) - content, err := inst.fs.ReadFile(path) - assert.NoError(err) - assert.Equal(tc.wantFile, content) - }) - } -} - -func TestDownloadToTempDir(t *testing.T) { - testCases := map[string]struct { - server httpBufconnServer - readonly bool - wantErr bool - wantFile []byte - }{ - "download works": { - server: newHTTPBufconnServerWithBody([]byte("file-contents")), - wantFile: []byte("file-contents"), - }, - "download fails": { - server: newHTTPBufconnServer(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(500) }), - wantErr: true, - }, - "creating temp file fails on RO fs": { - server: newHTTPBufconnServerWithBody([]byte("file-contents")), - readonly: true, - wantErr: true, - }, - "content length mismatch": { - server: newHTTPBufconnServer(func(writer http.ResponseWriter, _ *http.Request) { - writer.Header().Set("Content-Length", "1337") - writer.WriteHeader(200) - }), - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - defer tc.server.Close() - - hClient := http.Client{ - Transport: &http.Transport{ - DialContext: tc.server.DialContext, - Dial: tc.server.Dial, - DialTLSContext: tc.server.DialContext, - DialTLS: tc.server.Dial, - }, - } - - afs := afero.NewMemMapFs() - if tc.readonly { - afs = afero.NewReadOnlyFs(afs) - } - inst := OsInstaller{ - fs: &afero.Afero{Fs: afs}, - hClient: &hClient, - } - path, err := inst.downloadToTempDir(context.Background(), "http://server/path") - if tc.wantErr { - assert.Error(err) - return - } - - require.NoError(err) - contents, err := inst.fs.ReadFile(path) - assert.NoError(err) - assert.Equal(tc.wantFile, contents) - }) - } -} - -func TestCopy(t *testing.T) { - contents := []byte("file-contents") - existingFile := "/source" - testCases := map[string]struct { - oldname string - newname string - perm fs.FileMode - readonly bool - wantErr bool - }{ - "copy works": { - oldname: existingFile, - newname: "/destination", - perm: fs.ModePerm, - }, - "oldname does not exist": { - oldname: "missing", - newname: "/destination", - wantErr: true, - }, - "copy on readonly fs fails": { - oldname: existingFile, - newname: "/destination", - perm: fs.ModePerm, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - afs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(afs, existingFile, contents, fs.ModePerm)) - - if tc.readonly { - afs = afero.NewReadOnlyFs(afs) - } - - inst := OsInstaller{fs: &afero.Afero{Fs: afs}} - err := inst.copy(tc.oldname, tc.newname, tc.perm) - if tc.wantErr { - assert.Error(err) - return - } - - require.NoError(err) - - oldfile, err := afs.Open(tc.oldname) - assert.NoError(err) - newfile, err := afs.Open(tc.newname) - assert.NoError(err) - - oldContents, _ := io.ReadAll(oldfile) - newContents, _ := io.ReadAll(newfile) - assert.Equal(oldContents, newContents) - - newStat, _ := newfile.Stat() - assert.Equal(tc.perm, newStat.Mode()) - }) - } -} - -func TestVerifyTarPath(t *testing.T) { - testCases := map[string]struct { - path string - wantErr bool - }{ - "valid relative path": { - path: "a/b/c", - }, - "valid absolute path": { - path: "/a/b/c", - }, - "valid path with dot": { - path: "/a/b/.d", - }, - "valid path with dots": { - path: "/a/b/..d", - }, - "single dot in path is allowed": { - path: ".", - }, - "simple path traversal": { - path: "..", - wantErr: true, - }, - "simple path traversal 2": { - path: "../", - wantErr: true, - }, - "simple path traversal 3": { - path: "/..", - wantErr: true, - }, - "simple path traversal 4": { - path: "/../", - wantErr: true, - }, - "complex relative path traversal": { - path: "a/b/c/../../../../c/d/e", - wantErr: true, - }, - "complex absolute path traversal": { - path: "/a/b/c/../../../../c/d/e", - wantErr: true, - }, - "path traversal at the end": { - path: "a/..", - wantErr: true, - }, - "path traversal at the end with trailing /": { - path: "a/../", - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - err := verifyTarPath(tc.path) - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - - assert.Equal(tc.path, path.Clean(tc.path)) - }) - } -} - -type httpBufconnServer struct { - *httptest.Server - *bufconn.Listener -} - -func (s *httpBufconnServer) DialContext(ctx context.Context, _, _ string) (net.Conn, error) { - return s.Listener.DialContext(ctx) -} - -func (s *httpBufconnServer) Dial(_, _ string) (net.Conn, error) { - return s.Listener.Dial() -} - -func (s *httpBufconnServer) Close() { - s.Server.Close() - s.Listener.Close() -} - -func newHTTPBufconnServer(handlerFunc http.HandlerFunc) httpBufconnServer { - server := httptest.NewUnstartedServer(handlerFunc) - listener := bufconn.Listen(1024) - server.Listener = listener - server.Start() - return httpBufconnServer{ - Server: server, - Listener: listener, - } -} - -func newHTTPBufconnServerWithBody(body []byte) httpBufconnServer { - return newHTTPBufconnServer(func(writer http.ResponseWriter, _ *http.Request) { - if _, err := writer.Write(body); err != nil { - panic(err) - } - }) -} - -func newHTTPBufconnServerWithState(state chan int, body []byte) httpBufconnServer { - return newHTTPBufconnServer(func(w http.ResponseWriter, _ *http.Request) { - switch <-state { - case 500: - w.WriteHeader(500) - case 200: - if _, err := w.Write(body); err != nil { - panic(err) - } - default: - w.WriteHeader(402) - } - }) -} - -func createTarGz(contents []byte, path string) []byte { - tgzWriter := newTarGzWriter() - defer func() { _ = tgzWriter.Close() }() - - if err := tgzWriter.writeHeader(&tar.Header{ - Typeflag: tar.TypeReg, - Name: path, - Size: int64(len(contents)), - Mode: int64(fs.ModePerm), - }); err != nil { - panic(err) - } - if _, err := tgzWriter.writeTar(contents); err != nil { - panic(err) - } - - return tgzWriter.Bytes() -} - -func createTarGzWithFolder(contents []byte, pat string, dirnameOverride *string) []byte { - tgzWriter := newTarGzWriter() - defer func() { _ = tgzWriter.Close() }() - - dir := path.Dir(pat) - if dirnameOverride != nil { - dir = *dirnameOverride - } - - if err := tgzWriter.writeHeader(&tar.Header{ - Typeflag: tar.TypeDir, - Name: dir, - Mode: int64(fs.ModePerm), - }); err != nil { - panic(err) - } - if err := tgzWriter.writeHeader(&tar.Header{ - Typeflag: tar.TypeReg, - Name: pat, - Size: int64(len(contents)), - Mode: int64(fs.ModePerm), - }); err != nil { - panic(err) - } - if _, err := tgzWriter.writeTar(contents); err != nil { - panic(err) - } - - return tgzWriter.Bytes() -} - -func createTarGzWithSymlink(oldname, newname string) []byte { - tgzWriter := newTarGzWriter() - defer func() { _ = tgzWriter.Close() }() - - if err := tgzWriter.writeHeader(&tar.Header{ - Typeflag: tar.TypeSymlink, - Name: oldname, - Linkname: newname, - Mode: int64(fs.ModePerm), - }); err != nil { - panic(err) - } - - return tgzWriter.Bytes() -} - -func createTarGzWithFifo(name string) []byte { - tgzWriter := newTarGzWriter() - defer func() { _ = tgzWriter.Close() }() - - if err := tgzWriter.writeHeader(&tar.Header{ - Typeflag: tar.TypeFifo, - Name: name, - Mode: int64(fs.ModePerm), - }); err != nil { - panic(err) - } - - return tgzWriter.Bytes() -} - -func createGz(contents []byte) []byte { - tgzWriter := newTarGzWriter() - defer func() { _ = tgzWriter.Close() }() - - if _, err := tgzWriter.writeGz(contents); err != nil { - panic(err) - } - - return tgzWriter.Bytes() -} - -type tarGzWriter struct { - buf *bytes.Buffer - bufWriter *bufio.Writer - gzWriter *gzip.Writer - tarWriter *tar.Writer -} - -func newTarGzWriter() *tarGzWriter { - var buf bytes.Buffer - bufWriter := bufio.NewWriter(&buf) - gzipWriter := gzip.NewWriter(bufWriter) - tarWriter := tar.NewWriter(gzipWriter) - - return &tarGzWriter{ - buf: &buf, - bufWriter: bufWriter, - gzWriter: gzipWriter, - tarWriter: tarWriter, - } -} - -func (w *tarGzWriter) writeHeader(hdr *tar.Header) error { - return w.tarWriter.WriteHeader(hdr) -} - -func (w *tarGzWriter) writeTar(b []byte) (int, error) { - return w.tarWriter.Write(b) -} - -func (w *tarGzWriter) writeGz(b []byte) (int, error) { - return w.gzWriter.Write(b) -} - -func (w *tarGzWriter) Bytes() []byte { - _ = w.tarWriter.Flush() - _ = w.gzWriter.Flush() - _ = w.gzWriter.Close() // required to ensure clean EOF in gz reader - _ = w.bufWriter.Flush() - return w.buf.Bytes() -} - -func (w *tarGzWriter) Close() (retErr error) { - retErr = errors.Join(retErr, w.tarWriter.Close()) - retErr = errors.Join(retErr, w.gzWriter.Close()) - return retErr -} - -func stringPtr(s string) *string { - return &s -} diff --git a/internal/kms/README.md b/internal/kms/README.md deleted file mode 100644 index 3afa508..0000000 --- a/internal/kms/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# Key Management Service backend implementation - -This library provides an interface for the key management services used by Constellation. -Its intended to be used for secure managing for data encryption keys and other symmetric secrets. - -## [kms](./kms/) - -A Key Management Service (KMS) is where we store our key encryption key (KEK). - -We differentiate between two cases: - -* cluster KMS (cKMS): - - The Constellation cluster itself holds the master secret (KEK) and manages key derivation. - The KEK is generated by an admin on `constellation apply`. - Once send to the cluster, the KEK never leaves the confidential computing context. - As keys are only derived on demand, no DEK is ever persisted to memory by the cKMS. - -* external KMS (eKMS): - - An external KMS solution is used to hold and manage the KEK. - DEKs are encrypted and persisted to cloud storage solutions. - An admin is required to set up and configure the KMS before use. - -### KMS Credentials - -This section covers how credentials are used by the KMS plugins. - -#### AWS KMS - -The client requires the region the KMS is located, an access key ID, and an access key secret. -Read the [access key documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) for more details. - -The IAM role requires the following permissions on the key: - -* `kms:DescribeKey` -* `kms:Encrypt` -* `kms:Decrypt` - -#### Azure Key Vault / Azure managed HSM - -Authorization for Azure Key Vault happens through the use of manged identities. -The managed identity used by the client needs the following permissions on the KEK: - -* `keys/get` -* `keys/wrapKey` -* `keys/unwrapKey` - -The client is set up using the tenant ID, client ID, and client secret tuple. -Further, the vault type is chosen to configure whether or not the Key Vault is a managed HSM. - -### Google KMS - -Providing credentials to your application for Google's Cloud KMS happens through the usage of service accounts. -A credentials file for the service account is used to authorize the client. - -Note that the service account used for authentication requires the following permissions: - -* `cloudkms.cryptoKeyVersions.get` -* `cloudkms.cryptoKeyVersions.useToDecrypt` -* `cloudkms.cryptoKeyVersions.useToEncrypt` - -## [storage](./storage/) - -Storage is where the CSI Plugin stores the encrypted DEKs. - -Supported are: - -* In-memory (used for testing only) -* AWS S3, SSP -* GCP GCS -* Azure Blob - -### Storage Credentials - -Each Plugin requires credentials to authenticate itself to a CSP. - -#### AWS S3 Bucket - -For authentication an access key ID and an access key secret is used. -As a fallback, the client may try to automatically fetch the data from the local AWS directory. - -#### Azure Blob Storage - -Authorization for Azure Blob Storage happens through the use of manged identities. -The managed identity requires the following permissions: - -* `Microsoft.Storage/storageAccounts/blobServices/containers/write` (only if a storage container is not set up in advance) -* `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/write` -* `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read` -* `Microsoft.Storage/storageAccounts/blobServices/containers/blobs/add/action` - -#### Google Cloud Storage - -Providing credentials to your application for Google's Cloud Storage happens through the usage of service accounts. -A credentials file for the service account is used to authorize the client. - -Note that the service account requires the following permissions: - -* `storage.buckets.create` (only if a bucket is not set up in advance) -* `storage.buckets.get` -* `storage.objects.create` -* `storage.objects.get` -* `storage.objects.update` diff --git a/internal/kms/config/BUILD.bazel b/internal/kms/config/BUILD.bazel deleted file mode 100644 index 7a48864..0000000 --- a/internal/kms/config/BUILD.bazel +++ /dev/null @@ -1,8 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "config", - srcs = ["config.go"], - importpath = "cvm-reverse-proxy/internal/kms/config", - visibility = ["//:__subpackages__"], -) diff --git a/internal/kms/config/config.go b/internal/kms/config/config.go deleted file mode 100644 index 5af6d3e..0000000 --- a/internal/kms/config/config.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package config provides configuration constants for the KeyService. -package config - -const ( - // SymmetricKeyLength is the length of symmetric encryption keys in bytes. We use AES256, therefore this is 32 Bytes. - SymmetricKeyLength = 32 -) - -var ( - // KmsTags are the default tags for kms client created KMS solutions. - KmsTags = map[string]string{ - "createdBy": "constellation-kms-client", - "component": "constellation-kek", - } - // StorageTags are the default tags for kms client created storage solutions. - StorageTags = map[string]*string{ - "createdBy": toPtr("constellation-kms-client"), - "component": toPtr("constellation-dek-store"), - } - // AWSS3Tag is the default tag string for kms client created AWS S3 storage solutions. - AWSS3Tag = "createdBy=constellation-kms-client&component=constellation-dek-store" -) - -func toPtr[T any](v T) *T { - return &v -} diff --git a/internal/kms/kms/BUILD.bazel b/internal/kms/kms/BUILD.bazel deleted file mode 100644 index 72d1c38..0000000 --- a/internal/kms/kms/BUILD.bazel +++ /dev/null @@ -1,8 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "kms", - srcs = ["kms.go"], - importpath = "cvm-reverse-proxy/internal/kms/kms", - visibility = ["//:__subpackages__"], -) diff --git a/internal/kms/kms/aws/BUILD.bazel b/internal/kms/kms/aws/BUILD.bazel deleted file mode 100644 index 43d97f0..0000000 --- a/internal/kms/kms/aws/BUILD.bazel +++ /dev/null @@ -1,15 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "aws", - srcs = ["aws.go"], - importpath = "cvm-reverse-proxy/internal/kms/kms/aws", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/kms/kms", - "//internal/kms/kms/internal", - "//internal/kms/uri", - "@com_github_hashicorp_go_kms_wrapping_v2//:go-kms-wrapping", - "@com_github_hashicorp_go_kms_wrapping_wrappers_awskms_v2//:awskms", - ], -) diff --git a/internal/kms/kms/aws/aws.go b/internal/kms/kms/aws/aws.go deleted file mode 100644 index 85ebe5a..0000000 --- a/internal/kms/kms/aws/aws.go +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package aws implements a KMS backend for AWS KMS. -package aws - -import ( - "context" - "errors" - "fmt" - - kmsInterface "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/internal" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - wrapping "github.com/hashicorp/go-kms-wrapping/v2" - awskms "github.com/hashicorp/go-kms-wrapping/wrappers/awskms/v2" -) - -// KMSClient implements the CloudKMS interface for AWS. -type KMSClient struct { - kms *internal.KMSClient -} - -// New creates and initializes a new KMSClient for AWS. -func New(ctx context.Context, store kmsInterface.Storage, cfg uri.AWSConfig) (*KMSClient, error) { - if store == nil { - return nil, errors.New("no storage backend provided for KMS") - } - - wrapper := awskms.NewWrapper() - if _, err := wrapper.SetConfig( - ctx, - wrapping.WithKeyId(cfg.KeyName), - awskms.WithRegion(cfg.Region), - awskms.WithAccessKey(cfg.AccessKeyID), - awskms.WithSecretKey(cfg.AccessKey), - ); err != nil { - return nil, fmt.Errorf("setting AWS KMS config: %w", err) - } - return &KMSClient{ - kms: &internal.KMSClient{ - Storage: store, - Wrapper: wrapper, - }, - }, nil -} - -// GetDEK fetches an encrypted Data Encryption Key from storage and decrypts it using a KEK stored in AWS KMS. -func (c *KMSClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) { - return c.kms.GetDEK(ctx, keyID, dekSize) -} - -// Close is a no-op for AWS. -func (c *KMSClient) Close() {} diff --git a/internal/kms/kms/azure/BUILD.bazel b/internal/kms/kms/azure/BUILD.bazel deleted file mode 100644 index 8369a0a..0000000 --- a/internal/kms/kms/azure/BUILD.bazel +++ /dev/null @@ -1,14 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "azure", - srcs = ["azure.go"], - importpath = "cvm-reverse-proxy/internal/kms/kms/azure", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/kms/kms", - "//internal/kms/kms/internal", - "//internal/kms/uri", - "@com_github_hashicorp_go_kms_wrapping_wrappers_azurekeyvault_v2//:azurekeyvault", - ], -) diff --git a/internal/kms/kms/azure/azure.go b/internal/kms/kms/azure/azure.go deleted file mode 100644 index 201b0ba..0000000 --- a/internal/kms/kms/azure/azure.go +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package azure implements KMS backends for Azure Key Vault and Azure managed HSM. -package azure - -import ( - "context" - "errors" - "fmt" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/internal" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - azurekeyvault "github.com/hashicorp/go-kms-wrapping/wrappers/azurekeyvault/v2" -) - -// KMSClient implements the CloudKMS interface for Azure Key Vault. -type KMSClient struct { - kms *internal.KMSClient -} - -// New initializes a KMS client for Azure Key Vault. -func New(ctx context.Context, store kms.Storage, cfg uri.AzureConfig) (*KMSClient, error) { - if store == nil { - return nil, errors.New("no storage backend provided for KMS") - } - - wrapper := azurekeyvault.NewWrapper() - if _, err := wrapper.SetConfig( - ctx, - azurekeyvault.WithTenantId(cfg.TenantID), - azurekeyvault.WithClientId(cfg.ClientID), - azurekeyvault.WithClientSecret(cfg.ClientSecret), - azurekeyvault.WithResource(string(cfg.VaultType)), - azurekeyvault.WithVaultName(cfg.VaultName), - azurekeyvault.WithKeyName(cfg.KeyName), - ); err != nil { - return nil, fmt.Errorf("setting Azure Key Vault config: %w", err) - } - - return &KMSClient{ - kms: &internal.KMSClient{ - Storage: store, - Wrapper: wrapper, - }, - }, nil -} - -// GetDEK fetches an encrypted Data Encryption Key from storage and decrypts it using a KEK stored in Azure Key Vault. -func (c *KMSClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) { - return c.kms.GetDEK(ctx, keyID, dekSize) -} - -// Close is a no-op for Azure. -func (c *KMSClient) Close() {} diff --git a/internal/kms/kms/cluster/BUILD.bazel b/internal/kms/kms/cluster/BUILD.bazel deleted file mode 100644 index b863e5e..0000000 --- a/internal/kms/kms/cluster/BUILD.bazel +++ /dev/null @@ -1,22 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "cluster", - srcs = ["cluster.go"], - importpath = "cvm-reverse-proxy/internal/kms/kms/cluster", - visibility = ["//:__subpackages__"], - deps = ["//internal/crypto"], -) - -go_test( - name = "cluster_test", - srcs = ["cluster_test.go"], - embed = [":cluster"], - deps = [ - "//internal/crypto/testvector", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/kms/kms/cluster/cluster.go b/internal/kms/kms/cluster/cluster.go deleted file mode 100644 index a32a51d..0000000 --- a/internal/kms/kms/cluster/cluster.go +++ /dev/null @@ -1,52 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package cluster implements a KMS backend for in cluster key management. - -The cluster backend holds a master key, and corresponding salt. -Data Encryption Keys (DEK) are derived from master key and salt using HKDF. - -This backend does not require a storage backend, as keys are derived on demand and not stored anywhere. -For that purpose the special NoStoreURI can be used during KMS initialization. -*/ -package cluster - -import ( - "context" - "errors" - - "github.com/flashbots/cvm-reverse-proxy/internal/crypto" -) - -// KMS implements the kms.CloudKMS interface for in cluster key management. -type KMS struct { - masterKey []byte - salt []byte -} - -// New creates a new ClusterKMS. -func New(key []byte, salt []byte) (*KMS, error) { - if len(key) == 0 { - return nil, errors.New("missing master key") - } - if len(salt) == 0 { - return nil, errors.New("missing salt") - } - - return &KMS{masterKey: key, salt: salt}, nil -} - -// GetDEK derives a key from the KMS masterKey. -func (c *KMS) GetDEK(_ context.Context, dekID string, dekSize int) ([]byte, error) { - if len(c.masterKey) == 0 { - return nil, errors.New("master key not set for Constellation KMS") - } - return crypto.DeriveKey(c.masterKey, c.salt, []byte(dekID), uint(dekSize)) -} - -// Close is a no-op for cKMS. -func (c *KMS) Close() {} diff --git a/internal/kms/kms/cluster/cluster_test.go b/internal/kms/kms/cluster/cluster_test.go deleted file mode 100644 index d441591..0000000 --- a/internal/kms/kms/cluster/cluster_test.go +++ /dev/null @@ -1,114 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package cluster - -import ( - "context" - "strings" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/crypto/testvector" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1")) -} - -func TestClusterKMS(t *testing.T) { - testVector := testvector.HKDF0xFF - assert := assert.New(t) - require := require.New(t) - kms, err := New(testVector.Secret, testVector.Salt) - require.NoError(err) - - keyLower, err := kms.GetDEK( - context.Background(), - strings.ToLower(testVector.InfoPrefix+testVector.Info), - int(testVector.Length), - ) - assert.NoError(err) - assert.Equal(testVector.Output, keyLower) - - // output of the KMS should be case sensitive - keyUpper, err := kms.GetDEK( - context.Background(), - strings.ToUpper(testVector.InfoPrefix+testVector.Info), - int(testVector.Length), - ) - assert.NoError(err) - assert.NotEqual(keyLower, keyUpper) -} - -func TestVectorsHKDF(t *testing.T) { - testCases := map[string]struct { - kek []byte - salt []byte - dekID string - dekSize uint - wantKey []byte - wantErr bool - }{ - "rfc Test Case 1": { - kek: testvector.HKDFrfc1.Secret, - salt: testvector.HKDFrfc1.Salt, - dekID: testvector.HKDFrfc1.Info, - dekSize: testvector.HKDFrfc1.Length, - wantKey: testvector.HKDFrfc1.Output, - }, - "rfc Test Case 2": { - kek: testvector.HKDFrfc2.Secret, - salt: testvector.HKDFrfc2.Salt, - dekID: testvector.HKDFrfc2.Info, - dekSize: testvector.HKDFrfc2.Length, - wantKey: testvector.HKDFrfc2.Output, - }, - "rfc Test Case 3": { - kek: testvector.HKDFrfc3.Secret, - salt: testvector.HKDFrfc3.Salt, - dekID: testvector.HKDFrfc3.Info, - dekSize: testvector.HKDFrfc3.Length, - wantKey: testvector.HKDFrfc3.Output, - wantErr: true, - }, - "HKDF zero": { - kek: testvector.HKDFZero.Secret, - salt: testvector.HKDFZero.Salt, - dekID: testvector.HKDFZero.InfoPrefix + testvector.HKDFZero.Info, - dekSize: testvector.HKDFZero.Length, - wantKey: testvector.HKDFZero.Output, - }, - "HKDF 0xFF": { - kek: testvector.HKDF0xFF.Secret, - salt: testvector.HKDF0xFF.Salt, - dekID: testvector.HKDF0xFF.InfoPrefix + testvector.HKDF0xFF.Info, - dekSize: testvector.HKDF0xFF.Length, - wantKey: testvector.HKDF0xFF.Output, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - kms, err := New(tc.kek, tc.salt) - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - - out, err := kms.GetDEK(context.Background(), tc.dekID, int(tc.dekSize)) - require.NoError(err) - assert.Equal(tc.wantKey, out) - }) - } -} diff --git a/internal/kms/kms/gcp/BUILD.bazel b/internal/kms/kms/gcp/BUILD.bazel deleted file mode 100644 index f483329..0000000 --- a/internal/kms/kms/gcp/BUILD.bazel +++ /dev/null @@ -1,14 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "gcp", - srcs = ["gcp.go"], - importpath = "cvm-reverse-proxy/internal/kms/kms/gcp", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/kms/kms", - "//internal/kms/kms/internal", - "//internal/kms/uri", - "@com_github_hashicorp_go_kms_wrapping_wrappers_gcpckms_v2//:gcpckms", - ], -) diff --git a/internal/kms/kms/gcp/gcp.go b/internal/kms/kms/gcp/gcp.go deleted file mode 100644 index bda22a5..0000000 --- a/internal/kms/kms/gcp/gcp.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package gcp implements a KMS backend for Google Cloud KMS. - -The following permissions are required for the service account used to authenticate with GCP: - - cloudkms.cryptoKeys.get - - cloudkms.cryptoKeys.encrypt - - cloudkms.cryptoKeys.decrypt -*/ -package gcp - -import ( - "context" - "errors" - "fmt" - "io" - - kmsInterface "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/internal" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - gcpckms "github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2" -) - -// KMSClient implements the CloudKMS interface for Google Cloud Platform. -type KMSClient struct { - kms *internal.KMSClient - client io.Closer -} - -// New initializes a KMS client for Google Cloud Platform. -func New(_ context.Context, store kmsInterface.Storage, cfg uri.GCPConfig) (*KMSClient, error) { - if store == nil { - return nil, errors.New("no storage backend provided for KMS") - } - - wrapper := gcpckms.NewWrapper() - if _, err := wrapper.SetConfig( - context.Background(), - gcpckms.WithProject(cfg.ProjectID), - gcpckms.WithRegion(cfg.Location), - gcpckms.WithKeyRing(cfg.KeyRing), - gcpckms.WithCryptoKey(cfg.KeyName), - gcpckms.WithCredentials(cfg.CredentialsPath), - ); err != nil { - return nil, fmt.Errorf("setting GCP KMS config: %w", err) - } - - return &KMSClient{ - kms: &internal.KMSClient{ - Storage: store, - Wrapper: wrapper, - }, - client: wrapper.Client(), - }, nil -} - -// GetDEK fetches an encrypted Data Encryption Key from storage and decrypts it using a KEK stored in Google's KMS. -func (c *KMSClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) { - return c.kms.GetDEK(ctx, keyID, dekSize) -} - -// Close closes the KMS client. -func (c *KMSClient) Close() { - _ = c.client.Close() -} diff --git a/internal/kms/kms/internal/BUILD.bazel b/internal/kms/kms/internal/BUILD.bazel deleted file mode 100644 index 35cd489..0000000 --- a/internal/kms/kms/internal/BUILD.bazel +++ /dev/null @@ -1,32 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "internal", - srcs = ["internal.go"], - importpath = "cvm-reverse-proxy/internal/kms/kms/internal", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/crypto", - "//internal/kms/kms", - "//internal/kms/storage", - "@com_github_hashicorp_go_kms_wrapping_v2//:go-kms-wrapping", - ], -) - -go_test( - name = "internal_test", - srcs = ["internal_test.go"], - embed = [":internal"], - deps = [ - "//internal/kms/storage", - "@com_github_hashicorp_go_kms_wrapping_v2//:go-kms-wrapping", - "@com_github_hashicorp_go_kms_wrapping_wrappers_gcpckms_v2//:gcpckms", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@com_google_cloud_go_kms//apiv1", - "@org_golang_google_grpc//codes", - "@org_golang_google_grpc//status", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/kms/kms/internal/internal.go b/internal/kms/kms/internal/internal.go deleted file mode 100644 index 171ad61..0000000 --- a/internal/kms/kms/internal/internal.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package internal implements the CloudKMS interface using go-kms-wrapping. - -Adding support for a new KMS that is supported by go-kms-wrapping, -simply requires implementing a New function that initializes a KMSClient. -*/ -package internal - -import ( - "context" - "encoding/json" - "errors" - "fmt" - - "github.com/flashbots/cvm-reverse-proxy/internal/crypto" - kmsInterface "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - - wrapping "github.com/hashicorp/go-kms-wrapping/v2" -) - -type kmsWrapper interface { - Decrypt(context.Context, *wrapping.BlobInfo, ...wrapping.Option) ([]byte, error) - Encrypt(context.Context, []byte, ...wrapping.Option) (*wrapping.BlobInfo, error) -} - -// KMSClient implements the CloudKMS interface using go-kms-wrapping. -type KMSClient struct { - Storage kmsInterface.Storage - Wrapper kmsWrapper -} - -// GetDEK fetches an encrypted Data Encryption Key from storage. -// If the key exists, it is decrypted and returned. -// If no such key exists, a new one is generated, encrypted and saved to storage. -func (c *KMSClient) GetDEK(ctx context.Context, keyID string, dekSize int) ([]byte, error) { - encryptedDEK, err := c.Storage.Get(ctx, keyID) - if err != nil { - if !errors.Is(err, storage.ErrDEKUnset) { - return nil, fmt.Errorf("loading encrypted DEK from storage: %w", err) - } - - // If the DEK does not exist we generate a new random DEK and save it to storage - newDEK, err := crypto.GenerateRandomBytes(dekSize) - if err != nil { - return nil, fmt.Errorf("key generation: %w", err) - } - return newDEK, c.putDEK(ctx, keyID, newDEK) - } - - wrappedKey := &wrapping.BlobInfo{} - if err := json.Unmarshal(encryptedDEK, wrappedKey); err != nil { - return nil, fmt.Errorf("unmarshaling wrapped DEK: %w", err) - } - - dek, err := c.Wrapper.Decrypt(ctx, wrappedKey) - if err != nil { - return nil, fmt.Errorf("decrypting DEK: %w", err) - } - - return dek, nil -} - -// putDEK encrypts a Data Encryption Key and saves it to storage. -func (c *KMSClient) putDEK(ctx context.Context, keyID string, plainDEK []byte) error { - wrappedKey, err := c.Wrapper.Encrypt(ctx, plainDEK) - if err != nil { - return fmt.Errorf("encrypting DEK: %w", err) - } - - encryptedDEK, err := json.Marshal(wrappedKey) - if err != nil { - return fmt.Errorf("marshaling wrapped DEK: %w", err) - } - - return c.Storage.Put(ctx, keyID, encryptedDEK) -} diff --git a/internal/kms/kms/internal/internal_test.go b/internal/kms/kms/internal/internal_test.go deleted file mode 100644 index e599943..0000000 --- a/internal/kms/kms/internal/internal_test.go +++ /dev/null @@ -1,148 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package internal - -import ( - "context" - "encoding/json" - "errors" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - - cloudkms "cloud.google.com/go/kms/apiv1" - wrapping "github.com/hashicorp/go-kms-wrapping/v2" - "github.com/hashicorp/go-kms-wrapping/wrappers/gcpckms/v2" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/goleak" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, - // https://github.com/census-instrumentation/opencensus-go/issues/1262 - goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"), - goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1"), - ) -} - -type stubWrapper struct { - decryptErr error - decryptResponse []byte - encryptErr error - encryptResponse *wrapping.BlobInfo -} - -func (s *stubWrapper) Decrypt(context.Context, *wrapping.BlobInfo, ...wrapping.Option) ([]byte, error) { - return s.decryptResponse, s.decryptErr -} - -func (s *stubWrapper) Encrypt(context.Context, []byte, ...wrapping.Option) (*wrapping.BlobInfo, error) { - return s.encryptResponse, s.encryptErr -} - -func (s *stubWrapper) Client() *cloudkms.KeyManagementClient { - return &cloudkms.KeyManagementClient{} -} - -type stubStorage struct { - key []byte - getErr error - putErr error -} - -func (s *stubStorage) Get(context.Context, string) ([]byte, error) { - return s.key, s.getErr -} - -func (s *stubStorage) Put(context.Context, string, []byte) error { - return s.putErr -} - -func TestGetDEK(t *testing.T) { - someErr := errors.New("failed") - testKey := []byte("00112233445566778899aabbccddeeff") - savedTestKey, err := json.Marshal(&wrapping.BlobInfo{ - Ciphertext: []byte("encrypted-dek"), - Iv: []byte("iv"), - KeyInfo: &wrapping.KeyInfo{ - Mechanism: gcpckms.GcpCkmsEnvelopeAesGcmEncrypt, - KeyId: "kek-id", - WrappedKey: []byte("dek-encryption-key"), - }, - }) - require.NoError(t, err) - - testCases := map[string]struct { - wrapper *stubWrapper - storage *stubStorage - wantErr bool - }{ - "GetDEK successful for new key": { - wrapper: &stubWrapper{}, - storage: &stubStorage{getErr: storage.ErrDEKUnset}, - }, - "GetDEK successful for existing key": { - wrapper: &stubWrapper{decryptResponse: testKey}, - storage: &stubStorage{key: savedTestKey}, - }, - "Get from storage fails": { - wrapper: &stubWrapper{}, - storage: &stubStorage{getErr: someErr}, - wantErr: true, - }, - "Encrypt fails": { - wrapper: &stubWrapper{encryptErr: someErr}, - storage: &stubStorage{getErr: storage.ErrDEKUnset}, - wantErr: true, - }, - "Encrypt fails with notfound error": { - wrapper: &stubWrapper{encryptErr: status.Error(codes.NotFound, "error")}, - storage: &stubStorage{getErr: storage.ErrDEKUnset}, - wantErr: true, - }, - "Put to storage fails": { - wrapper: &stubWrapper{}, - storage: &stubStorage{ - getErr: storage.ErrDEKUnset, - putErr: someErr, - }, - wantErr: true, - }, - "Decrypt fails": { - wrapper: &stubWrapper{decryptErr: someErr}, - storage: &stubStorage{key: savedTestKey}, - wantErr: true, - }, - "Decrypt fails with notfound error": { - wrapper: &stubWrapper{decryptErr: status.Error(codes.NotFound, "error")}, - storage: &stubStorage{key: savedTestKey}, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - client := &KMSClient{ - Wrapper: tc.wrapper, - Storage: tc.storage, - } - - dek, err := client.GetDEK(context.Background(), "volume-01", 32) - if tc.wantErr { - assert.Error(err) - } else { - assert.NoError(err) - assert.Len(dek, 32) - } - }) - } -} diff --git a/internal/kms/kms/kms.go b/internal/kms/kms/kms.go deleted file mode 100644 index d14eb43..0000000 --- a/internal/kms/kms/kms.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package kms provides an abstract interface for Key Management Services. -package kms - -import ( - "context" -) - -// CloudKMS enables using cloud base Key Management Services. -type CloudKMS interface { - // GetDEK returns the DEK for dekID and kekID from the KMS. - // If the DEK does not exist, a new one is created and saved to storage. - GetDEK(ctx context.Context, dekID string, dekSize int) ([]byte, error) - // Close closes any open connection on the KMS client. - Close() -} - -// Storage provides an abstract interface for the storage backend used for DEKs. -type Storage interface { - // Get returns a DEK from the storage by key ID. If the DEK does not exist, returns storage.ErrDEKUnset. - Get(context.Context, string) ([]byte, error) - // Put saves a DEK to the storage by key ID. - Put(context.Context, string, []byte) error -} diff --git a/internal/kms/setup/BUILD.bazel b/internal/kms/setup/BUILD.bazel deleted file mode 100644 index 7f05481..0000000 --- a/internal/kms/setup/BUILD.bazel +++ /dev/null @@ -1,31 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "setup", - srcs = ["setup.go"], - importpath = "cvm-reverse-proxy/internal/kms/setup", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/kms/kms", - "//internal/kms/kms/aws", - "//internal/kms/kms/azure", - "//internal/kms/kms/cluster", - "//internal/kms/kms/gcp", - "//internal/kms/storage/awss3", - "//internal/kms/storage/azureblob", - "//internal/kms/storage/gcs", - "//internal/kms/uri", - ], -) - -go_test( - name = "setup_test", - srcs = ["setup_test.go"], - embed = [":setup"], - deps = [ - "//internal/kms/uri", - "@com_github_stretchr_testify//assert", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/kms/setup/setup.go b/internal/kms/setup/setup.go deleted file mode 100644 index 3313dd3..0000000 --- a/internal/kms/setup/setup.go +++ /dev/null @@ -1,133 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package setup provides functions to create a KMS and key store from a given URI. - -This package does not provide any functionality to interact with the KMS or key store, -but only to create them. - -Adding support for a new KMS or storage backend requires adding a new URI for that backend, -and implementing the corresponding get*Config function. -*/ -package setup - -import ( - "context" - "fmt" - "net/url" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/aws" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/azure" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/cluster" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/gcp" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/awss3" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/azureblob" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/gcs" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" -) - -// KMSInformation about an existing KMS. -type KMSInformation struct { - KMSURI string - StorageURI string - KeyEncryptionKeyID string -} - -// KMS creates a KMS and key store from the given parameters. -func KMS(ctx context.Context, storageURI, kmsURI string) (kms.CloudKMS, error) { - store, err := getStore(ctx, storageURI) - if err != nil { - return nil, err - } - - return getKMS(ctx, kmsURI, store) -} - -// getStore creates a key store depending on the given parameters. -func getStore(ctx context.Context, storageURI string) (kms.Storage, error) { - url, err := url.Parse(storageURI) - if err != nil { - return nil, err - } - if url.Scheme != "storage" { - return nil, fmt.Errorf("invalid storage URI: invalid scheme: %s", url.Scheme) - } - - switch url.Host { - case "aws": - cfg, err := uri.DecodeAWSS3ConfigFromURI(storageURI) - if err != nil { - return nil, err - } - return awss3.New(ctx, cfg) - - case "azure": - cfg, err := uri.DecodeAzureBlobConfigFromURI(storageURI) - if err != nil { - return nil, err - } - return azureblob.New(ctx, cfg) - - case "gcp": - cfg, err := uri.DecodeGoogleCloudStorageConfigFromURI(storageURI) - if err != nil { - return nil, err - } - return gcs.New(ctx, cfg) - - case "no-store": - return nil, nil - - default: - return nil, fmt.Errorf("unknown storage type: %s", url.Host) - } -} - -// getKMS creates a KMS client with the given key store and depending on the given parameters. -func getKMS(ctx context.Context, kmsURI string, store kms.Storage) (kms.CloudKMS, error) { - url, err := url.Parse(kmsURI) - if err != nil { - return nil, err - } - if url.Scheme != "kms" { - return nil, fmt.Errorf("invalid KMS URI: invalid scheme: %s", url.Scheme) - } - - switch url.Host { - case "aws": - cfg, err := uri.DecodeAWSConfigFromURI(kmsURI) - if err != nil { - return nil, fmt.Errorf("invalid AWS KMS URI: %w", err) - } - return aws.New(ctx, store, cfg) - - case "azure": - cfg, err := uri.DecodeAzureConfigFromURI(kmsURI) - if err != nil { - return nil, fmt.Errorf("invalid Azure Key Vault URI: %w", err) - } - return azure.New(ctx, store, cfg) - - case "gcp": - cfg, err := uri.DecodeGCPConfigFromURI(kmsURI) - if err != nil { - return nil, fmt.Errorf("invalid GCP KMS URI: %w", err) - } - return gcp.New(ctx, store, cfg) - - case "cluster-kms": - cfg, err := uri.DecodeMasterSecretFromURI(kmsURI) - if err != nil { - return nil, err - } - return cluster.New(cfg.Key, cfg.Salt) - - default: - return nil, fmt.Errorf("unknown KMS type: %s", url.Host) - } -} diff --git a/internal/kms/setup/setup_test.go b/internal/kms/setup/setup_test.go deleted file mode 100644 index 9a4169e..0000000 --- a/internal/kms/setup/setup_test.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package setup - -import ( - "context" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - "github.com/stretchr/testify/assert" - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, - // https://github.com/census-instrumentation/opencensus-go/issues/1262 - goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"), - goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1"), - ) -} - -func TestSetUpKMS(t *testing.T) { - assert := assert.New(t) - - kms, err := KMS(context.Background(), "storage://unknown", "kms://unknown") - assert.Error(err) - assert.Nil(kms) - - masterSecret := uri.MasterSecret{Key: []byte("key"), Salt: []byte("salt")} - kms, err = KMS(context.Background(), "storage://no-store", masterSecret.EncodeToURI()) - assert.NoError(err) - assert.NotNil(kms) -} diff --git a/internal/kms/storage/BUILD.bazel b/internal/kms/storage/BUILD.bazel deleted file mode 100644 index e18a63c..0000000 --- a/internal/kms/storage/BUILD.bazel +++ /dev/null @@ -1,8 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "storage", - srcs = ["storage.go"], - importpath = "cvm-reverse-proxy/internal/kms/storage", - visibility = ["//:__subpackages__"], -) diff --git a/internal/kms/storage/awss3/BUILD.bazel b/internal/kms/storage/awss3/BUILD.bazel deleted file mode 100644 index fa1e827..0000000 --- a/internal/kms/storage/awss3/BUILD.bazel +++ /dev/null @@ -1,30 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "awss3", - srcs = ["awss3.go"], - importpath = "cvm-reverse-proxy/internal/kms/storage/awss3", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/kms/config", - "//internal/kms/storage", - "//internal/kms/uri", - "@com_github_aws_aws_sdk_go_v2_config//:config", - "@com_github_aws_aws_sdk_go_v2_credentials//:credentials", - "@com_github_aws_aws_sdk_go_v2_service_s3//:s3", - "@com_github_aws_aws_sdk_go_v2_service_s3//types", - ], -) - -go_test( - name = "awss3_test", - srcs = ["awss3_test.go"], - embed = [":awss3"], - deps = [ - "//internal/kms/storage", - "@com_github_aws_aws_sdk_go_v2_service_s3//:s3", - "@com_github_aws_aws_sdk_go_v2_service_s3//types", - "@com_github_stretchr_testify//assert", - ], -) diff --git a/internal/kms/storage/awss3/awss3.go b/internal/kms/storage/awss3/awss3.go deleted file mode 100644 index 1e14e1f..0000000 --- a/internal/kms/storage/awss3/awss3.go +++ /dev/null @@ -1,123 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package awss3 implements a storage backend for the KMS using AWS S3: https://aws.amazon.com/s3/ -package awss3 - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/config" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - awsconfig "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/credentials" - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/aws/aws-sdk-go-v2/service/s3/types" -) - -type awsS3ClientAPI interface { - GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) - PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) - CreateBucket(ctx context.Context, params *s3.CreateBucketInput, optFns ...func(*s3.Options)) (*s3.CreateBucketOutput, error) - DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) -} - -// Storage is an implementation of the Storage interface, storing keys in AWS S3 buckets. -type Storage struct { - bucketID string - client awsS3ClientAPI -} - -// New creates a Storage client for AWS S3 using the provided config. -// -// See the AWS docs for more information: https://aws.amazon.com/s3/ -func New(ctx context.Context, cfg uri.AWSS3Config) (*Storage, error) { - clientCfg, err := awsconfig.LoadDefaultConfig( - ctx, - awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(cfg.AccessKeyID, cfg.AccessKey, "")), - awsconfig.WithRegion(cfg.Region), - ) - if err != nil { - return nil, fmt.Errorf("loading AWS S3 client config: %w", err) - } - - client := s3.NewFromConfig(clientCfg) - - store := &Storage{client: client, bucketID: cfg.Bucket} - - // Try to create new bucket, continue if bucket already exists - if err := store.createBucket(ctx, cfg.Bucket, cfg.Region); err != nil { - return nil, fmt.Errorf("creating storage bucket: %w", err) - } - return store, nil -} - -// Get returns a DEK from from AWS S3 Storage by key ID. -func (s *Storage) Get(ctx context.Context, keyID string) ([]byte, error) { - getObjectInput := &s3.GetObjectInput{ - Bucket: &s.bucketID, - Key: &keyID, - } - output, err := s.client.GetObject(ctx, getObjectInput) - if err != nil { - var nsk *types.NoSuchKey - if errors.As(err, &nsk) { - return nil, storage.ErrDEKUnset - } - return nil, fmt.Errorf("downloading DEK from storage: %w", err) - } - return io.ReadAll(output.Body) -} - -// Delete removes a DEK from AWS S3 Storage by key ID. -func (s *Storage) Delete(ctx context.Context, keyID string) error { - deleteObjectInput := &s3.DeleteObjectInput{ - Bucket: &s.bucketID, - Key: &keyID, - } - if _, err := s.client.DeleteObject(ctx, deleteObjectInput); err != nil { - return fmt.Errorf("deleting DEK from storage: %w", err) - } - return nil -} - -// Put saves a DEK to AWS S3 Storage by key ID. -func (s *Storage) Put(ctx context.Context, keyID string, data []byte) error { - putObjectInput := &s3.PutObjectInput{ - Bucket: &s.bucketID, - Key: &keyID, - Body: bytes.NewReader(data), - Tagging: &config.AWSS3Tag, - } - if _, err := s.client.PutObject(ctx, putObjectInput); err != nil { - return fmt.Errorf("uploading DEK to storage: %w", err) - } - return nil -} - -func (s *Storage) createBucket(ctx context.Context, bucketID, region string) error { - createBucketInput := &s3.CreateBucketInput{ - Bucket: &bucketID, - CreateBucketConfiguration: &types.CreateBucketConfiguration{ - LocationConstraint: types.BucketLocationConstraint(region), - }, - } - - if _, err := s.client.CreateBucket(ctx, createBucketInput); err != nil { - var bne *types.BucketAlreadyExists - var baowby *types.BucketAlreadyOwnedByYou - if !(errors.As(err, &bne) || errors.As(err, &baowby)) { - return fmt.Errorf("creating storage container: %w", err) - } - } - return nil -} diff --git a/internal/kms/storage/awss3/awss3_test.go b/internal/kms/storage/awss3/awss3_test.go deleted file mode 100644 index 522544e..0000000 --- a/internal/kms/storage/awss3/awss3_test.go +++ /dev/null @@ -1,176 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package awss3 - -import ( - "bytes" - "context" - "errors" - "io" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/aws/aws-sdk-go-v2/service/s3/types" - "github.com/stretchr/testify/assert" -) - -type stubAWSS3StorageClient struct { - getObjectOutputData []byte - getObjectErr error - putObjectErr error - savedObject []byte - createBucketCalled bool - createBucketErr error -} - -func (s *stubAWSS3StorageClient) GetObject(_ context.Context, _ *s3.GetObjectInput, _ ...func(*s3.Options)) (*s3.GetObjectOutput, error) { - return &s3.GetObjectOutput{ - Body: io.NopCloser(bytes.NewReader(s.getObjectOutputData)), - }, s.getObjectErr -} - -func (s *stubAWSS3StorageClient) PutObject(_ context.Context, params *s3.PutObjectInput, _ ...func(*s3.Options)) (*s3.PutObjectOutput, error) { - out, err := io.ReadAll(params.Body) - if err != nil { - panic(err) - } - s.savedObject = out - return &s3.PutObjectOutput{}, s.putObjectErr -} - -func (s *stubAWSS3StorageClient) DeleteObject(_ context.Context, _ *s3.DeleteObjectInput, _ ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) { - return &s3.DeleteObjectOutput{}, nil -} - -func (s *stubAWSS3StorageClient) CreateBucket(_ context.Context, _ *s3.CreateBucketInput, _ ...func(*s3.Options)) (*s3.CreateBucketOutput, error) { - s.createBucketCalled = true - return &s3.CreateBucketOutput{}, s.createBucketErr -} - -func TestAWSS3Get(t *testing.T) { - testCases := map[string]struct { - client *stubAWSS3StorageClient - unsetError bool - wantErr bool - }{ - "Get successful": { - client: &stubAWSS3StorageClient{getObjectOutputData: []byte("test-data")}, - }, - "GetObject fails": { - client: &stubAWSS3StorageClient{getObjectErr: errors.New("error")}, - wantErr: true, - }, - "GetObject fails with NoSuchKey": { - client: &stubAWSS3StorageClient{getObjectErr: &types.NoSuchKey{}}, - wantErr: true, - unsetError: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - store := &Storage{ - client: tc.client, - } - - out, err := store.Get(context.Background(), "test-key") - if tc.wantErr { - assert.Error(err) - - if tc.unsetError { - assert.ErrorIs(err, storage.ErrDEKUnset) - } else { - assert.False(errors.Is(err, storage.ErrDEKUnset)) - } - - } else { - assert.NoError(err) - assert.Equal(tc.client.getObjectOutputData, out) - } - }) - } -} - -func TestAWSS3Put(t *testing.T) { - testCases := map[string]struct { - client *stubAWSS3StorageClient - wantErr bool - }{ - "Put successful": { - client: &stubAWSS3StorageClient{}, - }, - "PutObject fails": { - client: &stubAWSS3StorageClient{putObjectErr: errors.New("error")}, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - store := &Storage{ - client: tc.client, - } - - testData := []byte{0x1, 0x2, 0x3} - - err := store.Put(context.Background(), "test-key", testData) - if tc.wantErr { - assert.Error(err) - } else { - assert.NoError(err) - assert.Equal(testData, tc.client.savedObject) - } - }) - } -} - -func TestAWSS3CreateBucket(t *testing.T) { - testCases := map[string]struct { - client *stubAWSS3StorageClient - wantErr bool - }{ - "CreateBucket successful": { - client: &stubAWSS3StorageClient{}, - }, - "CreateBucket fails": { - client: &stubAWSS3StorageClient{createBucketErr: errors.New("error")}, - wantErr: true, - }, - "CreateBucket fails with BucketAlreadyExists": { - client: &stubAWSS3StorageClient{createBucketErr: &types.BucketAlreadyExists{}}, - wantErr: false, - }, - "CreateBucket fails with BucketAlreadyOwnedByYou": { - client: &stubAWSS3StorageClient{createBucketErr: &types.BucketAlreadyOwnedByYou{}}, - wantErr: false, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - store := &Storage{ - client: tc.client, - } - - err := store.createBucket(context.Background(), "test-bucket", "test-region") - if tc.wantErr { - assert.Error(err) - } else { - assert.NoError(err) - assert.True(tc.client.createBucketCalled) - } - }) - } -} diff --git a/internal/kms/storage/azureblob/BUILD.bazel b/internal/kms/storage/azureblob/BUILD.bazel deleted file mode 100644 index e5f0464..0000000 --- a/internal/kms/storage/azureblob/BUILD.bazel +++ /dev/null @@ -1,35 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "azureblob", - srcs = ["azureblob.go"], - importpath = "cvm-reverse-proxy/internal/kms/storage/azureblob", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/kms/config", - "//internal/kms/storage", - "//internal/kms/uri", - "@com_github_azure_azure_sdk_for_go_sdk_azcore//:azcore", - "@com_github_azure_azure_sdk_for_go_sdk_azidentity//:azidentity", - "@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//:azblob", - "@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//blob", - "@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//bloberror", - "@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//container", - ], -) - -go_test( - name = "azureblob_test", - srcs = ["azureblob_test.go"], - embed = [":azureblob"], - deps = [ - "//internal/kms/storage", - "@com_github_azure_azure_sdk_for_go_sdk_azcore//:azcore", - "@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//:azblob", - "@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//blob", - "@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//bloberror", - "@com_github_azure_azure_sdk_for_go_sdk_storage_azblob//container", - "@com_github_stretchr_testify//assert", - ], -) diff --git a/internal/kms/storage/azureblob/azureblob.go b/internal/kms/storage/azureblob/azureblob.go deleted file mode 100644 index 58f454f..0000000 --- a/internal/kms/storage/azureblob/azureblob.go +++ /dev/null @@ -1,105 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package azureblob implements a storage backend for the KMS using Azure Blob Storage. -package azureblob - -import ( - "bytes" - "context" - "fmt" - "io" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/config" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" -) - -type azureBlobAPI interface { - CreateContainer(context.Context, string, *container.CreateOptions) (azblob.CreateContainerResponse, error) - DownloadStream(context.Context, string, string, *blob.DownloadStreamOptions) (azblob.DownloadStreamResponse, error) - UploadStream(context.Context, string, string, io.Reader, *azblob.UploadStreamOptions) (azblob.UploadStreamResponse, error) -} - -// Storage is an implementation of the Storage interface, storing keys in the Azure Blob Store. -type Storage struct { - client azureBlobAPI - container string -} - -// New initializes a storage client using Azure's Blob Storage using the provided config. -// -// See the Azure docs for more information: https://azure.microsoft.com/en-us/services/storage/blobs/ -func New(ctx context.Context, cfg uri.AzureBlobConfig) (*Storage, error) { - var creds azcore.TokenCredential - - creds, err := azidentity.NewClientSecretCredential(cfg.TenantID, cfg.ClientID, cfg.ClientSecret, nil) - if err != nil { - // Fallback: try to load default credentials - creds, err = azidentity.NewDefaultAzureCredential(nil) - if err != nil { - return nil, fmt.Errorf("invalid client-secret credentials. Trying to load default credentials: %w", err) - } - } - - client, err := azblob.NewClient(fmt.Sprintf("https://%s.blob.core.windows.net/", cfg.StorageAccount), creds, nil) - if err != nil { - return nil, fmt.Errorf("creating storage client: %w", err) - } - - s := &Storage{ - client: client, - container: cfg.Container, - } - - // Try to create a new storage container, continue if it already exists - if err := s.createContainerOrContinue(ctx); err != nil { - return nil, err - } - - return s, nil -} - -// Get returns a DEK from from Azure Blob Storage by key ID. -func (s *Storage) Get(ctx context.Context, keyID string) ([]byte, error) { - res, err := s.client.DownloadStream(ctx, s.container, keyID, nil) - if err != nil { - if bloberror.HasCode(err, bloberror.BlobNotFound) { - return nil, storage.ErrDEKUnset - } - return nil, fmt.Errorf("downloading DEK from storage: %w", err) - } - defer res.Body.Close() - return io.ReadAll(res.Body) -} - -// Put saves a DEK to Azure Blob Storage by key ID. -func (s *Storage) Put(ctx context.Context, keyID string, encDEK []byte) error { - if _, err := s.client.UploadStream(ctx, s.container, keyID, bytes.NewReader(encDEK), nil); err != nil { - return fmt.Errorf("uploading DEK to storage: %w", err) - } - - return nil -} - -// createContainerOrContinue creates a new storage container if necessary, or continues if it already exists. -func (s *Storage) createContainerOrContinue(ctx context.Context) error { - _, err := s.client.CreateContainer(ctx, s.container, &azblob.CreateContainerOptions{ - Metadata: config.StorageTags, - }) - if (err == nil) || bloberror.HasCode(err, bloberror.ContainerAlreadyExists) { - return nil - } - - return fmt.Errorf("creating storage container: %w", err) -} diff --git a/internal/kms/storage/azureblob/azureblob_test.go b/internal/kms/storage/azureblob/azureblob_test.go deleted file mode 100644 index 84e17c7..0000000 --- a/internal/kms/storage/azureblob/azureblob_test.go +++ /dev/null @@ -1,172 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package azureblob - -import ( - "bytes" - "context" - "errors" - "io" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" - "github.com/stretchr/testify/assert" -) - -func TestAzureGet(t *testing.T) { - testCases := map[string]struct { - client stubAzureBlobAPI - unsetError bool - wantErr bool - }{ - "success": { - client: stubAzureBlobAPI{downloadData: []byte{0x1, 0x2, 0x3}}, - }, - "DownloadBuffer fails": { - client: stubAzureBlobAPI{downloadErr: errors.New("failed")}, - wantErr: true, - }, - "BlobNotFound error": { - client: stubAzureBlobAPI{downloadErr: &azcore.ResponseError{ErrorCode: string(bloberror.BlobNotFound)}}, - unsetError: true, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - client := &Storage{ - client: &tc.client, - container: "test", - } - - out, err := client.Get(context.Background(), "test-key") - if tc.wantErr { - assert.Error(err) - - if tc.unsetError { - assert.ErrorIs(err, storage.ErrDEKUnset) - } else { - assert.False(errors.Is(err, storage.ErrDEKUnset)) - } - return - } - assert.NoError(err) - assert.Equal(tc.client.downloadData, out) - }) - } -} - -func TestAzurePut(t *testing.T) { - testCases := map[string]struct { - client stubAzureBlobAPI - wantErr bool - }{ - "success": { - client: stubAzureBlobAPI{}, - }, - "Upload fails": { - client: stubAzureBlobAPI{uploadErr: errors.New("failed")}, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - testData := []byte{0x1, 0x2, 0x3} - - client := &Storage{ - client: &tc.client, - container: "test", - } - - err := client.Put(context.Background(), "test-key", testData) - if tc.wantErr { - assert.Error(err) - return - } - assert.NoError(err) - assert.Equal(testData, tc.client.uploadData) - }) - } -} - -func TestCreateContainerOrContinue(t *testing.T) { - testCases := map[string]struct { - client stubAzureBlobAPI - wantErr bool - }{ - "success": { - client: stubAzureBlobAPI{}, - }, - "container already exists": { - client: stubAzureBlobAPI{createErr: &azcore.ResponseError{ErrorCode: string(bloberror.ContainerAlreadyExists)}}, - }, - "CreateContainer fails": { - client: stubAzureBlobAPI{createErr: errors.New("failed")}, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - client := &Storage{ - client: &tc.client, - container: "test", - } - - err := client.createContainerOrContinue(context.Background()) - if tc.wantErr { - assert.Error(err) - } else { - assert.NoError(err) - assert.True(tc.client.createCalled) - } - }) - } -} - -type stubAzureBlobAPI struct { - createErr error - createCalled bool - downloadErr error - downloadData []byte - uploadErr error - uploadData []byte -} - -func (s *stubAzureBlobAPI) CreateContainer(context.Context, string, *container.CreateOptions) (azblob.CreateContainerResponse, error) { - s.createCalled = true - return azblob.CreateContainerResponse{}, s.createErr -} - -func (s *stubAzureBlobAPI) DownloadStream(context.Context, string, string, *blob.DownloadStreamOptions) (blob.DownloadStreamResponse, error) { - res := blob.DownloadStreamResponse{} - res.Body = io.NopCloser(bytes.NewReader(s.downloadData)) - return res, s.downloadErr -} - -func (s *stubAzureBlobAPI) UploadStream(_ context.Context, _, _ string, data io.Reader, _ *azblob.UploadStreamOptions) (azblob.UploadStreamResponse, error) { - uploadData, err := io.ReadAll(data) - if err != nil { - return azblob.UploadStreamResponse{}, err - } - s.uploadData = uploadData - return azblob.UploadStreamResponse{}, s.uploadErr -} diff --git a/internal/kms/storage/gcs/BUILD.bazel b/internal/kms/storage/gcs/BUILD.bazel deleted file mode 100644 index 9b8dcfc..0000000 --- a/internal/kms/storage/gcs/BUILD.bazel +++ /dev/null @@ -1,26 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "gcs", - srcs = ["gcs.go"], - importpath = "cvm-reverse-proxy/internal/kms/storage/gcs", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/kms/storage", - "//internal/kms/uri", - "@com_google_cloud_go_storage//:storage", - "@org_golang_google_api//option", - ], -) - -go_test( - name = "gcs_test", - srcs = ["gcs_test.go"], - embed = [":gcs"], - deps = [ - "//internal/kms/storage", - "@com_github_stretchr_testify//assert", - "@com_google_cloud_go_storage//:storage", - ], -) diff --git a/internal/kms/storage/gcs/gcs.go b/internal/kms/storage/gcs/gcs.go deleted file mode 100644 index dc7b5c3..0000000 --- a/internal/kms/storage/gcs/gcs.go +++ /dev/null @@ -1,132 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package gcs implements a storage backend for the KMS using Google Cloud Storage (GCS). -package gcs - -import ( - "context" - "errors" - "io" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - gcstorage "cloud.google.com/go/storage" - "google.golang.org/api/option" -) - -type gcpStorageAPI interface { - Attrs(ctx context.Context, bucketName string) (*gcstorage.BucketAttrs, error) - Close() error - CreateBucket(ctx context.Context, bucketName, projectID string, attrs *gcstorage.BucketAttrs) error - NewWriter(ctx context.Context, bucketName, objectName string) io.WriteCloser - NewReader(ctx context.Context, bucketName, objectName string) (io.ReadCloser, error) -} - -type wrappedGCPClient struct { - *gcstorage.Client -} - -func (c *wrappedGCPClient) Attrs(ctx context.Context, bucketName string) (*gcstorage.BucketAttrs, error) { - return c.Client.Bucket(bucketName).Attrs(ctx) -} - -func (c *wrappedGCPClient) CreateBucket(ctx context.Context, bucketName, projectID string, attrs *gcstorage.BucketAttrs) error { - return c.Client.Bucket(bucketName).Create(ctx, projectID, attrs) -} - -func (c *wrappedGCPClient) NewWriter(ctx context.Context, bucketName, objectName string) io.WriteCloser { - return c.Client.Bucket(bucketName).Object(objectName).NewWriter(ctx) -} - -func (c *wrappedGCPClient) NewReader(ctx context.Context, bucketName, objectName string) (io.ReadCloser, error) { - return c.Client.Bucket(bucketName).Object(objectName).NewReader(ctx) -} - -// Storage is an implementation of the Storage interface, storing keys in Google Cloud Storage buckets. -type Storage struct { - newClient func(ctx context.Context) (gcpStorageAPI, error) - bucketName string -} - -// New creates a Storage client for Google Cloud Storage using the provided config. -// -// See the Google docs for more information: https://cloud.google.com/storage/docs/ -func New(ctx context.Context, cfg uri.GoogleCloudStorageConfig) (*Storage, error) { - s := &Storage{ - newClient: newGCPStorageClientFactory(cfg.CredentialsPath), - bucketName: cfg.Bucket, - } - - // Make sure the storage bucket exists, if not create it - if err := s.createContainerOrContinue(ctx, cfg.ProjectID); err != nil { - return nil, err - } - - return s, nil -} - -// Get returns a DEK from Google Cloud Storage by key ID. -func (s *Storage) Get(ctx context.Context, keyID string) ([]byte, error) { - client, err := s.newClient(ctx) - if err != nil { - return nil, err - } - defer client.Close() - - reader, err := client.NewReader(ctx, s.bucketName, keyID) - if err != nil { - if errors.Is(err, gcstorage.ErrObjectNotExist) { - return nil, storage.ErrDEKUnset - } - return nil, err - } - defer reader.Close() - - return io.ReadAll(reader) -} - -// Put saves a DEK to Google Cloud Storage by key ID. -func (s *Storage) Put(ctx context.Context, keyID string, data []byte) error { - client, err := s.newClient(ctx) - if err != nil { - return err - } - defer client.Close() - - writer := client.NewWriter(ctx, s.bucketName, keyID) - defer writer.Close() - - _, err = writer.Write(data) - return err -} - -func (s *Storage) createContainerOrContinue(ctx context.Context, projectID string) error { - client, err := s.newClient(ctx) - if err != nil { - return err - } - defer client.Close() - - if _, err := client.Attrs(ctx, s.bucketName); errors.Is(err, gcstorage.ErrBucketNotExist) { - return client.CreateBucket(ctx, s.bucketName, projectID, nil) - } else if err != nil { - return err - } - - return nil -} - -func newGCPStorageClientFactory(credPath string) func(context.Context) (gcpStorageAPI, error) { - return func(ctx context.Context) (gcpStorageAPI, error) { - client, err := gcstorage.NewClient(ctx, option.WithCredentialsFile(credPath)) - if err != nil { - return nil, err - } - return &wrappedGCPClient{client}, nil - } -} diff --git a/internal/kms/storage/gcs/gcs_test.go b/internal/kms/storage/gcs/gcs_test.go deleted file mode 100644 index 796ff2e..0000000 --- a/internal/kms/storage/gcs/gcs_test.go +++ /dev/null @@ -1,226 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package gcs - -import ( - "bytes" - "context" - "errors" - "io" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - - gcstorage "cloud.google.com/go/storage" - "github.com/stretchr/testify/assert" -) - -type stubGCPStorageAPI struct { - newClientErr error - attrsErr error - createBucketErr error - createBucketCalled bool - newReaderErr error - newReaderOutput []byte - writer *stubWriteCloser -} - -func (s *stubGCPStorageAPI) stubClientFactory(_ context.Context) (gcpStorageAPI, error) { - return s, s.newClientErr -} - -func (s *stubGCPStorageAPI) Attrs(_ context.Context, _ string) (*gcstorage.BucketAttrs, error) { - return &gcstorage.BucketAttrs{}, s.attrsErr -} - -func (s *stubGCPStorageAPI) Close() error { - return nil -} - -func (s *stubGCPStorageAPI) CreateBucket(_ context.Context, _, _ string, _ *gcstorage.BucketAttrs) error { - s.createBucketCalled = true - return s.createBucketErr -} - -func (s *stubGCPStorageAPI) NewWriter(_ context.Context, _, _ string) io.WriteCloser { - return s.writer -} - -func (s *stubGCPStorageAPI) NewReader(_ context.Context, _, _ string) (io.ReadCloser, error) { - return io.NopCloser(bytes.NewReader(s.newReaderOutput)), s.newReaderErr -} - -type stubWriteCloser struct { - result *[]byte - writeErr error - writeN int -} - -func (s stubWriteCloser) Write(p []byte) (int, error) { - *s.result = p - return s.writeN, s.writeErr -} - -func (s stubWriteCloser) Close() error { - return nil -} - -func TestGCPGet(t *testing.T) { - someErr := errors.New("error") - - testCases := map[string]struct { - client *stubGCPStorageAPI - unsetError bool - wantErr bool - }{ - "success": { - client: &stubGCPStorageAPI{newReaderOutput: []byte("test-data")}, - }, - "creating client fails": { - client: &stubGCPStorageAPI{newClientErr: someErr}, - wantErr: true, - }, - "NewReader fails": { - client: &stubGCPStorageAPI{newReaderErr: someErr}, - wantErr: true, - }, - "ErrObjectNotExist error": { - client: &stubGCPStorageAPI{newReaderErr: gcstorage.ErrObjectNotExist}, - unsetError: true, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - client := &Storage{ - newClient: tc.client.stubClientFactory, - bucketName: "test", - } - - out, err := client.Get(context.Background(), "test-key") - if tc.wantErr { - assert.Error(err) - - if tc.unsetError { - assert.ErrorIs(err, storage.ErrDEKUnset) - } else { - assert.False(errors.Is(err, storage.ErrDEKUnset)) - } - - } else { - assert.NoError(err) - assert.Equal(tc.client.newReaderOutput, out) - } - }) - } -} - -func TestGCPPut(t *testing.T) { - someErr := errors.New("error") - testCases := map[string]struct { - client *stubGCPStorageAPI - unsetError bool - wantErr bool - }{ - "success": { - client: &stubGCPStorageAPI{ - writer: &stubWriteCloser{ - result: new([]byte), - }, - }, - }, - "creating client fails": { - client: &stubGCPStorageAPI{newClientErr: someErr}, - wantErr: true, - }, - "NewWriter fails": { - client: &stubGCPStorageAPI{ - writer: &stubWriteCloser{ - result: new([]byte), - writeErr: someErr, - }, - }, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - client := &Storage{ - newClient: tc.client.stubClientFactory, - bucketName: "test", - } - testData := []byte{0x1, 0x2, 0x3} - - err := client.Put(context.Background(), "test-key", testData) - if tc.wantErr { - assert.Error(err) - } else { - assert.NoError(err) - assert.Equal(testData, *tc.client.writer.result) - } - }) - } -} - -func TestGCPCreateContainerOrContinue(t *testing.T) { - someErr := errors.New("error") - testCases := map[string]struct { - client *stubGCPStorageAPI - createNewBucket bool - wantErr bool - }{ - "success": { - client: &stubGCPStorageAPI{}, - }, - "container does not exist": { - client: &stubGCPStorageAPI{attrsErr: gcstorage.ErrBucketNotExist}, - createNewBucket: true, - }, - "creating client fails": { - client: &stubGCPStorageAPI{newClientErr: someErr}, - wantErr: true, - }, - "Attrs fails": { - client: &stubGCPStorageAPI{attrsErr: someErr}, - wantErr: true, - }, - "CreateBucket fails": { - client: &stubGCPStorageAPI{ - attrsErr: gcstorage.ErrBucketNotExist, - createBucketErr: someErr, - }, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - client := &Storage{ - newClient: tc.client.stubClientFactory, - bucketName: "test", - } - - err := client.createContainerOrContinue(context.Background(), "project") - if tc.wantErr { - assert.Error(err) - } else { - assert.NoError(err) - if tc.createNewBucket { - assert.True(tc.client.createBucketCalled) - } - } - }) - } -} diff --git a/internal/kms/storage/memfs/BUILD.bazel b/internal/kms/storage/memfs/BUILD.bazel deleted file mode 100644 index 67c6b83..0000000 --- a/internal/kms/storage/memfs/BUILD.bazel +++ /dev/null @@ -1,21 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "memfs", - srcs = ["memfs.go"], - importpath = "cvm-reverse-proxy/internal/kms/storage/memfs", - visibility = ["//:__subpackages__"], - deps = ["//internal/kms/storage"], -) - -go_test( - name = "memfs_test", - srcs = ["memfs_test.go"], - embed = [":memfs"], - deps = [ - "//internal/kms/storage", - "@com_github_stretchr_testify//assert", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/kms/storage/memfs/memfs.go b/internal/kms/storage/memfs/memfs.go deleted file mode 100644 index 6c44b79..0000000 --- a/internal/kms/storage/memfs/memfs.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package memfs implements a storage backend for the KMS that stores keys in memory only. -// This package should be used for testing only. -package memfs - -import ( - "context" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" -) - -// Storage is the standard implementation of the Storage interface, storing keys in memory only. -type Storage struct { - dekPool map[string][]byte -} - -// New creates and initializes a new Storage object. -func New() *Storage { - s := &Storage{ - dekPool: make(map[string][]byte), - } - - return s -} - -// Get returns a DEK from Storage by key ID. -func (s *Storage) Get(_ context.Context, keyID string) ([]byte, error) { - encDEK, ok := s.dekPool[keyID] - if ok { - return encDEK, nil - } - return nil, storage.ErrDEKUnset -} - -// Put saves a DEK to Storage by key ID. -func (s *Storage) Put(_ context.Context, keyID string, encDEK []byte) error { - s.dekPool[keyID] = encDEK - return nil -} diff --git a/internal/kms/storage/memfs/memfs_test.go b/internal/kms/storage/memfs/memfs_test.go deleted file mode 100644 index 46a3d74..0000000 --- a/internal/kms/storage/memfs/memfs_test.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package memfs - -import ( - "context" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - - "github.com/stretchr/testify/assert" - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, - // https://github.com/census-instrumentation/opencensus-go/issues/1262 - goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"), - goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1"), - ) -} - -func TestMemMapStorage(t *testing.T) { - assert := assert.New(t) - - store := New() - - testDEK1 := []byte("test DEK") - testDEK2 := []byte("more test DEK") - ctx := context.Background() - - // request unset value - _, err := store.Get(ctx, "test:input") - assert.ErrorIs(err, storage.ErrDEKUnset) - - // test Put method - assert.NoError(store.Put(ctx, "volume01", testDEK1)) - assert.NoError(store.Put(ctx, "volume02", testDEK2)) - - // make sure values have been set - val, err := store.Get(ctx, "volume01") - assert.NoError(err) - assert.Equal(testDEK1, val) - val, err = store.Get(ctx, "volume02") - assert.NoError(err) - assert.Equal(testDEK2, val) - - _, err = store.Get(ctx, "invalid:key") - assert.ErrorIs(err, storage.ErrDEKUnset) -} diff --git a/internal/kms/storage/storage.go b/internal/kms/storage/storage.go deleted file mode 100644 index d8ec42c..0000000 --- a/internal/kms/storage/storage.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package storage implements storage backends for DEKs. - -If an unset DEK is requested, the backend MUST return [ErrDEKUnset]. -*/ -package storage - -import ( - "errors" -) - -// ErrDEKUnset indicates if a key is not found in storage. -var ErrDEKUnset = errors.New("requested DEK not set") diff --git a/internal/kms/test/BUILD.bazel b/internal/kms/test/BUILD.bazel deleted file mode 100644 index 85dc949..0000000 --- a/internal/kms/test/BUILD.bazel +++ /dev/null @@ -1,29 +0,0 @@ -load("//bazel/go:go_test.bzl", "go_test") - -go_test( - name = "test_test", - srcs = [ - "aws_test.go", - "azure_test.go", - "gcp_test.go", - "integration_test.go", - ], - deps = [ - "//internal/kms/config", - "//internal/kms/kms", - "//internal/kms/kms/aws", - "//internal/kms/kms/azure", - "//internal/kms/kms/gcp", - "//internal/kms/storage", - "//internal/kms/storage/awss3", - "//internal/kms/storage/azureblob", - "//internal/kms/storage/gcs", - "//internal/kms/storage/memfs", - "//internal/kms/uri", - "@com_github_aws_aws_sdk_go_v2_config//:config", - "@com_github_aws_aws_sdk_go_v2_service_s3//:s3", - "@com_github_aws_aws_sdk_go_v2_service_s3//types", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - ], -) diff --git a/internal/kms/test/aws_test.go b/internal/kms/test/aws_test.go deleted file mode 100644 index b5442b6..0000000 --- a/internal/kms/test/aws_test.go +++ /dev/null @@ -1,122 +0,0 @@ -//go:build integration - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package test - -import ( - "context" - "flag" - "testing" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/aws" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/awss3" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/memfs" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/aws/aws-sdk-go-v2/service/s3/types" - "github.com/stretchr/testify/require" -) - -func TestAwsStorage(t *testing.T) { - if !*runAwsStorage { - t.Skip("Skipping AWS storage test") - } - if *awsAccessKey == "" || *awsAccessKeyID == "" || *awsBucket == "" || *awsRegion == "" { - flag.Usage() - t.Fatal("Required flags not set: --aws-access-key, --aws-access-key-id, --aws-bucket, --aws-region") - } - require := require.New(t) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - // create bucket - cfg := uri.AWSS3Config{ - Bucket: *awsBucket, - AccessKeyID: *awsAccessKeyID, - AccessKey: *awsAccessKey, - Region: *awsRegion, - } - store, err := awss3.New(ctx, cfg) - require.NoError(err) - - runStorageTest(t, store) - - cleanUpBucket(ctx, require, *awsBucket, *awsRegion) -} - -func cleanUpBucket(ctx context.Context, require *require.Assertions, bucketID, awsRegion string) { - cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(awsRegion)) - require.NoError(err) - client := s3.NewFromConfig(cfg) - - // List all objects of the bucket - listObjectsInput := &s3.ListObjectsV2Input{ - Bucket: &bucketID, - } - output, err := client.ListObjectsV2(ctx, listObjectsInput) - require.NoError(err) - var objects []string - var i int32 - for i = 0; i < *output.KeyCount; i++ { - objects = append(objects, *output.Contents[i].Key) - } - // Delete all objects of the bucket - require.NoError(cleanUpObjects(ctx, client, bucketID, objects)) - - // Delete the bucket - deleteBucketInput := &s3.DeleteBucketInput{ - Bucket: &bucketID, - } - _, err = client.DeleteBucket(ctx, deleteBucketInput) - require.NoError(err) -} - -func cleanUpObjects(ctx context.Context, client *s3.Client, bucketID string, objectsToDelete []string) error { - var objectsIdentifier []types.ObjectIdentifier - for _, object := range objectsToDelete { - objectsIdentifier = append(objectsIdentifier, types.ObjectIdentifier{Key: func(s string) *string { return &s }(object)}) - } - deleteObjectsInput := &s3.DeleteObjectsInput{ - Bucket: &bucketID, - Delete: &types.Delete{Objects: objectsIdentifier}, - } - _, err := client.DeleteObjects(ctx, deleteObjectsInput) - return err -} - -func TestAwsKms(t *testing.T) { - if !*runAwsKms { - t.Skip("Skipping AWS KMS test") - } - - if *kekID == "" || *awsAccessKeyID == "" || *awsAccessKey == "" || *awsRegion == "" { - flag.Usage() - t.Fatal("Required flags not set: --aws-access-key-id, --aws-access-key, --aws-region, --kek-id") - } - - require := require.New(t) - - store := memfs.New() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - cfg := uri.AWSConfig{ - KeyName: *kekID, - Region: *awsRegion, - AccessKeyID: *awsAccessKeyID, - AccessKey: *awsAccessKey, - } - kmsClient, err := aws.New(ctx, store, cfg) - require.NoError(err) - - runKMSTest(t, kmsClient) -} diff --git a/internal/kms/test/azure_test.go b/internal/kms/test/azure_test.go deleted file mode 100644 index f8e840c..0000000 --- a/internal/kms/test/azure_test.go +++ /dev/null @@ -1,107 +0,0 @@ -//go:build integration - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package test - -import ( - "context" - "flag" - "testing" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/azure" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/azureblob" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/memfs" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - "github.com/stretchr/testify/require" -) - -func TestAzureStorage(t *testing.T) { - if !*runAzStorage { - t.Skip("Skipping Azure storage test") - } - if *azStorageAccount == "" || *azContainer == "" || *azClientID == "" || *azClientSecret == "" || *azTenantID == "" { - flag.Usage() - t.Fatal("Required flags not set: --az-storage-account, --az-container, --az-tenant-id, --az-client-id, --az-client-secret") - } - require := require.New(t) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - cfg := uri.AzureBlobConfig{ - StorageAccount: *azStorageAccount, - Container: *azContainer, - TenantID: *azTenantID, - ClientID: *azClientID, - ClientSecret: *azClientSecret, - } - store, err := azureblob.New(ctx, cfg) - require.NoError(err) - - runStorageTest(t, store) -} - -func TestAzureKeyKMS(t *testing.T) { - if !*runAzKms { - t.Skip("Skipping Azure Key Vault test") - } - - if *kekID == "" || *azClientID == "" || *azClientSecret == "" || *azTenantID == "" || *azVaultName == "" { - flag.Usage() - t.Fatal("Required flags not set: --az-tenant-id, --az-client-id, --az-client-secret, --az-vault-name, --kek-id") - } - require := require.New(t) - - store := memfs.New() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - cfg := uri.AzureConfig{ - TenantID: *azTenantID, - ClientID: *azClientID, - ClientSecret: *azClientSecret, - VaultName: *azVaultName, - VaultType: uri.DefaultCloud, - KeyName: *kekID, - } - kmsClient, err := azure.New(ctx, store, cfg) - require.NoError(err) - - runKMSTest(t, kmsClient) -} - -func TestAzureKeyHSM(t *testing.T) { - if !*runAzHsm { - t.Skip("Skipping Azure HSM test") - } - - if *kekID == "" || *azClientID == "" || *azClientSecret == "" || *azTenantID == "" || *azVaultName == "" { - flag.Usage() - t.Fatal("Required flags not set: --az-tenant-id, --az-client-id, --az-client-secret, --az-vault-name, --kek-id") - } - require := require.New(t) - - store := memfs.New() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - cfg := uri.AzureConfig{ - TenantID: *azTenantID, - ClientID: *azClientID, - ClientSecret: *azClientSecret, - VaultName: *azVaultName, - VaultType: uri.HSMDefaultCloud, - KeyName: *kekID, - } - kmsClient, err := azure.New(ctx, store, cfg) - require.NoError(err) - - runKMSTest(t, kmsClient) -} diff --git a/internal/kms/test/gcp_test.go b/internal/kms/test/gcp_test.go deleted file mode 100644 index e91a740..0000000 --- a/internal/kms/test/gcp_test.go +++ /dev/null @@ -1,75 +0,0 @@ -//go:build integration - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package test - -import ( - "context" - "flag" - "testing" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms/gcp" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/gcs" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage/memfs" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/uri" - - "github.com/stretchr/testify/require" -) - -func TestGCPKMS(t *testing.T) { - if !*runGcpKms { - t.Skip("Skipping Google KMS test") - } - if *gcpProjectID == "" || *gcpLocation == "" || *gcpKeyRing == "" || *kekID == "" { - flag.Usage() - t.Fatal("Required flags not set: --gcp-project, --gcp-location, --gcp-keyring, --kek-id") - } - require := require.New(t) - - store := memfs.New() - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - cfg := uri.GCPConfig{ - CredentialsPath: *gcpCredentialsPath, - ProjectID: *gcpProjectID, - Location: *gcpLocation, - KeyRing: *gcpKeyRing, - KeyName: *kekID, - } - kmsClient, err := gcp.New(ctx, store, cfg) - require.NoError(err) - defer kmsClient.Close() - - runKMSTest(t, kmsClient) -} - -func TestGcpStorage(t *testing.T) { - if !*runGcpStorage { - t.Skip("Skipping Google Storage test") - } - if *gcpProjectID == "" || *gcpBucket == "" || *gcpCredentialsPath == "" { - flag.Usage() - t.Fatal("Required flags not set: --gcp-project, --gcp-bucket ") - } - require := require.New(t) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - cfg := uri.GoogleCloudStorageConfig{ - CredentialsPath: *gcpCredentialsPath, - ProjectID: *gcpProjectID, - Bucket: *gcpBucket, - } - store, err := gcs.New(ctx, cfg) - require.NoError(err) - - runStorageTest(t, store) -} diff --git a/internal/kms/test/integration_test.go b/internal/kms/test/integration_test.go deleted file mode 100644 index 2079fd3..0000000 --- a/internal/kms/test/integration_test.go +++ /dev/null @@ -1,116 +0,0 @@ -//go:build integration - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package test provides integration tests for KMS and storage backends. -package test - -import ( - "context" - "flag" - "math/rand" - "os" - "testing" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/kms/config" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/kms" - "github.com/flashbots/cvm-reverse-proxy/internal/kms/storage" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - kekID = flag.String("kek-id", "", "ID of the key to use for KMS test.") - - runAwsStorage = flag.Bool("aws-storage", false, "set to run AWS S3 Bucket Storage test") - runAwsKms = flag.Bool("aws-kms", false, "set to run AWS KMS test") - awsRegion = flag.String("aws-region", "us-east-1", "Region to use for AWS tests. Required for AWS KMS test.") - awsAccessKeyID = flag.String("aws-access-key-id", "", "ID of the Access key to use for AWS tests. Required for AWS KMS and storage test.") - awsAccessKey = flag.String("aws-access-key", "", "Access key to use for AWS tests. Required for AWS KMS and storage test.") - awsBucket = flag.String("aws-bucket", "", "Name of the S3 bucket to use for AWS storage test. Required for AWS storage test.") - - runAzStorage = flag.Bool("az-storage", false, "set to run Azure Storage test") - runAzKms = flag.Bool("az-kms", false, "set to run Azure KMS test") - runAzHsm = flag.Bool("az-hsm", false, "set to run Azure HSM test") - azVaultName = flag.String("az-vault-name", "", "Name of the Azure Key Vault to use. Required for Azure KMS/HSM and storage test.") - azTenantID = flag.String("az-tenant-id", "", "Tenant ID to use for Azure tests. Required for Azure KMS/HSM and storage test.") - azClientID = flag.String("az-client-id", "", "Client ID to use for Azure tests. Required for Azure KMS/HSM and storage test.") - azClientSecret = flag.String("az-client-secret", "", "Client secret to use for Azure tests. Required for Azure KMS/HSM and storage test.") - azStorageAccount = flag.String("az-storage-account", "", "Service URL for Azure storage account. Required for Azure storage test.") - azContainer = flag.String("az-container", "constellation-test-storage", "Container to save test data to. Required for Azure storage test.") - - runGcpKms = flag.Bool("gcp-kms", false, "set to run Google KMS test") - runGcpStorage = flag.Bool("gcp-storage", false, "set to run Google Storage test") - gcpCredentialsPath = flag.String("gcp-credentials-path", "", "Path to a credentials file. Optional for Google KMS and Google storage test.") - gcpBucket = flag.String("gcp-bucket", "", "Bucket to save test data to. Required for Google Storage test.") - gcpProjectID = flag.String("gcp-project", "", "Project ID to use for Google tests. Required for Google KMS and Google storage test.") - gcpKeyRing = flag.String("gcp-keyring", "", "Key ring to use for Google KMS test. Required for Google KMS test.") - gcpLocation = flag.String("gcp-location", "global", "Location of the keyring. Required for Google KMS test.") -) - -func TestMain(m *testing.M) { - flag.Parse() - os.Exit(m.Run()) -} - -func runKMSTest(t *testing.T, kms kms.CloudKMS) { - assert := assert.New(t) - require := require.New(t) - - dekName := "test-dek" - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - res, err := kms.GetDEK(ctx, dekName, config.SymmetricKeyLength) - require.NoError(err) - t.Logf("DEK 1: %x\n", res) - - res2, err := kms.GetDEK(ctx, dekName, config.SymmetricKeyLength) - require.NoError(err) - assert.Equal(res, res2) - t.Logf("DEK 2: %x\n", res2) - - res3, err := kms.GetDEK(ctx, addSuffix(dekName), config.SymmetricKeyLength) - require.NoError(err) - assert.Len(res3, config.SymmetricKeyLength) - assert.NotEqual(res, res3) - t.Logf("DEK 3: %x\n", res3) -} - -func runStorageTest(t *testing.T, store kms.Storage) { - assert := assert.New(t) - require := require.New(t) - - testData := []byte("Constellation test data") - testName := "constellation-test" - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - err := store.Put(ctx, testName, testData) - require.NoError(err) - - got, err := store.Get(ctx, testName) - require.NoError(err) - assert.Equal(testData, got) - - _, err = store.Get(ctx, addSuffix("does-not-exist")) - assert.ErrorIs(err, storage.ErrDEKUnset) -} - -func addSuffix(s string) string { - rand := rand.New(rand.NewSource(time.Now().UnixNano())) - letters := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - b := make([]rune, 5) - for i := range b { - b[i] = letters[rand.Intn(len(letters))] - } - return s + "-" + string(b) -} diff --git a/internal/kms/uri/BUILD.bazel b/internal/kms/uri/BUILD.bazel deleted file mode 100644 index e603671..0000000 --- a/internal/kms/uri/BUILD.bazel +++ /dev/null @@ -1,20 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "uri", - srcs = ["uri.go"], - importpath = "cvm-reverse-proxy/internal/kms/uri", - visibility = ["//:__subpackages__"], -) - -go_test( - name = "uri_test", - srcs = ["uri_test.go"], - embed = [":uri"], - deps = [ - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/kms/uri/uri.go b/internal/kms/uri/uri.go deleted file mode 100644 index 6a3de88..0000000 --- a/internal/kms/uri/uri.go +++ /dev/null @@ -1,529 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package uri provides URIs and parsing logic for KMS and storage URIs. - -The URI for a KMS is of the form: - - kms://? - -The URI for a storage is of the form: - - storage:/// - -A URI contains all information necessary to connect to the KMS or storage. -*/ -package uri - -import ( - "encoding/base64" - "fmt" - "net/url" -) - -const ( - // DefaultCloud is the URL for the default Vault URL. - DefaultCloud VaultBaseURL = "vault.azure.net" - // ChinaCloud is the URL for Vaults in Azure China Cloud. - ChinaCloud VaultBaseURL = "vault.azure.cn" - // USGovCloud is the URL for Vaults in Azure US Government Cloud. - USGovCloud VaultBaseURL = "vault.usgovcloudapi.net" - // GermanCloud is the URL for Vaults in Azure German Cloud. - GermanCloud VaultBaseURL = "vault.microsoftazure.de" - // HSMDefaultCloud is the URL for HSM Vaults. - HSMDefaultCloud VaultBaseURL = "managedhsm.azure.net" -) - -// VaultBaseURL is the base URL of the vault. -// It defines what type of key vault is used. -type VaultBaseURL string - -// Well known endpoints for KMS services. -const ( - awsKMSURI = "kms://aws?region=%s&accessKeyID=%s&accessKey=%s&keyName=%s" - azureKMSURI = "kms://azure?tenantID=%s&clientID=%s&clientSecret=%s&vaultName=%s&vaultType=%s&keyName=%s" - gcpKMSURI = "kms://gcp?projectID=%s&location=%s&keyRing=%s&credentialsPath=%s&keyName=%s" - clusterKMSURI = "kms://cluster-kms?key=%s&salt=%s" - awsS3URI = "storage://aws?bucket=%s®ion=%s&accessKeyID=%s&accessKey=%s" - azureBlobURI = "storage://azure?account=%s&container=%s&tenantID=%s&clientID=%s&clientSecret=%s" - gcpStorageURI = "storage://gcp?projectID=%s&bucket=%s&credentialsPath=%s" - // NoStoreURI is a URI that indicates that no storage is used. - // Should only be used with cluster KMS. - NoStoreURI = "storage://no-store" -) - -// MasterSecret holds the master key and salt for deriving keys. -type MasterSecret struct { - // Key is the secret value used in HKDF to derive keys. - Key []byte `json:"key"` - // Salt is the salt used in HKDF to derive keys. - Salt []byte `json:"salt"` -} - -// EncodeToURI returns a URI encoding the master secret. -func (m MasterSecret) EncodeToURI() string { - return fmt.Sprintf( - clusterKMSURI, - base64.URLEncoding.EncodeToString(m.Key), - base64.URLEncoding.EncodeToString(m.Salt), - ) -} - -// DecodeMasterSecretFromURI decodes a master secret from a URI. -func DecodeMasterSecretFromURI(uri string) (MasterSecret, error) { - u, err := url.Parse(uri) - if err != nil { - return MasterSecret{}, err - } - - if u.Scheme != "kms" { - return MasterSecret{}, fmt.Errorf("invalid scheme: %q", u.Scheme) - } - if u.Host != "cluster-kms" { - return MasterSecret{}, fmt.Errorf("invalid host: %q", u.Host) - } - - q := u.Query() - key, err := getBase64QueryParameter(q, "key") - if err != nil { - return MasterSecret{}, err - } - salt, err := getBase64QueryParameter(q, "salt") - if err != nil { - return MasterSecret{}, err - } - return MasterSecret{ - Key: key, - Salt: salt, - }, nil -} - -// AWSConfig is the configuration to authenticate with AWS KMS. -type AWSConfig struct { - // KeyName is the name of the key in AWS KMS. - KeyName string - // Region is the region of the key in AWS KMS. - Region string - // AccessKeyID is the ID of the access key used for authentication with the AWS API. - AccessKeyID string - // AccessKey is the secret value used for authentication with the AWS API. - AccessKey string -} - -// DecodeAWSConfigFromURI decodes an AWS configuration from a URI. -func DecodeAWSConfigFromURI(uri string) (AWSConfig, error) { - u, err := url.Parse(uri) - if err != nil { - return AWSConfig{}, err - } - - if u.Scheme != "kms" { - return AWSConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme) - } - if u.Host != "aws" { - return AWSConfig{}, fmt.Errorf("invalid host: %q", u.Host) - } - - q := u.Query() - keyName, err := getQueryParameter(q, "keyName") - if err != nil { - return AWSConfig{}, err - } - region, err := getQueryParameter(q, "region") - if err != nil { - return AWSConfig{}, err - } - accessKeyID, err := getQueryParameter(q, "accessKeyID") - if err != nil { - return AWSConfig{}, err - } - accessKey, err := getQueryParameter(q, "accessKey") - if err != nil { - return AWSConfig{}, err - } - - return AWSConfig{ - KeyName: keyName, - Region: region, - AccessKeyID: accessKeyID, - AccessKey: accessKey, - }, nil -} - -// EncodeToURI returns a URI encoding the AWS configuration. -func (c AWSConfig) EncodeToURI() string { - return fmt.Sprintf( - awsKMSURI, - c.Region, - c.AccessKeyID, - c.AccessKey, - c.KeyName, - ) -} - -// AWSS3Config is the configuration to authenticate with AWS S3 storage bucket. -type AWSS3Config struct { - // Bucket is the name of the S3 storage bucket to use. - Bucket string - // Region is the region storage bucket is located in. - Region string - // AccessKeyID is the ID of the access key used for authentication with the AWS API. - AccessKeyID string - // AccessKey is the secret value used for authentication with the AWS API. - AccessKey string -} - -// DecodeAWSS3ConfigFromURI decodes an S3 configuration from a URI. -func DecodeAWSS3ConfigFromURI(uri string) (AWSS3Config, error) { - u, err := url.Parse(uri) - if err != nil { - return AWSS3Config{}, err - } - - if u.Scheme != "storage" { - return AWSS3Config{}, fmt.Errorf("invalid scheme: %q", u.Scheme) - } - if u.Host != "aws" { - return AWSS3Config{}, fmt.Errorf("invalid host: %q", u.Host) - } - - q := u.Query() - bucket, err := getQueryParameter(q, "bucket") - if err != nil { - return AWSS3Config{}, err - } - region, err := getQueryParameter(q, "region") - if err != nil { - return AWSS3Config{}, err - } - accessKeyID, err := getQueryParameter(q, "accessKeyID") - if err != nil { - return AWSS3Config{}, err - } - accessKey, err := getQueryParameter(q, "accessKey") - if err != nil { - return AWSS3Config{}, err - } - - return AWSS3Config{ - Bucket: bucket, - Region: region, - AccessKeyID: accessKeyID, - AccessKey: accessKey, - }, nil -} - -// EncodeToURI returns a URI encoding the S3 configuration. -func (s AWSS3Config) EncodeToURI() string { - return fmt.Sprintf( - awsS3URI, - url.QueryEscape(s.Bucket), - url.QueryEscape(s.Region), - url.QueryEscape(s.AccessKeyID), - url.QueryEscape(s.AccessKey), - ) -} - -// AzureConfig is the configuration to authenticate with Azure Key Vault. -type AzureConfig struct { - // TenantID of the Azure Active Directory the Key Vault is located in. - TenantID string - // ClientID is the ID of the managed identity used to authenticate with the Azure API. - ClientID string - // ClientSecret is the secret-value/password of the managed identity used to authenticate with the Azure API. - ClientSecret string - // KeyName is the name of the key in Azure Key Vault. - KeyName string - // VaultName is the name of the vault. - VaultName string - // VaultType is the type of the vault. - // This defines whether or not the Key Vault is a managed HSM. - VaultType VaultBaseURL -} - -// DecodeAzureConfigFromURI decodes an Azure configuration from a URI. -func DecodeAzureConfigFromURI(uri string) (AzureConfig, error) { - u, err := url.Parse(uri) - if err != nil { - return AzureConfig{}, err - } - - if u.Scheme != "kms" { - return AzureConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme) - } - if u.Host != "azure" { - return AzureConfig{}, fmt.Errorf("invalid host: %q", u.Host) - } - - q := u.Query() - tenantID, err := getQueryParameter(q, "tenantID") - if err != nil { - return AzureConfig{}, err - } - clientID, err := getQueryParameter(q, "clientID") - if err != nil { - return AzureConfig{}, err - } - clientSecret, err := getQueryParameter(q, "clientSecret") - if err != nil { - return AzureConfig{}, err - } - vaultName, err := getQueryParameter(q, "vaultName") - if err != nil { - return AzureConfig{}, err - } - vaultType, err := getQueryParameter(q, "vaultType") - if err != nil { - return AzureConfig{}, err - } - keyName, err := getQueryParameter(q, "keyName") - if err != nil { - return AzureConfig{}, err - } - - return AzureConfig{ - TenantID: tenantID, - ClientID: clientID, - ClientSecret: clientSecret, - VaultName: vaultName, - VaultType: VaultBaseURL(vaultType), - KeyName: keyName, - }, nil -} - -// EncodeToURI returns a URI encoding the Azure configuration. -func (a AzureConfig) EncodeToURI() string { - return fmt.Sprintf( - azureKMSURI, - url.QueryEscape(a.TenantID), - url.QueryEscape(a.ClientID), - url.QueryEscape(a.ClientSecret), - url.QueryEscape(a.VaultName), - url.QueryEscape(string(a.VaultType)), - url.QueryEscape(a.KeyName), - ) -} - -// AzureBlobConfig is the configuration to authenticate with Azure Blob storage. -type AzureBlobConfig struct { - // StorageAccount is the name of the storage account to use. - StorageAccount string - // Container is the name of the container to use. - Container string - // TenantID of the Azure Active Directory the Key Vault is located in. - TenantID string - // ClientID is the ID of the managed identity used to authenticate with the Azure API. - ClientID string - // ClientSecret is the secret-value/password of the managed identity used to authenticate with the Azure API. - ClientSecret string -} - -// DecodeAzureBlobConfigFromURI decodes an Azure Blob configuration from a URI. -func DecodeAzureBlobConfigFromURI(uri string) (AzureBlobConfig, error) { - u, err := url.Parse(uri) - if err != nil { - return AzureBlobConfig{}, err - } - - if u.Scheme != "storage" { - return AzureBlobConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme) - } - if u.Host != "azure" { - return AzureBlobConfig{}, fmt.Errorf("invalid host: %q", u.Host) - } - - q := u.Query() - storageAccount, err := getQueryParameter(q, "account") - if err != nil { - return AzureBlobConfig{}, err - } - container, err := getQueryParameter(q, "container") - if err != nil { - return AzureBlobConfig{}, err - } - tenantID, err := getQueryParameter(q, "tenantID") - if err != nil { - return AzureBlobConfig{}, err - } - clientID, err := getQueryParameter(q, "clientID") - if err != nil { - return AzureBlobConfig{}, err - } - clientSecret, err := getQueryParameter(q, "clientSecret") - if err != nil { - return AzureBlobConfig{}, err - } - - return AzureBlobConfig{ - StorageAccount: storageAccount, - Container: container, - TenantID: tenantID, - ClientID: clientID, - ClientSecret: clientSecret, - }, nil -} - -// EncodeToURI returns a URI encoding the Azure Blob configuration. -func (a AzureBlobConfig) EncodeToURI() string { - return fmt.Sprintf( - azureBlobURI, - url.QueryEscape(a.StorageAccount), - url.QueryEscape(a.Container), - url.QueryEscape(a.TenantID), - url.QueryEscape(a.ClientID), - url.QueryEscape(a.ClientSecret), - ) -} - -// GCPConfig is the configuration to authenticate with GCP KMS. -type GCPConfig struct { - // CredentialsPath is the path to a credentials file of a service account used to authorize against the GCP API. - CredentialsPath string - // ProjectID is the name of the GCP project the KMS is located in. - ProjectID string - // Location is the location of the KMS. - Location string - // KeyRing is the name of the keyring. - KeyRing string - // KeyName is the name of the key in the GCP KMS. - KeyName string -} - -// DecodeGCPConfigFromURI decodes a GCP configuration from a URI. -func DecodeGCPConfigFromURI(uri string) (GCPConfig, error) { - u, err := url.Parse(uri) - if err != nil { - return GCPConfig{}, err - } - - if u.Scheme != "kms" { - return GCPConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme) - } - if u.Host != "gcp" { - return GCPConfig{}, fmt.Errorf("invalid host: %q", u.Host) - } - - q := u.Query() - credentials, err := getQueryParameter(q, "credentialsPath") - if err != nil { - return GCPConfig{}, err - } - projectID, err := getQueryParameter(q, "projectID") - if err != nil { - return GCPConfig{}, err - } - location, err := getQueryParameter(q, "location") - if err != nil { - return GCPConfig{}, err - } - keyRing, err := getQueryParameter(q, "keyRing") - if err != nil { - return GCPConfig{}, err - } - keyName, err := getQueryParameter(q, "keyName") - if err != nil { - return GCPConfig{}, err - } - - return GCPConfig{ - CredentialsPath: credentials, - ProjectID: projectID, - Location: location, - KeyRing: keyRing, - KeyName: keyName, - }, nil -} - -// EncodeToURI returns a URI encoding the GCP configuration. -func (g GCPConfig) EncodeToURI() string { - return fmt.Sprintf( - gcpKMSURI, - url.QueryEscape(g.ProjectID), - url.QueryEscape(g.Location), - url.QueryEscape(g.KeyRing), - url.QueryEscape(g.CredentialsPath), - url.QueryEscape(g.KeyName), - ) -} - -// GoogleCloudStorageConfig is the configuration to authenticate with Google Cloud Storage. -type GoogleCloudStorageConfig struct { - // CredentialsPath is the path to a credentials file of a service account used to authorize against the GCP API. - CredentialsPath string - // ProjectID is the name of the GCP project the storage bucket is located in. - ProjectID string - // Bucket is the name of the bucket to use. - Bucket string -} - -// DecodeGoogleCloudStorageConfigFromURI decodes a Google Cloud Storage configuration from a URI. -func DecodeGoogleCloudStorageConfigFromURI(uri string) (GoogleCloudStorageConfig, error) { - u, err := url.Parse(uri) - if err != nil { - return GoogleCloudStorageConfig{}, err - } - - if u.Scheme != "storage" { - return GoogleCloudStorageConfig{}, fmt.Errorf("invalid scheme: %q", u.Scheme) - } - if u.Host != "gcp" { - return GoogleCloudStorageConfig{}, fmt.Errorf("invalid host: %q", u.Host) - } - - q := u.Query() - credentials, err := getQueryParameter(q, "credentialsPath") - if err != nil { - return GoogleCloudStorageConfig{}, err - } - projectID, err := getQueryParameter(q, "projectID") - if err != nil { - return GoogleCloudStorageConfig{}, err - } - bucket, err := getQueryParameter(q, "bucket") - if err != nil { - return GoogleCloudStorageConfig{}, err - } - - return GoogleCloudStorageConfig{ - CredentialsPath: credentials, - ProjectID: projectID, - Bucket: bucket, - }, nil -} - -// EncodeToURI returns a URI encoding the Google Cloud Storage configuration. -func (g GoogleCloudStorageConfig) EncodeToURI() string { - return fmt.Sprintf( - gcpStorageURI, - url.QueryEscape(g.ProjectID), - url.QueryEscape(g.Bucket), - url.QueryEscape(g.CredentialsPath), - ) -} - -// getBase64QueryParameter returns the url-base64-decoded value for the given key from the query parameters. -func getBase64QueryParameter(q url.Values, key string) ([]byte, error) { - value, err := getQueryParameter(q, key) - if err != nil { - return nil, err - } - return base64.URLEncoding.DecodeString(value) -} - -// getQueryParameter returns the unescaped value for the given key from the query parameters. -func getQueryParameter(q url.Values, key string) (string, error) { - value := q.Get(key) - if value == "" { - return "", fmt.Errorf("missing query parameter %q", key) - } - - value, err := url.QueryUnescape(value) - if err != nil { - return "", fmt.Errorf("failed to unescape value for key: %q", key) - } - return value, nil -} diff --git a/internal/kms/uri/uri_test.go b/internal/kms/uri/uri_test.go deleted file mode 100644 index b7e2ea3..0000000 --- a/internal/kms/uri/uri_test.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package uri - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1")) -} - -func TestMasterSecretURI(t *testing.T) { - cfg := MasterSecret{ - Key: []byte("key"), - Salt: []byte("salt"), - } - - checkURI(t, cfg, DecodeMasterSecretFromURI) -} - -func TestAWSURI(t *testing.T) { - cfg := AWSConfig{ - KeyName: "key", - Region: "region", - AccessKeyID: "accessKeyID", - AccessKey: "accessKey", - } - - checkURI(t, cfg, DecodeAWSConfigFromURI) -} - -func TestAWSS3URI(t *testing.T) { - cfg := AWSS3Config{ - Bucket: "bucket", - Region: "region", - AccessKeyID: "accessKeyID", - AccessKey: "accessKey", - } - - checkURI(t, cfg, DecodeAWSS3ConfigFromURI) -} - -func TestAzureURI(t *testing.T) { - cfg := AzureConfig{ - KeyName: "key", - TenantID: "tenantID", - ClientID: "clientID", - ClientSecret: "clientSecret", - VaultName: "vaultName", - VaultType: DefaultCloud, - } - - checkURI(t, cfg, DecodeAzureConfigFromURI) -} - -func TestAzureBlobURI(t *testing.T) { - cfg := AzureBlobConfig{ - StorageAccount: "accountName", - Container: "containerName", - TenantID: "tenantID", - ClientID: "clientID", - ClientSecret: "clientSecret", - } - - checkURI(t, cfg, DecodeAzureBlobConfigFromURI) -} - -func TestGCPURI(t *testing.T) { - cfg := GCPConfig{ - KeyName: "key", - ProjectID: "project", - Location: "location", - KeyRing: "keyRing", - CredentialsPath: "/path/to/credentials", - } - - checkURI(t, cfg, DecodeGCPConfigFromURI) -} - -func TestGoogleCloudStorageURI(t *testing.T) { - cfg := GoogleCloudStorageConfig{ - ProjectID: "project", - Bucket: "bucket", - CredentialsPath: "/path/to/credentials", - } - - checkURI(t, cfg, DecodeGoogleCloudStorageConfigFromURI) -} - -type cfgStruct interface { - EncodeToURI() string -} - -func checkURI[T any](t *testing.T, cfg cfgStruct, decodeFunc func(string) (T, error)) { - t.Helper() - require := require.New(t) - assert := assert.New(t) - - uri := cfg.EncodeToURI() - decoded, err := decodeFunc(uri) - require.NoError(err, "failed to decode URI to config: %s", uri) - - assert.Equal(cfg, decoded, "decoded config does not match original config") -} diff --git a/internal/kubernetes/BUILD.bazel b/internal/kubernetes/BUILD.bazel deleted file mode 100644 index 64332a5..0000000 --- a/internal/kubernetes/BUILD.bazel +++ /dev/null @@ -1,43 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "kubernetes", - srcs = [ - "configmaps.go", - "kubernetes.go", - "marshal.go", - "secrets.go", - ], - importpath = "cvm-reverse-proxy/internal/kubernetes", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/constants", - "//internal/versions/components", - "@in_gopkg_yaml_v3//:yaml_v3", - "@io_k8s_api//core/v1:core", - "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", - "@io_k8s_apimachinery//pkg/runtime", - "@io_k8s_apimachinery//pkg/runtime/serializer", - "@io_k8s_apimachinery//pkg/runtime/serializer/json", - "@io_k8s_client_go//kubernetes/scheme", - ], -) - -go_test( - name = "kubernetes_test", - srcs = [ - "configmaps_test.go", - "marshal_test.go", - "secrets_test.go", - ], - embed = [":kubernetes"], - deps = [ - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@io_k8s_api//core/v1:core", - "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", - "@io_k8s_apimachinery//pkg/runtime", - "@org_golang_google_protobuf//proto", - ], -) diff --git a/internal/kubernetes/configmaps.go b/internal/kubernetes/configmaps.go deleted file mode 100644 index fc75097..0000000 --- a/internal/kubernetes/configmaps.go +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubernetes - -import ( - "encoding/json" - "fmt" - "strings" - - "github.com/flashbots/cvm-reverse-proxy/internal/constants" - "github.com/flashbots/cvm-reverse-proxy/internal/versions/components" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// ConfigMaps represent a list of k8s ConfigMap. -type ConfigMaps []*corev1.ConfigMap - -// Marshal marshals config maps into multiple YAML documents. -func (s ConfigMaps) Marshal() ([]byte, error) { - objects := make([]runtime.Object, len(s)) - for i := range s { - objects[i] = s[i] - } - return MarshalK8SResourcesList(objects) -} - -// ConstructK8sComponentsCM creates a k8s-components config map for the given components. -func ConstructK8sComponentsCM(components components.Components, clusterVersion string) (corev1.ConfigMap, error) { - componentsMarshalled, err := json.Marshal(components) - if err != nil { - return corev1.ConfigMap{}, fmt.Errorf("marshalling component versions: %w", err) - } - - componentsHash := components.GetHash() - componentConfigMapName := fmt.Sprintf("k8s-components-%s", strings.ReplaceAll(componentsHash, ":", "-")) - - return corev1.ConfigMap{ - TypeMeta: metav1.TypeMeta{ - APIVersion: "v1", - Kind: "ConfigMap", - }, - Immutable: toPtr(true), - ObjectMeta: metav1.ObjectMeta{ - Name: componentConfigMapName, - Namespace: "kube-system", - }, - Data: map[string]string{ - constants.ComponentsListKey: string(componentsMarshalled), - constants.K8sVersionFieldName: clusterVersion, - }, - }, nil -} - -func toPtr[T any](v T) *T { - return &v -} diff --git a/internal/kubernetes/configmaps_test.go b/internal/kubernetes/configmaps_test.go deleted file mode 100644 index 96c3f47..0000000 --- a/internal/kubernetes/configmaps_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubernetes - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - k8s "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestConfigMaps(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - - configMaps := ConfigMaps{ - &k8s.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - Data: map[string]string{"key": "value1"}, - }, - &k8s.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - Data: map[string]string{"key": "value2"}, - }, - } - data, err := configMaps.Marshal() - require.NoError(err) - - assert.Equal(`apiVersion: v1 -data: - key: value1 -kind: ConfigMap -metadata: - creationTimestamp: null ---- -apiVersion: v1 -data: - key: value2 -kind: ConfigMap -metadata: - creationTimestamp: null -`, string(data)) -} diff --git a/internal/kubernetes/kubectl/BUILD.bazel b/internal/kubernetes/kubectl/BUILD.bazel deleted file mode 100644 index df865f0..0000000 --- a/internal/kubernetes/kubectl/BUILD.bazel +++ /dev/null @@ -1,34 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "kubectl", - srcs = ["kubectl.go"], - importpath = "cvm-reverse-proxy/internal/kubernetes/kubectl", - visibility = ["//:__subpackages__"], - deps = [ - "@io_k8s_api//core/v1:core", - "@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1:apiextensions", - "@io_k8s_apiextensions_apiserver//pkg/client/clientset/clientset/typed/apiextensions/v1:apiextensions", - "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", - "@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured", - "@io_k8s_apimachinery//pkg/labels", - "@io_k8s_apimachinery//pkg/runtime", - "@io_k8s_apimachinery//pkg/runtime/schema", - "@io_k8s_apimachinery//pkg/runtime/serializer", - "@io_k8s_apimachinery//pkg/types", - "@io_k8s_client_go//dynamic", - "@io_k8s_client_go//kubernetes", - "@io_k8s_client_go//rest", - "@io_k8s_client_go//scale/scheme", - "@io_k8s_client_go//tools/clientcmd", - "@io_k8s_client_go//util/retry", - ], -) - -go_test( - name = "kubectl_test", - srcs = ["kubectl_test.go"], - embed = [":kubectl"], - deps = ["@com_github_stretchr_testify//assert"], -) diff --git a/internal/kubernetes/kubectl/kubectl.go b/internal/kubernetes/kubectl/kubectl.go deleted file mode 100644 index dae2e2d..0000000 --- a/internal/kubernetes/kubectl/kubectl.go +++ /dev/null @@ -1,256 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package kubectl provides a kubectl-like interface for Kubernetes. -Functions defined here should not make use of [os/exec]. -*/ -package kubectl - -import ( - "context" - "errors" - "fmt" - - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsclientv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "k8s.io/client-go/scale/scheme" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/retry" -) - -// Kubectl implements functionality of the Kubernetes "kubectl" tool. -type Kubectl struct { - kubernetes.Interface - dynamicClient dynamic.Interface - apiextensionClient apiextensionsclientv1.ApiextensionsV1Interface -} - -// NewUninitialized returns an empty Kubectl client. -// Initialize needs to be called before the client is usable. -func NewUninitialized() *Kubectl { - return &Kubectl{} -} - -// NewFromConfig returns a Kubectl client using the given kubeconfig. -func NewFromConfig(kubeconfig []byte) (*Kubectl, error) { - k := NewUninitialized() - if err := k.Initialize(kubeconfig); err != nil { - return nil, err - } - return k, nil -} - -// Initialize sets sets all required fields so the Kubectl client can be used. -func (k *Kubectl) Initialize(kubeconfig []byte) error { - clientConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig) - if err != nil { - return fmt.Errorf("creating k8s client config from kubeconfig: %w", err) - } - if err := k.initialize(clientConfig); err != nil { - return fmt.Errorf("initializing kubectl: %w", err) - } - return nil -} - -// ApplyCRD updates the given CRD by parsing it, querying it's version from the cluster and finally updating it. -func (k *Kubectl) ApplyCRD(ctx context.Context, rawCRD []byte) error { - crd, err := parseCRD(rawCRD) - if err != nil { - return fmt.Errorf("parsing crds: %w", err) - } - - clusterCRD, err := k.apiextensionClient.CustomResourceDefinitions().Get(ctx, crd.Name, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("getting crd: %w", err) - } - crd.ResourceVersion = clusterCRD.ResourceVersion - _, err = k.apiextensionClient.CustomResourceDefinitions().Update(ctx, crd, metav1.UpdateOptions{}) - return err -} - -// ListCRDs retrieves all custom resource definitions currently installed in the cluster. -func (k *Kubectl) ListCRDs(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error) { - crds, err := k.apiextensionClient.CustomResourceDefinitions().List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, fmt.Errorf("listing CRDs: %w", err) - } - - return crds.Items, nil -} - -// ListCRs retrieves all objects for a given CRD. -func (k *Kubectl) ListCRs(ctx context.Context, gvr schema.GroupVersionResource) ([]unstructured.Unstructured, error) { - unstructuredList, err := k.dynamicClient.Resource(gvr).List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, fmt.Errorf("listing CRs for GroupVersionResource %+v: %w", gvr, err) - } - - return unstructuredList.Items, nil -} - -// GetCR retrieves a Custom Resource given it's name and group version resource. -func (k *Kubectl) GetCR(ctx context.Context, gvr schema.GroupVersionResource, name string) (*unstructured.Unstructured, error) { - return k.dynamicClient.Resource(gvr).Get(ctx, name, metav1.GetOptions{}) -} - -// UpdateCR updates a Custom Resource given it's and group version resource. -func (k *Kubectl) UpdateCR(ctx context.Context, gvr schema.GroupVersionResource, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) { - return k.dynamicClient.Resource(gvr).Update(ctx, obj, metav1.UpdateOptions{}) -} - -// CreateConfigMap creates the provided configmap. -func (k *Kubectl) CreateConfigMap(ctx context.Context, configMap *corev1.ConfigMap) error { - _, err := k.CoreV1().ConfigMaps(configMap.ObjectMeta.Namespace).Create(ctx, configMap, metav1.CreateOptions{}) - if err != nil { - return err - } - return nil -} - -// GetConfigMap returns a ConfigMap given it's name and namespace. -func (k *Kubectl) GetConfigMap(ctx context.Context, namespace, name string) (*corev1.ConfigMap, error) { - return k.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{}) -} - -// UpdateConfigMap updates the given ConfigMap. -func (k *Kubectl) UpdateConfigMap(ctx context.Context, configMap *corev1.ConfigMap) (*corev1.ConfigMap, error) { - return k.CoreV1().ConfigMaps(configMap.ObjectMeta.Namespace).Update(ctx, configMap, metav1.UpdateOptions{}) -} - -// AnnotateNode adds the provided annotations to the node, identified by name. -func (k *Kubectl) AnnotateNode(ctx context.Context, nodeName, annotationKey, annotationValue string) error { - return retry.RetryOnConflict(retry.DefaultRetry, func() error { - node, err := k.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) - if err != nil { - return err - } - if node.Annotations == nil { - node.Annotations = map[string]string{} - } - node.Annotations[annotationKey] = annotationValue - _, err = k.CoreV1().Nodes().Update(ctx, node, metav1.UpdateOptions{}) - return err - }) -} - -// KubernetesVersion returns the Kubernetes version of the cluster. -func (k *Kubectl) KubernetesVersion() (string, error) { - serverVersion, err := k.Discovery().ServerVersion() - if err != nil { - return "", err - } - return serverVersion.GitVersion, nil -} - -// ListAllNamespaces returns all namespaces in the cluster. -func (k *Kubectl) ListAllNamespaces(ctx context.Context) (*corev1.NamespaceList, error) { - return k.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) -} - -// GetNodes returns all nodes in the cluster. -func (k *Kubectl) GetNodes(ctx context.Context) ([]corev1.Node, error) { - nodes, err := k.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, fmt.Errorf("listing nodes: %w", err) - } - return nodes.Items, nil -} - -// PatchFirstNodePodCIDR patches the firstNodePodCIDR of the first control-plane node for Cilium. -func (k *Kubectl) PatchFirstNodePodCIDR(ctx context.Context, firstNodePodCIDR string) error { - selector := labels.Set{"node-role.kubernetes.io/control-plane": ""}.AsSelector() - controlPlaneList, err := k.CoreV1().Nodes().List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) - if err != nil { - return err - } - if len(controlPlaneList.Items) != 1 { - return fmt.Errorf("expected 1 control-plane node, got %d", len(controlPlaneList.Items)) - } - nodeName := controlPlaneList.Items[0].Name - // Update the node's spec - _, err = k.CoreV1().Nodes().Patch(context.Background(), nodeName, types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"podCIDR":"%s"}}`, firstNodePodCIDR)), metav1.PatchOptions{}) - return err -} - -// AddNodeSelectorsToDeployment adds [K8s selectors] to the deployment, identified -// by name and namespace. -// -// [K8s selectors]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ -func (k *Kubectl) AddNodeSelectorsToDeployment(ctx context.Context, selectors map[string]string, name string, namespace string) error { - deployments := k.AppsV1().Deployments(namespace) - - // retry resource update if an error occurs - err := retry.RetryOnConflict(retry.DefaultRetry, func() error { - result, err := deployments.Get(ctx, name, metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("failed to get Deployment to add node selector: %w", err) - } - - for k, v := range selectors { - result.Spec.Template.Spec.NodeSelector[k] = v - } - - if _, err = deployments.Update(ctx, result, metav1.UpdateOptions{}); err != nil { - return err - } - return nil - }) - if err != nil { - return err - } - return nil -} - -func (k *Kubectl) initialize(clientConfig *rest.Config) error { - clientset, err := kubernetes.NewForConfig(clientConfig) - if err != nil { - return fmt.Errorf("creating k8s client from kubeconfig: %w", err) - } - k.Interface = clientset - - dynamicClient, err := dynamic.NewForConfig(clientConfig) - if err != nil { - return fmt.Errorf("creating unstructured client: %w", err) - } - k.dynamicClient = dynamicClient - - apiextensionClient, err := apiextensionsclientv1.NewForConfig(clientConfig) - if err != nil { - return fmt.Errorf("creating api extension client from kubeconfig: %w", err) - } - k.apiextensionClient = apiextensionClient - - return nil -} - -// parseCRD takes a byte slice of data and tries to create a CustomResourceDefinition object from it. -func parseCRD(crdString []byte) (*v1.CustomResourceDefinition, error) { - sch := runtime.NewScheme() - _ = scheme.AddToScheme(sch) - _ = v1.AddToScheme(sch) - obj, groupVersionKind, err := serializer.NewCodecFactory(sch).UniversalDeserializer().Decode(crdString, nil, nil) - if err != nil { - return nil, fmt.Errorf("decoding crd: %w", err) - } - if groupVersionKind.Kind == "CustomResourceDefinition" { - return obj.(*v1.CustomResourceDefinition), nil - } - - return nil, errors.New("parsed []byte, but did not find a CRD") -} diff --git a/internal/kubernetes/kubectl/kubectl_test.go b/internal/kubernetes/kubectl/kubectl_test.go deleted file mode 100644 index 3ca00e5..0000000 --- a/internal/kubernetes/kubectl/kubectl_test.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubectl - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestParseCRDs(t *testing.T) { - testCases := map[string]struct { - data string - wantErr bool - }{ - "success": { - data: "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n name: nodeversions.update.edgeless.systems\nspec:\n group: update.edgeless.systems\n names:\n kind: NodeImage\n", - wantErr: false, - }, - "wrong kind": { - data: "apiVersion: v1\nkind: Secret\ntype: Opaque\nmetadata:\n name: supersecret\n namespace: testNamespace\ndata:\n data: YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE=\n", - wantErr: true, - }, - "decoding error": { - data: "asdf", - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - _, err := parseCRD([]byte(tc.data)) - if tc.wantErr { - assert.Error(err) - return - } - assert.NoError(err) - }) - } -} diff --git a/internal/kubernetes/kubernetes.go b/internal/kubernetes/kubernetes.go deleted file mode 100644 index cf8c478..0000000 --- a/internal/kubernetes/kubernetes.go +++ /dev/null @@ -1,12 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -/* -Package kubernetes provides data types and custom marshalers for Kubernetes API objects. - -Interaction with the Kubernetes API should be handled by the kubectl subpackage. -*/ -package kubernetes diff --git a/internal/kubernetes/marshal.go b/internal/kubernetes/marshal.go deleted file mode 100644 index d402ce8..0000000 --- a/internal/kubernetes/marshal.go +++ /dev/null @@ -1,155 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubernetes - -import ( - "bytes" - "errors" - "fmt" - "io" - "reflect" - - "gopkg.in/yaml.v3" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/runtime/serializer/json" - "k8s.io/client-go/kubernetes/scheme" -) - -// Marshaler is used by all k8s resources that can be marshaled to YAML. -type Marshaler interface { - Marshal() ([]byte, error) -} - -// MarshalK8SResources marshals every field of a struct into a k8s resource YAML. -func MarshalK8SResources(resources any) ([]byte, error) { - if resources == nil { - return nil, errors.New("marshal on nil called") - } - serializer := json.NewYAMLSerializer(json.DefaultMetaFactory, nil, nil) - var buf bytes.Buffer - - // reflect over struct containing fields that are k8s resources - value := reflect.ValueOf(resources) - if value.Kind() != reflect.Ptr && value.Kind() != reflect.Interface { - return nil, errors.New("marshal on non-pointer called") - } - elem := value.Elem() - if elem.Kind() == reflect.Struct { - // iterate over all struct fields - for i := 0; i < elem.NumField(); i++ { - field := elem.Field(i) - var inter any - // check if value can be converted to interface - if field.CanInterface() { - inter = field.Addr().Interface() - } else { - continue - } - // convert field interface to runtime.Object - obj, ok := inter.(runtime.Object) - if !ok { - continue - } - - if i > 0 { - // separate YAML documents - buf.Write([]byte("---\n")) - } - // serialize k8s resource - if err := serializer.Encode(obj, &buf); err != nil { - return nil, err - } - } - } - - return buf.Bytes(), nil -} - -// UnmarshalK8SResources takes YAML and converts it into a k8s resources struct. -func UnmarshalK8SResources(data []byte, into any) error { - if into == nil { - return errors.New("unmarshal on nil called") - } - // reflect over struct containing fields that are k8s resources - value := reflect.ValueOf(into).Elem() - if value.Kind() != reflect.Struct { - return errors.New("can only reflect over struct") - } - - decoder := serializer.NewCodecFactory(scheme.Scheme).UniversalDecoder() - documents, err := splitYAML(data) - if err != nil { - return fmt.Errorf("splitting deployment YAML into multiple documents: %w", err) - } - if len(documents) != value.NumField() { - return fmt.Errorf("expected %v YAML documents, got %v", value.NumField(), len(documents)) - } - - for i := 0; i < value.NumField(); i++ { - field := value.Field(i) - var inter any - // check if value can be converted to interface - if !field.CanInterface() { - return fmt.Errorf("cannot use struct field %v as interface", i) - } - inter = field.Addr().Interface() - // convert field interface to runtime.Object - obj, ok := inter.(runtime.Object) - if !ok { - return fmt.Errorf("cannot convert struct field %v as k8s runtime object", i) - } - - // decode YAML document into struct field - if err := runtime.DecodeInto(decoder, documents[i], obj); err != nil { - return err - } - } - - return nil -} - -// MarshalK8SResourcesList marshals every element of a slice into a k8s resource YAML. -func MarshalK8SResourcesList(resources []runtime.Object) ([]byte, error) { - serializer := json.NewYAMLSerializer(json.DefaultMetaFactory, nil, nil) - var buf bytes.Buffer - - for i, obj := range resources { - if i > 0 { - // separate YAML documents - buf.Write([]byte("---\n")) - } - // serialize k8s resource - if err := serializer.Encode(obj, &buf); err != nil { - return nil, err - } - } - - return buf.Bytes(), nil -} - -// splitYAML splits a YAML multidoc into a slice of multiple YAML docs. -func splitYAML(resources []byte) ([][]byte, error) { - dec := yaml.NewDecoder(bytes.NewReader(resources)) - var res [][]byte - for { - var value any - err := dec.Decode(&value) - if errors.Is(err, io.EOF) { - break - } - if err != nil { - return nil, err - } - valueBytes, err := yaml.Marshal(value) - if err != nil { - return nil, err - } - res = append(res, valueBytes) - } - return res, nil -} diff --git a/internal/kubernetes/marshal_test.go b/internal/kubernetes/marshal_test.go deleted file mode 100644 index 9da4010..0000000 --- a/internal/kubernetes/marshal_test.go +++ /dev/null @@ -1,366 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubernetes - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - k8s "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -func TestMarshalK8SResources(t *testing.T) { - testCases := map[string]struct { - resources any - wantErr bool - wantYAML string - }{ - "ConfigMap as only field can be marshaled": { - resources: &struct { - ConfigMap k8s.ConfigMap - }{ - ConfigMap: k8s.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - Data: map[string]string{ - "key": "value", - }, - }, - }, - wantYAML: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null -`, - }, - "Multiple fields are correctly encoded": { - resources: &struct { - ConfigMap k8s.ConfigMap - Secret k8s.Secret - }{ - ConfigMap: k8s.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - Data: map[string]string{ - "key": "value", - }, - }, - Secret: k8s.Secret{ - TypeMeta: v1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - Data: map[string][]byte{ - "key": []byte("value"), - }, - }, - }, - wantYAML: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null ---- -apiVersion: v1 -data: - key: dmFsdWU= -kind: Secret -metadata: - creationTimestamp: null -`, - }, - "Non-pointer is detected": { - resources: "non-pointer", - wantErr: true, - }, - "Nil resource pointer is detected": { - resources: nil, - wantErr: true, - }, - "Non-pointer field is ignored": { - resources: &struct{ String string }{String: "somestring"}, - }, - "nil field is ignored": { - resources: &struct { - ConfigMap *k8s.ConfigMap - }{ - ConfigMap: nil, - }, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - yaml, err := MarshalK8SResources(tc.resources) - - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - - assert.Equal(tc.wantYAML, string(yaml)) - }) - } -} - -func TestUnmarshalK8SResources(t *testing.T) { - testCases := map[string]struct { - data string - into any - wantObj any - wantErr bool - }{ - "ConfigMap as only field can be unmarshaled": { - data: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null -`, - into: &struct { - ConfigMap k8s.ConfigMap - }{}, - wantObj: &struct { - ConfigMap k8s.ConfigMap - }{ - ConfigMap: k8s.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - Data: map[string]string{ - "key": "value", - }, - }, - }, - }, - "Multiple fields are correctly unmarshaled": { - data: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null ---- -apiVersion: v1 -data: - key: dmFsdWU= -kind: Secret -metadata: - creationTimestamp: null -`, - into: &struct { - ConfigMap k8s.ConfigMap - Secret k8s.Secret - }{}, - wantObj: &struct { - ConfigMap k8s.ConfigMap - Secret k8s.Secret - }{ - ConfigMap: k8s.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - Data: map[string]string{ - "key": "value", - }, - }, - Secret: k8s.Secret{ - TypeMeta: v1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - Data: map[string][]byte{ - "key": []byte("value"), - }, - }, - }, - }, - "Mismatching amount of fields is detected": { - data: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null ---- -apiVersion: v1 -data: - key: dmFsdWU= -kind: Secret -metadata: - creationTimestamp: null -`, - into: &struct { - ConfigMap k8s.ConfigMap - }{}, - wantErr: true, - }, - "Non-struct pointer is detected": { - into: proto.String("test"), - wantErr: true, - }, - "Nil into is detected": { - into: nil, - wantErr: true, - }, - "Invalid yaml is detected": { - data: `duplicateKey: value - duplicateKey: value`, - into: &struct { - ConfigMap k8s.ConfigMap - }{}, - wantErr: true, - }, - "Struct field cannot interface with runtime.Object": { - data: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null -`, - into: &struct { - String string - }{}, - wantErr: true, - }, - "Struct field mismatch": { - data: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null -`, - into: &struct { - Secret k8s.Secret - }{}, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - err := UnmarshalK8SResources([]byte(tc.data), tc.into) - - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - - assert.Equal(tc.wantObj, tc.into) - }) - } -} - -func TestMarshalK8SResourcesList(t *testing.T) { - testCases := map[string]struct { - resources []runtime.Object - wantErr bool - wantYAML string - }{ - "ConfigMap as only element be marshaled": { - resources: []runtime.Object{ - &k8s.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - Data: map[string]string{ - "key": "value", - }, - }, - }, - wantYAML: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null -`, - }, - "Multiple fields are correctly encoded": { - resources: []runtime.Object{ - &k8s.ConfigMap{ - TypeMeta: v1.TypeMeta{ - Kind: "ConfigMap", - APIVersion: "v1", - }, - Data: map[string]string{ - "key": "value", - }, - }, - &k8s.Secret{ - TypeMeta: v1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - Data: map[string][]byte{ - "key": []byte("value"), - }, - }, - }, - wantYAML: `apiVersion: v1 -data: - key: value -kind: ConfigMap -metadata: - creationTimestamp: null ---- -apiVersion: v1 -data: - key: dmFsdWU= -kind: Secret -metadata: - creationTimestamp: null -`, - }, - "Nil resource pointer is encodes": { - resources: []runtime.Object{nil}, - wantYAML: "null\n", - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - yaml, err := MarshalK8SResourcesList(tc.resources) - - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - - assert.Equal(tc.wantYAML, string(yaml)) - }) - } -} diff --git a/internal/kubernetes/secrets.go b/internal/kubernetes/secrets.go deleted file mode 100644 index 4c8847c..0000000 --- a/internal/kubernetes/secrets.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubernetes - -import ( - k8s "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" -) - -// Secrets represent a list of k8s Secret. -type Secrets []*k8s.Secret - -// Marshal marshals secrets into multiple YAML documents. -func (s Secrets) Marshal() ([]byte, error) { - objects := make([]runtime.Object, len(s)) - for i := range s { - objects[i] = s[i] - } - return MarshalK8SResourcesList(objects) -} diff --git a/internal/kubernetes/secrets_test.go b/internal/kubernetes/secrets_test.go deleted file mode 100644 index bc91da8..0000000 --- a/internal/kubernetes/secrets_test.go +++ /dev/null @@ -1,55 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package kubernetes - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - k8s "k8s.io/api/core/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestSecrets(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - - secrets := Secrets{ - &k8s.Secret{ - TypeMeta: v1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - Data: map[string][]byte{"key": []byte("value1")}, - }, - &k8s.Secret{ - TypeMeta: v1.TypeMeta{ - Kind: "Secret", - APIVersion: "v1", - }, - Data: map[string][]byte{"key": []byte("value2")}, - }, - } - data, err := secrets.Marshal() - require.NoError(err) - - assert.Equal(`apiVersion: v1 -data: - key: dmFsdWUx -kind: Secret -metadata: - creationTimestamp: null ---- -apiVersion: v1 -data: - key: dmFsdWUy -kind: Secret -metadata: - creationTimestamp: null -`, string(data)) -} diff --git a/internal/license/BUILD.bazel b/internal/license/BUILD.bazel deleted file mode 100644 index f0fec12..0000000 --- a/internal/license/BUILD.bazel +++ /dev/null @@ -1,34 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "license", - srcs = [ - # keep - "checker_enterprise.go", - # keep - "checker_oss.go", - "file.go", - "license.go", - ], - importpath = "cvm-reverse-proxy/internal/license", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/cloud/cloudprovider", - # keep - "//internal/constants", - ], -) - -go_test( - name = "license_test", - srcs = ["file_test.go"], - embed = [":license"], - tags = [ - "enterprise", - ], - deps = [ - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - ], -) diff --git a/internal/license/checker_enterprise.go b/internal/license/checker_enterprise.go deleted file mode 100644 index 15b4127..0000000 --- a/internal/license/checker_enterprise.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build enterprise - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package license - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "net/url" - - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" -) - -const ( - apiHost = "license.confidential.cloud" - licensePath = "api/v1/license" -) - -// Checker checks the Constellation license. -type Checker struct { - httpClient *http.Client -} - -// NewChecker creates a new Checker. -func NewChecker() *Checker { - return &Checker{ - httpClient: http.DefaultClient, - } -} - -// CheckLicense checks the Constellation license. If the license is valid, it returns the vCPU quota. -func (c *Checker) CheckLicense(ctx context.Context, csp cloudprovider.Provider, action Action, licenseID string) (int, error) { - checkRequest := quotaCheckRequest{ - Provider: csp.String(), - License: licenseID, - Action: action, - } - - reqBody, err := json.Marshal(checkRequest) - if err != nil { - return 0, fmt.Errorf("unable to marshal input: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, licenseURL().String(), bytes.NewBuffer(reqBody)) - if err != nil { - return 0, fmt.Errorf("unable to create request: %w", err) - } - resp, err := c.httpClient.Do(req) - if err != nil { - return 0, fmt.Errorf("unable to do request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return 0, fmt.Errorf("http error %d", resp.StatusCode) - } - - responseContentType := resp.Header.Get("Content-Type") - if responseContentType != "application/json" { - return 0, fmt.Errorf("expected server JSON response but got '%s'", responseContentType) - } - - var parsedResponse quotaCheckResponse - err = json.NewDecoder(resp.Body).Decode(&parsedResponse) - if err != nil { - return 0, fmt.Errorf("unable to parse response: %w", err) - } - - return parsedResponse.Quota, nil -} - -// quotaCheckRequest is JSON request to license server to check quota for a given license and action. -type quotaCheckRequest struct { - Action Action `json:"action"` - Provider string `json:"provider"` - License string `json:"license"` -} - -// quotaCheckResponse is JSON response by license server. -type quotaCheckResponse struct { - Quota int `json:"quota"` -} - -func licenseURL() *url.URL { - return &url.URL{ - Scheme: "https", - Host: apiHost, - Path: licensePath, - } -} diff --git a/internal/license/checker_enterprise_test.go b/internal/license/checker_enterprise_test.go deleted file mode 100644 index bd23cd4..0000000 --- a/internal/license/checker_enterprise_test.go +++ /dev/null @@ -1,97 +0,0 @@ -//go:build enterprise - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package license - -import ( - "bytes" - "context" - "io" - "net/http" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" - - "github.com/stretchr/testify/assert" -) - -// roundTripFunc . -type roundTripFunc func(req *http.Request) *http.Response - -// RoundTrip . -func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { - return f(req), nil -} - -// newTestClient returns *http.Client with Transport replaced to avoid making real calls. -func newTestClient(fn roundTripFunc) *http.Client { - return &http.Client{ - Transport: fn, - } -} - -func TestQuotaCheck(t *testing.T) { - testCases := map[string]struct { - license string - serverResponse string - serverResponseCode int - serverResponseContent string - wantQuota int - wantError bool - }{ - "success": { - license: "0c0a6558-f8af-4063-bf61-92e7ac4cb052", - serverResponse: "{\"quota\":256}", - serverResponseCode: http.StatusOK, - serverResponseContent: "application/json", - wantQuota: 256, - }, - "404": { - serverResponseCode: http.StatusNotFound, - wantError: true, - }, - "HTML not JSON": { - serverResponseCode: http.StatusOK, - serverResponseContent: "text/html", - wantError: true, - }, - "promise JSON but actually HTML": { - serverResponseCode: http.StatusOK, - serverResponse: "", - serverResponseContent: "application/json", - wantError: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - client := &Checker{ - httpClient: newTestClient(func(req *http.Request) *http.Response { - r := &http.Response{ - StatusCode: tc.serverResponseCode, - Body: io.NopCloser(bytes.NewBufferString(tc.serverResponse)), - Header: make(http.Header), - } - r.Header.Set("Content-Type", tc.serverResponseContent) - return r - }), - } - - quota, err := client.CheckLicense(context.Background(), cloudprovider.Unknown, Init, tc.license) - - if tc.wantError { - assert.Error(err) - return - } - assert.NoError(err) - assert.Equal(tc.wantQuota, quota) - }) - } -} diff --git a/internal/license/checker_oss.go b/internal/license/checker_oss.go deleted file mode 100644 index 9487edf..0000000 --- a/internal/license/checker_oss.go +++ /dev/null @@ -1,28 +0,0 @@ -//go:build !enterprise - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package license - -import ( - "context" - - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" -) - -// Checker checks the Constellation license. -type Checker struct{} - -// NewChecker creates a new Checker. -func NewChecker() *Checker { - return &Checker{} -} - -// CheckLicense is a no-op for open source version of Constellation. -func (c *Checker) CheckLicense(context.Context, cloudprovider.Provider, Action, string) (int, error) { - return 0, nil -} diff --git a/internal/license/file.go b/internal/license/file.go deleted file mode 100644 index 01f5afd..0000000 --- a/internal/license/file.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package license - -import ( - "encoding/base64" - "fmt" -) - -// FromBytes reads the given license bytes and returns it as a string. -func FromBytes(license []byte) (string, error) { - maxSize := base64.StdEncoding.DecodedLen(len(license)) - decodedLicense := make([]byte, maxSize) - n, err := base64.StdEncoding.Decode(decodedLicense, license) - if err != nil { - return "", fmt.Errorf("unable to base64 decode license file: %w", err) - } - if n != 36 { // length of UUID - return "", fmt.Errorf("license file corrupt: wrong length") - } - decodedLicense = decodedLicense[:n] - return string(decodedLicense), nil -} diff --git a/internal/license/file_test.go b/internal/license/file_test.go deleted file mode 100644 index 84101dd..0000000 --- a/internal/license/file_test.go +++ /dev/null @@ -1,62 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package license - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFromBytes(t *testing.T) { - testCases := map[string]struct { - licenseBytes []byte - wantLicense string - wantErr bool - }{ - "community license": { - licenseBytes: []byte("MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAw"), - wantLicense: CommunityLicense, - }, - "too short": { - licenseBytes: []byte("MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDA="), - wantErr: true, - }, - "too long": { - licenseBytes: []byte("MDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwMA=="), - wantErr: true, - }, - "not base64": { - licenseBytes: []byte("not base64"), - wantErr: true, - }, - "empty": { - licenseBytes: []byte(""), - wantErr: true, - }, - "nil": { - licenseBytes: nil, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - require := require.New(t) - assert := assert.New(t) - - out, err := FromBytes(tc.licenseBytes) - if tc.wantErr { - require.Error(err) - } else { - require.NoError(err) - } - assert.Equal(tc.wantLicense, out) - }) - } -} diff --git a/internal/license/integration/BUILD.bazel b/internal/license/integration/BUILD.bazel deleted file mode 100644 index 0a84478..0000000 --- a/internal/license/integration/BUILD.bazel +++ /dev/null @@ -1,16 +0,0 @@ -load("//bazel/go:go_test.bzl", "go_test") - -go_test( - name = "integration_test", - srcs = ["license_integration_test.go"], - tags = [ - "enterprise", - "integration", - "requires-network", - ], - deps = [ - "//internal/cloud/cloudprovider", - "//internal/license", - "@com_github_stretchr_testify//assert", - ], -) diff --git a/internal/license/integration/license_integration_test.go b/internal/license/integration/license_integration_test.go deleted file mode 100644 index 484386d..0000000 --- a/internal/license/integration/license_integration_test.go +++ /dev/null @@ -1,53 +0,0 @@ -//go:build integration - -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package integration - -import ( - "context" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" - "github.com/flashbots/cvm-reverse-proxy/internal/license" - - "github.com/stretchr/testify/assert" -) - -func TestQuotaCheckIntegration(t *testing.T) { - testCases := map[string]struct { - license string - wantQuota int - wantError bool - }{ - "OSS license has quota 8": { - license: license.CommunityLicense, - wantQuota: 8, - }, - "Empty license assumes community": { - license: "", - wantQuota: 8, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - - client := license.NewChecker() - - quota, err := client.CheckLicense(context.Background(), cloudprovider.Unknown, "test", tc.license) - - if tc.wantError { - assert.Error(err) - return - } - assert.NoError(err) - assert.Equal(tc.wantQuota, quota) - }) - } -} diff --git a/internal/license/license.go b/internal/license/license.go deleted file mode 100644 index 0010bd2..0000000 --- a/internal/license/license.go +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package license provides functions to check a user's Constellation license. -package license - -// Action performed by Constellation. -type Action string - -const ( - // CommunityLicense is used by everyone who has not bought an enterprise license. - CommunityLicense = "00000000-0000-0000-0000-000000000000" - // MarketplaceLicense is used by everyone who uses a marketplace image. - MarketplaceLicense = "11111111-1111-1111-1111-111111111111" - - // Init action denotes the initialization of a Constellation cluster. - Init Action = "init" - // Apply action denotes an update of a Constellation cluster. - // It is used after a cluster has already been initialized once. - Apply Action = "apply" -) diff --git a/internal/mpimage/BUILD.bazel b/internal/mpimage/BUILD.bazel deleted file mode 100644 index 576d926..0000000 --- a/internal/mpimage/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "mpimage", - srcs = [ - "mpimage.go", - "uri.go", - ], - importpath = "cvm-reverse-proxy/internal/mpimage", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/cloud/cloudprovider", - "//internal/constants", - "//internal/semver", - ], -) - -go_test( - name = "mpimage_test", - srcs = ["uri_test.go"], - embed = [":mpimage"], - deps = ["@com_github_stretchr_testify//assert"], -) diff --git a/internal/mpimage/mpimage.go b/internal/mpimage/mpimage.go deleted file mode 100644 index 89b6d1f..0000000 --- a/internal/mpimage/mpimage.go +++ /dev/null @@ -1,8 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// The mpimage package provides utilities for handling CSP marketplace OS images. -package mpimage diff --git a/internal/mpimage/uri.go b/internal/mpimage/uri.go deleted file mode 100644 index 62a17a3..0000000 --- a/internal/mpimage/uri.go +++ /dev/null @@ -1,80 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package mpimage - -import ( - "fmt" - "net/url" - "strings" - - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" - "github.com/flashbots/cvm-reverse-proxy/internal/constants" - "github.com/flashbots/cvm-reverse-proxy/internal/semver" -) - -// MarketplaceImage represents a CSP-agnostic marketplace image. -type MarketplaceImage interface { - URI() string -} - -// NewFromURI returns a new MarketplaceImage for the given image URI. -func NewFromURI(uri string) (MarketplaceImage, error) { - u, err := url.Parse(uri) - if err != nil { - return nil, err - } - - if u.Scheme != constants.MarketplaceImageURIScheme { - return nil, fmt.Errorf("invalid scheme: %s", u.Scheme) - } - - switch u.Host { - case cloudprovider.Azure.String(): - ver, err := semver.New(u.Query().Get(constants.AzureMarketplaceImageVersionKey)) - if err != nil { - return nil, fmt.Errorf("invalid image version: %w", err) - } - return NewAzureMarketplaceImage(ver), nil - default: - return nil, fmt.Errorf("invalid host: %s", u.Host) - } -} - -// AzureMarketplaceImage represents an Azure marketplace image. -type AzureMarketplaceImage struct { - Publisher string - Offer string - SKU string - Version string -} - -// NewAzureMarketplaceImage returns a new Constellation marketplace image for the given version. -func NewAzureMarketplaceImage(version semver.Semver) AzureMarketplaceImage { - return AzureMarketplaceImage{ - Publisher: constants.AzureMarketplaceImagePublisher, - Offer: constants.AzureMarketplaceImageOffer, - SKU: constants.AzureMarketplaceImagePlan, - Version: strings.TrimPrefix(version.String(), "v"), // Azure requires X.Y.Z format - } -} - -// URI returns the URI for the image. -func (i AzureMarketplaceImage) URI() string { - u := &url.URL{ - Scheme: constants.MarketplaceImageURIScheme, - Host: cloudprovider.Azure.String(), - } - - q := u.Query() - q.Set(constants.AzureMarketplaceImagePublisherKey, i.Publisher) - q.Set(constants.AzureMarketplaceImageOfferKey, i.Offer) - q.Set(constants.AzureMarketplaceImageSkuKey, i.SKU) - q.Set(constants.AzureMarketplaceImageVersionKey, i.Version) - u.RawQuery = q.Encode() - - return u.String() -} diff --git a/internal/mpimage/uri_test.go b/internal/mpimage/uri_test.go deleted file mode 100644 index f7dfd3f..0000000 --- a/internal/mpimage/uri_test.go +++ /dev/null @@ -1,83 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package mpimage - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNewFromURI(t *testing.T) { - testCases := map[string]struct { - uri string - want MarketplaceImage - wantErr bool - }{ - "azure valid": { - uri: "constellation-marketplace-image://Azure?offer=constellation&publisher=edgelesssystems&sku=constellation&version=1.2.3", - want: AzureMarketplaceImage{ - Publisher: "edgelesssystems", - Offer: "constellation", - SKU: "constellation", - Version: "1.2.3", - }, - }, - "azure invalid version": { - uri: "constellation-marketplace-image://Azure?offer=constellation&publisher=edgelesssystems&sku=constellation&version=asdf", - wantErr: true, - }, - "invalid scheme": { - uri: "invalid://Azure?offer=constellation&publisher=edgelesssystems&sku=constellation&version=1.2.3", - wantErr: true, - }, - "invalid host": { - uri: "constellation-marketplace-image://invalid?offer=constellation&publisher=edgelesssystems&sku=constellation&version=1.2.3", - wantErr: true, - }, - "no uri": { - uri: "no uri", - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - got, err := NewFromURI(tc.uri) - if tc.wantErr { - assert.Error(err) - } else { - assert.NoError(err) - assert.Equal(tc.want, got) - } - }) - } -} - -func TestAzureURI(t *testing.T) { - testCases := map[string]struct { - image AzureMarketplaceImage - want string - }{ - "valid": { - image: AzureMarketplaceImage{ - Publisher: "foo", - Offer: "bar", - SKU: "baz", - Version: "1.2.3", - }, - want: "constellation-marketplace-image://Azure?offer=bar&publisher=foo&sku=baz&version=1.2.3", - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert.Equal(t, tc.want, tc.image.URI()) - }) - } -} diff --git a/internal/nodestate/BUILD.bazel b/internal/nodestate/BUILD.bazel deleted file mode 100644 index 06e438d..0000000 --- a/internal/nodestate/BUILD.bazel +++ /dev/null @@ -1,27 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "nodestate", - srcs = ["nodestate.go"], - importpath = "cvm-reverse-proxy/internal/nodestate", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/file", - "//internal/role", - ], -) - -go_test( - name = "nodestate_test", - srcs = ["nodestate_test.go"], - embed = [":nodestate"], - deps = [ - "//internal/file", - "//internal/role", - "@com_github_spf13_afero//:afero", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/nodestate/nodestate.go b/internal/nodestate/nodestate.go deleted file mode 100644 index 21120d0..0000000 --- a/internal/nodestate/nodestate.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// Package nodestate is used to persist the state of a Constellation node to disk. -package nodestate - -import ( - "fmt" - - "github.com/flashbots/cvm-reverse-proxy/internal/file" - "github.com/flashbots/cvm-reverse-proxy/internal/role" -) - -const nodeStatePath = "/run/state/constellation/node_state.json" - -// NodeState is the state of a constellation node that is required to recover from a reboot. -// Can be persisted to disk and reloaded later. -type NodeState struct { - Role role.Role - MeasurementSalt []byte -} - -// FromFile reads a NodeState from disk. -func FromFile(fileHandler file.Handler) (*NodeState, error) { - nodeState := &NodeState{} - if err := fileHandler.ReadJSON(nodeStatePath, nodeState); err != nil { - return nil, fmt.Errorf("loading node state: %w", err) - } - return nodeState, nil -} - -// ToFile writes a NodeState to disk. -func (nodeState *NodeState) ToFile(fileHandler file.Handler) error { - return fileHandler.WriteJSON(nodeStatePath, nodeState, file.OptMkdirAll) -} diff --git a/internal/nodestate/nodestate_test.go b/internal/nodestate/nodestate_test.go deleted file mode 100644 index 505361f..0000000 --- a/internal/nodestate/nodestate_test.go +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package nodestate - -import ( - "path/filepath" - "testing" - - "github.com/flashbots/cvm-reverse-proxy/internal/file" - "github.com/flashbots/cvm-reverse-proxy/internal/role" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1")) -} - -func TestFromFile(t *testing.T) { - testCases := map[string]struct { - fileContents string - wantState *NodeState - wantErr bool - }{ - "nodestate exists": { - fileContents: `{ "Role": "ControlPlane", "MeasurementSalt": "U2FsdA==" }`, - wantState: &NodeState{ - Role: role.ControlPlane, - MeasurementSalt: []byte("Salt"), - }, - }, - "nodestate file does not exist": { - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - fs := afero.NewMemMapFs() - if tc.fileContents != "" { - require.NoError(fs.MkdirAll(filepath.Dir(nodeStatePath), 0o755)) - require.NoError(afero.WriteFile(fs, nodeStatePath, []byte(tc.fileContents), 0o644)) - } - fileHandler := file.NewHandler(fs) - state, err := FromFile(fileHandler) - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - assert.Equal(tc.wantState, state) - }) - } -} - -func TestToFile(t *testing.T) { - testCases := map[string]struct { - precreateFile bool - state *NodeState - wantFile string - wantErr bool - }{ - "writing works": { - state: &NodeState{ - Role: role.ControlPlane, - MeasurementSalt: []byte("Salt"), - }, - wantFile: `{ - "Role": "ControlPlane", - "MeasurementSalt": "U2FsdA==" -}`, - }, - "file exists already": { - precreateFile: true, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - fs := afero.NewMemMapFs() - if tc.precreateFile { - require.NoError(fs.MkdirAll(filepath.Dir(nodeStatePath), 0o755)) - require.NoError(afero.WriteFile(fs, nodeStatePath, []byte("pre-existing"), 0o644)) - } - fileHandler := file.NewHandler(fs) - err := tc.state.ToFile(fileHandler) - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - - fileContents, err := afero.ReadFile(fs, nodeStatePath) - require.NoError(err) - assert.Equal(tc.wantFile, string(fileContents)) - }) - } -} diff --git a/internal/osimage/BUILD.bazel b/internal/osimage/BUILD.bazel deleted file mode 100644 index 055bca0..0000000 --- a/internal/osimage/BUILD.bazel +++ /dev/null @@ -1,12 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "osimage", - srcs = ["osimage.go"], - importpath = "cvm-reverse-proxy/internal/osimage", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/api/versionsapi", - "//internal/cloud/cloudprovider", - ], -) diff --git a/internal/osimage/archive/BUILD.bazel b/internal/osimage/archive/BUILD.bazel deleted file mode 100644 index a47f4a5..0000000 --- a/internal/osimage/archive/BUILD.bazel +++ /dev/null @@ -1,16 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "archive", - srcs = ["archive.go"], - importpath = "cvm-reverse-proxy/internal/osimage/archive", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/api/versionsapi", - "//internal/constants", - "//internal/staticupload", - "@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager", - "@com_github_aws_aws_sdk_go_v2_service_s3//:s3", - "@com_github_aws_aws_sdk_go_v2_service_s3//types", - ], -) diff --git a/internal/osimage/archive/archive.go b/internal/osimage/archive/archive.go deleted file mode 100644 index d162c47..0000000 --- a/internal/osimage/archive/archive.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// package archive is used to archive OS images in S3. -package archive - -import ( - "context" - "fmt" - "io" - "log/slog" - "net/url" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" - "github.com/flashbots/cvm-reverse-proxy/internal/constants" - "github.com/flashbots/cvm-reverse-proxy/internal/staticupload" - - s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager" - "github.com/aws/aws-sdk-go-v2/service/s3" - s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" -) - -// Archivist uploads OS images to S3. -type Archivist struct { - uploadClient uploadClient - uploadClientClose func(ctx context.Context) error - // bucket is the name of the S3 bucket to use. - bucket string - - log *slog.Logger -} - -// New creates a new Archivist. -func New(ctx context.Context, region, bucket, distributionID string, log *slog.Logger) (*Archivist, CloseFunc, error) { - staticUploadClient, staticUploadClientClose, err := staticupload.New(ctx, staticupload.Config{ - Region: region, - Bucket: bucket, - DistributionID: distributionID, - CacheInvalidationStrategy: staticupload.CacheInvalidateBatchOnFlush, - CacheInvalidationWaitTimeout: 10 * time.Minute, - }, log) - if err != nil { - return nil, nil, err - } - - archivist := &Archivist{ - uploadClient: staticUploadClient, - uploadClientClose: staticUploadClientClose, - bucket: bucket, - log: log, - } - archivistClose := func(ctx context.Context) error { - return archivist.Close(ctx) - } - - return archivist, archivistClose, nil -} - -// Close closes the uploader. -// It invalidates the CDN cache for all uploaded files. -func (a *Archivist) Close(ctx context.Context) error { - if a.uploadClientClose == nil { - return nil - } - return a.uploadClientClose(ctx) -} - -// Archive reads the OS image in img and uploads it as key. -func (a *Archivist) Archive(ctx context.Context, version versionsapi.Version, csp, attestationVariant string, img io.Reader) (string, error) { - key, err := url.JoinPath(version.ArtifactPath(versionsapi.APIV1), version.Kind().String(), "csp", csp, attestationVariant, "image.raw") - if err != nil { - return "", err - } - a.log.Debug(fmt.Sprintf("Archiving OS image %q to s3://%s/%s", fmt.Sprintf("%s %s %v", csp, attestationVariant, version.ShortPath()), a.bucket, key)) - _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{ - Bucket: &a.bucket, - Key: &key, - Body: img, - ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256, - }) - return constants.CDNRepositoryURL + "/" + key, err -} - -type uploadClient interface { - Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) -} - -// CloseFunc is a function that closes the client. -type CloseFunc func(ctx context.Context) error diff --git a/internal/osimage/imageinfo/BUILD.bazel b/internal/osimage/imageinfo/BUILD.bazel deleted file mode 100644 index 24cf776..0000000 --- a/internal/osimage/imageinfo/BUILD.bazel +++ /dev/null @@ -1,16 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "imageinfo", - srcs = ["imageinfo.go"], - importpath = "cvm-reverse-proxy/internal/osimage/imageinfo", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/api/versionsapi", - "//internal/constants", - "//internal/staticupload", - "@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager", - "@com_github_aws_aws_sdk_go_v2_service_s3//:s3", - "@com_github_aws_aws_sdk_go_v2_service_s3//types", - ], -) diff --git a/internal/osimage/imageinfo/imageinfo.go b/internal/osimage/imageinfo/imageinfo.go deleted file mode 100644 index 3f2d531..0000000 --- a/internal/osimage/imageinfo/imageinfo.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// package imageinfo is used to upload image info JSON files to S3. -package imageinfo - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "log/slog" - "net/url" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" - "github.com/flashbots/cvm-reverse-proxy/internal/constants" - "github.com/flashbots/cvm-reverse-proxy/internal/staticupload" - - s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager" - "github.com/aws/aws-sdk-go-v2/service/s3" - s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" -) - -// Uploader uploads image info to S3. -type Uploader struct { - uploadClient uploadClient - uploadClientClose func(ctx context.Context) error - // bucket is the name of the S3 bucket to use. - bucket string - - log *slog.Logger -} - -// New creates a new Uploader. -func New(ctx context.Context, region, bucket, distributionID string, log *slog.Logger) (*Uploader, CloseFunc, error) { - staticUploadClient, staticUploadClientClose, err := staticupload.New(ctx, staticupload.Config{ - Region: region, - Bucket: bucket, - DistributionID: distributionID, - CacheInvalidationStrategy: staticupload.CacheInvalidateBatchOnFlush, - CacheInvalidationWaitTimeout: 10 * time.Minute, - }, log) - if err != nil { - return nil, nil, err - } - - uploader := &Uploader{ - uploadClient: staticUploadClient, - uploadClientClose: staticUploadClientClose, - bucket: bucket, - log: log, - } - uploaderClose := func(ctx context.Context) error { - return uploader.Close(ctx) - } - return uploader, uploaderClose, nil -} - -// Close closes the uploader. -// It invalidates the CDN cache for all uploaded files. -func (a *Uploader) Close(ctx context.Context) error { - if a.uploadClientClose == nil { - return nil - } - return a.uploadClientClose(ctx) -} - -// Upload marshals the image info to JSON and uploads it to S3. -func (a *Uploader) Upload(ctx context.Context, imageInfo versionsapi.ImageInfo) (string, error) { - ver, err := versionsapi.NewVersion(imageInfo.Ref, imageInfo.Stream, imageInfo.Version, versionsapi.VersionKindImage) - if err != nil { - return "", fmt.Errorf("creating version: %w", err) - } - key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind().String(), "info.json") - if err != nil { - return "", err - } - a.log.Debug(fmt.Sprintf("Archiving image info to s3://%s/%s", a.bucket, key)) - buf := &bytes.Buffer{} - if err := json.NewEncoder(buf).Encode(imageInfo); err != nil { - return "", err - } - _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{ - Bucket: &a.bucket, - Key: &key, - Body: buf, - ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256, - }) - return constants.CDNRepositoryURL + "/" + key, err -} - -type uploadClient interface { - Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) -} - -// CloseFunc is a function that closes the client. -type CloseFunc func(ctx context.Context) error diff --git a/internal/osimage/measurementsuploader/BUILD.bazel b/internal/osimage/measurementsuploader/BUILD.bazel deleted file mode 100644 index 9a94f81..0000000 --- a/internal/osimage/measurementsuploader/BUILD.bazel +++ /dev/null @@ -1,17 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "measurementsuploader", - srcs = ["measurementsuploader.go"], - importpath = "cvm-reverse-proxy/internal/osimage/measurementsuploader", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/api/versionsapi", - "//internal/attestation/measurements", - "//internal/constants", - "//internal/staticupload", - "@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager", - "@com_github_aws_aws_sdk_go_v2_service_s3//:s3", - "@com_github_aws_aws_sdk_go_v2_service_s3//types", - ], -) diff --git a/internal/osimage/measurementsuploader/measurementsuploader.go b/internal/osimage/measurementsuploader/measurementsuploader.go deleted file mode 100644 index d607d7b..0000000 --- a/internal/osimage/measurementsuploader/measurementsuploader.go +++ /dev/null @@ -1,121 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// package measurementsuploader is used to upload measurements (v2) JSON files (and signatures) to S3. -package measurementsuploader - -import ( - "context" - "encoding/json" - "fmt" - "io" - "log/slog" - "net/url" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" - "github.com/flashbots/cvm-reverse-proxy/internal/attestation/measurements" - "github.com/flashbots/cvm-reverse-proxy/internal/constants" - "github.com/flashbots/cvm-reverse-proxy/internal/staticupload" - - s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager" - "github.com/aws/aws-sdk-go-v2/service/s3" - s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" -) - -// Uploader uploads image info to S3. -type Uploader struct { - uploadClient uploadClient - uploadClientClose func(ctx context.Context) error - // bucket is the name of the S3 bucket to use. - bucket string - - log *slog.Logger -} - -// New creates a new Uploader. -func New(ctx context.Context, region, bucket, distributionID string, log *slog.Logger) (*Uploader, CloseFunc, error) { - staticUploadClient, staticUploadClientClose, err := staticupload.New(ctx, staticupload.Config{ - Region: region, - Bucket: bucket, - DistributionID: distributionID, - CacheInvalidationStrategy: staticupload.CacheInvalidateBatchOnFlush, - CacheInvalidationWaitTimeout: 10 * time.Minute, - }, log) - if err != nil { - return nil, nil, err - } - - uploader := &Uploader{ - uploadClient: staticUploadClient, - uploadClientClose: staticUploadClientClose, - bucket: bucket, - log: log, - } - uploaderClose := func(ctx context.Context) error { - return uploader.Close(ctx) - } - return uploader, uploaderClose, nil -} - -// Close closes the uploader. -// It invalidates the CDN cache for all uploaded files. -func (a *Uploader) Close(ctx context.Context) error { - if a.uploadClientClose == nil { - return nil - } - return a.uploadClientClose(ctx) -} - -// Upload uploads the measurements v2 JSON file and its signature to S3. -func (a *Uploader) Upload(ctx context.Context, rawMeasurement, signature io.ReadSeeker) (string, string, error) { - // parse the measurements to get the ref, stream, and version - var measurements measurements.ImageMeasurementsV2 - if err := json.NewDecoder(rawMeasurement).Decode(&measurements); err != nil { - return "", "", err - } - if _, err := rawMeasurement.Seek(0, io.SeekStart); err != nil { - return "", "", err - } - - ver, err := versionsapi.NewVersion(measurements.Ref, measurements.Stream, measurements.Version, versionsapi.VersionKindImage) - if err != nil { - return "", "", fmt.Errorf("creating version: %w", err) - } - key, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind().String(), constants.CDNMeasurementsFile) - if err != nil { - return "", "", err - } - sigKey, err := url.JoinPath(ver.ArtifactPath(versionsapi.APIV2), ver.Kind().String(), constants.CDNMeasurementsSignature) - if err != nil { - return "", "", err - } - a.log.Debug(fmt.Sprintf("Archiving image measurements to s3://%s/%s and s3://%s/%s", a.bucket, key, a.bucket, sigKey)) - if _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{ - Bucket: &a.bucket, - Key: &key, - Body: rawMeasurement, - ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256, - }); err != nil { - return "", "", fmt.Errorf("uploading measurements: %w", err) - } - if _, err = a.uploadClient.Upload(ctx, &s3.PutObjectInput{ - Bucket: &a.bucket, - Key: &sigKey, - Body: signature, - ChecksumAlgorithm: s3types.ChecksumAlgorithmSha256, - }); err != nil { - return "", "", fmt.Errorf("uploading measurements signature: %w", err) - } - return constants.CDNRepositoryURL + "/" + key, constants.CDNRepositoryURL + "/" + sigKey, nil -} - -type uploadClient interface { - Upload(ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader)) (*s3manager.UploadOutput, error) -} - -// CloseFunc is a function that closes the client. -type CloseFunc func(ctx context.Context) error diff --git a/internal/osimage/nop/BUILD.bazel b/internal/osimage/nop/BUILD.bazel deleted file mode 100644 index c7bda86..0000000 --- a/internal/osimage/nop/BUILD.bazel +++ /dev/null @@ -1,12 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "nop", - srcs = ["nop.go"], - importpath = "cvm-reverse-proxy/internal/osimage/nop", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/api/versionsapi", - "//internal/osimage", - ], -) diff --git a/internal/osimage/nop/nop.go b/internal/osimage/nop/nop.go deleted file mode 100644 index f325c96..0000000 --- a/internal/osimage/nop/nop.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// package nop implements a no-op for CSPs that don't require custom image upload functionality. -package nop - -import ( - "context" - "fmt" - "log/slog" - - "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" - "github.com/flashbots/cvm-reverse-proxy/internal/osimage" -) - -// Uploader is a no-op uploader. -type Uploader struct { - log *slog.Logger -} - -// New creates a new Uploader. -func New(log *slog.Logger) *Uploader { - return &Uploader{log: log} -} - -// Upload pretends to upload images to a csp. -func (u *Uploader) Upload(_ context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) { - u.log.Debug(fmt.Sprintf("Skipping image upload of %q since this CSP does not require images to be uploaded in advance.", req.Version.ShortPath())) - return nil, nil -} diff --git a/internal/osimage/osimage.go b/internal/osimage/osimage.go deleted file mode 100644 index 0408f45..0000000 --- a/internal/osimage/osimage.go +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// package osimage is used to handle osimages in the CI (uploading and maintenance). -package osimage - -import ( - "io" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" -) - -// UploadRequest is a request to upload an os image. -type UploadRequest struct { - Provider cloudprovider.Provider - Version versionsapi.Version - AttestationVariant string - Timestamp time.Time - ImageReader func() (io.ReadSeekCloser, error) - ImagePath string -} diff --git a/internal/osimage/secureboot/BUILD.bazel b/internal/osimage/secureboot/BUILD.bazel deleted file mode 100644 index 678c5ce..0000000 --- a/internal/osimage/secureboot/BUILD.bazel +++ /dev/null @@ -1,25 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//bazel/go:go_test.bzl", "go_test") - -go_library( - name = "secureboot", - srcs = [ - "secureboot.go", - "zlibdict.go", - ], - importpath = "cvm-reverse-proxy/internal/osimage/secureboot", - visibility = ["//:__subpackages__"], - deps = ["@com_github_spf13_afero//:afero"], -) - -go_test( - name = "secureboot_test", - srcs = ["secureboot_test.go"], - embed = [":secureboot"], - deps = [ - "@com_github_spf13_afero//:afero", - "@com_github_stretchr_testify//assert", - "@com_github_stretchr_testify//require", - "@org_uber_go_goleak//:goleak", - ], -) diff --git a/internal/osimage/secureboot/secureboot.go b/internal/osimage/secureboot/secureboot.go deleted file mode 100644 index 363355e..0000000 --- a/internal/osimage/secureboot/secureboot.go +++ /dev/null @@ -1,285 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// package secureboot holds secure boot configuration for image uploads. -package secureboot - -import ( - "bytes" - "compress/zlib" - "encoding/base64" - "encoding/binary" - "fmt" - "hash/crc32" - "io" - "os" - - "github.com/spf13/afero" -) - -// Database holds the secure boot database that cloud providers should -// use when enabling secure boot for a Constellation OS image. -type Database struct { - // PK is the platform key. - PK []byte - // Keks are trusted key-exchange-keys - Keks [][]byte - // DBs are entries of the signature database. - DBs [][]byte -} - -// DatabaseFromFiles creates the secure boot database from individual files. -func DatabaseFromFiles(fs afero.Fs, pk string, keks []string, dbs []string) (Database, error) { - rawPK, err := afero.ReadFile(fs, pk) - if err != nil { - return Database{}, fmt.Errorf("loading PK %s: %w", pk, err) - } - rawKEKs := make([][]byte, len(keks)) - for i, kek := range keks { - rawKEK, err := afero.ReadFile(fs, kek) - if err != nil { - return Database{}, fmt.Errorf("loading KEK %s: %w", kek, err) - } - rawKEKs[i] = rawKEK - } - rawDBs := make([][]byte, len(dbs)) - for i, db := range dbs { - rawDB, err := afero.ReadFile(fs, db) - if err != nil { - return Database{}, fmt.Errorf("loading DB %s: %w", db, err) - } - rawDBs[i] = rawDB - } - return Database{ - PK: rawPK, - Keks: rawKEKs, - DBs: rawDBs, - }, nil -} - -// UEFIVarStore is a UEFI variable store. -// It is a collection of UEFIVar structs. -// This is an abstract var store that can convert to a concrete var store -// for a specific CSP. -type UEFIVarStore []UEFIVar - -// VarStoreFromFiles creates the UEFI variable store -// from "EFI Signature List" (esl) files. -func VarStoreFromFiles(fs afero.Fs, pk, kek, db, dbx string) (UEFIVarStore, error) { - vars := UEFIVarStore{} - pkF, err := fs.OpenFile(pk, os.O_RDONLY, os.ModePerm) - if err != nil { - return UEFIVarStore{}, fmt.Errorf("opening PK ESL %s: %w", pk, err) - } - defer pkF.Close() - pkVar, err := ReadVar(pkF, "PK", globalEFIGUID) - if err != nil { - return UEFIVarStore{}, fmt.Errorf("reading PK ESL %s: %w", pk, err) - } - vars = append(vars, pkVar) - kekF, err := fs.OpenFile(kek, os.O_RDONLY, os.ModePerm) - if err != nil { - return UEFIVarStore{}, fmt.Errorf("opening KEK ESL %s: %w", kek, err) - } - defer kekF.Close() - kekVar, err := ReadVar(kekF, "KEK", globalEFIGUID) - if err != nil { - return UEFIVarStore{}, fmt.Errorf("reading KEK ESL %s: %w", kek, err) - } - vars = append(vars, kekVar) - dbF, err := fs.OpenFile(db, os.O_RDONLY, os.ModePerm) - if err != nil { - return UEFIVarStore{}, fmt.Errorf("opening DB ESL %s: %w", db, err) - } - defer dbF.Close() - dbVar, err := ReadVar(dbF, "db", secureDatabaseGUID) - if err != nil { - return UEFIVarStore{}, fmt.Errorf("reading DB ESL %s: %w", db, err) - } - vars = append(vars, dbVar) - if len(dbx) == 0 { - return vars, nil - } - dbxF, err := fs.OpenFile(dbx, os.O_RDONLY, os.ModePerm) - if err != nil { - return UEFIVarStore{}, fmt.Errorf("opening DBX ESL %s: %w", dbx, err) - } - defer dbxF.Close() - dbxVar, err := ReadVar(dbxF, "dbx", secureDatabaseGUID) - if err != nil { - return UEFIVarStore{}, fmt.Errorf("reading DBX ESL %s: %w", dbx, err) - } - vars = append(vars, dbxVar) - return vars, nil -} - -// ToAWS converts the UEFI variable store to the AWS UEFI vars v0 format. -// The format is documented here: -// https://github.com/awslabs/python-uefivars -// It is structured as follows: -// Header: -// - 4 bytes: magic number -// - 4 bytes: crc32 of the rest of the file -// - 4 bytes: version number -// -// Body is zlib compressed stream of: -// 8 bytes number of entries -// for each entry: -// - name (variable length field, utf8) -// - data (variable length field) -// - guid (16 bytes) -// - attr (int32 in little endian) -// OPTIONAL (if attr has EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set): -// - timestamp (16 bytes) -// - digest (variable length field). -func (s UEFIVarStore) ToAWS() (string, error) { - payload := bytes.Buffer{} - // Write the number of entries. - if err := binary.Write(&payload, binary.LittleEndian, uint64(len(s))); err != nil { - return "", fmt.Errorf("writing number of entries: %w", err) - } - // Write the entries. - for _, entry := range s { - rawEntry, err := entry.AWSEntry() - if err != nil { - return "", fmt.Errorf("serializing entry: %w", err) - } - if _, err := payload.Write(rawEntry); err != nil { - return "", fmt.Errorf("writing entry: %w", err) - } - } - // Compress the payload. - compressed := bytes.Buffer{} - zlibW, err := zlib.NewWriterLevelDict(&compressed, zlib.BestCompression, zlibDict) - if err != nil { - return "", fmt.Errorf("creating compressor: %w", err) - } - if _, err := zlibW.Write(payload.Bytes()); err != nil { - return "", fmt.Errorf("compressing payload: %w", err) - } - if err := zlibW.Close(); err != nil { - return "", fmt.Errorf("closing compressor: %w", err) - } - compressedData := compressed.Bytes() - // Calculate the CRC32 (Castagnoli) of the version + compressed payload. - crcData := append(awsVersion, compressedData...) - crc := crc32.Checksum(crcData, crc32.MakeTable(crc32.Castagnoli)) - out := bytes.Buffer{} - // Write the header. - if _, err := out.Write(awsMagic); err != nil { - return "", fmt.Errorf("writing magic: %w", err) - } - if err := binary.Write(&out, binary.LittleEndian, crc); err != nil { - return "", fmt.Errorf("writing crc: %w", err) - } - // Write the version + compressed payload. - if _, err := out.Write(crcData); err != nil { - return "", fmt.Errorf("writing compressed payload: %w", err) - } - return base64.StdEncoding.EncodeToString(out.Bytes()), nil -} - -// UEFIVar is a UEFI variable. -type UEFIVar struct { - Name string - Data []byte - GUID []byte - Attr uint32 - Timestamp []byte - Digest []byte -} - -// ReadVar reads a UEFI variable from an ESL file. -func ReadVar(reader io.Reader, name string, guid []byte) (UEFIVar, error) { - attr := uint32( - EFIVariableNonVolatile | - EFIVariableBootServiceAccess | - EFIVariableRuntimeAccess | - EFIVariableTimeBasedAuthenticatedWriteAccess, - ) - data, err := io.ReadAll(reader) - if err != nil { - return UEFIVar{}, err - } - return UEFIVar{ - Name: name, - Data: data, - GUID: guid, - Attr: attr, - Timestamp: []byte{ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }, - Digest: []byte{ - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - }, - }, nil -} - -// AWSEntry returns the AWS format entry for the UEFI variable. -func (v UEFIVar) AWSEntry() ([]byte, error) { - var buf bytes.Buffer - if err := appendVariableLengthField(&buf, []byte(v.Name)); err != nil { - return nil, err - } - if err := appendVariableLengthField(&buf, v.Data); err != nil { - return nil, err - } - if _, err := buf.Write(v.GUID); err != nil { - return nil, err - } - if err := appendAttr(&buf, v.Attr); err != nil { - return nil, err - } - if v.Attr&EFIVariableTimeBasedAuthenticatedWriteAccess == 0 { - return buf.Bytes(), nil - } - if _, err := buf.Write(v.Timestamp); err != nil { - return nil, err - } - if err := appendVariableLengthField(&buf, v.Digest); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func appendVariableLengthField(w io.Writer, data []byte) error { - // variable length is encoded as unsigned, 64 bit little endian - // followed by the data - if err := binary.Write(w, binary.LittleEndian, uint64(len(data))); err != nil { - return err - } - _, err := w.Write(data) - return err -} - -func appendAttr(w io.Writer, attr uint32) error { - return binary.Write(w, binary.LittleEndian, attr) -} - -// EFI constants. -const ( - EFIVariableNonVolatile = 0x00000001 - EFIVariableBootServiceAccess = 0x00000002 - EFIVariableRuntimeAccess = 0x00000004 - EFIVariableTimeBasedAuthenticatedWriteAccess = 0x00000020 -) - -var ( - awsMagic = []byte("AMZNUEFI") - awsVersion = []byte{0, 0, 0, 0} - globalEFIGUID = []byte{ - 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, - 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, - } - secureDatabaseGUID = []byte{ - 0xcb, 0xb2, 0x19, 0xd7, 0x3a, 0x3d, 0x96, 0x45, - 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f, - } -) diff --git a/internal/osimage/secureboot/secureboot_test.go b/internal/osimage/secureboot/secureboot_test.go deleted file mode 100644 index 6798881..0000000 --- a/internal/osimage/secureboot/secureboot_test.go +++ /dev/null @@ -1,182 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package secureboot - -import ( - "encoding/base64" - "testing" - - "github.com/spf13/afero" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.uber.org/goleak" -) - -func TestMain(m *testing.M) { - goleak.VerifyTestMain(m, goleak.IgnoreAnyFunction("github.com/bazelbuild/rules_go/go/tools/bzltestutil.RegisterTimeoutHandler.func1")) -} - -func TestDatabaseFromFile(t *testing.T) { - testCases := map[string]struct { - pk string - keks []string - dbs []string - wantErr bool - }{ - "found": { - pk: "pki/pk.cer", - keks: []string{"pki/kek.cer"}, - dbs: []string{"pki/db.cer"}, - }, - "pk not found": { - pk: "pki/missing", - keks: []string{"pki/kek.cer"}, - dbs: []string{"pki/db.cer"}, - wantErr: true, - }, - "kek not found": { - pk: "pki/pk.cer", - keks: []string{"pki/missing"}, - dbs: []string{"pki/db.cer"}, - wantErr: true, - }, - "db not found": { - pk: "pki/pk.cer", - keks: []string{"pki/kek.cer"}, - dbs: []string{"pki/missing"}, - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "pki/pk.cer", []byte("pk"), 0o644)) - require.NoError(afero.WriteFile(fs, "pki/kek.cer", []byte("kek"), 0o644)) - require.NoError(afero.WriteFile(fs, "pki/db.cer", []byte("db"), 0o644)) - db, err := DatabaseFromFiles(fs, tc.pk, tc.keks, tc.dbs) - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - assert.Len(db.Keks, 1) - assert.Len(db.DBs, 1) - assert.Equal("pk", string(db.PK)) - assert.Equal("kek", string(db.Keks[0])) - assert.Equal("db", string(db.DBs[0])) - }) - } -} - -func TestVarsStoreFromFiles(t *testing.T) { - testCases := map[string]struct { - pk string - kek string - db string - dbx string - wantErr bool - }{ - "found": { - pk: "pki/pk.esl", - kek: "pki/kek.esl", - db: "pki/db.esl", - dbx: "pki/dbx.esl", - }, - "no dbx": { - pk: "pki/pk.esl", - kek: "pki/kek.esl", - db: "pki/db.esl", - }, - "pk not found": { - pk: "pki/missing", - kek: "pki/kek.esl", - db: "pki/db.esl", - wantErr: true, - }, - "kek not found": { - pk: "pki/pk.esl", - kek: "pki/missing", - db: "pki/db.esl", - wantErr: true, - }, - "db not found": { - pk: "pki/pk.esl", - kek: "pki/kek.esl", - db: "pki/missing", - wantErr: true, - }, - "dbx not found": { - pk: "pki/pk.esl", - kek: "pki/kek.esl", - db: "pki/db.esl", - dbx: "pki/missing", - wantErr: true, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "pki/pk.esl", []byte("pk"), 0o644)) - require.NoError(afero.WriteFile(fs, "pki/kek.esl", []byte("kek"), 0o644)) - require.NoError(afero.WriteFile(fs, "pki/db.esl", []byte("db"), 0o644)) - require.NoError(afero.WriteFile(fs, "pki/dbx.esl", []byte("dbx"), 0o644)) - store, err := VarStoreFromFiles(fs, tc.pk, tc.kek, tc.db, tc.dbx) - if tc.wantErr { - assert.Error(err) - return - } - require.NoError(err) - if tc.dbx == "" { - assert.Len(store, 3) - } else { - assert.Len(store, 4) - assert.Equal("dbx", store[3].Name) - assert.Equal("dbx", string(store[3].Data)) - assert.Equal(secureDatabaseGUID, store[3].GUID) - } - assert.Equal("PK", store[0].Name) - assert.Equal("pk", string(store[0].Data)) - assert.Equal(globalEFIGUID, store[0].GUID) - assert.Equal("KEK", store[1].Name) - assert.Equal("kek", string(store[1].Data)) - assert.Equal(globalEFIGUID, store[1].GUID) - assert.Equal("db", store[2].Name) - assert.Equal("db", string(store[2].Data)) - assert.Equal(secureDatabaseGUID, store[2].GUID) - }) - } -} - -func TestToAWS(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - fs := afero.NewMemMapFs() - require.NoError(afero.WriteFile(fs, "pki/pk.esl", []byte("pk"), 0o644)) - require.NoError(afero.WriteFile(fs, "pki/kek.esl", []byte("kek"), 0o644)) - require.NoError(afero.WriteFile(fs, "pki/db.esl", []byte("db"), 0o644)) - require.NoError(afero.WriteFile(fs, "pki/dbx.esl", []byte("dbx"), 0o644)) - store, err := VarStoreFromFiles(fs, "pki/pk.esl", "pki/kek.esl", "pki/db.esl", "pki/dbx.esl") - require.NoError(err) - awsData, err := store.ToAWS() - require.NoError(err) - out, err := base64.StdEncoding.DecodeString(awsData) - assert.NoError(err) - assert.Equal([]byte{ - 0x41, 0x4d, 0x5a, 0x4e, 0x55, 0x45, 0x46, 0x49, 0x5d, 0x52, 0x8c, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x78, 0xf9, 0x6b, 0xb7, 0xd9, 0xf7, 0x62, 0x81, 0xda, 0xc4, 0x04, 0xa5, 0x03, 0xbc, 0x61, 0xac, - 0x82, 0x6c, 0x74, 0x5b, 0xd5, 0xb1, 0xb8, 0x50, 0x81, 0xc8, 0x25, 0xbe, 0xb0, 0x69, 0x3d, 0x6f, - 0x57, 0x6f, 0x18, 0x33, 0x3b, 0x95, 0xaa, 0x36, 0xc0, 0xdc, 0x9d, 0x92, 0x84, 0x60, 0xa1, 0xb7, - 0xcc, 0xa9, 0xe1, 0x83, 0x94, 0xa4, 0x0a, 0x24, 0x26, 0x35, 0x6d, 0x00, 0x04, 0x00, 0x00, 0xff, - 0xff, 0xd6, 0x50, 0x28, 0x9b, - }, out) -} diff --git a/internal/osimage/secureboot/zlibdict.go b/internal/osimage/secureboot/zlibdict.go deleted file mode 100644 index 34e8136..0000000 --- a/internal/osimage/secureboot/zlibdict.go +++ /dev/null @@ -1,961 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package secureboot - -// zlibDict is the zlib dictionary used for compressing the aws efivars. -// See also https://github.com/awslabs/python-uefivars/blob/9679002a4392d8e7831d2dbda3fab41ccc5c6b8c/pyuefivars/aws_v0.py. -var zlibDict = []byte{ - 0x37, 0xa4, 0x30, 0xec, 0x12, 0x44, 0xfc, 0x0a, 0x1d, 0x10, 0x28, 0x9d, - 0x00, 0x00, 0x87, 0xae, 0xe3, 0x9e, 0xd0, 0x73, 0x2d, 0x2b, 0x4b, 0xca, - 0x1e, 0xac, 0x1c, 0xfb, 0x79, 0xc8, 0x7c, 0x05, 0xfb, 0xf4, 0xf4, 0xf5, - 0xf7, 0x01, 0x43, 0x05, 0x28, 0x00, 0x06, 0x03, 0x84, 0xe2, 0xd1, 0x60, - 0x28, 0x4b, 0x06, 0x77, 0x00, 0x04, 0x80, 0xc0, 0x04, 0x0a, 0x1c, 0x0e, - 0x06, 0x10, 0x08, 0x0a, 0x09, 0x05, 0x07, 0x34, 0x07, 0x04, 0x0a, 0x0f, - 0x03, 0x07, 0x09, 0x0c, 0x07, 0x04, 0x05, 0x06, 0x08, 0x85, 0xc1, 0x60, - 0x61, 0x68, 0x48, 0x20, 0x09, 0x07, 0x03, 0x71, 0xa1, 0x3c, 0xd5, 0x87, - 0x98, 0x01, 0xd8, 0x00, 0x24, 0x51, 0x0f, 0x22, 0x11, 0x4c, 0x51, 0x0c, - 0x02, 0x01, 0x00, 0x00, 0x82, 0x00, 0x21, 0xc8, 0xb8, 0x60, 0x68, 0x68, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, - 0x00, 0x00, 0x02, 0x1f, 0x03, 0x12, 0x0a, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x00, 0xb0, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, - 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, - 0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x02, - 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, - 0x05, 0x00, 0x01, 0x03, 0x0d, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, - 0x87, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, - 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, - 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x02, 0x01, - 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, - 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, 0x05, - 0x00, 0x0f, 0x01, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, - 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, - 0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x02, - 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, - 0x05, 0x00, 0xef, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, - 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, - 0x6f, 0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, - 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, - 0x01, 0x05, 0x00, 0x2b, 0x29, 0x58, 0x9e, 0x68, 0x7c, 0x7d, 0x49, 0xa0, - 0xce, 0x65, 0x00, 0xfd, 0x9f, 0x1b, 0x95, 0x2c, 0xaf, 0x2c, 0x64, 0xfe, - 0xff, 0xff, 0xff, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, - 0x2e, 0x30, 0x2c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x25, 0x4d, 0x69, - 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x57, 0x69, 0x6e, 0x64, - 0x6f, 0x77, 0x73, 0x20, 0x50, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x20, 0x50, 0x43, 0x41, 0x20, 0x32, 0x30, 0x31, 0x31, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x0a, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x61, 0xdf, - 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, - 0x2b, 0x8c, 0x4c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, 0x00, 0x00, - 0x65, 0x6e, 0x67, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, - 0x1a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, - 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, - 0x50, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x74, 0x00, 0x66, 0x00, 0x6f, 0x00, - 0x72, 0x00, 0x6d, 0x00, 0x4c, 0x00, 0x61, 0x00, 0x6e, 0x00, 0x67, 0x00, - 0x00, 0x00, 0x65, 0x6e, 0x00, 0xff, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, - 0x07, 0x00, 0x28, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x16, 0xd6, - 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f, - 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, - 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x65, 0x00, - 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, - 0x65, 0x00, 0x72, 0x00, 0x28, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, - 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, 0xcc, 0xad, - 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00, - 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, - 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, - 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x28, 0x00, 0x00, 0x00, 0x05, 0x00, - 0x00, 0x00, 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, - 0xcc, 0xad, 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, - 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, - 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, - 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x28, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, - 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, - 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, - 0x74, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, - 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, - 0x00, 0x21, 0xe5, 0x7f, 0x93, 0xae, 0x95, 0x1a, 0x4d, 0x89, 0x29, 0x48, - 0xbc, 0xd9, 0x0a, 0xd3, 0x1a, 0x35, 0x00, 0x32, 0x00, 0x35, 0x00, 0x34, - 0x00, 0x30, 0x00, 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, - 0x00, 0x35, 0x00, 0x36, 0x00, 0xe8, 0x7f, 0xb3, 0x04, 0xae, 0xf6, 0x0b, - 0x48, 0xbd, 0xd5, 0x37, 0xd9, 0x8c, 0x5e, 0x89, 0xaa, 0x56, 0x00, 0x61, - 0x00, 0x72, 0x00, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00, 0x6f, 0x00, 0x72, - 0x00, 0x46, 0x00, 0x6c, 0x00, 0x61, 0x00, 0x67, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x88, 0x9c, 0xd0, 0xf7, 0xb6, - 0xc4, 0x7a, 0xd5, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x65, 0x00, 0x0e, 0x00, - 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6e, 0xe5, 0xbe, 0xd9, 0xdc, 0x75, - 0xd9, 0x49, 0xb4, 0xd7, 0xb5, 0x34, 0x21, 0x0f, 0x63, 0x7a, 0x63, 0x00, - 0x65, 0x00, 0x72, 0x00, 0x74, 0x00, 0x64, 0x00, 0x62, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x23, 0x00, - 0x00, 0x04, 0x01, 0x2a, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xbc, - 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x6f, 0xbd, 0x26, 0x17, 0x0a, 0xae, 0xeb, 0x11, 0xaf, 0xcc, 0xb2, - 0x6f, 0x04, 0x02, 0x45, 0xc6, 0x02, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, - 0x00, 0x00, 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, - 0xcc, 0xad, 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, - 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, - 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, - 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x12, 0x34, 0x56, 0xff, - 0xff, 0xaa, 0x55, 0x50, 0x00, 0x58, 0x00, 0x45, 0x00, 0x76, 0x00, 0x43, - 0x00, 0x75, 0x00, 0x72, 0x00, 0x72, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, - 0x00, 0x00, 0x57, 0x00, 0x69, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, - 0x77, 0x00, 0x73, 0x00, 0x11, 0xd4, 0x9a, 0x46, 0x00, 0x90, 0x27, 0x3f, - 0xc1, 0x4d, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, - 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, - 0x8c, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, - 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, - 0x00, 0xd0, 0x41, 0x01, 0x05, 0x00, 0x02, 0xbd, 0x9a, 0xfa, 0x77, 0x59, - 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x06, - 0x0b, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x3d, 0x03, 0x0e, 0x00, - 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, - 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, - 0x6f, 0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, - 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, - 0x01, 0x05, 0x00, 0x11, 0xd4, 0x9a, 0x38, 0x00, 0x90, 0x27, 0x3f, 0xc1, - 0x4d, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xbd, 0x9a, 0xfa, 0x77, 0x59, - 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x00, - 0x50, 0x00, 0x6f, 0x00, 0x6c, 0x00, 0x69, 0x00, 0x63, 0x00, 0x79, 0x00, - 0x2d, 0x88, 0x11, 0xd3, 0x9a, 0x16, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d, - 0x00, 0xaf, 0xaf, 0x04, 0x00, 0x00, 0x00, 0x02, 0x00, 0x6f, 0x22, 0xec, - 0xea, 0xa3, 0xc9, 0x7a, 0x47, 0xa8, 0x26, 0xdd, 0xc7, 0x16, 0xcd, 0xc0, - 0xe3, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x6f, 0x22, 0xec, 0xea, - 0xa3, 0xc9, 0x7a, 0x47, 0xa8, 0x26, 0xdd, 0xc7, 0x16, 0xcd, 0xc0, 0xe3, - 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, - 0x00, 0x05, 0x00, 0x06, 0x00, 0x11, 0xd2, 0x8e, 0x39, 0x00, 0xa0, 0xc9, - 0x69, 0x72, 0x3b, 0xbd, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, - 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x03, 0x18, 0x04, 0x00, - 0x7f, 0xff, 0x04, 0x00, 0x4e, 0xac, 0x08, 0x81, 0x11, 0x9f, 0x59, 0x4d, - 0x85, 0x0e, 0xe2, 0x1a, 0x52, 0x2c, 0x59, 0xb2, 0x00, 0x20, 0x00, 0x51, - 0x00, 0x4d, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x0e, - 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, - 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x45, - 0x00, 0x72, 0x00, 0x72, 0x00, 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, - 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, - 0x41, 0x01, 0x05, 0x00, 0x30, 0x00, 0x00, 0x00, 0xbd, 0x9a, 0xfa, 0x77, - 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, - 0x80, 0xb4, 0xd9, 0x69, 0x31, 0xbf, 0x0d, 0x02, 0xfd, 0x91, 0xa6, 0x1e, - 0x19, 0xd1, 0x4f, 0x1d, 0xa4, 0x52, 0xe6, 0x6d, 0xb2, 0x40, 0x8c, 0xa8, - 0x60, 0x4d, 0x41, 0x1f, 0x92, 0x65, 0x9f, 0x0a, 0xbd, 0x9a, 0xfa, 0x77, - 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0x07, 0x00, 0xaa, 0x55, 0x3d, - 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, - 0x00, 0x58, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, - 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x17, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, - 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, - 0x98, 0x6b, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, - 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, - 0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, - 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, - 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, - 0x00, 0x00, 0x00, 0x7f, 0x2a, 0x30, 0x28, 0x06, 0x03, 0x55, 0x04, 0x03, - 0x13, 0x21, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, - 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, - 0x4b, 0x45, 0x4b, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x31, 0x31, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0xf3, 0x00, 0x00, 0x00, 0x61, - 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, - 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e, - 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, - 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, - 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x4b, 0x00, 0x65, 0x00, - 0x72, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x5f, 0x00, 0x00, 0x37, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x45, 0x00, 0x46, - 0x00, 0x49, 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x65, - 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x20, 0x00, 0x53, - 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x04, - 0x07, 0x14, 0x00, 0xc9, 0xbd, 0xb8, 0x7c, 0xeb, 0xf8, 0x34, 0x4f, 0xaa, - 0xea, 0x3e, 0x0e, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, - 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x4b, 0x00, - 0x65, 0x00, 0x79, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x51, 0xd7, 0x97, 0x9f, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, - 0x00, 0x76, 0x00, 0x34, 0x00, 0x20, 0x00, 0x28, 0x00, 0x4d, 0x00, 0x41, - 0x00, 0x43, 0x00, 0x3a, 0x00, 0x35, 0x00, 0x32, 0x00, 0x35, 0x00, 0x34, - 0x00, 0x30, 0x00, 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, - 0x00, 0x35, 0x00, 0x36, 0x00, 0x29, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, - 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, - 0x00, 0x00, 0x02, 0x03, 0x0b, 0x25, 0x00, 0x52, 0x54, 0x00, 0x12, 0x34, - 0x56, 0x00, 0x00, 0x53, 0x00, 0x69, 0x00, 0x53, 0x00, 0x74, 0x00, 0x61, - 0x00, 0x74, 0x00, 0x75, 0x00, 0x73, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xbd, - 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, - 0x8f, 0x78, 0x4b, 0x4b, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x65, - 0x00, 0x6c, 0x00, 0x5f, 0x00, 0x50, 0x00, 0x4b, 0x00, 0x00, 0x00, 0xa1, - 0x59, 0xc0, 0xa5, 0xe4, 0x94, 0xa7, 0x4a, 0x87, 0xb5, 0xab, 0x15, 0x5c, - 0x2b, 0xf0, 0x72, 0xd0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb4, - 0x03, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, - 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x30, 0x82, 0x03, 0xa0, 0x30, - 0x82, 0x02, 0x88, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00, 0xfe, - 0xf5, 0x88, 0xe8, 0xf3, 0x96, 0xc0, 0xf1, 0x30, 0x0d, 0x06, 0xb9, 0x00, - 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, - 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, - 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, - 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, - 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x7f, 0x7a, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, - 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, - 0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, - 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, - 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, - 0x00, 0x00, 0x00, 0x7f, 0x37, 0x32, 0x31, 0x32, 0x32, 0x34, 0x35, 0x5a, - 0x17, 0x0d, 0x32, 0x36, 0x30, 0x36, 0x32, 0x37, 0x32, 0x31, 0x33, 0x32, - 0x34, 0x35, 0x5a, 0x30, 0x81, 0x81, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, - 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, - 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, - 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, - 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x00, 0x76, 0x00, 0x36, 0x00, - 0x20, 0x00, 0x28, 0x00, 0x4d, 0x00, 0x41, 0x00, 0x43, 0x00, 0x3a, 0x00, - 0x35, 0x00, 0x32, 0x00, 0x35, 0x00, 0x34, 0x00, 0x30, 0x00, 0x30, 0x00, - 0x31, 0x00, 0x32, 0x00, 0x33, 0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00, - 0x29, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x02, 0x03, 0x0b, - 0x25, 0x32, 0x30, 0x30, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x29, 0x4d, - 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x52, 0x6f, 0x6f, - 0x74, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, - 0x32, 0x30, 0x31, 0x30, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x31, 0x30, - 0x31, 0x39, 0x31, 0x38, 0x34, 0x31, 0x34, 0x32, 0x5a, 0x17, 0x0d, 0x32, - 0x36, 0x31, 0x30, 0x31, 0x39, 0x31, 0x38, 0x35, 0x31, 0x34, 0x32, 0x5a, - 0x30, 0x81, 0x84, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x74, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, - 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, 0x00, 0x14, 0xfc, 0x7c, 0x71, 0x51, - 0xa5, 0x79, 0xc2, 0x6e, 0xb2, 0xef, 0x39, 0x3e, 0xbc, 0x3c, 0x52, 0x0f, - 0x6e, 0x2b, 0x3f, 0x10, 0x13, 0x73, 0xfe, 0xa8, 0x68, 0xd0, 0x48, 0xa6, - 0x34, 0x4d, 0x8a, 0x96, 0x05, 0x26, 0xee, 0x31, 0x46, 0x90, 0x61, 0x79, - 0xd6, 0x7f, 0xff, 0x04, 0x00, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, - 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, - 0xe0, 0x98, 0x03, 0x2b, 0xc3, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, - 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, - 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, - 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, - 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x14, 0x00, 0x00, 0x00, 0x04, - 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, - 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, - 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xaa, 0x55, 0x3f, 0x00, 0x07, - 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0xec, 0x76, - 0xc0, 0x28, 0x70, 0x99, 0x43, 0xa0, 0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b, - 0x9f, 0x43, 0x00, 0x75, 0x00, 0x73, 0x00, 0x74, 0x00, 0x6f, 0x00, 0x6d, - 0x00, 0x4d, 0x00, 0x6f, 0x00, 0x64, 0x00, 0x65, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xaa, - 0x55, 0x3f, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, - 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x9f, 0x04, 0x19, 0x4c, 0x37, - 0x41, 0xd3, 0x4d, 0x9c, 0x10, 0x8b, 0x97, 0xa8, 0x3f, 0xfd, 0xfa, 0x4d, - 0x00, 0x65, 0x00, 0x6d, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x79, 0x00, 0x54, - 0x00, 0x79, 0x00, 0x70, 0x00, 0x65, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x66, - 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, - 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x80, - 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x55, 0x3c, 0x00, 0x03, - 0x00, 0xd5, 0xf6, 0x56, 0xcb, 0x8f, 0xe8, 0xa2, 0x5c, 0x62, 0x68, 0xd1, - 0x3d, 0x94, 0x90, 0x5b, 0xd7, 0xce, 0x9a, 0x18, 0xc4, 0x30, 0x56, 0x06, - 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x4f, 0x30, 0x4d, 0x30, 0x4b, 0xa0, 0x49, - 0xa0, 0x47, 0x86, 0x45, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, - 0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, 0x63, 0x72, 0x6c, - 0x2f, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x2f, 0x4d, 0x69, - 0x63, 0x52, 0x6f, 0x6f, 0x43, 0x65, 0x72, 0x41, 0x75, 0x74, 0x5f, 0x32, - 0x30, 0x31, 0x30, 0x2d, 0x30, 0x36, 0x2d, 0x32, 0x33, 0x2e, 0x63, 0x72, - 0x6c, 0x30, 0x5a, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, - 0x01, 0x04, 0x4e, 0x30, 0x4c, 0x30, 0x4a, 0x06, 0x08, 0x2b, 0x06, 0x01, - 0x05, 0x05, 0x07, 0x30, 0x02, 0x86, 0x3e, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, - 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x0c, 0x00, 0x00, 0x00, 0x22, 0x00, - 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, - 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, - 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, - 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, - 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, - 0x7f, 0xff, 0x04, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x49, 0x00, - 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, - 0x00, 0xe0, 0x98, 0x03, 0x03, 0x0f, 0x0b, 0x00, 0xff, 0xff, 0xff, 0xff, - 0x03, 0x01, 0x01, 0x7f, 0x01, 0x04, 0x00, 0x01, 0x04, 0x14, 0x00, 0x9b, - 0x5a, 0x5a, 0x86, 0x5d, 0xb8, 0x4c, 0x47, 0x84, 0x55, 0x65, 0xd1, 0xbe, - 0x84, 0x4b, 0xe2, 0x03, 0x0e, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x0a, - 0x14, 0x00, 0x53, 0x47, 0xc1, 0xe0, 0xbe, 0x8d, 0x2b, 0xf1, 0xff, 0x96, - 0x76, 0x8b, 0x4c, 0xa9, 0x85, 0x27, 0x47, 0x07, 0x5b, 0x4f, 0x50, 0x00, - 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x46, 0x56, 0x48, 0xff, - 0xfe, 0x04, 0x00, 0x48, 0x00, 0x19, 0xf9, 0x00, 0x00, 0x00, 0x02, 0x20, - 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x78, 0x2c, 0xf3, 0xaa, 0x7b, 0x94, 0x9a, 0x43, 0xa1, - 0x80, 0x2e, 0x14, 0x4e, 0xc3, 0x77, 0x92, 0xb8, 0xdf, 0x00, 0x00, 0x5a, - 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xaa, 0x55, 0x3c, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x0c, 0xec, 0x76, 0xc0, 0x28, 0x70, 0x99, 0x43, 0xa0, - 0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f, 0xf1, 0x00, 0x00, 0x00, 0x61, - 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, - 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x4f, 0x00, 0x75, - 0x00, 0x74, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, - 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, - 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x03, - 0x0e, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0xff, - 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x1a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xe4, 0x73, - 0x90, 0xec, 0x60, 0x6e, 0x4b, 0x99, 0x03, 0x4c, 0x22, 0x3c, 0x26, 0x0f, - 0x3c, 0x56, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x64, 0x00, 0x6f, 0x00, 0x72, - 0x00, 0x4b, 0x00, 0x65, 0x00, 0x79, 0x00, 0x73, 0x00, 0x4e, 0x00, 0x76, - 0x00, 0x00, 0x00, 0x00, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, - 0x00, 0xc7, 0x0b, 0xa3, 0xf0, 0x08, 0xaf, 0x56, 0x45, 0x99, 0xc4, 0x00, - 0x10, 0x09, 0xc9, 0x3a, 0x44, 0x53, 0x00, 0x65, 0x00, 0x63, 0x00, 0x75, - 0x00, 0x72, 0x00, 0x65, 0x00, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, - 0x00, 0x45, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x62, 0x00, 0x6c, 0x00, 0x65, - 0x00, 0x00, 0x00, 0x01, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0xd6, - 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f, - 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, - 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x65, 0x00, - 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, - 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0xcb, 0xb2, 0x19, 0xd7, 0x3a, 0x3d, - 0x96, 0x45, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f, 0x64, 0x00, - 0x62, 0x00, 0x78, 0x00, 0x00, 0x00, 0x26, 0x16, 0xc4, 0xc1, 0x4c, 0x50, - 0x92, 0x40, 0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, 0x43, 0x28, 0x4c, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0xa3, 0xa8, - 0xba, 0xa0, 0x1d, 0x04, 0xa8, 0x48, 0xbc, 0x87, 0xc3, 0x6d, 0x12, 0x1b, - 0x5e, 0x3d, 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, - 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, - 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55, 0x26, 0x16, - 0xc4, 0xc1, 0x4c, 0x50, 0x92, 0x40, 0xac, 0xa9, 0x41, 0xf9, 0x36, 0x93, - 0x43, 0x28, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x11, 0x40, - 0x70, 0xeb, 0x02, 0x14, 0xd3, 0x11, 0x8e, 0x77, 0x00, 0xa0, 0xc9, 0x69, - 0x72, 0x3b, 0x4d, 0x00, 0x54, 0x00, 0x43, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0xaa, 0x55, 0x3c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x16, 0xd6, - 0x47, 0x4b, 0xd6, 0xa8, 0x52, 0x45, 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f, - 0x4c, 0xf9, 0x49, 0x00, 0x6e, 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, - 0x61, 0x00, 0x6c, 0x00, 0x41, 0x00, 0x74, 0x00, 0x74, 0x00, 0x65, 0x00, - 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, - 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xaa, 0x55, - 0x3f, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, - 0x00, 0x00, 0x19, 0x04, 0x00, 0x00, 0x45, 0x49, 0x32, 0x59, 0x44, 0xec, - 0x0d, 0x4c, 0xb1, 0xcd, 0x9d, 0xb1, 0x39, 0xdf, 0x07, 0x0c, 0x41, 0x00, - 0x74, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, 0x00, - 0x20, 0x00, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x74, 0x74, 0x65, - 0x6d, 0x70, 0x74, 0x20, 0x31, 0x00, 0xf5, 0x2f, 0x83, 0xa3, 0xfa, 0x9c, - 0xfb, 0xd6, 0x92, 0x0f, 0x72, 0x28, 0x24, 0xdb, 0xe4, 0x03, 0x45, 0x34, - 0xd2, 0x5b, 0x85, 0x07, 0x24, 0x6b, 0x3b, 0x95, 0x7d, 0xac, 0x6e, 0x1b, - 0xce, 0x7a, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, - 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc5, 0xd9, 0xd8, 0xa1, 0x86, 0xe2, - 0xc8, 0x2d, 0x09, 0xaf, 0xaa, 0x2a, 0x6f, 0x7f, 0x2e, 0x73, 0x87, 0x0d, - 0x3e, 0x64, 0xf7, 0x2c, 0x4e, 0x08, 0xef, 0x67, 0x79, 0x6a, 0x84, 0x0f, - 0x0f, 0xbd, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, - 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x1a, 0xec, 0x84, 0xb8, 0x4b, 0x6c, - 0x65, 0xa5, 0x12, 0x20, 0xa9, 0xbe, 0x71, 0x81, 0x96, 0x52, 0x30, 0x21, - 0x0d, 0x62, 0xd6, 0xd3, 0x3c, 0x48, 0x99, 0x9c, 0x6b, 0x29, 0x5a, 0x2b, - 0x0a, 0x06, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, - 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc3, 0xa9, 0x9a, 0x46, 0x0d, 0xa4, - 0x64, 0xa0, 0x57, 0xc3, 0x58, 0x6d, 0x83, 0xce, 0xf5, 0xf4, 0xae, 0x08, - 0xb7, 0x10, 0x39, 0x79, 0xed, 0x89, 0x32, 0x74, 0x2d, 0xf0, 0xed, 0x53, - 0x0c, 0x66, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, - 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x58, 0xfb, 0x94, 0x1a, 0xef, 0x95, - 0xa2, 0x59, 0x43, 0xb3, 0xfb, 0x5f, 0x25, 0x10, 0xa0, 0xdf, 0x3f, 0xe4, - 0x4c, 0x58, 0xc9, 0x5e, 0x0a, 0xb8, 0x04, 0x87, 0x29, 0x75, 0x68, 0xab, - 0x97, 0x71, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, - 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x53, 0x91, 0xc3, 0xa2, 0xfb, 0x11, - 0x21, 0x02, 0xa6, 0xaa, 0x1e, 0xdc, 0x25, 0xae, 0x77, 0xe1, 0x9f, 0x5d, - 0x6f, 0x09, 0xcd, 0x09, 0xee, 0xb2, 0x50, 0x99, 0x22, 0xbf, 0xcd, 0x59, - 0x92, 0xea, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, - 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xd6, 0x26, 0x15, 0x7e, 0x1d, 0x6a, - 0x71, 0x8b, 0xc1, 0x24, 0xab, 0x8d, 0xa2, 0x7c, 0xbb, 0x65, 0x07, 0x2c, - 0xa0, 0x3a, 0x7b, 0x6b, 0x25, 0x7d, 0xbd, 0xcb, 0xbd, 0x60, 0xf6, 0x5e, - 0xf3, 0xd1, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, - 0x28, 0xf4, 0xe7, 0xff, 0x04, 0x00, 0xff, 0xff, 0xff, 0xaa, 0x55, 0x3c, - 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, - 0x00, 0xc6, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, - 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0xff, 0x04, 0x00, 0xaa, 0x55, 0x3c, - 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, - 0x00, 0xb4, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, - 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x28, 0x00, 0x00, - 0x00, 0x08, 0x00, 0x00, 0x00, 0x16, 0xd6, 0x47, 0x4b, 0xd6, 0xa8, 0x52, - 0x45, 0x9d, 0x44, 0xcc, 0xad, 0x2e, 0x0f, 0x4c, 0xf9, 0x49, 0x00, 0x6e, - 0x00, 0x69, 0x00, 0x74, 0x00, 0x69, 0x00, 0x61, 0x00, 0x6c, 0x00, 0x41, - 0x00, 0x74, 0x00, 0x74, 0x00, 0x65, 0x00, 0x6d, 0x00, 0x70, 0x00, 0x74, - 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xaa, 0x55, 0x3f, - 0x00, 0x03, 0x00, 0xdd, 0x0c, 0xbb, 0xa2, 0xe4, 0x2e, 0x09, 0xe3, 0xe7, - 0xc5, 0xf7, 0x96, 0x69, 0xbc, 0x00, 0x21, 0xbd, 0x69, 0x33, 0x33, 0xef, - 0xad, 0x04, 0xcb, 0x54, 0x80, 0xee, 0x06, 0x83, 0xbb, 0xc5, 0x20, 0x84, - 0xd9, 0xf7, 0xd2, 0x8b, 0xf3, 0x38, 0xb0, 0xab, 0xa4, 0xad, 0x2d, 0x7c, - 0x62, 0x79, 0x05, 0xff, 0xe3, 0x4a, 0x3f, 0x04, 0x35, 0x20, 0x70, 0xe3, - 0xc4, 0xe7, 0x6b, 0xe0, 0x9c, 0xc0, 0x36, 0x75, 0xe9, 0x8a, 0x31, 0xdd, - 0x8d, 0x70, 0xe5, 0xdc, 0x37, 0xb5, 0x74, 0x46, 0x96, 0x28, 0x5b, 0x87, - 0x60, 0x23, 0x2c, 0xbf, 0xdc, 0x47, 0xa5, 0x67, 0xf7, 0x51, 0x27, 0x9e, - 0x72, 0xeb, 0x07, 0xa6, 0xc9, 0xb9, 0x1e, 0x3b, 0x53, 0x35, 0x7c, 0xe5, - 0xd3, 0xec, 0x27, 0xb9, 0x87, 0x1c, 0xfe, 0xb9, 0xc9, 0x23, 0x09, 0x6f, - 0xa8, 0x46, 0x91, 0xc1, 0x6e, 0x96, 0x3c, 0x41, 0xd3, 0xcb, 0xa3, 0x3f, - 0x5d, 0x02, 0x6a, 0x4d, 0xec, 0x69, 0x1f, 0x25, 0x28, 0x5c, 0x36, 0xff, - 0xfd, 0x43, 0x15, 0x0a, 0x94, 0xe0, 0x19, 0xb4, 0xcf, 0xdf, 0xc2, 0x12, - 0xe2, 0xc2, 0x5b, 0x27, 0xee, 0x27, 0x78, 0x30, 0x8b, 0x5b, 0x2a, 0x09, - 0x6b, 0x22, 0x89, 0x53, 0x60, 0x16, 0x2c, 0xc0, 0x68, 0x1d, 0x53, 0xba, - 0xec, 0x49, 0xf3, 0x9d, 0x61, 0x8c, 0x85, 0x68, 0x09, 0x73, 0x44, 0x5d, - 0x7d, 0xa2, 0x54, 0x2b, 0xdd, 0x79, 0xf7, 0x15, 0xcf, 0x35, 0x5d, 0x6c, - 0x1c, 0x2b, 0x5c, 0xce, 0xbc, 0x9c, 0x23, 0x8b, 0x6f, 0x6e, 0xb5, 0x26, - 0xd9, 0x36, 0x13, 0xc3, 0x4f, 0xd6, 0x27, 0xae, 0xb9, 0x32, 0x3b, 0x41, - 0x92, 0x2c, 0xe1, 0xc7, 0xcd, 0x77, 0xe8, 0xaa, 0x54, 0x4e, 0xf7, 0x5c, - 0x0b, 0x04, 0x87, 0x65, 0xb4, 0x43, 0x18, 0xa8, 0xb2, 0xe0, 0x6d, 0x19, - 0x77, 0xec, 0x5a, 0x24, 0xfa, 0x48, 0x03, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x43, 0x30, 0x82, 0x01, 0x3f, 0x30, 0x10, 0x06, 0x09, - 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x03, 0x02, - 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0xa9, 0x29, 0x02, 0x39, 0x8e, 0x16, 0xc4, 0x97, 0x78, 0xcd, 0x90, - 0xf9, 0x9e, 0x4f, 0x9a, 0xe1, 0x7c, 0x55, 0xaf, 0x53, 0x30, 0x19, 0x06, - 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c, - 0x1e, 0x0a, 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, - 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x86, 0x30, 0x0f, 0xc4, 0xe8, 0xb5, 0x8a, 0xbf, 0xad, 0x57, 0x26, 0xb0, - 0x26, 0xc3, 0xea, 0xe7, 0xfb, 0x57, 0x7a, 0x44, 0x02, 0x5d, 0x07, 0x0d, - 0xda, 0x4a, 0xe5, 0x74, 0x2a, 0xe6, 0xb0, 0x0f, 0xec, 0x6d, 0xeb, 0xec, - 0x7f, 0xb9, 0xe3, 0x5a, 0x63, 0x32, 0x7c, 0x11, 0x17, 0x4f, 0x0e, 0xe3, - 0x0b, 0xa7, 0x38, 0x15, 0x93, 0x8e, 0xc6, 0xf5, 0xe0, 0x84, 0xb1, 0x9a, - 0x9b, 0x2c, 0xe7, 0xf5, 0xb7, 0x91, 0xd6, 0x09, 0xe1, 0xe2, 0xc0, 0x04, - 0xa8, 0xac, 0x30, 0x1c, 0xdf, 0x48, 0xf3, 0x06, 0x50, 0x9a, 0x64, 0xa7, - 0x51, 0x7f, 0xc8, 0x85, 0x4f, 0x8f, 0x20, 0x86, 0xce, 0xfe, 0x2f, 0xe1, - 0x9f, 0xff, 0x82, 0xc0, 0xed, 0xe9, 0xcd, 0xce, 0xf4, 0x53, 0x6a, 0x62, - 0x3a, 0x0b, 0x43, 0xb9, 0xe2, 0x25, 0xfd, 0xfe, 0x05, 0xf9, 0xd4, 0xc4, - 0x14, 0xab, 0x11, 0xe2, 0x23, 0x89, 0x8d, 0x70, 0xb7, 0xa4, 0x1d, 0x4d, - 0xec, 0xae, 0xe5, 0x9c, 0xfa, 0x16, 0xc2, 0xd7, 0xc1, 0xcb, 0xd4, 0xe8, - 0xc4, 0x2f, 0xe5, 0x99, 0xee, 0x24, 0x8b, 0x03, 0xec, 0x8d, 0xf2, 0x8b, - 0xea, 0xc3, 0x4a, 0xfb, 0x43, 0x11, 0x12, 0x0b, 0x7e, 0xb5, 0x47, 0x92, - 0x6c, 0xdc, 0xe6, 0x04, 0x89, 0xeb, 0xf5, 0x33, 0x04, 0xeb, 0x10, 0x01, - 0x2a, 0x71, 0xe5, 0xf9, 0x83, 0x13, 0x3c, 0xff, 0x25, 0x09, 0x2f, 0x68, - 0x76, 0x46, 0xff, 0xba, 0x4f, 0xbe, 0xdc, 0xad, 0x71, 0x2a, 0x58, 0xaa, - 0xfb, 0x0e, 0xd2, 0x79, 0x3d, 0xe4, 0x9b, 0x65, 0x3b, 0xcc, 0x29, 0x2a, - 0x9f, 0xfc, 0x72, 0x59, 0xa2, 0xeb, 0xae, 0x92, 0xef, 0xf6, 0x35, 0x13, - 0x80, 0xc6, 0x02, 0xec, 0xe4, 0x5f, 0xcc, 0x9d, 0x76, 0xcd, 0xef, 0x63, - 0x92, 0xc1, 0xaf, 0x79, 0x40, 0x84, 0x79, 0x87, 0x7f, 0xe3, 0x52, 0xa8, - 0xe8, 0x9d, 0x7b, 0x07, 0x69, 0x8f, 0x15, 0x02, 0x03, 0x01, 0x00, 0x01, - 0xa3, 0x82, 0x01, 0x4f, 0x30, 0x82, 0x01, 0x4b, 0x30, 0x10, 0x06, 0x09, - 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x01, 0x04, 0x03, 0x02, - 0x01, 0x00, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, - 0x14, 0x62, 0xfc, 0x43, 0xcd, 0xa0, 0x3e, 0xa4, 0xcb, 0x67, 0x12, 0xd2, - 0x5b, 0xd9, 0x55, 0xac, 0x7b, 0xcc, 0xb6, 0x8a, 0x5f, 0x30, 0x19, 0x06, - 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c, - 0x1e, 0x0a, 0x00, 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, - 0x30, 0x0b, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, - 0x86, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, - 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1f, 0x06, 0xff, 0x04, 0x00, - 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x0e, 0x00, 0x00, 0x00, 0x92, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, - 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, - 0x35, 0x08, 0x42, 0xff, 0x30, 0xcc, 0xce, 0xf7, 0x76, 0x0c, 0xad, 0x10, - 0x68, 0x58, 0x35, 0x29, 0x46, 0x32, 0x76, 0x27, 0x7c, 0xef, 0x12, 0x41, - 0x27, 0x42, 0x1b, 0x4a, 0xaa, 0x6d, 0x81, 0x38, 0x48, 0x59, 0x13, 0x55, - 0xf3, 0xe9, 0x58, 0x34, 0xa6, 0x16, 0x0b, 0x82, 0xaa, 0x5d, 0xad, 0x82, - 0xda, 0x80, 0x83, 0x41, 0x06, 0x8f, 0xb4, 0x1d, 0xf2, 0x03, 0xb9, 0xf3, - 0x1a, 0x5d, 0x1b, 0xf1, 0x50, 0x90, 0xf9, 0xb3, 0x55, 0x84, 0x42, 0x28, - 0x1c, 0x20, 0xbd, 0xb2, 0xae, 0x51, 0x14, 0xc5, 0xc0, 0xac, 0x97, 0x95, - 0x21, 0x1c, 0x90, 0xdb, 0x0f, 0xfc, 0x77, 0x9e, 0x95, 0x73, 0x91, 0x88, - 0xca, 0xbd, 0xbd, 0x52, 0xb9, 0x05, 0x50, 0x0d, 0xdf, 0x57, 0x9e, 0xa0, - 0x61, 0xed, 0x0d, 0xe5, 0x6d, 0x25, 0xd9, 0x40, 0x0f, 0x17, 0x40, 0xc8, - 0xce, 0xa3, 0x4a, 0xc2, 0x4d, 0xaf, 0x9a, 0x12, 0x1d, 0x08, 0x54, 0x8f, - 0xbd, 0xc7, 0xbc, 0xb9, 0x2b, 0x3d, 0x49, 0x2b, 0x1f, 0x32, 0xfc, 0x6a, - 0x21, 0x69, 0x4f, 0x9b, 0xc8, 0x7e, 0x42, 0x34, 0xfc, 0x36, 0x06, 0x17, - 0x8b, 0x8f, 0x20, 0x40, 0xc0, 0xb3, 0x9a, 0x25, 0x75, 0x27, 0xcd, 0xc9, - 0x03, 0xa3, 0xf6, 0x5d, 0xd1, 0xe7, 0x36, 0x54, 0x7a, 0xb9, 0x50, 0xb5, - 0xd3, 0x12, 0xd1, 0x07, 0xbf, 0xbb, 0x74, 0xdf, 0xdc, 0x1e, 0x8f, 0x80, - 0xd5, 0xed, 0x18, 0xf4, 0x2f, 0x14, 0x16, 0x6b, 0x2f, 0xde, 0x66, 0x8c, - 0xb0, 0x23, 0xe5, 0xc7, 0x84, 0xd8, 0xed, 0xea, 0xc1, 0x33, 0x82, 0xad, - 0x56, 0x4b, 0x18, 0x2d, 0xf1, 0x68, 0x95, 0x07, 0xcd, 0xcf, 0xf0, 0x72, - 0xf0, 0xae, 0xbb, 0xdd, 0x86, 0x85, 0x98, 0x2c, 0x21, 0x4c, 0x33, 0x2b, - 0xf0, 0x0f, 0x4a, 0xf0, 0x68, 0x87, 0xb5, 0x92, 0x55, 0x32, 0x75, 0xa1, - 0x6a, 0x82, 0x6a, 0x3c, 0xa3, 0x25, 0x11, 0xa4, 0xed, 0xad, 0xd7, 0x04, - 0xae, 0xcb, 0xd8, 0x40, 0x59, 0xa0, 0x84, 0xd1, 0x95, 0x4c, 0x62, 0x91, - 0x22, 0x1a, 0x74, 0x1d, 0x8c, 0x3d, 0x47, 0x0e, 0x44, 0xa6, 0xe4, 0xb0, - 0x9b, 0x34, 0x35, 0xb1, 0xfa, 0xb6, 0x53, 0xa8, 0x2c, 0x81, 0xec, 0xa4, - 0x05, 0x71, 0xc8, 0x9d, 0xb8, 0xba, 0xe8, 0x1b, 0x44, 0x66, 0xe4, 0x47, - 0x54, 0x0e, 0x8e, 0x56, 0x7f, 0xb3, 0x9f, 0x16, 0x98, 0xb2, 0x86, 0xd0, - 0x68, 0x3e, 0x90, 0x23, 0xb5, 0x2f, 0x5e, 0x8f, 0x50, 0x85, 0x8d, 0xc6, - 0x8d, 0x82, 0x5f, 0x41, 0xa1, 0xf4, 0x2e, 0x0d, 0xe0, 0x99, 0xd2, 0x6c, - 0x75, 0xe4, 0xb6, 0x69, 0xb5, 0x21, 0x86, 0xfa, 0x07, 0xd1, 0xf6, 0xe2, - 0x4d, 0xd1, 0xda, 0xad, 0x2c, 0x77, 0x53, 0x1e, 0x25, 0x32, 0x37, 0xc7, - 0x6c, 0x52, 0x72, 0x95, 0x86, 0xb0, 0xf1, 0x35, 0x61, 0x6a, 0x19, 0xf5, - 0xb2, 0x3b, 0x81, 0x50, 0x56, 0xa6, 0x32, 0x2d, 0xfe, 0xa2, 0x89, 0xf9, - 0x42, 0x86, 0x27, 0x18, 0x55, 0xa1, 0x82, 0xca, 0x5a, 0x9b, 0xf8, 0x30, - 0x98, 0x54, 0x14, 0xa6, 0x47, 0x96, 0x25, 0x2f, 0xc8, 0x26, 0xe4, 0x41, - 0x94, 0x1a, 0x5c, 0x02, 0x3f, 0xe5, 0x96, 0xe3, 0x85, 0x5b, 0x3c, 0x3e, - 0x3f, 0xbb, 0x47, 0x16, 0x72, 0x55, 0xe2, 0x25, 0x22, 0xb1, 0xd9, 0x7b, - 0xe7, 0x03, 0x06, 0x2a, 0xa3, 0xf7, 0x1e, 0x90, 0x46, 0xc3, 0x00, 0x0d, - 0xd6, 0x19, 0x89, 0xe3, 0x0e, 0x35, 0x27, 0x62, 0x03, 0x71, 0x15, 0xa6, - 0xef, 0xd0, 0x27, 0xa0, 0xa0, 0x59, 0x37, 0x60, 0xf8, 0x38, 0x94, 0xb8, - 0xe0, 0x78, 0x70, 0xf8, 0xba, 0x4c, 0x86, 0x87, 0x94, 0xf6, 0xe0, 0xae, - 0x02, 0x45, 0xee, 0x65, 0xc2, 0xb6, 0xa3, 0x7e, 0x69, 0x16, 0x75, 0x07, - 0x92, 0x9b, 0xf5, 0xa6, 0xbc, 0x59, 0x83, 0x58, 0xff, 0xff, 0xff, 0xaa, - 0x55, 0x3c, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xe4, 0x07, 0x09, 0x10, 0x10, 0x36, 0x05, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x00, 0x02, 0x02, 0x09, 0x00, 0xfe, 0xf5, 0x88, 0xe8, 0xf3, 0x96, 0xc0, - 0xf1, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x0b, 0x05, 0x00, 0x30, 0x51, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, - 0x55, 0x04, 0x03, 0x13, 0x22, 0x52, 0x65, 0x64, 0x20, 0x48, 0x61, 0x74, - 0x20, 0x53, 0x65, 0x63, 0x75, 0x72, 0x65, 0x20, 0x42, 0x6f, 0x6f, 0x74, - 0x20, 0x28, 0x50, 0x4b, 0x2f, 0x4b, 0x45, 0x4b, 0x20, 0x6b, 0x65, 0x79, - 0x20, 0x31, 0x29, 0x31, 0x22, 0x30, 0x20, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x13, 0x73, 0x65, 0x63, 0x61, - 0x6c, 0x65, 0x72, 0x74, 0x40, 0x72, 0x65, 0x64, 0x68, 0x61, 0x74, 0x2e, - 0x63, 0x6f, 0x6d, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x34, 0x31, 0x30, 0x33, - 0x31, 0x31, 0x31, 0x31, 0x35, 0x33, 0x37, 0x5a, 0x17, 0x0d, 0x33, 0x37, - 0x31, 0x30, 0x32, 0x35, 0x31, 0x31, 0x31, 0x35, 0x33, 0x37, 0x5a, 0x30, - 0x51, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22, - 0x52, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x61, - 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, - 0x03, 0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, - 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00, - 0x00, 0x2c, 0x00, 0x55, 0x00, 0x69, 0x00, 0x41, 0x00, 0x70, 0x00, 0x70, - 0x00, 0x00, 0x00, 0x04, 0x07, 0x14, 0xd4, 0x84, 0x88, 0xf5, 0x14, 0x94, - 0x18, 0x02, 0xca, 0x2a, 0x3c, 0xfb, 0x2a, 0x92, 0x1c, 0x0c, 0xd7, 0xa0, - 0xd1, 0xf1, 0xe8, 0x52, 0x66, 0xa8, 0xee, 0xa2, 0xb5, 0x75, 0x7a, 0x90, - 0x00, 0xaa, 0x2d, 0xa4, 0x76, 0x5a, 0xea, 0x79, 0xb7, 0xb9, 0x37, 0x6a, - 0x51, 0x7b, 0x10, 0x64, 0xf6, 0xe1, 0x64, 0xf2, 0x02, 0x67, 0xbe, 0xf7, - 0xa8, 0x1b, 0x78, 0xbd, 0xba, 0xce, 0x88, 0x58, 0x64, 0x0c, 0xd6, 0x57, - 0xc8, 0x19, 0xa3, 0x5f, 0x05, 0xd6, 0xdb, 0xc6, 0xd0, 0x69, 0xce, 0x48, - 0x4b, 0x32, 0xb7, 0xeb, 0x5d, 0xd2, 0x30, 0xf5, 0xc0, 0xf5, 0xb8, 0xba, - 0x78, 0x07, 0xa3, 0x2b, 0xfe, 0x9b, 0xdb, 0x34, 0x56, 0x84, 0xec, 0x82, - 0xca, 0xae, 0x41, 0x25, 0x70, 0x9c, 0x6b, 0xe9, 0xfe, 0x90, 0x0f, 0xd7, - 0x96, 0x1f, 0xe5, 0xe7, 0x94, 0x1f, 0xb2, 0x2a, 0x0c, 0x8d, 0x4b, 0xff, - 0x28, 0x29, 0x10, 0x7b, 0xf7, 0xd7, 0x7c, 0xa5, 0xd1, 0x76, 0xb9, 0x05, - 0xc8, 0x79, 0xed, 0x0f, 0x90, 0x92, 0x9c, 0xc2, 0xfe, 0xdf, 0x6f, 0x7e, - 0x6c, 0x0f, 0x7b, 0xd4, 0xc1, 0x45, 0xdd, 0x34, 0x51, 0x96, 0x39, 0x0f, - 0xe5, 0x5e, 0x56, 0xd8, 0x18, 0x05, 0x96, 0xf4, 0x07, 0xa6, 0x42, 0xb3, - 0xa0, 0x77, 0xfd, 0x08, 0x19, 0xf2, 0x71, 0x56, 0xcc, 0x9f, 0x86, 0x23, - 0xa4, 0x87, 0xcb, 0xa6, 0xfd, 0x58, 0x7e, 0xd4, 0x69, 0x67, 0x15, 0x91, - 0x7e, 0x81, 0xf2, 0x7f, 0x13, 0xe5, 0x0d, 0x8b, 0x8a, 0x3c, 0x87, 0x84, - 0xeb, 0xe3, 0xce, 0xbd, 0x43, 0xe5, 0xad, 0x2d, 0x84, 0x93, 0x8e, 0x6a, - 0x2b, 0x5a, 0x7c, 0x44, 0xfa, 0x52, 0xaa, 0x81, 0xc8, 0x2d, 0x1c, 0xbb, - 0xe0, 0x52, 0xdf, 0x00, 0x11, 0xf8, 0x9a, 0x3d, 0xc1, 0x60, 0xb0, 0xe1, - 0x33, 0xb5, 0xa3, 0x88, 0xd1, 0x65, 0x19, 0x0a, 0x1a, 0xe7, 0xac, 0x7c, - 0xa4, 0xc1, 0x82, 0x87, 0x4e, 0x38, 0xb1, 0x2f, 0x0d, 0xc5, 0x14, 0x87, - 0x6f, 0xfd, 0x8d, 0x2e, 0xbc, 0x39, 0xb6, 0xe7, 0xe6, 0xc3, 0xe0, 0xe4, - 0xcd, 0x27, 0x84, 0xef, 0x94, 0x42, 0xef, 0x29, 0x8b, 0x90, 0x46, 0x41, - 0x3b, 0x81, 0x1b, 0x67, 0xd8, 0xf9, 0x43, 0x59, 0x65, 0xcb, 0x0d, 0xbc, - 0xfd, 0x00, 0x92, 0x4f, 0xf4, 0x75, 0x3b, 0xa7, 0xa9, 0x24, 0xfc, 0x50, - 0x41, 0x40, 0x79, 0xe0, 0x2d, 0x4f, 0x0a, 0x6a, 0x27, 0x76, 0x6e, 0x52, - 0xed, 0x96, 0x69, 0x7b, 0xaf, 0x0f, 0xf7, 0x87, 0x05, 0xd0, 0x45, 0xc2, - 0xad, 0x53, 0x14, 0x81, 0x1f, 0xfb, 0x30, 0x04, 0xaa, 0x37, 0x36, 0x61, - 0xda, 0x4a, 0x69, 0x1b, 0x34, 0xd8, 0x68, 0xed, 0xd6, 0x02, 0xcf, 0x6c, - 0x94, 0x0c, 0xd3, 0xcf, 0x6c, 0x22, 0x79, 0xad, 0xb1, 0xf0, 0xbc, 0x03, - 0xa2, 0x46, 0x60, 0xa9, 0xc4, 0x07, 0xc2, 0x21, 0x82, 0xf1, 0xfd, 0xf2, - 0xe8, 0x79, 0x32, 0x60, 0xbf, 0xd8, 0xac, 0xa5, 0x22, 0x14, 0x4b, 0xca, - 0xc1, 0xd8, 0x4b, 0xeb, 0x7d, 0x3f, 0x57, 0x35, 0xb2, 0xe6, 0x4f, 0x75, - 0xb4, 0xb0, 0x60, 0x03, 0x22, 0x53, 0xae, 0x91, 0x79, 0x1d, 0xd6, 0x9b, - 0x41, 0x1f, 0x15, 0x86, 0x54, 0x70, 0xb2, 0xde, 0x0d, 0x35, 0x0f, 0x7c, - 0xb0, 0x34, 0x72, 0xba, 0x97, 0x60, 0x3b, 0xf0, 0x79, 0xeb, 0xa2, 0xb2, - 0x1c, 0x5d, 0xa2, 0x16, 0xb8, 0x87, 0xc5, 0xe9, 0x1b, 0xf6, 0xb5, 0x97, - 0x25, 0x6f, 0x38, 0x9f, 0xe3, 0x91, 0xfa, 0x8a, 0x79, 0x98, 0xc3, 0x69, - 0x0e, 0xb7, 0xa3, 0x1c, 0x20, 0x05, 0x97, 0xf8, 0xca, 0x14, 0xae, 0x00, - 0xd7, 0xc4, 0xf3, 0xc0, 0x14, 0x10, 0x75, 0x6b, 0x34, 0xa0, 0x1b, 0xb5, - 0x99, 0x60, 0xf3, 0x5c, 0xb0, 0xc5, 0x57, 0x4e, 0x36, 0xd2, 0x32, 0x84, - 0xbf, 0x9e, 0xaa, 0x55, 0x3f, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe4, 0x07, 0x09, 0x10, 0x10, 0x36, - 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xd0, 0x03, 0x00, 0x00, 0x61, 0xdf, - 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xd0, 0x63, 0xec, - 0x28, 0xf6, 0x7e, 0xba, 0x53, 0xf1, 0x64, 0x2d, 0xbf, 0x7d, 0xff, 0x33, - 0xc6, 0xa3, 0x2a, 0xdd, 0x86, 0x9f, 0x60, 0x13, 0xfe, 0x16, 0x2e, 0x2c, - 0x32, 0xf1, 0xcb, 0xe5, 0x6d, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x29, 0xc6, 0xeb, - 0x52, 0xb4, 0x3c, 0x3a, 0xa1, 0x8b, 0x2c, 0xd8, 0xed, 0x6e, 0xa8, 0x60, - 0x7c, 0xef, 0x3c, 0xfa, 0xe1, 0xba, 0xfe, 0x11, 0x65, 0x75, 0x5c, 0xf2, - 0xe6, 0x14, 0x84, 0x4a, 0x44, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x90, 0xfb, 0xe7, - 0x0e, 0x69, 0xd6, 0x33, 0x40, 0x8d, 0x3e, 0x17, 0x0c, 0x68, 0x32, 0xdb, - 0xb2, 0xd2, 0x09, 0xe0, 0x27, 0x25, 0x27, 0xdf, 0xb6, 0x3d, 0x49, 0xd2, - 0x95, 0x72, 0xa6, 0xf4, 0x4c, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x10, 0x6f, 0xac, - 0xea, 0xcf, 0xec, 0xfd, 0x4e, 0x30, 0x3b, 0x74, 0xf4, 0x80, 0xa0, 0x80, - 0x98, 0xe2, 0xd0, 0x80, 0x2b, 0x93, 0x6f, 0x8e, 0xc7, 0x74, 0xce, 0x21, - 0xf3, 0x16, 0x86, 0x68, 0x9c, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x17, 0x4e, 0x3a, - 0x0b, 0x5b, 0x43, 0xc6, 0xa6, 0x07, 0xbb, 0xd3, 0x40, 0x4f, 0x05, 0x34, - 0x1e, 0x3d, 0xcf, 0x39, 0x62, 0x67, 0xce, 0x94, 0xf8, 0xb5, 0x0e, 0x2e, - 0x23, 0xa9, 0xda, 0x92, 0x0c, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x2b, 0x99, 0xcf, - 0x26, 0x42, 0x2e, 0x92, 0xfe, 0x36, 0x5f, 0xbf, 0x4b, 0xc3, 0x0d, 0x27, - 0x08, 0x6c, 0x9e, 0xe1, 0x4b, 0x7a, 0x6f, 0xff, 0x44, 0xfb, 0x2f, 0x6b, - 0x90, 0x01, 0x69, 0x99, 0x39, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x2e, 0x70, 0x91, - 0x67, 0x86, 0xa6, 0xf7, 0x73, 0x51, 0x1f, 0xa7, 0x18, 0x1f, 0xab, 0x0f, - 0x1d, 0x70, 0xb5, 0x57, 0xc6, 0x32, 0x2e, 0xa9, 0x23, 0xb2, 0xa8, 0xd3, - 0xb9, 0x2b, 0x51, 0xaf, 0x7d, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x3f, 0xce, 0x9b, - 0x9f, 0xdf, 0x3e, 0xf0, 0x9d, 0x54, 0x52, 0xb0, 0xf9, 0x5e, 0xe4, 0x81, - 0xc2, 0xb7, 0xf0, 0x6d, 0x74, 0x3a, 0x73, 0x79, 0x71, 0x55, 0x8e, 0x70, - 0x13, 0x6a, 0xce, 0x3e, 0x73, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x47, 0xcc, 0x08, - 0x61, 0x27, 0xe2, 0x06, 0x9a, 0x86, 0xe0, 0x3a, 0x6b, 0xef, 0x2c, 0xd4, - 0x10, 0xf8, 0xc5, 0x5a, 0x6d, 0x6b, 0xdb, 0x36, 0x21, 0x68, 0xc3, 0x1b, - 0x2c, 0xe3, 0x2a, 0x5a, 0xdf, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x71, 0xf2, 0x90, - 0x6f, 0xd2, 0x22, 0x49, 0x7e, 0x54, 0xa3, 0x46, 0x62, 0xab, 0x24, 0x97, - 0xfc, 0xc8, 0x10, 0x20, 0x77, 0x0f, 0xf5, 0x13, 0x68, 0xe9, 0xe3, 0xd9, - 0xbf, 0xcb, 0xfd, 0x63, 0x75, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x82, 0xdb, 0x3b, - 0xce, 0xb4, 0xf6, 0x08, 0x43, 0xce, 0x9d, 0x97, 0xc3, 0xd1, 0x87, 0xcd, - 0x9b, 0x59, 0x41, 0xcd, 0x3d, 0xe8, 0x10, 0x0e, 0x58, 0x6f, 0x2b, 0xda, - 0x56, 0x37, 0x57, 0x5f, 0x67, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x8a, 0xd6, 0x48, - 0x59, 0xf1, 0x95, 0xb5, 0xf5, 0x8d, 0xaf, 0xaa, 0x94, 0x0b, 0x6a, 0x61, - 0x67, 0xac, 0xd6, 0x7a, 0x88, 0x6e, 0x8f, 0x46, 0x93, 0x64, 0x17, 0x72, - 0x21, 0xc5, 0x59, 0x45, 0xb9, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x8d, 0x8e, 0xa2, - 0x89, 0xcf, 0xe7, 0x0a, 0x1c, 0x07, 0xab, 0x73, 0x65, 0xcb, 0x28, 0xee, - 0x51, 0xed, 0xd3, 0x3c, 0xf2, 0x50, 0x6d, 0xe8, 0x88, 0xfb, 0xad, 0xd6, - 0x0e, 0xbf, 0x80, 0x48, 0x1c, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xae, 0xeb, 0xae, - 0x31, 0x51, 0x27, 0x12, 0x73, 0xed, 0x95, 0xaa, 0x2e, 0x67, 0x11, 0x39, - 0xed, 0x31, 0xa9, 0x85, 0x67, 0x30, 0x3a, 0x33, 0x22, 0x98, 0xf8, 0x37, - 0x09, 0xa9, 0xd5, 0x5a, 0xa1, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc4, 0x09, 0xbd, - 0xac, 0x47, 0x75, 0xad, 0xd8, 0xdb, 0x92, 0xaa, 0x22, 0xb5, 0xb7, 0x18, - 0xfb, 0x8c, 0x94, 0xa1, 0x46, 0x2c, 0x1f, 0xe9, 0xa4, 0x16, 0xb9, 0x5d, - 0x8a, 0x33, 0x88, 0xc2, 0xfc, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc6, 0x17, 0xc1, - 0xa8, 0xb1, 0xee, 0x2a, 0x81, 0x1c, 0x28, 0xb5, 0xa8, 0x1b, 0x4c, 0x83, - 0xd7, 0xc9, 0x8b, 0x5b, 0x0c, 0x27, 0x28, 0x1d, 0x61, 0x02, 0x07, 0xeb, - 0xe6, 0x92, 0xc2, 0x96, 0x7f, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0xc9, 0x0f, 0x33, - 0x66, 0x17, 0xb8, 0xe7, 0xf9, 0x83, 0x97, 0x54, 0x13, 0xc9, 0x97, 0xf1, - 0x0b, 0x73, 0xeb, 0x26, 0x7f, 0xd8, 0xa1, 0x0c, 0xb9, 0xe3, 0xbd, 0xbf, - 0xc6, 0x67, 0xab, 0xdb, 0x8b, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x64, 0x57, 0x5b, - 0xd9, 0x12, 0x78, 0x9a, 0x2e, 0x14, 0xad, 0x56, 0xf6, 0x34, 0x1f, 0x52, - 0xaf, 0x6b, 0xf8, 0x0c, 0xf9, 0x44, 0x00, 0x78, 0x59, 0x75, 0xe9, 0xf0, - 0x4e, 0x2d, 0x64, 0xd7, 0x45, 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, - 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x45, 0xc7, 0xc8, - 0xae, 0x75, 0x0a, 0xcf, 0xbb, 0x48, 0xfc, 0x37, 0x52, 0x7d, 0x64, 0x12, - 0xdd, 0x64, 0x4d, 0xae, 0xd8, 0x91, 0x3c, 0xcd, 0x8a, 0x24, 0xc9, 0x4d, - 0x85, 0x69, 0x67, 0xdf, 0x8e, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, - 0x02, 0x01, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, - 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x43, 0x00, 0x6f, 0x00, - 0x6e, 0x00, 0x49, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, - 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, - 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x7f, 0x01, 0x04, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, - 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, - 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x6c, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, - 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, - 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, 0x00, 0x30, - 0x00, 0x30, 0x00, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, - 0x00, 0x55, 0x00, 0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x20, 0x00, 0x51, - 0x00, 0x45, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x20, 0x00, 0x44, 0x00, 0x56, - 0x00, 0x44, 0x00, 0x2d, 0x00, 0x52, 0x00, 0x4f, 0x00, 0x4d, 0x00, 0x20, - 0x00, 0x51, 0x00, 0x4d, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, 0x31, - 0x00, 0x31, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, - 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x02, - 0x1f, 0x03, 0x12, 0x0a, 0x00, 0x05, 0x00, 0xff, 0xff, 0x00, 0x00, 0x7f, - 0xff, 0x04, 0x00, 0x4e, 0xac, 0x08, 0x81, 0x11, 0x9f, 0x59, 0x4d, 0x85, - 0x0e, 0xe2, 0x1a, 0x52, 0x2c, 0x59, 0xb2, 0xff, 0xff, 0xaa, 0x55, 0x3c, - 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, - 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, - 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x00, 0x6f, - 0x00, 0x6f, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, - 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0xff, - 0xff, 0xaa, 0x55, 0x3d, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x12, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, - 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, - 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, 0x00, 0x30, - 0x00, 0x30, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, - 0x00, 0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x20, 0x00, 0x49, 0x00, 0x6e, - 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x6c, - 0x00, 0x20, 0x00, 0x53, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x6c, - 0x00, 0x00, 0x00, 0x04, 0x07, 0x14, 0x00, 0xc9, 0xbd, 0xb8, 0x7c, 0xeb, - 0xf8, 0x34, 0x4f, 0xaa, 0xea, 0x3e, 0xe4, 0xaf, 0x65, 0x16, 0xa1, 0x04, - 0x06, 0x14, 0x00, 0x83, 0xa5, 0x04, 0x7c, 0x3e, 0x9e, 0x1c, 0x4f, 0xad, - 0x65, 0xe0, 0x52, 0x68, 0xd0, 0xb4, 0xd1, 0x7f, 0xff, 0x04, 0x00, 0xff, - 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x01, 0xff, 0xaa, 0x55, 0x3f, - 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xe4, 0x07, 0x09, 0x10, 0x10, 0x36, 0x05, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, - 0x00, 0x47, 0x0c, 0x00, 0x00, 0xcb, 0xb2, 0x19, 0xd7, 0x3a, 0x3d, 0x96, - 0x45, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f, 0x64, 0x00, 0x62, - 0x00, 0x00, 0x00, 0xa1, 0x59, 0xc0, 0xa5, 0xe4, 0x94, 0xa7, 0x4a, 0x87, - 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72, 0x07, 0x06, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xeb, 0x05, 0x00, 0x00, 0xbd, 0x9a, 0xfa, 0x77, 0x59, - 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, 0xe7, 0x8f, 0x78, 0x4b, 0x30, - 0x82, 0x05, 0xd7, 0x30, 0x82, 0x03, 0xbf, 0xa0, 0x03, 0x02, 0x01, 0x02, - 0x02, 0x0a, 0x61, 0x07, 0x76, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x30, 0x81, 0x88, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, - 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, - 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, - 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, - 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, 0x6f, 0x6e, 0x64, 0x31, 0x1e, - 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x6c, 0x00, 0x00, 0x00, 0x61, 0xdf, - 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, - 0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, 0x00, - 0x30, 0x00, 0x30, 0x00, 0x31, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x20, 0x00, 0x55, 0x00, 0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x20, 0x00, - 0x51, 0x00, 0x45, 0x00, 0x4d, 0x00, 0x55, 0x00, 0x20, 0x00, 0x44, 0x00, - 0x56, 0x00, 0x44, 0x00, 0x2d, 0x00, 0x52, 0x00, 0x4f, 0x00, 0x4d, 0x00, - 0x20, 0x00, 0x51, 0x00, 0x4d, 0x00, 0x30, 0x00, 0x30, 0x00, 0x30, 0x00, - 0x31, 0x00, 0x31, 0x00, 0x20, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, - 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, - 0x02, 0x1f, 0x03, 0x12, 0x0a, 0x00, 0x05, 0x00, 0xff, 0xff, 0x00, 0x00, - 0x7f, 0xff, 0x04, 0x00, 0x4e, 0xac, 0x08, 0x81, 0x11, 0x9f, 0x59, 0x4d, - 0x85, 0x0e, 0xe2, 0x1a, 0x52, 0x2c, 0x59, 0xb2, 0xff, 0xff, 0xaa, 0x55, - 0x3c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, - 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, - 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x00, - 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, - 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, - 0xff, 0xff, 0xaa, 0x55, 0x3d, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x61, 0xdf, - 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, - 0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, 0x30, 0x00, - 0x30, 0x00, 0x30, 0x00, 0x32, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x2c, 0x00, 0x45, 0x00, 0x46, 0x00, 0x49, 0x00, 0x20, 0x00, 0x49, 0x00, - 0x6e, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x6e, 0x00, 0x61, 0x00, - 0x6c, 0x00, 0x20, 0x00, 0x53, 0x00, 0x68, 0x00, 0x65, 0x00, 0x6c, 0x00, - 0x6c, 0x00, 0x00, 0x00, 0x04, 0x07, 0x14, 0x00, 0xc9, 0xbd, 0xb8, 0x7c, - 0xeb, 0xf8, 0x34, 0x4f, 0xaa, 0xea, 0x3e, 0xe4, 0xaf, 0x65, 0x16, 0xa1, - 0x04, 0x06, 0x14, 0x00, 0x83, 0xa5, 0x04, 0x7c, 0x3e, 0x9e, 0x1c, 0x4f, - 0xad, 0x65, 0xe0, 0x52, 0x68, 0xd0, 0xb4, 0xd1, 0x7f, 0xff, 0x04, 0x00, - 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x30, 0x81, 0x91, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, - 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, - 0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x13, 0x15, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, - 0x43, 0x6f, 0x72, 0x70, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x31, - 0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x4d, 0x69, - 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x20, 0x43, 0x6f, 0x72, 0x70, - 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x54, 0x68, 0x69, 0x72, - 0x64, 0x20, 0x50, 0x61, 0x72, 0x74, 0x79, 0x20, 0x4d, 0x61, 0x72, 0x6b, - 0x65, 0x74, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x20, 0x52, 0x6f, 0x6f, 0x74, - 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x31, 0x30, 0x36, 0x32, 0x34, 0x32, 0x30, - 0x34, 0x31, 0x32, 0x39, 0x5a, 0x17, 0x0d, 0x32, 0x36, 0x30, 0x36, 0x32, - 0x34, 0x32, 0x30, 0x35, 0x31, 0x32, 0x39, 0x5a, 0x30, 0x81, 0x80, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x13, 0x0a, 0x57, - 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, - 0x0e, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x07, 0x52, 0x65, 0x64, 0x6d, - 0x6f, 0x6e, 0x64, 0x31, 0x1e, 0x30, 0x1c, 0x06, 0x03, 0x55, 0x04, 0x0a, - 0x01, 0x04, 0x14, 0x00, 0x9b, 0x5a, 0x5a, 0x86, 0x5d, 0xb8, 0x4c, 0x47, - 0x84, 0x55, 0x65, 0xd1, 0xbe, 0x84, 0x4b, 0xe2, 0x03, 0x0e, 0x13, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, 0x0a, 0x14, 0x00, 0x53, 0x47, 0xc1, 0xe0, 0xbe, - 0xf9, 0xd2, 0x11, 0x9a, 0x0c, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d, 0x7f, - 0xff, 0x04, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x20, - 0x55, 0x45, 0x46, 0x49, 0x20, 0x43, 0x41, 0x20, 0x32, 0x30, 0x31, 0x31, - 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa5, 0x08, 0x6c, - 0x4c, 0xc7, 0x45, 0x09, 0x6a, 0x4b, 0x0c, 0xa4, 0xc0, 0x87, 0x7f, 0x06, - 0x75, 0x0c, 0x43, 0x01, 0x54, 0x64, 0xe0, 0x16, 0x7f, 0x07, 0xed, 0x92, - 0x7d, 0x0b, 0xb2, 0x73, 0xbf, 0x0c, 0x0a, 0xc6, 0x4a, 0x45, 0x61, 0xa0, - 0xc5, 0x16, 0x2d, 0x96, 0xd3, 0xf5, 0x2b, 0xa0, 0xfb, 0x4d, 0x49, 0x9b, - 0x41, 0x80, 0x90, 0x3c, 0xb9, 0x54, 0xfd, 0xe6, 0xbc, 0xd1, 0x9d, 0xc4, - 0xa4, 0x18, 0x8a, 0x7f, 0x41, 0x8a, 0x5c, 0x59, 0x83, 0x68, 0x32, 0xbb, - 0x8c, 0x47, 0xc9, 0xee, 0x71, 0xbc, 0x21, 0x4f, 0x9a, 0x8a, 0x7c, 0xff, - 0x44, 0x3f, 0x8d, 0x8f, 0x32, 0xb2, 0x26, 0x48, 0xae, 0x75, 0xb5, 0xee, - 0xc9, 0x4c, 0x1e, 0x4a, 0x19, 0x7e, 0xe4, 0x82, 0x9a, 0x1d, 0x78, 0x77, - 0x4d, 0x0c, 0xb0, 0xbd, 0xf6, 0x0f, 0xd3, 0x16, 0xd3, 0xbc, 0xfa, 0x2b, - 0xa5, 0x51, 0x38, 0x5d, 0xf5, 0xfb, 0xba, 0xdb, 0x78, 0x02, 0xdb, 0xff, - 0xec, 0x0a, 0x1b, 0x96, 0xd5, 0x83, 0xb8, 0x19, 0x13, 0xe9, 0xb6, 0xc0, - 0x7b, 0x40, 0x7b, 0xe1, 0x1f, 0x28, 0x27, 0xc9, 0xfa, 0xef, 0x56, 0x5e, - 0x1c, 0xe6, 0x7e, 0x94, 0x7e, 0xc0, 0xf0, 0x44, 0xb2, 0x79, 0x39, 0xe5, - 0xda, 0xb2, 0x62, 0x8b, 0x4d, 0xbf, 0x38, 0x70, 0xe2, 0x68, 0x24, 0x14, - 0xc9, 0x33, 0xa4, 0x08, 0x37, 0xd5, 0x58, 0x69, 0x5e, 0xd3, 0x7c, 0xed, - 0xc1, 0x04, 0x53, 0x08, 0xe7, 0x4e, 0xb0, 0x2a, 0x87, 0x63, 0x08, 0x61, - 0x6f, 0x63, 0x15, 0x59, 0xea, 0xb2, 0x2b, 0x79, 0xd7, 0x0c, 0x61, 0x67, - 0x8a, 0x5b, 0xfd, 0x5e, 0xad, 0x87, 0x7f, 0xba, 0x86, 0x67, 0x4f, 0x71, - 0x58, 0x12, 0x22, 0x04, 0x22, 0x22, 0xce, 0x8b, 0xef, 0x54, 0x71, 0x00, - 0xce, 0x50, 0x35, 0x58, 0x76, 0x95, 0x08, 0xee, 0x6a, 0xb1, 0xa2, 0x01, - 0xd5, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x76, 0x30, 0x82, - 0x01, 0x72, 0x30, 0x12, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, - 0x37, 0x15, 0x01, 0x04, 0x05, 0x02, 0x03, 0x01, 0x00, 0x01, 0x30, 0x23, - 0x06, 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x15, 0x02, 0x04, - 0x16, 0x04, 0x14, 0xf8, 0xc1, 0x6b, 0xb7, 0x7f, 0x77, 0x53, 0x4a, 0xf3, - 0x25, 0x37, 0x1d, 0x4e, 0xa1, 0x26, 0x7b, 0x0f, 0x20, 0x70, 0x80, 0x30, - 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x13, 0xad, - 0xbf, 0x43, 0x09, 0xbd, 0x82, 0x70, 0x9c, 0x8c, 0xd5, 0x4f, 0x31, 0x6e, - 0xd5, 0x22, 0x98, 0x8a, 0x1b, 0xd4, 0x30, 0x19, 0x06, 0x09, 0x2b, 0x06, - 0x01, 0x04, 0x01, 0x82, 0x37, 0x14, 0x02, 0x04, 0x0c, 0x1e, 0x0a, 0x00, - 0x53, 0x00, 0x75, 0x00, 0x62, 0x00, 0x43, 0x00, 0x41, 0x30, 0x0b, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x0f, - 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, - 0x01, 0x01, 0xff, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, - 0x30, 0x16, 0x80, 0x14, 0x45, 0x66, 0x52, 0x43, 0xe1, 0x7e, 0x58, 0x11, - 0xbf, 0xd6, 0x4e, 0x9e, 0x23, 0x55, 0x08, 0x3b, 0x3a, 0x22, 0x6a, 0xa8, - 0x30, 0x5c, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x55, 0x30, 0x53, 0x30, - 0x51, 0xa0, 0x4f, 0xa0, 0x4d, 0x86, 0x4b, 0x68, 0x74, 0x74, 0x70, 0x3a, - 0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, - 0x6f, 0x66, 0x74, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, - 0x63, 0x72, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, - 0x2f, 0x4d, 0x69, 0x63, 0x43, 0x6f, 0x72, 0x54, 0x68, 0x69, 0x50, 0x61, - 0x72, 0x4d, 0x61, 0x72, 0x52, 0x6f, 0x6f, 0x5f, 0x32, 0x30, 0x31, 0x30, - 0x2d, 0x31, 0x30, 0x2d, 0x30, 0x35, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x60, - 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x54, - 0x30, 0x52, 0x30, 0x50, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, - 0x30, 0x02, 0x86, 0x44, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, - 0x77, 0x77, 0x2e, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x6b, 0x69, 0x2f, 0x63, 0x65, 0x72, - 0x74, 0x73, 0x2f, 0x4d, 0x69, 0x63, 0x43, 0x6f, 0x72, 0x54, 0x68, 0x69, - 0x50, 0x61, 0x72, 0x4d, 0x61, 0x72, 0x52, 0x6f, 0x6f, 0x5f, 0x32, 0x30, - 0x31, 0x30, 0x2d, 0x31, 0x30, 0x2d, 0x30, 0x35, 0x2e, 0x63, 0x72, 0x74, - 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, - 0x0b, 0x05, 0x00, 0x03, 0x82, 0x02, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x92, 0x00, - 0x00, 0x00, 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, - 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00, - 0x4f, 0x00, 0x75, 0x00, 0x74, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, - 0xd0, 0x41, 0x03, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, - 0x00, 0x1f, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, 0x00, 0x00, 0x00, - 0x61, 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, - 0x98, 0x03, 0x2b, 0x8c, 0x42, 0x00, 0x6f, 0x00, 0x6f, 0x00, 0x74, 0x00, - 0x4f, 0x00, 0x72, 0x00, 0x64, 0x00, 0x65, 0x00, 0x72, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, - 0x06, 0x00, 0xff, 0xff, 0xaa, 0x55, 0x3f, 0x00, 0x07, 0x00, 0x82, 0x01, - 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, - 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0x90, 0x1f, 0x84, 0x7b, 0x8d, 0xbc, - 0xeb, 0x97, 0x26, 0x82, 0x6d, 0x88, 0xab, 0x8a, 0xc9, 0x8c, 0x68, 0x70, - 0xf9, 0xdf, 0x4b, 0x07, 0xb2, 0x37, 0x83, 0x0b, 0x02, 0xc8, 0x67, 0x68, - 0x30, 0x9e, 0xe3, 0xf0, 0xf0, 0x99, 0x4a, 0xb8, 0x59, 0x57, 0xc6, 0x41, - 0xf6, 0x38, 0x8b, 0xfe, 0x66, 0x4c, 0x49, 0xe9, 0x37, 0x37, 0x92, 0x2e, - 0x98, 0x01, 0x1e, 0x5b, 0x14, 0x50, 0xe6, 0xa8, 0x8d, 0x25, 0x0d, 0xf5, - 0x86, 0xe6, 0xab, 0x30, 0xcb, 0x40, 0x16, 0xea, 0x8d, 0x8b, 0x16, 0x86, - 0x70, 0x43, 0x37, 0xf2, 0xce, 0xc0, 0x91, 0xdf, 0x71, 0x14, 0x8e, 0x99, - 0x0e, 0x89, 0xb6, 0x4c, 0x6d, 0x24, 0x1e, 0x8c, 0xe4, 0x2f, 0x4f, 0x25, - 0xd0, 0xba, 0x06, 0xf8, 0xc6, 0xe8, 0x19, 0x18, 0x76, 0x73, 0x1d, 0x81, - 0x6d, 0xa8, 0xd8, 0x05, 0xcf, 0x3a, 0xc8, 0x7b, 0x28, 0xc8, 0x36, 0xa3, - 0x16, 0x0d, 0x29, 0x8c, 0x99, 0x9a, 0x68, 0xdc, 0xab, 0xc0, 0x4d, 0x8d, - 0xbf, 0x5a, 0xbb, 0x2b, 0xa9, 0x39, 0x4b, 0x04, 0x97, 0x1c, 0xf9, 0x36, - 0xbb, 0xc5, 0x3a, 0x86, 0x04, 0xae, 0xaf, 0xd4, 0x82, 0x7b, 0xe0, 0xab, - 0xde, 0x49, 0x05, 0x68, 0xfc, 0xf6, 0xae, 0x68, 0x1a, 0x6c, 0x90, 0x4d, - 0x57, 0x19, 0x3c, 0x64, 0x66, 0x03, 0xf6, 0xc7, 0x52, 0x9b, 0xf7, 0x94, - 0xcf, 0x93, 0x6a, 0xa1, 0x68, 0xc9, 0xaa, 0xcf, 0x99, 0x6b, 0xbc, 0xaa, - 0x5e, 0x08, 0xe7, 0x39, 0x1c, 0xf7, 0xf8, 0x0f, 0xba, 0x06, 0x7e, 0xf1, - 0xcb, 0xe8, 0x76, 0xdd, 0xfe, 0x22, 0xda, 0xad, 0x3a, 0x5e, 0x5b, 0x34, - 0xea, 0xb3, 0xc9, 0xe0, 0x4d, 0x04, 0x29, 0x7e, 0xb8, 0x60, 0xb9, 0x05, - 0xef, 0xb5, 0xd9, 0x17, 0x58, 0x56, 0x16, 0x60, 0xb9, 0x30, 0x32, 0xf0, - 0x36, 0x4a, 0xc3, 0xf2, 0x79, 0x8d, 0x12, 0x40, 0x70, 0xf3, 0x02, 0x03, - 0x01, 0x00, 0x01, 0xa3, 0x7b, 0x30, 0x79, 0x30, 0x09, 0x06, 0x03, 0x55, - 0x1d, 0x13, 0x04, 0x02, 0x30, 0x00, 0x30, 0x2c, 0x06, 0x09, 0x60, 0x86, - 0x48, 0x01, 0x86, 0xf8, 0x42, 0x01, 0x0d, 0x04, 0x1f, 0x16, 0x1d, 0x4f, - 0x70, 0x65, 0x6e, 0x53, 0x53, 0x4c, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x64, 0x20, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, - 0x16, 0x04, 0x14, 0x3c, 0xe9, 0x60, 0xe3, 0xff, 0x19, 0xa1, 0x0a, 0x7b, - 0xa3, 0x42, 0xf4, 0x8d, 0x42, 0x2e, 0xb4, 0xd5, 0x9c, 0x72, 0xec, 0x30, - 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, - 0x3c, 0xe9, 0x60, 0xe3, 0xff, 0x19, 0xa1, 0x0a, 0x7b, 0xa3, 0x42, 0xf4, - 0x8d, 0x42, 0x2e, 0xb4, 0xd5, 0x9c, 0x72, 0xec, 0x30, 0x0d, 0x06, 0x09, - 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, - 0x82, 0x01, 0x01, 0x00, 0x5c, 0x4d, 0x92, 0x88, 0xb4, 0x82, 0x5f, 0x1d, - 0xad, 0x8b, 0x11, 0xec, 0xdf, 0x06, 0xa6, 0x7a, 0xa5, 0x2b, 0x9f, 0x37, - 0x55, 0x0c, 0x8d, 0x6e, 0x05, 0x00, 0xad, 0xb7, 0x0c, 0x41, 0x89, 0x69, - 0xcf, 0xd6, 0x65, 0x06, 0x9b, 0x51, 0x78, 0xd2, 0xad, 0xc7, 0xbf, 0x9c, - 0xdc, 0x05, 0x73, 0x7f, 0xe7, 0x1e, 0x39, 0x13, 0xb4, 0xea, 0xb6, 0x30, - 0x7d, 0x40, 0x75, 0xab, 0x9c, 0x43, 0x0b, 0xdf, 0xb0, 0xc2, 0x1b, 0xbf, - 0x30, 0xe0, 0xf4, 0xfe, 0xc0, 0xdb, 0x62, 0x21, 0x98, 0xf6, 0xc5, 0xaf, - 0xde, 0x3b, 0x4f, 0x49, 0x0a, 0xe6, 0x1e, 0xf9, 0x86, 0xb0, 0x3f, 0x0d, - 0xd6, 0xd4, 0x46, 0x37, 0xdb, 0x54, 0x74, 0x5e, 0xff, 0x11, 0xc2, 0x60, - 0xc6, 0x70, 0x58, 0xc5, 0x1c, 0x6f, 0xec, 0xb2, 0xd8, 0x6e, 0x6f, 0xc3, - 0xbc, 0x33, 0x87, 0x38, 0xa4, 0xf3, 0x44, 0x64, 0x9c, 0x34, 0x3b, 0x28, - 0x94, 0x26, 0x78, 0x27, 0x9f, 0x16, 0x17, 0xe8, 0x3b, 0x69, 0x0a, 0x25, - 0xa9, 0x73, 0x36, 0x7e, 0x9e, 0x37, 0x5c, 0xec, 0xe8, 0x3f, 0xdb, 0x91, - 0xf9, 0x12, 0xb3, 0x3d, 0xce, 0xe7, 0xdd, 0x15, 0xc3, 0xae, 0x8c, 0x05, - 0x20, 0x61, 0x9b, 0x95, 0xde, 0x9b, 0xaf, 0xfa, 0xb1, 0x5c, 0x1c, 0xe5, - 0x97, 0xe7, 0xc3, 0x34, 0x11, 0x85, 0xf5, 0x8a, 0x27, 0x26, 0xa4, 0x70, - 0x36, 0xec, 0x0c, 0xf6, 0x83, 0x3d, 0x90, 0xf7, 0x36, 0xf3, 0xf9, 0xf3, - 0x15, 0xd4, 0x90, 0x62, 0xbe, 0x53, 0xb4, 0xaf, 0xd3, 0x49, 0xaf, 0xef, - 0xf4, 0x73, 0xe8, 0x7b, 0x76, 0xe4, 0x44, 0x2a, 0x37, 0xba, 0x81, 0xa4, - 0x99, 0x0c, 0x3a, 0x31, 0x24, 0x71, 0xa0, 0xe4, 0xe4, 0xb7, 0x1a, 0xcb, - 0x47, 0xe4, 0xaa, 0x22, 0xcf, 0xef, 0x75, 0x61, 0x80, 0xe3, 0x43, 0xb7, - 0x48, 0x57, 0x73, 0x11, 0x3d, 0x78, 0x9b, 0x69, 0xa1, 0x59, 0xc0, 0xa5, - 0xe4, 0x94, 0xa7, 0x4a, 0x87, 0xb5, 0xab, 0x15, 0x5c, 0x2b, 0xf0, 0x72, - 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x05, 0x00, 0x00, - 0xbd, 0x9a, 0xfa, 0x77, 0x59, 0x03, 0x32, 0x4d, 0xbd, 0x60, 0x28, 0xf4, - 0xe7, 0x8f, 0x78, 0x4b, 0x30, 0x82, 0x05, 0xe8, 0x30, 0x82, 0x03, 0xd0, - 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x0a, 0x61, 0x0a, 0xd1, 0x88, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x08, 0x01, 0x01, 0x03, 0x0a, 0x14, 0x00, 0x53, 0x47, 0xc1, 0xe0, 0xbe, - 0xf9, 0xd2, 0x11, 0x9a, 0x0c, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d, 0x7f, - 0x01, 0x04, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, 0x0a, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, 0x01, 0x0c, - 0x00, 0xd0, 0x41, 0x01, 0x05, 0x01, 0x00, 0x00, 0x00, 0x03, 0x0e, 0x13, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x08, 0x01, 0x01, 0x03, 0x0a, 0x14, 0x00, 0x53, 0x47, 0xc1, 0xe0, - 0xbe, 0xf9, 0xff, 0xaa, 0x55, 0x3c, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd1, 0x00, 0x00, 0x00, 0x61, - 0xdf, 0xe4, 0x8b, 0xca, 0x93, 0xd2, 0x11, 0xaa, 0x0d, 0x00, 0xe0, 0x98, - 0x03, 0x2b, 0x8c, 0x45, 0x00, 0x72, 0x00, 0x72, 0x00, 0x4f, 0x00, 0x75, - 0x00, 0x74, 0x00, 0x00, 0x00, 0x02, 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x03, - 0x0a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x06, 0x00, 0x00, 0x1f, 0x02, - 0x01, 0x0c, 0x00, 0xd0, 0x41, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x03, - 0x0e, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc2, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x08, 0x01, 0x01, 0x03, 0x0a, 0x14, 0x00, 0x53, 0x47, - 0xc1, 0xe0, 0xbe, 0xf9, 0xd2, -} diff --git a/internal/osimage/uplosi/BUILD.bazel b/internal/osimage/uplosi/BUILD.bazel deleted file mode 100644 index b97e23d..0000000 --- a/internal/osimage/uplosi/BUILD.bazel +++ /dev/null @@ -1,15 +0,0 @@ -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "uplosi", - srcs = ["uplosiupload.go"], - embedsrcs = ["uplosi.conf.in"], - importpath = "cvm-reverse-proxy/internal/osimage/uplosi", - visibility = ["//:__subpackages__"], - deps = [ - "//internal/api/versionsapi", - "//internal/cloud/cloudprovider", - "//internal/osimage", - "@com_github_burntsushi_toml//:toml", - ], -) diff --git a/internal/osimage/uplosi/uplosi.conf.in b/internal/osimage/uplosi/uplosi.conf.in deleted file mode 100644 index 74ec454..0000000 --- a/internal/osimage/uplosi/uplosi.conf.in +++ /dev/null @@ -1,26 +0,0 @@ -[base] -name = "constellation" - -[base.aws] -region = "eu-central-1" -replicationRegions = ["eu-west-1", "eu-west-3", "us-east-1", "us-east-2", "ap-south-1"] -bucket = "constellation-images" -publish = true - -[base.azure] -subscriptionID = "0d202bbb-4fa7-4af8-8125-58c269a05435" -location = "northeurope" -resourceGroup = "constellation-images" -sharingNamePrefix = "constellation" -sku = "constellation" -publisher = "edgelesssys" - -[base.gcp] -project = "constellation-images" -location = "europe-west3" -bucket = "constellation-os-images" - -[base.openstack] -cloud = "stackit" -visibility = "private" -properties = { "hw_firmware_type" = "uefi", "os_type" = "linux" } diff --git a/internal/osimage/uplosi/uplosiupload.go b/internal/osimage/uplosi/uplosiupload.go deleted file mode 100644 index 012459a..0000000 --- a/internal/osimage/uplosi/uplosiupload.go +++ /dev/null @@ -1,287 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -// package uplosi implements uploading os images using uplosi. -package uplosi - -import ( - "bytes" - "context" - _ "embed" - "errors" - "fmt" - "log/slog" - "os" - "os/exec" - "path/filepath" - "regexp" - "strings" - "time" - - "github.com/flashbots/cvm-reverse-proxy/internal/api/versionsapi" - "github.com/flashbots/cvm-reverse-proxy/internal/cloud/cloudprovider" - "github.com/flashbots/cvm-reverse-proxy/internal/osimage" - - "github.com/BurntSushi/toml" -) - -//go:embed uplosi.conf.in -var uplosiConfigTemplate string - -const timestampFormat = "20060102150405" - -// Uploader can upload os images using uplosi. -type Uploader struct { - uplosiPath string - - log *slog.Logger -} - -// New creates a new Uploader. -func New(uplosiPath string, log *slog.Logger) *Uploader { - return &Uploader{ - uplosiPath: uplosiPath, - log: log, - } -} - -// Upload uploads the given os image using uplosi. -func (u *Uploader) Upload(ctx context.Context, req *osimage.UploadRequest) ([]versionsapi.ImageInfoEntry, error) { - config, err := prepareUplosiConfig(req) - if err != nil { - return nil, err - } - - workspace, err := prepareWorkspace(config) - if err != nil { - return nil, err - } - defer os.RemoveAll(workspace) - - uplosiOutput, err := runUplosi(ctx, u.uplosiPath, workspace, req.ImagePath) - if err != nil { - return nil, err - } - - return parseUplosiOutput(uplosiOutput, req.Provider, req.AttestationVariant) -} - -func prepareUplosiConfig(req *osimage.UploadRequest) ([]byte, error) { - var config map[string]any - if _, err := toml.Decode(uplosiConfigTemplate, &config); err != nil { - return nil, err - } - - imageVersionStr, err := imageVersion(req.Provider, req.Version, req.Timestamp) - if err != nil { - return nil, err - } - baseConfig := config["base"].(map[string]any) - awsConfig := baseConfig["aws"].(map[string]any) - azureConfig := baseConfig["azure"].(map[string]any) - gcpConfig := baseConfig["gcp"].(map[string]any) - openstackConfig := baseConfig["openstack"].(map[string]any) - - baseConfig["imageVersion"] = imageVersionStr - baseConfig["provider"] = strings.ToLower(req.Provider.String()) - extendAWSConfig(awsConfig, req.Version, req.AttestationVariant, req.Timestamp) - extendAzureConfig(azureConfig, req.Version, req.AttestationVariant, req.Timestamp) - extendGCPConfig(gcpConfig, req.Version, req.AttestationVariant) - extendOpenStackConfig(openstackConfig, req.Version, req.AttestationVariant) - - buf := new(bytes.Buffer) - if err := toml.NewEncoder(buf).Encode(config); err != nil { - return nil, err - } - - return buf.Bytes(), nil -} - -func prepareWorkspace(config []byte) (string, error) { - workspace, err := os.MkdirTemp("", "uplosi-") - if err != nil { - return "", err - } - // write config to workspace - configPath := filepath.Join(workspace, "uplosi.conf") - if err := os.WriteFile(configPath, config, 0o644); err != nil { - return "", err - } - return workspace, nil -} - -func runUplosi(ctx context.Context, uplosiPath string, workspace string, rawImage string) ([]byte, error) { - imagePath, err := filepath.Abs(rawImage) - if err != nil { - return nil, err - } - - uplosiCmd := exec.CommandContext(ctx, uplosiPath, "upload", imagePath) - uplosiCmd.Dir = workspace - uplosiCmd.Stderr = os.Stderr - return uplosiCmd.Output() -} - -func parseUplosiOutput(output []byte, csp cloudprovider.Provider, attestationVariant string) ([]versionsapi.ImageInfoEntry, error) { - lines := strings.Split(string(output), "\n") - var imageReferences []versionsapi.ImageInfoEntry - for _, line := range lines { - if len(line) == 0 { - continue - } - var region, reference string - if csp == cloudprovider.AWS { - var err error - region, reference, err = awsParseAMIARN(line) - if err != nil { - return nil, err - } - } else { - reference = line - } - imageReferences = append(imageReferences, versionsapi.ImageInfoEntry{ - CSP: strings.ToLower(csp.String()), - AttestationVariant: attestationVariant, - Reference: reference, - Region: region, - }) - } - return imageReferences, nil -} - -func imageVersion(csp cloudprovider.Provider, version versionsapi.Version, timestamp time.Time) (string, error) { - cleanSemver := strings.TrimPrefix(regexp.MustCompile(`^v\d+\.\d+\.\d+`).FindString(version.Version()), "v") - if csp != cloudprovider.Azure { - return cleanSemver, nil - } - - switch { - case version.Stream() == "stable": - fallthrough - case version.Stream() == "debug" && version.Ref() == "-": - return cleanSemver, nil - } - - formattedTime := timestamp.Format(timestampFormat) - if len(formattedTime) != len(timestampFormat) { - return "", errors.New("invalid timestamp") - } - // ..