api: restructure api pkg (#1851)

* api: rename AttestationVersionRepo to Client
* api: move client into separate subpkg for
clearer import paths.
* api: rename configapi -> attestationconfig
* api: rename versionsapi -> versions
* api: rename sut to client
* api: split versionsapi client and make it public
* api: split versionapi fetcher and make it public
* config: move attestationversion type to config
* api: fix attestationconfig client test

Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com>
This commit is contained in:
Otto Bittner 2023-06-02 09:19:23 +02:00 committed by GitHub
parent 289665eb22
commit 30f2b332b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 1042 additions and 916 deletions

View File

@ -48,8 +48,10 @@ go_library(
"//cli/internal/terraform",
"//cli/internal/upgrade",
"//disk-mapper/recoverproto",
"//internal/api/attestationconfig/fetcher",
"//internal/api/fetcher",
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/api/versions/fetcher",
"//internal/atls",
"//internal/attestation/measurements",
"//internal/cloud/azureshared",
@ -135,8 +137,8 @@ go_test(
"//cli/internal/terraform",
"//cli/internal/upgrade",
"//disk-mapper/recoverproto",
"//internal/api/configapi",
"//internal/api/versionsapi",
"//internal/api/attestationconfig",
"//internal/api/versions",
"//internal/atls",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",

View File

@ -15,8 +15,8 @@ import (
"time"
"github.com/edgelesssys/constellation/v2/cli/internal/featureset"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/file"
@ -65,13 +65,13 @@ func runConfigFetchMeasurements(cmd *cobra.Command, _ []string) error {
}
cfm := &configFetchMeasurementsCmd{log: log, canFetchMeasurements: featureset.CanFetchMeasurements}
fetcher := fetcher.NewConfigAPIFetcherWithClient(http.DefaultClient)
fetcher := attestationconfigfetcher.NewWithClient(http.DefaultClient)
return cfm.configFetchMeasurements(cmd, sigstore.CosignVerifier{}, rekor, fileHandler, fetcher, http.DefaultClient)
}
func (cfm *configFetchMeasurementsCmd) configFetchMeasurements(
cmd *cobra.Command, cosign cosignVerifier, rekor rekorVerifier,
fileHandler file.Handler, fetcher fetcher.ConfigAPIFetcher, client *http.Client,
fileHandler file.Handler, fetcher attestationconfigfetcher.AttestationConfigAPIFetcher, client *http.Client,
) error {
flags, err := cfm.parseFetchMeasurementsFlags(cmd)
if err != nil {

View File

@ -16,8 +16,8 @@ import (
"net/url"
"testing"
"github.com/edgelesssys/constellation/v2/internal/api/configapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -305,8 +305,10 @@ func (f fakeConfigFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ configap
}, nil
}
func (f fakeConfigFetcher) FetchLatestAzureSEVSNPVersion(_ context.Context, _ versionsapi.Version) (configapi.AzureSEVSNPVersion, error) {
return testCfg, nil
func (f fakeConfigFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context, _ versionsapi.Version) (configapi.AzureSEVSNPVersionGet, error) {
return configapi.AzureSEVSNPVersionGet{
AzureSEVSNPVersion: testCfg,
}, nil
}
var testCfg = configapi.AzureSEVSNPVersion{

View File

@ -13,7 +13,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -59,11 +59,11 @@ func runCreate(cmd *cobra.Command, _ []string) error {
fileHandler := file.NewHandler(afero.NewOsFs())
creator := cloudcmd.NewCreator(spinner)
c := &createCmd{log: log}
fetcher := fetcher.NewConfigAPIFetcher()
fetcher := attestationconfigfetcher.New()
return c.create(cmd, creator, fileHandler, spinner, fetcher)
}
func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler, spinner spinnerInterf, fetcher fetcher.ConfigAPIFetcher) (retErr error) {
func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler, spinner spinnerInterf, fetcher attestationconfigfetcher.AttestationConfigAPIFetcher) (retErr error) {
flags, err := c.parseCreateFlags(cmd)
if err != nil {
return err

View File

@ -19,7 +19,7 @@ import (
"text/tabwriter"
"time"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
@ -98,13 +98,13 @@ func runInitialize(cmd *cobra.Command, _ []string) error {
defer cancel()
cmd.SetContext(ctx)
i := &initCmd{log: log, spinner: spinner, merger: &kubeconfigMerger{log: log}, fh: &fileHandler}
fetcher := fetcher.NewConfigAPIFetcher()
fetcher := attestationconfigfetcher.New()
return i.initialize(cmd, newDialer, fileHandler, license.NewClient(), fetcher)
}
// initialize initializes a Constellation.
func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.Validator) *dialer.Dialer,
fileHandler file.Handler, quotaChecker license.QuotaChecker, configFetcher fetcher.ConfigAPIFetcher,
fileHandler file.Handler, quotaChecker license.QuotaChecker, configFetcher attestationconfigfetcher.AttestationConfigAPIFetcher,
) error {
flags, err := i.evalFlagArgs(cmd)
if err != nil {

View File

@ -15,7 +15,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
@ -46,7 +46,7 @@ func newMiniUpCmd() *cobra.Command {
type miniUpCmd struct {
log debugLog
configFetcher fetcher.ConfigAPIFetcher
configFetcher attestationconfigfetcher.AttestationConfigAPIFetcher
}
func runUp(cmd *cobra.Command, _ []string) error {
@ -62,7 +62,7 @@ func runUp(cmd *cobra.Command, _ []string) error {
defer spinner.Stop()
creator := cloudcmd.NewCreator(spinner)
m := &miniUpCmd{log: log, configFetcher: fetcher.NewConfigAPIFetcher()}
m := &miniUpCmd{log: log, configFetcher: attestationconfigfetcher.New()}
return m.up(cmd, creator, spinner)
}

View File

@ -18,7 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
@ -50,7 +50,7 @@ func NewRecoverCmd() *cobra.Command {
type recoverCmd struct {
log debugLog
configFetcher fetcher.ConfigAPIFetcher
configFetcher attestationconfigfetcher.AttestationConfigAPIFetcher
}
func runRecover(cmd *cobra.Command, _ []string) error {
@ -63,7 +63,7 @@ func runRecover(cmd *cobra.Command, _ []string) error {
newDialer := func(validator atls.Validator) *dialer.Dialer {
return dialer.New(nil, validator, &net.Dialer{})
}
r := &recoverCmd{log: log, configFetcher: fetcher.NewConfigAPIFetcher()}
r := &recoverCmd{log: log, configFetcher: attestationconfigfetcher.New()}
return r.recover(cmd, fileHandler, 5*time.Second, &recoverDoer{log: r.log}, newDialer)
}

View File

@ -19,7 +19,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/cli/internal/upgrade"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
"github.com/edgelesssys/constellation/v2/internal/config"
@ -67,7 +67,7 @@ func runUpgradeApply(cmd *cobra.Command, _ []string) error {
}
imagefetcher := imagefetcher.New()
configFetcher := fetcher.NewConfigAPIFetcher()
configFetcher := attestationconfigfetcher.New()
applyCmd := upgradeApplyCmd{upgrader: upgrader, log: log, imageFetcher: imagefetcher, configFetcher: configFetcher}
return applyCmd.upgradeApply(cmd, fileHandler)
@ -76,7 +76,7 @@ func runUpgradeApply(cmd *cobra.Command, _ []string) error {
type upgradeApplyCmd struct {
upgrader cloudUpgrader
imageFetcher imageFetcher
configFetcher fetcher.ConfigAPIFetcher
configFetcher attestationconfigfetcher.AttestationConfigAPIFetcher
log debugLog
}

View File

@ -18,8 +18,10 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/featureset"
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/kubernetes"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
versionfetcher "github.com/edgelesssys/constellation/v2/internal/api/versions/fetcher"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"
@ -68,7 +70,7 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error {
if err != nil {
return err
}
versionListFetcher := fetcher.NewVersionAPIFetcher()
versionListFetcher := versionfetcher.New()
rekor, err := sigstore.NewRekor()
if err != nil {
return fmt.Errorf("constructing Rekor client: %w", err)
@ -86,12 +88,12 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error {
flags: flags,
cliVersion: compatibility.EnsurePrefixV(constants.VersionInfo()),
log: log,
versionsapi: fetcher.NewVersionAPIFetcher(),
versionsapi: versionfetcher.New(),
},
log: log,
}
return up.upgradeCheck(cmd, fileHandler, fetcher.NewConfigAPIFetcher(), flags)
return up.upgradeCheck(cmd, fileHandler, attestationconfigfetcher.New(), flags)
}
func parseUpgradeCheckFlags(cmd *cobra.Command) (upgradeCheckFlags, error) {
@ -131,7 +133,7 @@ type upgradeCheckCmd struct {
}
// upgradePlan plans an upgrade of a Constellation cluster.
func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Handler, fetcher fetcher.ConfigAPIFetcher, flags upgradeCheckFlags) error {
func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Handler, fetcher attestationconfigfetcher.AttestationConfigAPIFetcher, flags upgradeCheckFlags) error {
conf, err := config.New(fileHandler, flags.configPath, fetcher, flags.force)
var configValidationErr *config.ValidationError
if errors.As(err, &configValidationErr) {

View File

@ -15,7 +15,7 @@ import (
"strings"
"testing"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"

View File

@ -21,7 +21,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/atls"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/config"
@ -72,11 +72,11 @@ func runVerify(cmd *cobra.Command, _ []string) error {
}
v := &verifyCmd{log: log}
fetcher := fetcher.NewConfigAPIFetcher()
fetcher := attestationconfigfetcher.New()
return v.verify(cmd, fileHandler, verifyClient, formatter, fetcher)
}
func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyClient verifyClient, formatter attestationDocFormatter, configFetcher fetcher.ConfigAPIFetcher) error {
func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyClient verifyClient, formatter attestationDocFormatter, configFetcher attestationconfigfetcher.AttestationConfigAPIFetcher) error {
flags, err := c.parseVerifyFlags(cmd, fileHandler)
if err != nil {
return fmt.Errorf("parsing flags: %w", err)

View File

@ -14,7 +14,7 @@ go_library(
"//cli/internal/helm",
"//cli/internal/terraform",
"//cli/internal/upgrade",
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/compatibility",

View File

@ -18,7 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/helm"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/cli/internal/upgrade"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"

View File

@ -14,7 +14,7 @@ go_library(
"//debugd/internal/filetransfer",
"//debugd/internal/filetransfer/streamer",
"//debugd/service",
"//internal/api/fetcher",
"//internal/api/attestationconfig/fetcher",
"//internal/config",
"//internal/constants",
"//internal/file",

View File

@ -20,7 +20,7 @@ import (
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer"
"github.com/edgelesssys/constellation/v2/debugd/internal/filetransfer/streamer"
pb "github.com/edgelesssys/constellation/v2/debugd/service"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
@ -69,7 +69,7 @@ func runDeploy(cmd *cobra.Command, _ []string) error {
fileHandler := file.NewHandler(fs)
streamer := streamer.New(fs)
transfer := filetransfer.New(log, streamer, filetransfer.ShowProgress)
constellationConfig, err := config.New(fileHandler, configName, fetcher.NewConfigAPIFetcher(), force)
constellationConfig, err := config.New(fileHandler, configName, attestationconfigfetcher.New(), force)
var configValidationErr *config.ValidationError
if errors.As(err, &configValidationErr) {
cmd.PrintErrln(configValidationErr.LongMessage())

View File

@ -11,7 +11,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/e2e/internal/upgrade",
visibility = ["//e2e:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/constants",
@ -26,6 +26,7 @@ go_library(
go_test(
name = "upgrade_test",
srcs = ["upgrade_test.go"],
# keep
count = 1,
data = [
"//cli:cli_oss_linux_amd64",
@ -34,11 +35,12 @@ go_test(
env = {
"PATH_CLI": "$(location //cli:cli_oss_linux_amd64)",
},
# keep
gotags = ["e2e"],
tags = ["manual"],
deps = [
"//e2e/internal/kubectl",
"//internal/api/fetcher",
"//internal/api/attestationconfig/fetcher",
"//internal/config",
"//internal/constants",
"//internal/file",

View File

@ -12,7 +12,7 @@ import (
"context"
"net/http"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/imagefetcher"

View File

@ -21,7 +21,7 @@ import (
"time"
"github.com/edgelesssys/constellation/v2/e2e/internal/kubectl"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
@ -233,7 +233,7 @@ func testNodesEventuallyAvailable(t *testing.T, k *kubernetes.Clientset, wantCon
func writeUpgradeConfig(require *require.Assertions, image string, kubernetes string, microservices string) versionContainer {
fileHandler := file.NewHandler(afero.NewOsFs())
fetcher := fetcher.NewConfigAPIFetcher()
fetcher := attestationconfigfetcher.New()
cfg, err := config.New(fileHandler, constants.ConfigFilename, fetcher, true)
var cfgErr *config.ValidationError
var longMsg string

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/hack/azure-snp-report-verify",
visibility = ["//visibility:private"],
deps = [
"//internal/api/configapi",
"//internal/api/attestationconfig",
"@in_gopkg_square_go_jose_v2//:go-jose_v2",
"@in_gopkg_square_go_jose_v2//jwt",
],

View File

@ -20,7 +20,7 @@ import (
"os"
"time"
"github.com/edgelesssys/constellation/v2/internal/api/configapi"
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
"gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)

View File

@ -6,8 +6,8 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/hack/cli-k8s-compatibility",
visibility = ["//visibility:private"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versionsapi/client",
"//internal/api/versions",
"//internal/api/versions/client",
"//internal/logger",
"//internal/versions",
"@org_uber_go_zap//zapcore",

View File

@ -11,8 +11,8 @@ import (
"context"
"flag"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi/client"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/api/versions/client"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/versions"
"go.uber.org/zap/zapcore"

View File

@ -6,7 +6,8 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/hack/configapi/cmd",
visibility = ["//visibility:public"],
deps = [
"//internal/api/configapi",
"//internal/api/attestationconfig",
"//internal/api/attestationconfig/client",
"//internal/staticupload",
"@com_github_spf13_cobra//:cobra",
],

View File

@ -12,7 +12,8 @@ import (
"os"
"time"
"github.com/edgelesssys/constellation/v2/internal/api/configapi"
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
attestationconfigclient "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/client"
"github.com/edgelesssys/constellation/v2/internal/staticupload"
"github.com/spf13/cobra"
)
@ -75,7 +76,7 @@ func runCmd(cmd *cobra.Command, _ []string) error {
return fmt.Errorf("unmarshalling version file: %w", err)
}
sut, err := configapi.NewAttestationVersionRepo(ctx, cfg, []byte(cosignPwd), privateKey)
sut, err := attestationconfigclient.New(ctx, cfg, []byte(cosignPwd), privateKey)
if err != nil {
return fmt.Errorf("creating repo: %w", err)
}

View File

@ -24,7 +24,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/image/upload/internal/cmd",
visibility = ["//image/upload:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/logger",

View File

@ -10,7 +10,7 @@ import (
"context"
"io"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/osimage"
)

View File

@ -12,7 +12,7 @@ import (
"path/filepath"
"time"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/spf13/cobra"
"go.uber.org/zap/zapcore"

View File

@ -11,7 +11,7 @@ import (
"fmt"
"os"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/logger"
infoupload "github.com/edgelesssys/constellation/v2/internal/osimage/imageinfo"
"github.com/spf13/cobra"

View File

@ -13,7 +13,7 @@ import (
"io"
"strings"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/osimage"
)

View File

@ -0,0 +1,15 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "attestationconfig",
srcs = [
"azure.go",
"configapi.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig",
visibility = ["//:__subpackages__"],
deps = [
"//internal/constants",
"//internal/variant",
],
)

View File

@ -3,7 +3,8 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package configapi
package attestationconfig
import (
"fmt"

View File

@ -2,16 +2,12 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "configapi",
srcs = [
"attestation.go",
"attestationversion.go",
"configapi.go",
"repo.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/configapi",
name = "client",
srcs = ["client.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/client",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/attestationconfig",
"//internal/constants",
"//internal/kms/storage",
"//internal/sigstore",
@ -22,15 +18,18 @@ go_library(
)
go_test(
name = "configapi_test",
srcs = [
"attestationversion_test.go",
"repo_test.go",
],
embed = [":configapi"],
name = "client_test",
srcs = ["client_test.go"],
# keep
count = 1,
# keep
gotags = ["e2e"],
# keep
tags = ["manual"],
deps = [
":client",
"//internal/api/attestationconfig",
"//internal/staticupload",
"@com_github_stretchr_testify//require",
"@in_gopkg_yaml_v3//:yaml_v3",
],
)

View File

@ -3,7 +3,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package configapi
package client
import (
"bytes"
@ -17,30 +17,32 @@ import (
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
"github.com/edgelesssys/constellation/v2/internal/staticupload"
"github.com/edgelesssys/constellation/v2/internal/variant"
)
// AttestationVersionRepo manages (modifies) the version information for the attestation variants.
type AttestationVersionRepo struct {
// Client manages (modifies) the version information for the attestation variants.
type Client struct {
*staticupload.Client
cosignPwd []byte // used to decrypt the cosign private key
privKey []byte // used to sign
}
// NewAttestationVersionRepo returns a new AttestationVersionRepo.
func NewAttestationVersionRepo(ctx context.Context, cfg staticupload.Config, cosignPwd, privateKey []byte) (*AttestationVersionRepo, error) {
// New returns a new Client.
func New(ctx context.Context, cfg staticupload.Config, cosignPwd, privateKey []byte) (*Client, error) {
client, err := staticupload.New(ctx, cfg)
if err != nil {
return nil, fmt.Errorf("failed to create s3 storage: %w", err)
}
return &AttestationVersionRepo{client, cosignPwd, privateKey}, nil
return &Client{client, cosignPwd, privateKey}, nil
}
// UploadAzureSEVSNP uploads the latest version numbers of the Azure SEVSNP.
func (a AttestationVersionRepo) UploadAzureSEVSNP(ctx context.Context, versions AzureSEVSNPVersion, date time.Time) error {
func (a Client) UploadAzureSEVSNP(ctx context.Context, versions attestationconfig.AzureSEVSNPVersion, date time.Time) error {
versionBytes, err := json.Marshal(versions)
if err != nil {
return err
@ -48,7 +50,7 @@ func (a AttestationVersionRepo) UploadAzureSEVSNP(ctx context.Context, versions
variant := variant.AzureSEVSNP{}
fname := date.Format("2006-01-02-15-04") + ".json"
filePath := fmt.Sprintf("%s/%s/%s", attestationURLPath, variant.String(), fname)
filePath := fmt.Sprintf("%s/%s/%s", constants.CDNAttestationConfigPrefixV1, variant.String(), fname)
err = put(ctx, a.Client, filePath, versionBytes)
if err != nil {
return err
@ -62,7 +64,7 @@ func (a AttestationVersionRepo) UploadAzureSEVSNP(ctx context.Context, versions
}
// createAndUploadSignature signs the given content and uploads the signature to the given filePath with the .sig suffix.
func (a AttestationVersionRepo) createAndUploadSignature(ctx context.Context, content []byte, filePath string) error {
func (a Client) createAndUploadSignature(ctx context.Context, content []byte, filePath string) error {
signature, err := sigstore.SignContent(a.cosignPwd, a.privKey, content)
if err != nil {
return fmt.Errorf("sign version file: %w", err)
@ -75,8 +77,8 @@ func (a AttestationVersionRepo) createAndUploadSignature(ctx context.Context, co
}
// List returns the list of versions for the given attestation type.
func (a AttestationVersionRepo) List(ctx context.Context, attestation variant.Variant) ([]string, error) {
key := path.Join(attestationURLPath, attestation.String(), "list")
func (a Client) List(ctx context.Context, attestation variant.Variant) ([]string, error) {
key := path.Join(constants.CDNAttestationConfigPrefixV1, attestation.String(), "list")
bt, err := get(ctx, a.Client, key)
if err != nil {
return nil, err
@ -89,18 +91,18 @@ func (a AttestationVersionRepo) List(ctx context.Context, attestation variant.Va
}
// DeleteList empties the list of versions for the given attestation type.
func (a AttestationVersionRepo) DeleteList(ctx context.Context, attestation variant.Variant) error {
func (a Client) DeleteList(ctx context.Context, attestation variant.Variant) error {
versions := []string{}
bt, err := json.Marshal(&versions)
if err != nil {
return err
}
return put(ctx, a.Client, path.Join(attestationURLPath, attestation.String(), "list"), bt)
return put(ctx, a.Client, path.Join(constants.CDNAttestationConfigPrefixV1, attestation.String(), "list"), bt)
}
func (a AttestationVersionRepo) addVersionToList(ctx context.Context, attestation variant.Variant, fname string) error {
func (a Client) addVersionToList(ctx context.Context, attestation variant.Variant, fname string) error {
versions := []string{}
key := path.Join(attestationURLPath, attestation.String(), "list")
key := path.Join(constants.CDNAttestationConfigPrefixV1, attestation.String(), "list")
bt, err := get(ctx, a.Client, key)
if err == nil {
if err := json.Unmarshal(bt, &versions); err != nil {

View File

@ -5,7 +5,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package configapi_test
package client_test
import (
"context"
@ -16,7 +16,8 @@ import (
"testing"
"time"
"github.com/edgelesssys/constellation/v2/internal/api/configapi"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/client"
"github.com/edgelesssys/constellation/v2/internal/staticupload"
"github.com/stretchr/testify/require"
)
@ -64,7 +65,7 @@ func TestMain(m *testing.M) {
os.Exit(m.Run())
}
var versionValues = configapi.AzureSEVSNPVersion{
var versionValues = attestationconfig.AzureSEVSNPVersion{
Bootloader: 2,
TEE: 0,
SNP: 6,
@ -73,8 +74,8 @@ var versionValues = configapi.AzureSEVSNPVersion{
func TestUploadAzureSEVSNPVersions(t *testing.T) {
ctx := context.Background()
sut, err := configapi.NewAttestationVersionRepo(ctx, cfg, []byte(*cosignPwd), privateKey)
client, err := client.New(ctx, cfg, []byte(*cosignPwd), privateKey)
require.NoError(t, err)
d := time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC)
require.NoError(t, sut.UploadAzureSEVSNP(ctx, versionValues, d))
require.NoError(t, client.UploadAzureSEVSNP(ctx, versionValues, d))
}

View File

@ -0,0 +1,23 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
/*
# AttestationConfig API
The AttestationConfig API provides values for the attestation key in the Constellation config.
This package defines API types that represents objects of the AttestationConfig API.
The types provide helper methods for validation and commonly used operations on the
information contained in the objects. Especially the paths used for the API are defined
in these helper methods.
Regarding the decision to implement new types over using the existing types from internal/config:
AttesationCfg objects for AttestationCfg API need to hold some version information (for sorting, recognizing latest).
Thus, existing config types (AWSNitroTPM, AzureSEVSNP, ...) can not be extended to implement apiObject interface.
Instead, we need a separate type that wraps _all_ attestation types. In the codebase this is done using the AttestationCfg interface.
The new type AttestationCfgGet needs to be located inside internal/config in order to implement UnmarshalJSON.
*/
package attestationconfig

View File

@ -0,0 +1,27 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "fetcher",
srcs = ["fetcher.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/attestationconfig",
"//internal/api/fetcher",
"//internal/api/versions",
"//internal/sigstore",
],
)
go_test(
name = "fetcher_test",
srcs = ["fetcher_test.go"],
embed = [":fetcher"],
deps = [
"//internal/api/attestationconfig",
"//internal/api/versions",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
],
)

View File

@ -0,0 +1,120 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package fetcher
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
)
// AttestationConfigAPIFetcher fetches config API resources without authentication.
type AttestationConfigAPIFetcher interface {
FetchAzureSEVSNPVersion(ctx context.Context, azureVersion attestationconfig.AzureSEVSNPVersionGet, version versionsapi.Version) (attestationconfig.AzureSEVSNPVersionGet, error)
FetchAzureSEVSNPVersionList(ctx context.Context, attestation attestationconfig.AzureSEVSNPVersionList) (attestationconfig.AzureSEVSNPVersionList, error)
FetchAzureSEVSNPVersionLatest(ctx context.Context, version versionsapi.Version) (attestationconfig.AzureSEVSNPVersionGet, error)
}
// Fetcher fetches AttestationCfg API resources without authentication.
type Fetcher struct {
fetcher.HTTPClient
}
// New returns a new Fetcher.
func New() *Fetcher {
return NewWithClient(fetcher.NewHTTPClient())
}
// NewWithClient returns a new Fetcher with custom http client.
func NewWithClient(client fetcher.HTTPClient) *Fetcher {
return &Fetcher{client}
}
// FetchAzureSEVSNPVersionList fetches the version list information from the config API.
func (f *Fetcher) FetchAzureSEVSNPVersionList(ctx context.Context, attestation attestationconfig.AzureSEVSNPVersionList) (attestationconfig.AzureSEVSNPVersionList, error) {
return fetcher.Fetch(ctx, f.HTTPClient, attestation)
}
// FetchAzureSEVSNPVersion fetches the version information from the config API.
func (f *Fetcher) FetchAzureSEVSNPVersion(ctx context.Context, attestation attestationconfig.AzureSEVSNPVersionGet, version versionsapi.Version) (attestationconfig.AzureSEVSNPVersionGet, error) {
cosignPublicKey, err := sigstore.CosignPublicKeyForVersion(version)
if err != nil {
return attestationconfig.AzureSEVSNPVersionGet{}, fmt.Errorf("get public key for config: %w", err)
}
urlString, err := attestation.URL()
if err != nil {
return attestationconfig.AzureSEVSNPVersionGet{}, err
}
fetchedVersion, err := fetcher.Fetch(ctx, f.HTTPClient, attestation)
if err != nil {
return fetchedVersion, fmt.Errorf("fetch version %s: %w", fetchedVersion.Version, err)
}
versionBytes, err := json.Marshal(fetchedVersion)
if err != nil {
return fetchedVersion, fmt.Errorf("marshal version for verify %s: %w", fetchedVersion.Version, err)
}
signature, err := fetchBytesFromRawURL(ctx, fmt.Sprintf("%s.sig", urlString), f.HTTPClient)
if err != nil {
return fetchedVersion, fmt.Errorf("fetch version %s signature: %w", fetchedVersion.Version, err)
}
err = sigstore.CosignVerifier{}.VerifySignature(versionBytes, signature, cosignPublicKey)
if err != nil {
return fetchedVersion, fmt.Errorf("verify version %s signature: %w", fetchedVersion.Version, err)
}
return fetchedVersion, nil
}
// FetchAzureSEVSNPVersionLatest returns the latest versions of the given type.
func (f *Fetcher) FetchAzureSEVSNPVersionLatest(ctx context.Context, version versionsapi.Version) (res attestationconfig.AzureSEVSNPVersionGet, err error) {
var list attestationconfig.AzureSEVSNPVersionList
list, err = f.FetchAzureSEVSNPVersionList(ctx, list)
if err != nil {
return res, fmt.Errorf("fetching versions list: %w", err)
}
get := attestationconfig.AzureSEVSNPVersionGet{Version: list[0]} // get latest version (as sorted reversely alphanumerically)
get, err = f.FetchAzureSEVSNPVersion(ctx, get, version)
if err != nil {
return res, fmt.Errorf("failed fetching version: %w", err)
}
return get, nil
}
func fetchBytesFromRawURL(ctx context.Context, urlString string, client fetcher.HTTPClient) ([]byte, error) {
url, err := url.Parse(urlString)
if err != nil {
return nil, fmt.Errorf("parse version url %s: %w", urlString, err)
}
return getFromURL(ctx, client, url)
}
// getFromURL fetches the content from the given URL and returns the content as a byte slice.
func getFromURL(ctx context.Context, client fetcher.HTTPClient, sourceURL *url.URL) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, sourceURL.String(), http.NoBody)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http status code: %d", resp.StatusCode)
}
return io.ReadAll(resp.Body)
}

View File

@ -14,24 +14,26 @@ import (
"net/http"
"testing"
"github.com/edgelesssys/constellation/v2/internal/api/configapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var testCfg = configapi.AzureSEVSNPVersion{
var testCfg = configapi.AzureSEVSNPVersionGet{
AzureSEVSNPVersion: configapi.AzureSEVSNPVersion{
Microcode: 93,
TEE: 0,
SNP: 6,
Bootloader: 2,
},
}
func TestFetchLatestAzureSEVSNPVersion(t *testing.T) {
testcases := map[string]struct {
signature []byte
wantErr bool
want configapi.AzureSEVSNPVersion
want configapi.AzureSEVSNPVersionGet
}{
"get version with valid signature": {
signature: []byte("MEUCIQDNn6wiSh9Nz9mtU9RvxvfkH3fNDFGeqopjTIRoBNkyrAIgSsKgdYNQXvPevaLWmmpnj/9WcgrltAQ+KfI+bQfklAo="),
@ -52,10 +54,10 @@ func TestFetchLatestAzureSEVSNPVersion(t *testing.T) {
require := require.New(t)
version, err := versionsapi.NewVersionFromShortPath("stream/debug/v9.9.9", versionsapi.VersionKindImage)
require.NoError(err)
fetcher := NewConfigAPIFetcherWithClient(client)
fetcher := NewWithClient(client)
assert := assert.New(t)
res, err := fetcher.FetchLatestAzureSEVSNPVersion(context.Background(), version)
res, err := fetcher.FetchAzureSEVSNPVersionLatest(context.Background(), version)
if tc.wantErr {
assert.Error(err)
} else {

View File

@ -3,11 +3,9 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "client",
srcs = ["client.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/versionsapi/client",
importpath = "github.com/edgelesssys/constellation/v2/internal/api/client",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/constants",
"//internal/logger",
"@com_github_aws_aws_sdk_go_v2//aws",
"@com_github_aws_aws_sdk_go_v2_config//:config",
@ -16,6 +14,6 @@ go_library(
"@com_github_aws_aws_sdk_go_v2_service_cloudfront//types",
"@com_github_aws_aws_sdk_go_v2_service_s3//:s3",
"@com_github_aws_aws_sdk_go_v2_service_s3//types",
"@org_golang_x_mod//semver",
"@org_uber_go_zap//:zap",
],
)

View File

@ -0,0 +1,291 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
/*
Package client provides a client for the versions API.
The client needs to be authenticated with AWS. It should be used in internal
development and CI tools for administrative tasks. For just fetching information
from the API, use the fetcher package instead.
Needed IAM permissions for read mode:
- "s3:GetObject"
- "s3:ListBucket"
Additional needed IAM permissions for write mode:
- "s3:PutObject"
- "s3:DeleteObject"
- "cloudfront:CreateInvalidation"
Thread-safety of the bucket is not guaranteed. The client is not thread-safe.
Each sub-API included in the Constellation Resource API should define it's resources by implementing types that implement apiObject.
The new package can then call this package's Fetch and Update functions to get/modify resources from the API.
*/
package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
cftypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/edgelesssys/constellation/v2/internal/logger"
"go.uber.org/zap"
)
// Client is the client for the versions API.
type Client struct {
config aws.Config
cloudfrontClient *cloudfront.Client
s3Client *s3.Client
uploadClient *s3manager.Uploader
bucket string
distributionID string
cacheInvalidationWaitTimeout time.Duration
dirtyPaths []string // written paths to be invalidated
DryRun bool // no write operations are performed
Log *logger.Logger
}
// NewReadOnlyClient creates a new read-only client.
// This client can be used to fetch objects but cannot write updates.
func NewReadOnlyClient(ctx context.Context, region, bucket, distributionID string,
log *logger.Logger,
) (*Client, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
s3c := s3.NewFromConfig(cfg)
return &Client{
config: cfg,
s3Client: s3c,
bucket: bucket,
distributionID: distributionID,
DryRun: true,
Log: log,
}, nil
}
// NewClient creates a new client for the versions API.
func NewClient(ctx context.Context, region, bucket, distributionID string, dryRun bool,
log *logger.Logger,
) (*Client, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
cloudfrontC := cloudfront.NewFromConfig(cfg)
s3C := s3.NewFromConfig(cfg)
uploadC := s3manager.NewUploader(s3C)
return &Client{
config: cfg,
cloudfrontClient: cloudfrontC,
s3Client: s3C,
uploadClient: uploadC,
bucket: bucket,
distributionID: distributionID,
DryRun: dryRun,
Log: log,
cacheInvalidationWaitTimeout: 10 * time.Minute,
}, nil
}
// InvalidateCache invalidates the CDN cache for the paths that have been written.
// The function should be deferred after the client has been created.
func (c *Client) InvalidateCache(ctx context.Context) error {
if len(c.dirtyPaths) == 0 {
c.Log.Debugf("No dirty paths, skipping cache invalidation")
return nil
}
if c.DryRun {
c.Log.With(zap.String("distributionID", c.distributionID), zap.Strings("dirtyPaths", c.dirtyPaths)).Debugf("DryRun: cloudfront create invalidation")
return nil
}
c.Log.Debugf("Paths to invalidate: %v", c.dirtyPaths)
in := &cloudfront.CreateInvalidationInput{
DistributionId: &c.distributionID,
InvalidationBatch: &cftypes.InvalidationBatch{
CallerReference: ptr(fmt.Sprintf("%d", time.Now().Unix())),
Paths: &cftypes.Paths{
Items: c.dirtyPaths,
Quantity: ptr(int32(len(c.dirtyPaths))),
},
},
}
invalidation, err := c.cloudfrontClient.CreateInvalidation(ctx, in)
if err != nil {
return fmt.Errorf("creating invalidation: %w", err)
}
c.Log.Debugf("Waiting for invalidation %s to complete", *invalidation.Invalidation.Id)
waiter := cloudfront.NewInvalidationCompletedWaiter(c.cloudfrontClient)
waitIn := &cloudfront.GetInvalidationInput{
DistributionId: &c.distributionID,
Id: invalidation.Invalidation.Id,
}
if err := waiter.Wait(ctx, waitIn, c.cacheInvalidationWaitTimeout); err != nil {
return fmt.Errorf("waiting for invalidation to complete: %w", err)
}
return nil
}
// DeletePath deletes all objects at a given path from a s3 bucket.
func (c *Client) DeletePath(ctx context.Context, path string) error {
listIn := &s3.ListObjectsV2Input{
Bucket: &c.bucket,
Prefix: &path,
}
c.Log.Debugf("Listing objects in %s", path)
objs := []s3types.Object{}
out := &s3.ListObjectsV2Output{IsTruncated: true}
for out.IsTruncated {
var err error
out, err = c.s3Client.ListObjectsV2(ctx, listIn)
if err != nil {
return fmt.Errorf("listing objects in %s: %w", path, err)
}
objs = append(objs, out.Contents...)
}
c.Log.Debugf("Found %d objects in %s", len(objs), path)
if len(objs) == 0 {
c.Log.Warnf("Path %s is already empty", path)
return nil
}
objIDs := make([]s3types.ObjectIdentifier, len(objs))
for i, obj := range objs {
objIDs[i] = s3types.ObjectIdentifier{Key: obj.Key}
}
if c.DryRun {
c.Log.Debugf("DryRun: Deleting %d objects with IDs %v", len(objs), objIDs)
return nil
}
c.dirtyPaths = append(c.dirtyPaths, "/"+path)
deleteIn := &s3.DeleteObjectsInput{
Bucket: &c.bucket,
Delete: &s3types.Delete{
Objects: objIDs,
},
}
c.Log.Debugf("Deleting %d objects in %s", len(objs), path)
if _, err := c.s3Client.DeleteObjects(ctx, deleteIn); err != nil {
return fmt.Errorf("deleting objects in %s: %w", path, err)
}
return nil
}
func ptr[T any](t T) *T {
return &t
}
type apiObject interface {
ValidateRequest() error
Validate() error
JSONPath() string
}
// Fetch fetches the given apiObject from the public Constellation CDN.
func Fetch[T apiObject](ctx context.Context, c *Client, obj T) (T, error) {
if err := obj.ValidateRequest(); err != nil {
return *new(T), fmt.Errorf("validating request for %T: %w", obj, err)
}
in := &s3.GetObjectInput{
Bucket: &c.bucket,
Key: ptr(obj.JSONPath()),
}
c.Log.Debugf("Fetching %T from s3: %s", obj, obj.JSONPath())
out, err := c.s3Client.GetObject(ctx, in)
var noSuchkey *s3types.NoSuchKey
if errors.As(err, &noSuchkey) {
return *new(T), &NotFoundError{err: err}
} else if err != nil {
return *new(T), fmt.Errorf("getting s3 object at %s: %w", obj.JSONPath(), err)
}
defer out.Body.Close()
var newObj T
if err := json.NewDecoder(out.Body).Decode(&newObj); err != nil {
return *new(T), fmt.Errorf("decoding %T: %w", obj, err)
}
if newObj.Validate() != nil {
return *new(T), fmt.Errorf("received invalid %T: %w", newObj, newObj.Validate())
}
return newObj, nil
}
// Update creates/updates the given apiObject in the public Constellation CDN.
func Update[T apiObject](ctx context.Context, c *Client, obj T) error {
if err := obj.Validate(); err != nil {
return fmt.Errorf("validating %T struct: %w", obj, err)
}
rawJSON, err := json.Marshal(obj)
if err != nil {
return fmt.Errorf("marshaling %T struct: %w", obj, err)
}
if c.DryRun {
c.Log.With(zap.String("bucket", c.bucket), zap.String("key", obj.JSONPath()), zap.String("body", string(rawJSON))).Debugf("DryRun: s3 put object")
return nil
}
in := &s3.PutObjectInput{
Bucket: &c.bucket,
Key: ptr(obj.JSONPath()),
Body: bytes.NewBuffer(rawJSON),
}
c.dirtyPaths = append(c.dirtyPaths, "/"+obj.JSONPath())
c.Log.Debugf("Uploading %T to s3: %v", obj, obj.JSONPath())
if _, err := c.uploadClient.Upload(ctx, in); err != nil {
return fmt.Errorf("uploading %T: %w", obj, err)
}
return nil
}
// NotFoundError is an error that is returned when a resource is not found.
type NotFoundError struct {
err error
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("the requested resource was not found: %s", e.err.Error())
}
func (e *NotFoundError) Unwrap() error {
return e.err
}

View File

@ -1,20 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
/*
# Config API
The Config API provides information about versions of Constellation components.
This package defines API types that represents objects of the config API.
The types provide helper methods for validation and commonly used operations on the
information contained in the objects. Especially the paths used for the API are defined
in these helper methods.
The package also provides helper functions that can be used in context of the config API,
e.g. to validate versions.
*/
package configapi

View File

@ -1,34 +1,8 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "fetcher",
srcs = [
"configapi.go",
"fetcher.go",
"versionapi.go",
],
srcs = ["fetcher.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/fetcher",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/configapi",
"//internal/api/versionsapi",
"//internal/sigstore",
],
)
go_test(
name = "fetcher_test",
srcs = [
"configapi_test.go",
"versionapi_test.go",
],
embed = [":fetcher"],
deps = [
"//internal/api/configapi",
"//internal/api/versionsapi",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
"@org_uber_go_goleak//:goleak",
],
)

View File

@ -1,101 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package fetcher
import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/edgelesssys/constellation/v2/internal/api/configapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
)
// ConfigAPIFetcher fetches config API resources without authentication.
type ConfigAPIFetcher interface {
FetchAzureSEVSNPVersionList(ctx context.Context, attestation configapi.AzureSEVSNPVersionList) (configapi.AzureSEVSNPVersionList, error)
FetchAzureSEVSNPVersion(ctx context.Context, azureVersion configapi.AzureSEVSNPVersionGet, version versionsapi.Version) (configapi.AzureSEVSNPVersionGet, error)
FetchLatestAzureSEVSNPVersion(ctx context.Context, version versionsapi.Version) (configapi.AzureSEVSNPVersion, error)
}
// configAPIFetcher fetches config API resources without authentication.
type configAPIFetcher struct {
*fetcher
}
// NewConfigAPIFetcher returns a new Fetcher.
func NewConfigAPIFetcher() ConfigAPIFetcher {
return NewConfigAPIFetcherWithClient(NewHTTPClient())
}
// NewConfigAPIFetcherWithClient returns a new Fetcher with custom http client.
func NewConfigAPIFetcherWithClient(client HTTPClient) ConfigAPIFetcher {
return &configAPIFetcher{
fetcher: newFetcherWith(client),
}
}
// FetchAzureSEVSNPVersionList fetches the version list information from the config API.
func (f *configAPIFetcher) FetchAzureSEVSNPVersionList(ctx context.Context, attestation configapi.AzureSEVSNPVersionList) (configapi.AzureSEVSNPVersionList, error) {
return fetch(ctx, f.httpc, attestation)
}
// FetchAzureSEVSNPVersion fetches the version information from the config API.
func (f *configAPIFetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion configapi.AzureSEVSNPVersionGet, version versionsapi.Version) (configapi.AzureSEVSNPVersionGet, error) {
cosignPublicKey, err := sigstore.CosignPublicKeyForVersion(version)
if err != nil {
return azureVersion, fmt.Errorf("get public key for config: %w", err)
}
urlString, err := azureVersion.URL()
if err != nil {
return azureVersion, err
}
fetchedVersion, err := fetch(ctx, f.httpc, azureVersion)
if err != nil {
return fetchedVersion, fmt.Errorf("fetch version %s: %w", fetchedVersion.Version, err)
}
versionBytes, err := json.Marshal(fetchedVersion)
if err != nil {
return fetchedVersion, fmt.Errorf("marshal version for verify %s: %w", fetchedVersion.Version, err)
}
signature, err := fetchBytesFromRawURL(ctx, fmt.Sprintf("%s.sig", urlString), f.httpc)
if err != nil {
return fetchedVersion, fmt.Errorf("fetch version %s signature: %w", fetchedVersion.Version, err)
}
err = sigstore.CosignVerifier{}.VerifySignature(versionBytes, signature, cosignPublicKey)
if err != nil {
return fetchedVersion, fmt.Errorf("verify version %s signature: %w", fetchedVersion.Version, err)
}
return fetchedVersion, nil
}
// FetchLatestAzureSEVSNPVersion returns the latest versions of the given type.
func (f *configAPIFetcher) FetchLatestAzureSEVSNPVersion(ctx context.Context, version versionsapi.Version) (res configapi.AzureSEVSNPVersion, err error) {
var versions configapi.AzureSEVSNPVersionList
versions, err = f.FetchAzureSEVSNPVersionList(ctx, versions)
if err != nil {
return res, fmt.Errorf("fetching versions list: %w", err)
}
get := configapi.AzureSEVSNPVersionGet{Version: versions[0]} // get latest version (as sorted reversely alphanumerically)
get, err = f.FetchAzureSEVSNPVersion(ctx, get, version)
if err != nil {
return res, fmt.Errorf("failed fetching version: %w", err)
}
return get.AzureSEVSNPVersion, nil
}
func fetchBytesFromRawURL(ctx context.Context, urlString string, client HTTPClient) ([]byte, error) {
url, err := url.Parse(urlString)
if err != nil {
return nil, fmt.Errorf("parse version url %s: %w", urlString, err)
}
return getFromURL(ctx, client, url)
}

View File

@ -5,11 +5,15 @@ SPDX-License-Identifier: AGPL-3.0-only
*/
/*
Package fetcher implements a client for the versions API.
Package fetcher implements a client for the Constellation Resource API.
The fetcher is used to get information from the versions API without having to
authenticate with AWS, where the API is currently hosted. This package should be
used in user-facing application code most of the time, like the CLI.
Each sub-API included in the Constellation Resource API should define it's resources by implementing types that implement apiObject.
The new package can then call this package's Fetch function to get the resource from the API.
To modify resources, pkg internal/api/client should be used in a similar fashion.
*/
package fetcher
@ -17,56 +21,17 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
// fetcher fetches versions API resources without authentication.
type fetcher struct {
httpc HTTPClient
}
// NewHTTPClient returns a new http client.
func NewHTTPClient() HTTPClient {
return &http.Client{Transport: &http.Transport{DisableKeepAlives: true}} // DisableKeepAlives fixes concurrency issue see https://stackoverflow.com/a/75816347
}
func newFetcherWith(client HTTPClient) *fetcher {
return &fetcher{
httpc: client,
}
}
func newFetcher() *fetcher {
return newFetcherWith(NewHTTPClient()) // DisableKeepAlives fixes concurrency issue see https://stackoverflow.com/a/75816347
}
type apiObject interface {
ValidateRequest() error
Validate() error
URL() (string, error)
}
// getFromURL fetches the content from the given URL and returns the content as a byte slice.
func getFromURL(ctx context.Context, client HTTPClient, sourceURL *url.URL) ([]byte, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, sourceURL.String(), http.NoBody)
if err != nil {
return nil, err
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("http status code: %d", resp.StatusCode)
}
return io.ReadAll(resp.Body)
}
func fetch[T apiObject](ctx context.Context, c HTTPClient, obj T) (T, error) {
// Fetch fetches the given apiObject from the public Constellation CDN.
// Fetch does not require authentication.
func Fetch[T apiObject](ctx context.Context, c HTTPClient, obj T) (T, error) {
if err := obj.ValidateRequest(); err != nil {
return *new(T), fmt.Errorf("validating request for %T: %w", obj, err)
}
@ -123,3 +88,9 @@ func (e *NotFoundError) Unwrap() error {
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
type apiObject interface {
ValidateRequest() error
Validate() error
URL() (string, error)
}

View File

@ -1,43 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package fetcher
import (
"context"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
)
// VersionAPIFetcher fetches version API resources without authentication.
type VersionAPIFetcher struct {
*fetcher
}
// NewVersionAPIFetcher returns a new Fetcher.
func NewVersionAPIFetcher() *VersionAPIFetcher {
return &VersionAPIFetcher{newFetcher()}
}
// FetchVersionList fetches the given version list from the versions API.
func (f *VersionAPIFetcher) FetchVersionList(ctx context.Context, list versionsapi.List) (versionsapi.List, error) {
return fetch(ctx, f.httpc, list)
}
// FetchVersionLatest fetches the latest version from the versions API.
func (f *VersionAPIFetcher) FetchVersionLatest(ctx context.Context, latest versionsapi.Latest) (versionsapi.Latest, error) {
return fetch(ctx, f.httpc, latest)
}
// FetchImageInfo fetches the given image info from the versions API.
func (f *VersionAPIFetcher) FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) {
return fetch(ctx, f.httpc, imageInfo)
}
// FetchCLIInfo fetches the given cli info from the versions API.
func (f *VersionAPIFetcher) FetchCLIInfo(ctx context.Context, cliInfo versionsapi.CLIInfo) (versionsapi.CLIInfo, error) {
return fetch(ctx, f.httpc, cliInfo)
}

View File

@ -2,7 +2,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "versionsapi",
name = "versions",
srcs = [
"apiconstants.go",
"cliinfo.go",
@ -12,7 +12,7 @@ go_library(
"version.go",
"versionsapi.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/versionsapi",
importpath = "github.com/edgelesssys/constellation/v2/internal/api/versions",
visibility = ["//:__subpackages__"],
deps = [
"//internal/constants",
@ -21,7 +21,7 @@ go_library(
)
go_test(
name = "versionsapi_test",
name = "versions_test",
srcs = [
"cliinfo_test.go",
"imageinfo_test.go",
@ -29,7 +29,7 @@ go_test(
"list_test.go",
"version_test.go",
],
embed = [":versionsapi"],
embed = [":versions"],
deps = [
"//internal/cloud/cloudprovider",
"//internal/constants",

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
var (
// APIV1 is the v1 API version.

View File

@ -9,11 +9,12 @@ go_library(
"main.go",
"rm.go",
],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/versionsapi/cli",
importpath = "github.com/edgelesssys/constellation/v2/internal/api/versions/cli",
visibility = ["//visibility:private"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versionsapi/client",
"//internal/api/fetcher",
"//internal/api/versions",
"//internal/api/versions/client",
"//internal/constants",
"//internal/logger",
"@com_github_aws_aws_sdk_go_v2_config//:config",

View File

@ -11,8 +11,9 @@ import (
"errors"
"fmt"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
verclient "github.com/edgelesssys/constellation/v2/internal/api/versionsapi/client"
apifetcher "github.com/edgelesssys/constellation/v2/internal/api/fetcher"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
verclient "github.com/edgelesssys/constellation/v2/internal/api/versions/client"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/cobra"
"go.uber.org/zap/zapcore"
@ -104,7 +105,7 @@ func runAdd(cmd *cobra.Command, _ []string) (retErr error) {
return nil
}
func ensureVersion(ctx context.Context, client *verclient.Client, kind versionsapi.VersionKind, ver versionsapi.Version, gran versionsapi.Granularity,
func ensureVersion(ctx context.Context, client *verclient.VersionsClient, kind versionsapi.VersionKind, ver versionsapi.Version, gran versionsapi.Granularity,
log *logger.Logger,
) error {
verListReq := versionsapi.List{
@ -115,7 +116,7 @@ func ensureVersion(ctx context.Context, client *verclient.Client, kind versionsa
Kind: kind,
}
verList, err := client.FetchVersionList(ctx, verListReq)
var notFoundErr *verclient.NotFoundError
var notFoundErr *apifetcher.NotFoundError
if errors.As(err, &notFoundErr) {
log.Infof("Version list for %s versions under %q does not exist. Creating new list", gran.String(), ver.Major())
verList = verListReq
@ -144,14 +145,14 @@ func ensureVersion(ctx context.Context, client *verclient.Client, kind versionsa
return nil
}
func updateLatest(ctx context.Context, client *verclient.Client, kind versionsapi.VersionKind, ver versionsapi.Version, log *logger.Logger) error {
func updateLatest(ctx context.Context, client *verclient.VersionsClient, kind versionsapi.VersionKind, ver versionsapi.Version, log *logger.Logger) error {
latest := versionsapi.Latest{
Ref: ver.Ref,
Stream: ver.Stream,
Kind: kind,
}
latest, err := client.FetchVersionLatest(ctx, latest)
var notFoundErr *verclient.NotFoundError
var notFoundErr *apifetcher.NotFoundError
if errors.As(err, &notFoundErr) {
log.Debugf("Latest version for ref %q and stream %q not found", ver.Ref, ver.Stream)
} else if err != nil {

View File

@ -10,8 +10,8 @@ import (
"encoding/json"
"fmt"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
verclient "github.com/edgelesssys/constellation/v2/internal/api/versionsapi/client"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
verclient "github.com/edgelesssys/constellation/v2/internal/api/versions/client"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/spf13/cobra"
"go.uber.org/zap/zapcore"

View File

@ -16,8 +16,9 @@ import (
"go.uber.org/zap/zapcore"
"golang.org/x/mod/semver"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
verclient "github.com/edgelesssys/constellation/v2/internal/api/versionsapi/client"
apifetcher "github.com/edgelesssys/constellation/v2/internal/api/fetcher"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
verclient "github.com/edgelesssys/constellation/v2/internal/api/versions/client"
"github.com/edgelesssys/constellation/v2/internal/logger"
)
@ -63,7 +64,7 @@ func runList(cmd *cobra.Command, _ []string) error {
} else {
log.Debugf("Getting minor versions")
minorVersions, err = listMinorVersions(cmd.Context(), client, flags.ref, flags.stream)
var errNotFound *verclient.NotFoundError
var errNotFound *apifetcher.NotFoundError
if err != nil && errors.As(err, &errNotFound) {
log.Infof("No minor versions found for ref %q and stream %q.", flags.ref, flags.stream)
return nil
@ -74,7 +75,7 @@ func runList(cmd *cobra.Command, _ []string) error {
log.Debugf("Getting patch versions")
patchVersions, err := listPatchVersions(cmd.Context(), client, flags.ref, flags.stream, minorVersions)
var errNotFound *verclient.NotFoundError
var errNotFound *apifetcher.NotFoundError
if err != nil && errors.As(err, &errNotFound) {
log.Infof("No patch versions found for ref %q, stream %q and minor versions %v.", flags.ref, flags.stream, minorVersions)
return nil
@ -104,7 +105,7 @@ func runList(cmd *cobra.Command, _ []string) error {
return nil
}
func listMinorVersions(ctx context.Context, client *verclient.Client, ref string, stream string) ([]string, error) {
func listMinorVersions(ctx context.Context, client *verclient.VersionsClient, ref string, stream string) ([]string, error) {
list := versionsapi.List{
Ref: ref,
Stream: stream,
@ -120,7 +121,7 @@ func listMinorVersions(ctx context.Context, client *verclient.Client, ref string
return list.Versions, nil
}
func listPatchVersions(ctx context.Context, client *verclient.Client, ref string, stream string, minorVer []string,
func listPatchVersions(ctx context.Context, client *verclient.VersionsClient, ref string, stream string, minorVer []string,
) ([]versionsapi.Version, error) {
var patchVers []versionsapi.Version

View File

@ -24,8 +24,9 @@ import (
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
"github.com/aws/smithy-go"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
verclient "github.com/edgelesssys/constellation/v2/internal/api/versionsapi/client"
apifetcher "github.com/edgelesssys/constellation/v2/internal/api/fetcher"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
verclient "github.com/edgelesssys/constellation/v2/internal/api/versions/client"
"github.com/edgelesssys/constellation/v2/internal/logger"
gaxv2 "github.com/googleapis/gax-go/v2"
"github.com/spf13/cobra"
@ -157,7 +158,7 @@ func deleteRef(ctx context.Context, clients rmImageClients, ref string, dryrun b
log.Infof("Listing versions of stream %s", stream)
minorVersions, err := listMinorVersions(ctx, clients.version, ref, stream)
var notFoundErr *verclient.NotFoundError
var notFoundErr *apifetcher.NotFoundError
if errors.As(err, &notFoundErr) {
log.Debugf("No minor versions found for stream %s", stream)
continue
@ -202,7 +203,7 @@ func deleteImage(ctx context.Context, clients rmImageClients, ver versionsapi.Ve
Version: ver.Version,
}
imageInfo, err := clients.version.FetchImageInfo(ctx, imageInfo)
var notFound *verclient.NotFoundError
var notFound *apifetcher.NotFoundError
if errors.As(err, &notFound) {
log.Warnf("Image info for %s not found", ver.Version)
log.Warnf("Skipping image deletion")
@ -239,7 +240,7 @@ func deleteImage(ctx context.Context, clients rmImageClients, ver versionsapi.Ve
}
type rmImageClients struct {
version *verclient.Client
version *verclient.VersionsClient
gcp *gcpClient
aws *awsClient
az *azureClient

View File

@ -0,0 +1,16 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "client",
srcs = ["client.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/versions/client",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/client",
"//internal/api/fetcher",
"//internal/api/versions",
"//internal/constants",
"//internal/logger",
"@org_golang_x_mod//semver",
],
)

View File

@ -0,0 +1,230 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
/*
Package client provides a versions API specific implementation of the general API client.
*/
package client
import (
"context"
"errors"
"fmt"
"path"
"golang.org/x/mod/semver"
apiclient "github.com/edgelesssys/constellation/v2/internal/api/client"
apifetcher "github.com/edgelesssys/constellation/v2/internal/api/fetcher"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"
)
// VersionsClient is a client for the versions API.
type VersionsClient struct {
*apiclient.Client
}
// NewClient creates a new client for the versions API.
func NewClient(ctx context.Context, region, bucket, distributionID string, dryRun bool,
log *logger.Logger,
) (*VersionsClient, error) {
genericClient, err := apiclient.NewClient(ctx, region, bucket, distributionID, dryRun, log)
return &VersionsClient{genericClient}, err
}
// NewReadOnlyClient creates a new read-only client.
// This client can be used to fetch objects but cannot write updates.
func NewReadOnlyClient(ctx context.Context, region, bucket, distributionID string,
log *logger.Logger,
) (*VersionsClient, error) {
genericClient, err := apiclient.NewReadOnlyClient(ctx, region, bucket, distributionID, log)
return &VersionsClient{genericClient}, err
}
// FetchVersionList fetches the given version list from the versions API.
func (c *VersionsClient) FetchVersionList(ctx context.Context, list versionsapi.List) (versionsapi.List, error) {
return apiclient.Fetch(ctx, c.Client, list)
}
// UpdateVersionList updates the given version list in the versions API.
func (c *VersionsClient) UpdateVersionList(ctx context.Context, list versionsapi.List) error {
semver.Sort(list.Versions)
return apiclient.Update(ctx, c.Client, list)
}
// FetchVersionLatest fetches the latest version from the versions API.
func (c *VersionsClient) FetchVersionLatest(ctx context.Context, latest versionsapi.Latest) (versionsapi.Latest, error) {
return apiclient.Fetch(ctx, c.Client, latest)
}
// UpdateVersionLatest updates the latest version in the versions API.
func (c *VersionsClient) UpdateVersionLatest(ctx context.Context, latest versionsapi.Latest) error {
return apiclient.Update(ctx, c.Client, latest)
}
// FetchImageInfo fetches the given image info from the versions API.
func (c *VersionsClient) FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) {
return apiclient.Fetch(ctx, c.Client, imageInfo)
}
// UpdateImageInfo updates the given image info in the versions API.
func (c *VersionsClient) UpdateImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) error {
return apiclient.Update(ctx, c.Client, imageInfo)
}
// FetchCLIInfo fetches the given CLI info from the versions API.
func (c *VersionsClient) FetchCLIInfo(ctx context.Context, cliInfo versionsapi.CLIInfo) (versionsapi.CLIInfo, error) {
return apiclient.Fetch(ctx, c.Client, cliInfo)
}
// UpdateCLIInfo updates the given CLI info in the versions API.
func (c *VersionsClient) UpdateCLIInfo(ctx context.Context, cliInfo versionsapi.CLIInfo) error {
return apiclient.Update(ctx, c.Client, cliInfo)
}
// DeleteRef deletes the given ref from the versions API.
func (c *VersionsClient) DeleteRef(ctx context.Context, ref string) error {
if err := versionsapi.ValidateRef(ref); err != nil {
return fmt.Errorf("validating ref: %w", err)
}
refPath := path.Join(constants.CDNAPIPrefix, "ref", ref)
if err := c.Client.DeletePath(ctx, refPath); err != nil {
return fmt.Errorf("deleting ref path: %w", err)
}
return nil
}
// DeleteVersion deletes the given version from the versions API.
// The version will be removed from version lists and latest versions, and the versioned
// objects are deleted.
// Notice that the versions API can get into an inconsistent state if the version is the latest
// version but there is no older version of the same minor version available.
// Manual update of latest versions is required in this case.
func (c *VersionsClient) DeleteVersion(ctx context.Context, ver versionsapi.Version) error {
var retErr error
c.Client.Log.Debugf("Deleting version %s from minor version list", ver.Version)
possibleNewLatest, err := c.deleteVersionFromMinorVersionList(ctx, ver)
if err != nil {
retErr = errors.Join(retErr, fmt.Errorf("removing from minor version list: %w", err))
}
c.Client.Log.Debugf("Checking latest version for %s", ver.Version)
if err := c.deleteVersionFromLatest(ctx, ver, possibleNewLatest); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("updating latest version: %w", err))
}
c.Client.Log.Debugf("Deleting artifact path %s for %s", ver.ArtifactPath(versionsapi.APIV1), ver.Version)
if err := c.Client.DeletePath(ctx, ver.ArtifactPath(versionsapi.APIV1)); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting artifact path: %w", err))
}
return retErr
}
func (c *VersionsClient) deleteVersionFromMinorVersionList(ctx context.Context, ver versionsapi.Version,
) (*versionsapi.Latest, error) {
minorList := versionsapi.List{
Ref: ver.Ref,
Stream: ver.Stream,
Granularity: versionsapi.GranularityMinor,
Base: ver.WithGranularity(versionsapi.GranularityMinor),
Kind: versionsapi.VersionKindImage,
}
c.Client.Log.Debugf("Fetching minor version list for version %s", ver.Version)
minorList, err := c.FetchVersionList(ctx, minorList)
var notFoundErr *apifetcher.NotFoundError
if errors.As(err, &notFoundErr) {
c.Client.Log.Warnf("Minor version list for version %s not found", ver.Version)
c.Client.Log.Warnf("Skipping update of minor version list")
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("fetching minor version list for version %s: %w", ver.Version, err)
}
if !minorList.Contains(ver.Version) {
c.Client.Log.Warnf("Version %s is not in minor version list %s", ver.Version, minorList.JSONPath())
c.Client.Log.Warnf("Skipping update of minor version list")
return nil, nil
}
semver.Sort(minorList.Versions)
for i, v := range minorList.Versions {
if v == ver.Version {
minorList.Versions = append(minorList.Versions[:i], minorList.Versions[i+1:]...)
break
}
}
var latest *versionsapi.Latest
if len(minorList.Versions) != 0 {
latest = &versionsapi.Latest{
Ref: ver.Ref,
Stream: ver.Stream,
Kind: versionsapi.VersionKindImage,
Version: minorList.Versions[len(minorList.Versions)-1],
}
c.Client.Log.Debugf("Possible latest version replacement %q", latest.Version)
}
if c.Client.DryRun {
c.Client.Log.Debugf("DryRun: Updating minor version list %s to %v", minorList.JSONPath(), minorList)
return latest, nil
}
c.Client.Log.Debugf("Updating minor version list %s", minorList.JSONPath())
if err := c.UpdateVersionList(ctx, minorList); err != nil {
return latest, fmt.Errorf("updating minor version list %s: %w", minorList.JSONPath(), err)
}
c.Client.Log.Debugf("Removed version %s from minor version list %s", ver.Version, minorList.JSONPath())
return latest, nil
}
func (c *VersionsClient) deleteVersionFromLatest(ctx context.Context, ver versionsapi.Version, possibleNewLatest *versionsapi.Latest,
) error {
latest := versionsapi.Latest{
Ref: ver.Ref,
Stream: ver.Stream,
Kind: versionsapi.VersionKindImage,
}
c.Client.Log.Debugf("Fetching latest version from %s", latest.JSONPath())
latest, err := c.FetchVersionLatest(ctx, latest)
var notFoundErr *apifetcher.NotFoundError
if errors.As(err, &notFoundErr) {
c.Client.Log.Warnf("Latest version for %s not found", latest.JSONPath())
return nil
} else if err != nil {
return fmt.Errorf("fetching latest version: %w", err)
}
if latest.Version != ver.Version {
c.Client.Log.Debugf("Latest version is %s, not the deleted version %s", latest.Version, ver.Version)
return nil
}
if possibleNewLatest == nil {
c.Client.Log.Errorf("Latest version is %s, but no new latest version was found", latest.Version)
c.Client.Log.Errorf("A manual update of latest at %s might be needed", latest.JSONPath())
return fmt.Errorf("latest version is %s, but no new latest version was found", latest.Version)
}
if c.Client.DryRun {
c.Client.Log.Debugf("Would update latest version from %s to %s", latest.Version, possibleNewLatest.Version)
return nil
}
c.Client.Log.Infof("Updating latest version from %s to %s", latest.Version, possibleNewLatest.Version)
if err := c.UpdateVersionLatest(ctx, *possibleNewLatest); err != nil {
return fmt.Errorf("updating latest version: %w", err)
}
return nil
}

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"errors"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"testing"

View File

@ -0,0 +1,25 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//bazel/go:go_test.bzl", "go_test")
go_library(
name = "fetcher",
srcs = ["fetcher.go"],
importpath = "github.com/edgelesssys/constellation/v2/internal/api/versions/fetcher",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/fetcher",
"//internal/api/versions",
],
)
go_test(
name = "fetcher_test",
srcs = ["fetcher_test.go"],
embed = [":fetcher"],
deps = [
"//internal/api/versions",
"@com_github_stretchr_testify//assert",
"@com_github_stretchr_testify//require",
"@org_uber_go_goleak//:goleak",
],
)

View File

@ -0,0 +1,44 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package fetcher
import (
"context"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/api/versions"
)
// Fetcher fetches version API resources without authentication.
type Fetcher struct {
fetcher.HTTPClient
}
// New returns a new Fetcher.
func New() *Fetcher {
return &Fetcher{fetcher.NewHTTPClient()}
}
// FetchVersionList fetches the given version list from the versions API.
func (f *Fetcher) FetchVersionList(ctx context.Context, list versions.List) (versions.List, error) {
return fetcher.Fetch(ctx, f.HTTPClient, list)
}
// FetchVersionLatest fetches the latest version from the versions API.
func (f *Fetcher) FetchVersionLatest(ctx context.Context, latest versions.Latest) (versions.Latest, error) {
return fetcher.Fetch(ctx, f.HTTPClient, latest)
}
// FetchImageInfo fetches the given image info from the versions API.
func (f *Fetcher) FetchImageInfo(ctx context.Context, imageInfo versions.ImageInfo) (versions.ImageInfo, error) {
return fetcher.Fetch(ctx, f.HTTPClient, imageInfo)
}
// FetchCLIInfo fetches the given cli info from the versions API.
func (f *Fetcher) FetchCLIInfo(ctx context.Context, cliInfo versions.CLIInfo) (versions.CLIInfo, error) {
return fetcher.Fetch(ctx, f.HTTPClient, cliInfo)
}

View File

@ -14,7 +14,7 @@ import (
"net/http"
"testing"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
@ -190,7 +190,7 @@ func TestFetchVersionList(t *testing.T) {
return tc.serverResp
})
fetcher := VersionAPIFetcher{&fetcher{httpc: client}}
fetcher := Fetcher{client}
list, err := fetcher.FetchVersionList(context.Background(), tc.list)

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"errors"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"testing"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"errors"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"testing"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"errors"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"testing"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"encoding/json"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package versionsapi
package versions
import (
"fmt"

View File

@ -17,4 +17,4 @@ in these helper methods.
The package also provides helper functions that can be used in context of the versions API,
e.g. to validate versions.
*/
package versionsapi
package versions

View File

@ -1,472 +0,0 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
/*
Package client provides a client for the versions API.
The client needs to be authenticated with AWS. It should be used in internal
development and CI tools for administrative tasks. For just fetching information
from the API, use the fetcher package instead.
Needed IAM permissions for read mode:
- "s3:GetObject"
- "s3:ListBucket"
Additional needed IAM permissions for write mode:
- "s3:PutObject"
- "s3:DeleteObject"
- "cloudfront:CreateInvalidation"
Thread-safety of the bucket is not guaranteed. The client is not thread-safe.
*/
package client
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"path"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/cloudfront"
cftypes "github.com/aws/aws-sdk-go-v2/service/cloudfront/types"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"golang.org/x/mod/semver"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"
)
// Client is the client for the versions API.
type Client struct {
config aws.Config
cloudfrontClient *cloudfront.Client
s3Client *s3.Client
uploadClient *s3manager.Uploader
bucket string
distributionID string
cacheInvalidationWaitTimeout time.Duration
dirtyPaths []string // written paths to be invalidated
dryRun bool // no write operations are performed
log *logger.Logger
}
// NewReadOnlyClient creates a new read-only client.
// This client can be used to fetch objects but cannot write updates.
func NewReadOnlyClient(ctx context.Context, region, bucket, distributionID string,
log *logger.Logger,
) (*Client, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
s3c := s3.NewFromConfig(cfg)
return &Client{
config: cfg,
s3Client: s3c,
bucket: bucket,
distributionID: distributionID,
dryRun: true,
log: log,
}, nil
}
// NewClient creates a new client for the versions API.
func NewClient(ctx context.Context, region, bucket, distributionID string, dryRun bool,
log *logger.Logger,
) (*Client, error) {
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region))
if err != nil {
return nil, err
}
cloudfrontC := cloudfront.NewFromConfig(cfg)
s3C := s3.NewFromConfig(cfg)
uploadC := s3manager.NewUploader(s3C)
return &Client{
config: cfg,
cloudfrontClient: cloudfrontC,
s3Client: s3C,
uploadClient: uploadC,
bucket: bucket,
distributionID: distributionID,
dryRun: dryRun,
log: log,
cacheInvalidationWaitTimeout: 10 * time.Minute,
}, nil
}
// FetchVersionList fetches the given version list from the versions API.
func (c *Client) FetchVersionList(ctx context.Context, list versionsapi.List) (versionsapi.List, error) {
return fetch(ctx, c, list)
}
// UpdateVersionList updates the given version list in the versions API.
func (c *Client) UpdateVersionList(ctx context.Context, list versionsapi.List) error {
semver.Sort(list.Versions)
return update(ctx, c, list)
}
// FetchVersionLatest fetches the latest version from the versions API.
func (c *Client) FetchVersionLatest(ctx context.Context, latest versionsapi.Latest) (versionsapi.Latest, error) {
return fetch(ctx, c, latest)
}
// UpdateVersionLatest updates the latest version in the versions API.
func (c *Client) UpdateVersionLatest(ctx context.Context, latest versionsapi.Latest) error {
return update(ctx, c, latest)
}
// FetchImageInfo fetches the given image info from the versions API.
func (c *Client) FetchImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) (versionsapi.ImageInfo, error) {
return fetch(ctx, c, imageInfo)
}
// UpdateImageInfo updates the given image info in the versions API.
func (c *Client) UpdateImageInfo(ctx context.Context, imageInfo versionsapi.ImageInfo) error {
return update(ctx, c, imageInfo)
}
// FetchCLIInfo fetches the given CLI info from the versions API.
func (c *Client) FetchCLIInfo(ctx context.Context, cliInfo versionsapi.CLIInfo) (versionsapi.CLIInfo, error) {
return fetch(ctx, c, cliInfo)
}
// UpdateCLIInfo updates the given CLI info in the versions API.
func (c *Client) UpdateCLIInfo(ctx context.Context, cliInfo versionsapi.CLIInfo) error {
return update(ctx, c, cliInfo)
}
// DeleteRef deletes the given ref from the versions API.
func (c *Client) DeleteRef(ctx context.Context, ref string) error {
if err := versionsapi.ValidateRef(ref); err != nil {
return fmt.Errorf("validating ref: %w", err)
}
refPath := path.Join(constants.CDNAPIPrefix, "ref", ref)
if err := c.deletePath(ctx, refPath); err != nil {
return fmt.Errorf("deleting ref path: %w", err)
}
return nil
}
// DeleteVersion deletes the given version from the versions API.
// The version will be removed from version lists and latest versions, and the versioned
// objects are deleted.
// Notice that the versions API can get into an inconsistent state if the version is the latest
// version but there is no older version of the same minor version available.
// Manual update of latest versions is required in this case.
func (c *Client) DeleteVersion(ctx context.Context, ver versionsapi.Version) error {
var retErr error
c.log.Debugf("Deleting version %s from minor version list", ver.Version)
possibleNewLatest, err := c.deleteVersionFromMinorVersionList(ctx, ver)
if err != nil {
retErr = errors.Join(retErr, fmt.Errorf("removing from minor version list: %w", err))
}
c.log.Debugf("Checking latest version for %s", ver.Version)
if err := c.deleteVersionFromLatest(ctx, ver, possibleNewLatest); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("updating latest version: %w", err))
}
c.log.Debugf("Deleting artifact path %s for %s", ver.ArtifactPath(versionsapi.APIV1), ver.Version)
if err := c.deletePath(ctx, ver.ArtifactPath(versionsapi.APIV1)); err != nil {
retErr = errors.Join(retErr, fmt.Errorf("deleting artifact path: %w", err))
}
return retErr
}
// InvalidateCache invalidates the CDN cache for the paths that have been written.
// The function should be deferred after the client has been created.
func (c *Client) InvalidateCache(ctx context.Context) error {
if len(c.dirtyPaths) == 0 {
c.log.Debugf("No dirty paths, skipping cache invalidation")
return nil
}
if c.dryRun {
c.log.Debugf("DryRun: cloudfront create invalidation {DistributionID: %v, Paths: %v}", c.distributionID, c.dirtyPaths)
return nil
}
c.log.Debugf("Paths to invalidate: %v", c.dirtyPaths)
in := &cloudfront.CreateInvalidationInput{
DistributionId: &c.distributionID,
InvalidationBatch: &cftypes.InvalidationBatch{
CallerReference: ptr(fmt.Sprintf("%d", time.Now().Unix())),
Paths: &cftypes.Paths{
Items: c.dirtyPaths,
Quantity: ptr(int32(len(c.dirtyPaths))),
},
},
}
invalidation, err := c.cloudfrontClient.CreateInvalidation(ctx, in)
if err != nil {
return fmt.Errorf("creating invalidation: %w", err)
}
c.log.Debugf("Waiting for invalidation %s to complete", *invalidation.Invalidation.Id)
waiter := cloudfront.NewInvalidationCompletedWaiter(c.cloudfrontClient)
waitIn := &cloudfront.GetInvalidationInput{
DistributionId: &c.distributionID,
Id: invalidation.Invalidation.Id,
}
if err := waiter.Wait(ctx, waitIn, c.cacheInvalidationWaitTimeout); err != nil {
return fmt.Errorf("waiting for invalidation to complete: %w", err)
}
return nil
}
type apiObject interface {
ValidateRequest() error
Validate() error
JSONPath() string
}
func fetch[T apiObject](ctx context.Context, c *Client, obj T) (T, error) {
if err := obj.ValidateRequest(); err != nil {
return *new(T), fmt.Errorf("validating request for %T: %w", obj, err)
}
in := &s3.GetObjectInput{
Bucket: &c.bucket,
Key: ptr(obj.JSONPath()),
}
c.log.Debugf("Fetching %T from s3: %s", obj, obj.JSONPath())
out, err := c.s3Client.GetObject(ctx, in)
var noSuchkey *s3types.NoSuchKey
if errors.As(err, &noSuchkey) {
return *new(T), &NotFoundError{err: err}
} else if err != nil {
return *new(T), fmt.Errorf("getting s3 object at %s: %w", obj.JSONPath(), err)
}
defer out.Body.Close()
var newObj T
if err := json.NewDecoder(out.Body).Decode(&newObj); err != nil {
return *new(T), fmt.Errorf("decoding %T: %w", obj, err)
}
if newObj.Validate() != nil {
return *new(T), fmt.Errorf("received invalid %T: %w", newObj, newObj.Validate())
}
return newObj, nil
}
func update[T apiObject](ctx context.Context, c *Client, obj T) error {
if err := obj.Validate(); err != nil {
return fmt.Errorf("validating %T struct: %w", obj, err)
}
rawJSON, err := json.Marshal(obj)
if err != nil {
return fmt.Errorf("marshaling %T struct: %w", obj, err)
}
if c.dryRun {
c.log.Debugf("DryRun: s3 put object {Bucket: %v, Key: %v, Body: %v", c.bucket, obj.JSONPath(), string(rawJSON))
return nil
}
in := &s3.PutObjectInput{
Bucket: &c.bucket,
Key: ptr(obj.JSONPath()),
Body: bytes.NewBuffer(rawJSON),
}
c.dirtyPaths = append(c.dirtyPaths, "/"+obj.JSONPath())
c.log.Debugf("Uploading %T to s3: %v", obj, obj.JSONPath())
if _, err := c.uploadClient.Upload(ctx, in); err != nil {
return fmt.Errorf("uploading %T: %w", obj, err)
}
return nil
}
func (c *Client) deleteVersionFromMinorVersionList(ctx context.Context, ver versionsapi.Version,
) (*versionsapi.Latest, error) {
minorList := versionsapi.List{
Ref: ver.Ref,
Stream: ver.Stream,
Granularity: versionsapi.GranularityMinor,
Base: ver.WithGranularity(versionsapi.GranularityMinor),
Kind: versionsapi.VersionKindImage,
}
c.log.Debugf("Fetching minor version list for version %s", ver.Version)
minorList, err := c.FetchVersionList(ctx, minorList)
var notFoundErr *NotFoundError
if errors.As(err, &notFoundErr) {
c.log.Warnf("Minor version list for version %s not found", ver.Version)
c.log.Warnf("Skipping update of minor version list")
return nil, nil
} else if err != nil {
return nil, fmt.Errorf("fetching minor version list for version %s: %w", ver.Version, err)
}
if !minorList.Contains(ver.Version) {
c.log.Warnf("Version %s is not in minor version list %s", ver.Version, minorList.JSONPath())
c.log.Warnf("Skipping update of minor version list")
return nil, nil
}
semver.Sort(minorList.Versions)
for i, v := range minorList.Versions {
if v == ver.Version {
minorList.Versions = append(minorList.Versions[:i], minorList.Versions[i+1:]...)
break
}
}
var latest *versionsapi.Latest
if len(minorList.Versions) != 0 {
latest = &versionsapi.Latest{
Ref: ver.Ref,
Stream: ver.Stream,
Kind: versionsapi.VersionKindImage,
Version: minorList.Versions[len(minorList.Versions)-1],
}
c.log.Debugf("Possible latest version replacement %q", latest.Version)
}
if c.dryRun {
c.log.Debugf("DryRun: Updating minor version list %s to %v", minorList.JSONPath(), minorList)
return latest, nil
}
c.log.Debugf("Updating minor version list %s", minorList.JSONPath())
if err := c.UpdateVersionList(ctx, minorList); err != nil {
return latest, fmt.Errorf("updating minor version list %s: %w", minorList.JSONPath(), err)
}
c.log.Debugf("Removed version %s from minor version list %s", ver.Version, minorList.JSONPath())
return latest, nil
}
func (c *Client) deleteVersionFromLatest(ctx context.Context, ver versionsapi.Version, possibleNewLatest *versionsapi.Latest,
) error {
latest := versionsapi.Latest{
Ref: ver.Ref,
Stream: ver.Stream,
Kind: versionsapi.VersionKindImage,
}
c.log.Debugf("Fetching latest version from %s", latest.JSONPath())
latest, err := c.FetchVersionLatest(ctx, latest)
var notFoundErr *NotFoundError
if errors.As(err, &notFoundErr) {
c.log.Warnf("Latest version for %s not found", latest.JSONPath())
return nil
} else if err != nil {
return fmt.Errorf("fetching latest version: %w", err)
}
if latest.Version != ver.Version {
c.log.Debugf("Latest version is %s, not the deleted version %s", latest.Version, ver.Version)
return nil
}
if possibleNewLatest == nil {
c.log.Errorf("Latest version is %s, but no new latest version was found", latest.Version)
c.log.Errorf("A manual update of latest at %s might be needed", latest.JSONPath())
return fmt.Errorf("latest version is %s, but no new latest version was found", latest.Version)
}
if c.dryRun {
c.log.Debugf("Would update latest version from %s to %s", latest.Version, possibleNewLatest.Version)
return nil
}
c.log.Infof("Updating latest version from %s to %s", latest.Version, possibleNewLatest.Version)
if err := c.UpdateVersionLatest(ctx, *possibleNewLatest); err != nil {
return fmt.Errorf("updating latest version: %w", err)
}
return nil
}
func (c *Client) deletePath(ctx context.Context, path string) error {
listIn := &s3.ListObjectsV2Input{
Bucket: &c.bucket,
Prefix: &path,
}
c.log.Debugf("Listing objects in %s", path)
objs := []s3types.Object{}
out := &s3.ListObjectsV2Output{IsTruncated: true}
for out.IsTruncated {
var err error
out, err = c.s3Client.ListObjectsV2(ctx, listIn)
if err != nil {
return fmt.Errorf("listing objects in %s: %w", path, err)
}
objs = append(objs, out.Contents...)
}
c.log.Debugf("Found %d objects in %s", len(objs), path)
if len(objs) == 0 {
c.log.Warnf("Path %s is already empty", path)
return nil
}
objIDs := make([]s3types.ObjectIdentifier, len(objs))
for i, obj := range objs {
objIDs[i] = s3types.ObjectIdentifier{Key: obj.Key}
}
if c.dryRun {
c.log.Debugf("DryRun: Deleting %d objects with IDs %v", len(objs), objIDs)
return nil
}
c.dirtyPaths = append(c.dirtyPaths, "/"+path)
deleteIn := &s3.DeleteObjectsInput{
Bucket: &c.bucket,
Delete: &s3types.Delete{
Objects: objIDs,
},
}
c.log.Debugf("Deleting %d objects in %s", len(objs), path)
if _, err := c.s3Client.DeleteObjects(ctx, deleteIn); err != nil {
return fmt.Errorf("deleting objects in %s: %w", path, err)
}
return nil
}
// NotFoundError is an error that is returned when a resource is not found.
type NotFoundError struct {
err error
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("the requested resource was not found: %s", e.err.Error())
}
func (e *NotFoundError) Unwrap() error {
return e.err
}
func ptr[T any](t T) *T {
return &t
}

View File

@ -13,7 +13,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/measurements",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/cloud/cloudprovider",
"//internal/sigstore",
"//internal/variant",
@ -28,7 +28,7 @@ go_test(
srcs = ["measurements_test.go"],
embed = [":measurements"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/cloud/cloudprovider",
"//internal/sigstore",
"//internal/variant",

View File

@ -7,7 +7,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/attestation/measurements/measurement-generator",
visibility = ["//visibility:private"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/sigstore",

View File

@ -23,7 +23,7 @@ import (
"strconv"
"strings"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/sigstore"

View File

@ -31,7 +31,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
"gopkg.in/yaml.v3"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
"github.com/edgelesssys/constellation/v2/internal/variant"

View File

@ -20,7 +20,7 @@ import (
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
"github.com/edgelesssys/constellation/v2/internal/variant"

View File

@ -5,6 +5,7 @@ go_library(
name = "config",
srcs = [
"attestation.go",
"attestationversion.go",
"azure.go",
"config.go",
"config_doc.go",
@ -18,9 +19,9 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/config",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/configapi",
"//internal/api/fetcher",
"//internal/api/versionsapi",
"//internal/api/attestationconfig",
"//internal/api/attestationconfig/fetcher",
"//internal/api/versions",
"//internal/attestation/idkeydigest",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
@ -44,14 +45,15 @@ go_test(
name = "config_test",
srcs = [
"attestation_test.go",
"attestationversion_test.go",
"config_test.go",
"validation_test.go",
],
data = glob(["testdata/**"]),
embed = [":config"],
deps = [
"//internal/api/configapi",
"//internal/api/versionsapi",
"//internal/api/attestationconfig",
"//internal/api/versions",
"//internal/attestation/measurements",
"//internal/cloud/cloudprovider",
"//internal/config/instancetypes",

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package configapi
package config
import (
"encoding/json"

View File

@ -4,7 +4,7 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package configapi
package config
import (
"testing"

View File

@ -11,9 +11,10 @@ import (
"context"
"fmt"
"github.com/edgelesssys/constellation/v2/internal/api/configapi"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -27,16 +28,16 @@ type AzureSEVSNP struct {
Measurements measurements.M `json:"measurements" yaml:"measurements" validate:"required,no_placeholders"`
// description: |
// Lowest acceptable bootloader version.
BootloaderVersion configapi.AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"`
BootloaderVersion AttestationVersion `json:"bootloaderVersion" yaml:"bootloaderVersion"`
// description: |
// Lowest acceptable TEE version.
TEEVersion configapi.AttestationVersion `json:"teeVersion" yaml:"teeVersion"`
TEEVersion AttestationVersion `json:"teeVersion" yaml:"teeVersion"`
// description: |
// Lowest acceptable SEV-SNP version.
SNPVersion configapi.AttestationVersion `json:"snpVersion" yaml:"snpVersion"`
SNPVersion AttestationVersion `json:"snpVersion" yaml:"snpVersion"`
// description: |
// Lowest acceptable microcode version.
MicrocodeVersion configapi.AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"`
MicrocodeVersion AttestationVersion `json:"microcodeVersion" yaml:"microcodeVersion"`
// description: |
// Configuration for validating the firmware signature.
FirmwareSignerConfig SNPFirmwareSignerConfig `json:"firmwareSignerConfig" yaml:"firmwareSignerConfig"`
@ -50,10 +51,10 @@ type AzureSEVSNP struct {
func DefaultForAzureSEVSNP() *AzureSEVSNP {
return &AzureSEVSNP{
Measurements: measurements.DefaultsFor(cloudprovider.Azure, variant.AzureSEVSNP{}),
BootloaderVersion: configapi.NewLatestPlaceholderVersion(),
TEEVersion: configapi.NewLatestPlaceholderVersion(),
SNPVersion: configapi.NewLatestPlaceholderVersion(),
MicrocodeVersion: configapi.NewLatestPlaceholderVersion(),
BootloaderVersion: NewLatestPlaceholderVersion(),
TEEVersion: NewLatestPlaceholderVersion(),
SNPVersion: NewLatestPlaceholderVersion(),
MicrocodeVersion: NewLatestPlaceholderVersion(),
FirmwareSignerConfig: SNPFirmwareSignerConfig{
AcceptedKeyDigests: idkeydigest.DefaultList(),
EnforcementPolicy: idkeydigest.MAAFallback,
@ -97,13 +98,13 @@ func (c AzureSEVSNP) EqualTo(old AttestationCfg) (bool, error) {
}
// FetchAndSetLatestVersionNumbers fetches the latest version numbers from the configapi and sets them.
func (c *AzureSEVSNP) FetchAndSetLatestVersionNumbers(fetcher fetcher.ConfigAPIFetcher, version versionsapi.Version) error {
versions, err := fetcher.FetchLatestAzureSEVSNPVersion(context.Background(), version)
func (c *AzureSEVSNP) FetchAndSetLatestVersionNumbers(fetcher attestationconfigfetcher.AttestationConfigAPIFetcher, version versionsapi.Version) error {
versions, err := fetcher.FetchAzureSEVSNPVersionLatest(context.Background(), version)
if err != nil {
return err
}
// set number and keep isLatest flag
c.mergeVersionNumbers(versions)
c.mergeVersionNumbers(versions.AzureSEVSNPVersion)
return nil
}

View File

@ -31,8 +31,8 @@ import (
"github.com/go-playground/validator/v10"
en_translations "github.com/go-playground/validator/v10/translations/en"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
attestationconfigfetcher "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/idkeydigest"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -385,7 +385,7 @@ func fromFile(fileHandler file.Handler, name string) (*Config, error) {
// 2. For "latest" version values of the attestation variants fetch the version numbers.
// 3. Read secrets from environment variables.
// 4. Validate config. If `--force` is set the version validation will be disabled and any version combination is allowed.
func New(fileHandler file.Handler, name string, fetcher fetcher.ConfigAPIFetcher, force bool) (*Config, error) {
func New(fileHandler file.Handler, name string, fetcher attestationconfigfetcher.AttestationConfigAPIFetcher, force bool) (*Config, error) {
// Read config file
c, err := fromFile(fileHandler, name)
if err != nil {

View File

@ -21,8 +21,8 @@ import (
"go.uber.org/goleak"
"gopkg.in/yaml.v3"
"github.com/edgelesssys/constellation/v2/internal/api/configapi"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config/instancetypes"
@ -75,11 +75,11 @@ func TestReadConfigFile(t *testing.T) {
configName: constants.ConfigFilename,
wantResult: func() *Config {
conf := Default()
conf.Attestation.AzureSEVSNP.BootloaderVersion = configapi.AttestationVersion{
conf.Attestation.AzureSEVSNP.BootloaderVersion = AttestationVersion{
Value: 1,
IsLatest: false,
}
conf.Attestation.AzureSEVSNP.TEEVersion = configapi.AttestationVersion{
conf.Attestation.AzureSEVSNP.TEEVersion = AttestationVersion{
Value: 2,
IsLatest: false,
}
@ -892,8 +892,10 @@ func (f fakeConfigFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ configap
}, nil
}
func (f fakeConfigFetcher) FetchLatestAzureSEVSNPVersion(_ context.Context, _ versionsapi.Version) (configapi.AzureSEVSNPVersion, error) {
return testCfg, nil
func (f fakeConfigFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context, _ versionsapi.Version) (configapi.AzureSEVSNPVersionGet, error) {
return configapi.AzureSEVSNPVersionGet{
AzureSEVSNPVersion: testCfg,
}, nil
}
var testCfg = configapi.AzureSEVSNPVersion{

View File

@ -19,7 +19,7 @@ import (
"github.com/go-playground/validator/v10"
"golang.org/x/mod/semver"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/compatibility"

View File

@ -193,6 +193,8 @@ const (
CDNAPIPrefix = CDNAPIBase + "/v1"
// CDNAPIPrefixV2 is the prefix of the Constellation API (v2).
CDNAPIPrefixV2 = CDNAPIBase + "/v2"
// CDNAttestationConfigPrefixV1 is the prefix of the Constellation AttestationConfig API (v1).
CDNAttestationConfigPrefixV1 = CDNAPIPrefix + "/attestation"
// CDNMeasurementsFile is name of file containing image measurements.
CDNMeasurementsFile = "measurements.json"
// CDNMeasurementsSignature is name of file containing signature for CDNMeasurementsFile.

View File

@ -11,7 +11,8 @@ go_library(
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/fetcher",
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/api/versions/fetcher",
"//internal/cloud/cloudprovider",
"//internal/variant",
"@com_github_schollz_progressbar_v3//:progressbar",
@ -27,7 +28,7 @@ go_test(
],
embed = [":imagefetcher"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/cloud/cloudprovider",
"//internal/file",
"//internal/variant",

View File

@ -20,7 +20,8 @@ import (
"regexp"
"github.com/edgelesssys/constellation/v2/internal/api/fetcher"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
versionsfetcher "github.com/edgelesssys/constellation/v2/internal/api/versions/fetcher"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/variant"
"github.com/spf13/afero"
@ -35,7 +36,7 @@ type Fetcher struct {
// New returns a new image fetcher.
func New() *Fetcher {
return &Fetcher{
fetcher: fetcher.NewVersionAPIFetcher(),
fetcher: versionsfetcher.New(),
fs: &afero.Afero{Fs: afero.NewOsFs()},
}
}

View File

@ -13,7 +13,7 @@ import (
"net/http"
"testing"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/edgelesssys/constellation/v2/internal/variant"

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/cloud/cloudprovider",
"//internal/osimage/secureboot",
],

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/archive",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/constants",
"//internal/logger",
"@com_github_aws_aws_sdk_go_v2_config//:config",

View File

@ -16,7 +16,7 @@ import (
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"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"
)

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/aws",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/logger",
"//internal/osimage",
"//internal/osimage/secureboot",

View File

@ -23,7 +23,7 @@ import (
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/aws/smithy-go"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/osimage/secureboot"

View File

@ -9,7 +9,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/azure",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/logger",
"//internal/osimage",
"@com_github_azure_azure_sdk_for_go_sdk_azcore//runtime",

View File

@ -21,7 +21,7 @@ import (
armcomputev4 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v4"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/pageblob"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
)

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/gcp",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/logger",
"//internal/osimage",
"//internal/osimage/secureboot",

View File

@ -19,7 +19,7 @@ import (
compute "cloud.google.com/go/compute/apiv1"
"cloud.google.com/go/compute/apiv1/computepb"
"cloud.google.com/go/storage"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/osimage"
"github.com/edgelesssys/constellation/v2/internal/osimage/secureboot"

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/imageinfo",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/constants",
"//internal/logger",
"@com_github_aws_aws_sdk_go_v2_config//:config",

View File

@ -17,7 +17,7 @@ import (
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"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"
)

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/measurementsuploader",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/attestation/measurements",
"//internal/constants",
"//internal/logger",

View File

@ -18,7 +18,7 @@ import (
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"
"github.com/edgelesssys/constellation/v2/internal/api/versionsapi"
versionsapi "github.com/edgelesssys/constellation/v2/internal/api/versions"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/logger"

View File

@ -6,7 +6,7 @@ go_library(
importpath = "github.com/edgelesssys/constellation/v2/internal/osimage/nop",
visibility = ["//:__subpackages__"],
deps = [
"//internal/api/versionsapi",
"//internal/api/versions",
"//internal/logger",
"//internal/osimage",
],

Some files were not shown because too many files have changed in this diff Show More