mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-26 07:16:08 -05:00
config: Azure SNP tool can delete specific version from attestation API (#1863)
* client supports delete version * rename to new attestation / fetcher naming * add delete command to upload tool * test client delete * bazel update * use general client in attestation client * Update hack/configapi/cmd/delete.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * daniel feedback * unit test azure sev upload * Update hack/configapi/cmd/delete.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * add client integration test * new client cmds use apiObject --------- Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
This commit is contained in:
parent
315b6c2f01
commit
c446f36b0f
@ -16,7 +16,7 @@ import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
configapi "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
|
||||
"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"
|
||||
@ -281,7 +281,7 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
||||
require.NoError(err)
|
||||
cfm := &configFetchMeasurementsCmd{canFetchMeasurements: true, log: logger.NewTest(t)}
|
||||
|
||||
err = cfm.configFetchMeasurements(cmd, tc.cosign, tc.rekor, fileHandler, fakeConfigFetcher{}, client)
|
||||
err = cfm.configFetchMeasurements(cmd, tc.cosign, tc.rekor, fileHandler, fakeAttestationFetcher{}, client)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
return
|
||||
@ -291,27 +291,27 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type fakeConfigFetcher struct{}
|
||||
type fakeAttestationFetcher struct{}
|
||||
|
||||
func (f fakeConfigFetcher) FetchAzureSEVSNPVersionList(_ context.Context, _ configapi.AzureSEVSNPVersionList) (configapi.AzureSEVSNPVersionList, error) {
|
||||
return configapi.AzureSEVSNPVersionList(
|
||||
func (f fakeAttestationFetcher) FetchAzureSEVSNPVersionList(_ context.Context, _ attestationconfig.AzureSEVSNPVersionList) (attestationconfig.AzureSEVSNPVersionList, error) {
|
||||
return attestationconfig.AzureSEVSNPVersionList(
|
||||
[]string{},
|
||||
), nil
|
||||
}
|
||||
|
||||
func (f fakeConfigFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ configapi.AzureSEVSNPVersionGet) (configapi.AzureSEVSNPVersionGet, error) {
|
||||
return configapi.AzureSEVSNPVersionGet{
|
||||
func (f fakeAttestationFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ attestationconfig.AzureSEVSNPVersionAPI) (attestationconfig.AzureSEVSNPVersionAPI, error) {
|
||||
return attestationconfig.AzureSEVSNPVersionAPI{
|
||||
AzureSEVSNPVersion: testCfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f fakeConfigFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context) (configapi.AzureSEVSNPVersionGet, error) {
|
||||
return configapi.AzureSEVSNPVersionGet{
|
||||
func (f fakeAttestationFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context) (attestationconfig.AzureSEVSNPVersionAPI, error) {
|
||||
return attestationconfig.AzureSEVSNPVersionAPI{
|
||||
AzureSEVSNPVersion: testCfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var testCfg = configapi.AzureSEVSNPVersion{
|
||||
var testCfg = attestationconfig.AzureSEVSNPVersion{
|
||||
Microcode: 93,
|
||||
TEE: 0,
|
||||
SNP: 6,
|
||||
|
@ -202,7 +202,7 @@ func TestCreate(t *testing.T) {
|
||||
|
||||
fileHandler := file.NewHandler(tc.setupFs(require, tc.provider))
|
||||
c := &createCmd{log: logger.NewTest(t)}
|
||||
err := c.create(cmd, tc.creator, fileHandler, &nopSpinner{}, fakeConfigFetcher{})
|
||||
err := c.create(cmd, tc.creator, fileHandler, &nopSpinner{}, fakeAttestationFetcher{})
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
|
@ -175,7 +175,7 @@ func TestInitialize(t *testing.T) {
|
||||
defer cancel()
|
||||
cmd.SetContext(ctx)
|
||||
i := &initCmd{log: logger.NewTest(t), spinner: &nopSpinner{}}
|
||||
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, fakeConfigFetcher{})
|
||||
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, fakeAttestationFetcher{})
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
@ -519,7 +519,7 @@ func TestAttestation(t *testing.T) {
|
||||
cmd.SetContext(ctx)
|
||||
|
||||
i := &initCmd{log: logger.NewTest(t), spinner: &nopSpinner{}}
|
||||
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, fakeConfigFetcher{})
|
||||
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, fakeAttestationFetcher{})
|
||||
assert.Error(err)
|
||||
// make sure the error is actually a TLS handshake error
|
||||
assert.Contains(err.Error(), "transport: authentication handshake failed")
|
||||
|
@ -163,7 +163,7 @@ func TestRecover(t *testing.T) {
|
||||
))
|
||||
|
||||
newDialer := func(atls.Validator) *dialer.Dialer { return nil }
|
||||
r := &recoverCmd{log: logger.NewTest(t), configFetcher: fakeConfigFetcher{}}
|
||||
r := &recoverCmd{log: logger.NewTest(t), configFetcher: fakeAttestationFetcher{}}
|
||||
err := r.recover(cmd, fileHandler, time.Millisecond, tc.doer, newDialer)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
|
@ -142,7 +142,7 @@ func TestUpgradeApply(t *testing.T) {
|
||||
require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg))
|
||||
require.NoError(handler.WriteJSON(constants.ClusterIDsFileName, clusterid.File{}))
|
||||
|
||||
upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t), imageFetcher: tc.fetcher, configFetcher: fakeConfigFetcher{}}
|
||||
upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t), imageFetcher: tc.fetcher, configFetcher: fakeAttestationFetcher{}}
|
||||
err := upgrader.upgradeApply(cmd, handler)
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
|
@ -271,7 +271,7 @@ func TestUpgradeCheck(t *testing.T) {
|
||||
|
||||
cmd := newUpgradeCheckCmd()
|
||||
|
||||
err := checkCmd.upgradeCheck(cmd, fileHandler, fakeConfigFetcher{}, tc.flags)
|
||||
err := checkCmd.upgradeCheck(cmd, fileHandler, fakeAttestationFetcher{}, tc.flags)
|
||||
if tc.wantError {
|
||||
assert.Error(err)
|
||||
return
|
||||
|
@ -190,7 +190,7 @@ func TestVerify(t *testing.T) {
|
||||
}
|
||||
|
||||
v := &verifyCmd{log: logger.NewTest(t)}
|
||||
err := v.verify(cmd, fileHandler, tc.protoClient, tc.formatter, fakeConfigFetcher{})
|
||||
err := v.verify(cmd, fileHandler, tc.protoClient, tc.formatter, fakeAttestationFetcher{})
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
|
@ -3,24 +3,33 @@ load("//bazel/go:go_test.bzl", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "cmd",
|
||||
srcs = ["root.go"],
|
||||
srcs = [
|
||||
"delete.go",
|
||||
"root.go",
|
||||
],
|
||||
importpath = "github.com/edgelesssys/constellation/v2/hack/configapi/cmd",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//internal/api/attestationconfig",
|
||||
"//internal/api/attestationconfig/client",
|
||||
"//internal/api/attestationconfig/fetcher",
|
||||
"//internal/logger",
|
||||
"//internal/staticupload",
|
||||
"@com_github_spf13_cobra//:cobra",
|
||||
"@org_uber_go_zap//:zap",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "cmd_test",
|
||||
srcs = ["root_test.go"],
|
||||
srcs = [
|
||||
"delete_test.go",
|
||||
"root_test.go",
|
||||
],
|
||||
embed = [":cmd"],
|
||||
deps = [
|
||||
"//internal/api/attestationconfig",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
],
|
||||
)
|
||||
|
63
hack/configapi/cmd/delete.go
Normal file
63
hack/configapi/cmd/delete.go
Normal file
@ -0,0 +1,63 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/client"
|
||||
"github.com/edgelesssys/constellation/v2/internal/staticupload"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// newDeleteCmd creates the delete command.
|
||||
func newDeleteCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "delete a specific version from the config api",
|
||||
RunE: runDelete,
|
||||
}
|
||||
cmd.Flags().StringP("version", "v", "", "Name of the version to delete (without .json suffix)")
|
||||
must(enforceRequiredFlags(cmd, "version"))
|
||||
return cmd
|
||||
}
|
||||
|
||||
type deleteCmd struct {
|
||||
attestationClient deleteClient
|
||||
}
|
||||
|
||||
type deleteClient interface {
|
||||
DeleteAzureSEVSNPVersion(ctx context.Context, versionStr string) error
|
||||
}
|
||||
|
||||
func (d deleteCmd) delete(cmd *cobra.Command) error {
|
||||
version, err := cmd.Flags().GetString("version")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.attestationClient.DeleteAzureSEVSNPVersion(cmd.Context(), version)
|
||||
}
|
||||
|
||||
func runDelete(cmd *cobra.Command, _ []string) error {
|
||||
cfg := staticupload.Config{
|
||||
Bucket: awsBucket,
|
||||
Region: awsRegion,
|
||||
}
|
||||
repo, closefn, err := client.New(cmd.Context(), cfg, []byte(cosignPwd), []byte(privateKey), false, log())
|
||||
if err != nil {
|
||||
return fmt.Errorf("create attestation client: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := closefn(cmd.Context()); err != nil {
|
||||
cmd.Printf("close client: %s\n", err.Error())
|
||||
}
|
||||
}()
|
||||
deleteCmd := deleteCmd{
|
||||
attestationClient: repo,
|
||||
}
|
||||
return deleteCmd.delete(cmd)
|
||||
}
|
38
hack/configapi/cmd/delete_test.go
Normal file
38
hack/configapi/cmd/delete_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDeleteVersion(t *testing.T) {
|
||||
client := &fakeAttestationClient{}
|
||||
sut := deleteCmd{
|
||||
attestationClient: client,
|
||||
}
|
||||
cmd := newDeleteCmd()
|
||||
require.NoError(t, cmd.Flags().Set("version", "2021-01-01"))
|
||||
assert.NoError(t, sut.delete(cmd))
|
||||
assert.True(t, client.isCalled)
|
||||
}
|
||||
|
||||
type fakeAttestationClient struct {
|
||||
isCalled bool
|
||||
}
|
||||
|
||||
func (f *fakeAttestationClient) DeleteAzureSEVSNPVersion(_ context.Context, version string) error {
|
||||
if version == "2021-01-01" {
|
||||
f.isCalled = true
|
||||
return nil
|
||||
}
|
||||
return errors.New("version does not exist")
|
||||
}
|
@ -16,6 +16,8 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
|
||||
attestationconfigclient "github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/client"
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/staticupload"
|
||||
"github.com/spf13/cobra"
|
||||
@ -54,8 +56,8 @@ func newRootCmd() *cobra.Command {
|
||||
RunE: runCmd,
|
||||
}
|
||||
rootCmd.PersistentFlags().StringVarP(&versionFilePath, "version-file", "f", "", "File path to the version json file.")
|
||||
must(enforceRequiredFlags(rootCmd, "version-file"))
|
||||
|
||||
must(enforcePersistentRequiredFlags(rootCmd, "version-file"))
|
||||
rootCmd.AddCommand(newDeleteCmd())
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
@ -93,11 +95,11 @@ func runCmd(cmd *cobra.Command, _ []string) error {
|
||||
return fmt.Errorf("comparing versions: %w", err)
|
||||
}
|
||||
if isNewer {
|
||||
fmt.Printf("Input version: %+v is newer than latest API version: %+v\n", inputVersion, latestAPIVersion)
|
||||
sut, sutClose, err := attestationconfigclient.New(ctx, cfg, []byte(cosignPwd), []byte(privateKey))
|
||||
cmd.Printf("Input version: %+v is newer than latest API version: %+v\n", inputVersion, latestAPIVersion)
|
||||
sut, sutClose, err := attestationconfigclient.New(ctx, cfg, []byte(cosignPwd), []byte(privateKey), false, log())
|
||||
defer func() {
|
||||
if err := sutClose(ctx); err != nil {
|
||||
fmt.Printf("closing repo: %v\n", err)
|
||||
cmd.Printf("closing repo: %v\n", err)
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
@ -143,6 +145,15 @@ func isInputNewerThanLatestAPI(input, latest attestationconfig.AzureSEVSNPVersio
|
||||
}
|
||||
|
||||
func enforceRequiredFlags(cmd *cobra.Command, flags ...string) error {
|
||||
for _, flag := range flags {
|
||||
if err := cmd.MarkFlagRequired(flag); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func enforcePersistentRequiredFlags(cmd *cobra.Command, flags ...string) error {
|
||||
for _, flag := range flags {
|
||||
if err := cmd.MarkPersistentFlagRequired(flag); err != nil {
|
||||
return err
|
||||
@ -156,3 +167,7 @@ func must(err error) {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func log() *logger.Logger {
|
||||
return logger.New(logger.PlainLog, zap.DebugLevel).Named("attestationconfig")
|
||||
}
|
||||
|
@ -34,29 +34,53 @@ type AzureSEVSNPVersion struct {
|
||||
Microcode uint8 `json:"microcode"`
|
||||
}
|
||||
|
||||
// AzureSEVSNPVersionGet is the request to get the version information of the specific version in the config api.
|
||||
type AzureSEVSNPVersionGet struct {
|
||||
// AzureSEVSNPVersionSignature is the object to perform CRUD operations on the config api.
|
||||
type AzureSEVSNPVersionSignature struct {
|
||||
Version string `json:"-"`
|
||||
Signature []byte `json:"signature"`
|
||||
}
|
||||
|
||||
// JSONPath returns the path to the JSON file for the request to the config api.
|
||||
func (s AzureSEVSNPVersionSignature) JSONPath() string {
|
||||
return path.Join(attestationURLPath, variant.AzureSEVSNP{}.String(), s.Version, ".sig")
|
||||
}
|
||||
|
||||
// URL returns the URL for the request to the config api.
|
||||
func (s AzureSEVSNPVersionSignature) URL() (string, error) {
|
||||
return getURL(s)
|
||||
}
|
||||
|
||||
// ValidateRequest validates the request.
|
||||
func (s AzureSEVSNPVersionSignature) ValidateRequest() error {
|
||||
if !strings.HasSuffix(s.Version, ".json") {
|
||||
return fmt.Errorf("version has no .json suffix")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate is a No-Op at the moment.
|
||||
func (s AzureSEVSNPVersionSignature) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AzureSEVSNPVersionAPI is the request to get the version information of the specific version in the config api.
|
||||
type AzureSEVSNPVersionAPI struct {
|
||||
Version string `json:"-"`
|
||||
AzureSEVSNPVersion
|
||||
}
|
||||
|
||||
// URL returns the URL for the request to the config api.
|
||||
func (i AzureSEVSNPVersionGet) URL() (string, error) {
|
||||
url, err := url.Parse(constants.CDNRepositoryURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing CDN URL: %w", err)
|
||||
}
|
||||
url.Path = i.JSONPath()
|
||||
return url.String(), nil
|
||||
func (i AzureSEVSNPVersionAPI) URL() (string, error) {
|
||||
return getURL(i)
|
||||
}
|
||||
|
||||
// JSONPath returns the path to the JSON file for the request to the config api.
|
||||
func (i AzureSEVSNPVersionGet) JSONPath() string {
|
||||
func (i AzureSEVSNPVersionAPI) JSONPath() string {
|
||||
return path.Join(attestationURLPath, variant.AzureSEVSNP{}.String(), i.Version)
|
||||
}
|
||||
|
||||
// ValidateRequest validates the request.
|
||||
func (i AzureSEVSNPVersionGet) ValidateRequest() error {
|
||||
func (i AzureSEVSNPVersionAPI) ValidateRequest() error {
|
||||
if !strings.HasSuffix(i.Version, ".json") {
|
||||
return fmt.Errorf("version has no .json suffix")
|
||||
}
|
||||
@ -64,7 +88,7 @@ func (i AzureSEVSNPVersionGet) ValidateRequest() error {
|
||||
}
|
||||
|
||||
// Validate is a No-Op at the moment.
|
||||
func (i AzureSEVSNPVersionGet) Validate() error {
|
||||
func (i AzureSEVSNPVersionAPI) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -73,12 +97,7 @@ type AzureSEVSNPVersionList []string
|
||||
|
||||
// URL returns the URL for the request to the config api.
|
||||
func (i AzureSEVSNPVersionList) URL() (string, error) {
|
||||
url, err := url.Parse(constants.CDNRepositoryURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing CDN URL: %w", err)
|
||||
}
|
||||
url.Path = i.JSONPath()
|
||||
return url.String(), nil
|
||||
return getURL(i)
|
||||
}
|
||||
|
||||
// JSONPath returns the path to the JSON file for the request to the config api.
|
||||
@ -98,3 +117,16 @@ func (i AzureSEVSNPVersionList) Validate() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getURL(obj jsoPather) (string, error) {
|
||||
url, err := url.Parse(constants.CDNRepositoryURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("parsing CDN URL: %w", err)
|
||||
}
|
||||
url.Path = obj.JSONPath()
|
||||
return url.String(), nil
|
||||
}
|
||||
|
||||
type jsoPather interface {
|
||||
JSONPath() string
|
||||
}
|
||||
|
@ -8,13 +8,13 @@ go_library(
|
||||
visibility = ["//:__subpackages__"],
|
||||
deps = [
|
||||
"//internal/api/attestationconfig",
|
||||
"//internal/api/attestationconfig/fetcher",
|
||||
"//internal/api/client",
|
||||
"//internal/constants",
|
||||
"//internal/kms/storage",
|
||||
"//internal/logger",
|
||||
"//internal/sigstore",
|
||||
"//internal/staticupload",
|
||||
"//internal/variant",
|
||||
"@com_github_aws_aws_sdk_go_v2_feature_s3_manager//:manager",
|
||||
"@com_github_aws_aws_sdk_go_v2_service_s3//:s3",
|
||||
],
|
||||
)
|
||||
|
||||
@ -23,14 +23,13 @@ go_test(
|
||||
srcs = ["client_test.go"],
|
||||
# keep
|
||||
count = 1,
|
||||
embed = [":client"],
|
||||
# keep
|
||||
gotags = ["e2e"],
|
||||
# keep
|
||||
tags = ["manual"],
|
||||
deps = [
|
||||
":client",
|
||||
"//internal/api/attestationconfig",
|
||||
"//internal/staticupload",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
],
|
||||
)
|
||||
|
@ -6,21 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/fetcher"
|
||||
apiclient "github.com/edgelesssys/constellation/v2/internal/api/client"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/kms/storage"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||
"github.com/edgelesssys/constellation/v2/internal/staticupload"
|
||||
"github.com/edgelesssys/constellation/v2/internal/variant"
|
||||
@ -28,70 +24,90 @@ import (
|
||||
|
||||
// Client manages (modifies) the version information for the attestation variants.
|
||||
type Client struct {
|
||||
s3Client
|
||||
s3Client *apiclient.Client
|
||||
s3ClientClose func(ctx context.Context) error
|
||||
bucketID string
|
||||
cosignPwd []byte // used to decrypt the cosign private key
|
||||
privKey []byte // used to sign
|
||||
signer sigstore.Signer
|
||||
fetcher fetcher.AttestationConfigAPIFetcher
|
||||
}
|
||||
|
||||
// New returns a new Client.
|
||||
func New(ctx context.Context, cfg staticupload.Config, cosignPwd, privateKey []byte) (*Client, CloseFunc, error) {
|
||||
client, clientClose, err := staticupload.New(ctx, cfg)
|
||||
func New(ctx context.Context, cfg staticupload.Config, cosignPwd, privateKey []byte, dryRun bool, log *logger.Logger) (*Client, apiclient.CloseFunc, error) {
|
||||
s3Client, clientClose, err := apiclient.NewClient(ctx, cfg.Region, cfg.Bucket, cfg.DistributionID, dryRun, log)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to create s3 storage: %w", err)
|
||||
}
|
||||
|
||||
repo := &Client{
|
||||
s3Client: client,
|
||||
s3Client: s3Client,
|
||||
s3ClientClose: clientClose,
|
||||
signer: sigstore.NewSigner(cosignPwd, privateKey),
|
||||
bucketID: cfg.Bucket,
|
||||
cosignPwd: cosignPwd,
|
||||
privKey: privateKey,
|
||||
fetcher: fetcher.New(),
|
||||
}
|
||||
repoClose := func(ctx context.Context) error {
|
||||
return repo.Close(ctx)
|
||||
}
|
||||
return repo, repoClose, nil
|
||||
return repo, clientClose, nil
|
||||
}
|
||||
|
||||
// Close closes the Client.
|
||||
func (a Client) Close(ctx context.Context) error {
|
||||
if a.s3ClientClose == nil {
|
||||
return nil
|
||||
func (a Client) uploadAzureSEVSNP(versions attestationconfig.AzureSEVSNPVersion, versionNames []string, date time.Time) (res []putCmd, err error) {
|
||||
dateStr := date.Format("2006-01-02-15-04") + ".json"
|
||||
|
||||
res = append(res, putCmd{attestationconfig.AzureSEVSNPVersionAPI{Version: dateStr, AzureSEVSNPVersion: versions}})
|
||||
|
||||
versionBytes, err := json.Marshal(versions)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
return a.s3ClientClose(ctx)
|
||||
signature, err := a.createSignature(versionBytes, dateStr)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
res = append(res, putCmd{signature})
|
||||
newVersions := addVersion(versionNames, dateStr)
|
||||
res = append(res, putCmd{attestationconfig.AzureSEVSNPVersionList(newVersions)})
|
||||
return
|
||||
}
|
||||
|
||||
// UploadAzureSEVSNP uploads the latest version numbers of the Azure SEVSNP.
|
||||
func (a Client) UploadAzureSEVSNP(ctx context.Context, versions attestationconfig.AzureSEVSNPVersion, date time.Time) error {
|
||||
versionBytes, err := json.Marshal(versions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (a Client) UploadAzureSEVSNP(ctx context.Context, version attestationconfig.AzureSEVSNPVersion, date time.Time) error {
|
||||
variant := variant.AzureSEVSNP{}
|
||||
fname := date.Format("2006-01-02-15-04") + ".json"
|
||||
|
||||
filePath := fmt.Sprintf("%s/%s/%s", constants.CDNAttestationConfigPrefixV1, variant.String(), fname)
|
||||
err = put(ctx, a.s3Client, a.bucketID, filePath, versionBytes)
|
||||
dateStr := date.Format("2006-01-02-15-04") + ".json"
|
||||
err := apiclient.Update(ctx, a.s3Client, attestationconfig.AzureSEVSNPVersionAPI{Version: dateStr, AzureSEVSNPVersion: version})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
versionBytes, err := json.Marshal(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filePath := fmt.Sprintf("%s/%s/%s", constants.CDNAttestationConfigPrefixV1, variant.String(), dateStr)
|
||||
err = a.createAndUploadSignature(ctx, versionBytes, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return a.addVersionToList(ctx, variant, fname)
|
||||
|
||||
return a.addVersionToList(ctx, variant, dateStr)
|
||||
}
|
||||
|
||||
func (a Client) createSignature(content []byte, dateStr string) (res attestationconfig.AzureSEVSNPVersionSignature, err error) {
|
||||
signature, err := a.signer.Sign(content)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("sign version file: %w", err)
|
||||
}
|
||||
return attestationconfig.AzureSEVSNPVersionSignature{
|
||||
Signature: signature,
|
||||
Version: dateStr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createAndUploadSignature signs the given content and uploads the signature to the given filePath with the .sig suffix.
|
||||
func (a Client) createAndUploadSignature(ctx context.Context, content []byte, filePath string) error {
|
||||
signature, err := sigstore.SignContent(a.cosignPwd, a.privKey, content)
|
||||
signature, err := a.createSignature(content, filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("sign version file: %w", err)
|
||||
return err
|
||||
}
|
||||
err = put(ctx, a.s3Client, a.bucketID, filePath+".sig", signature)
|
||||
if err != nil {
|
||||
if err := apiclient.Update(ctx, a.s3Client, signature); err != nil {
|
||||
return fmt.Errorf("upload signature: %w", err)
|
||||
}
|
||||
return nil
|
||||
@ -99,81 +115,114 @@ func (a Client) createAndUploadSignature(ctx context.Context, content []byte, fi
|
||||
|
||||
// List returns the list of versions for the given attestation type.
|
||||
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.s3Client, a.bucketID, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if attestation.Equal(variant.AzureSEVSNP{}) {
|
||||
versions, err := apiclient.Fetch(ctx, a.s3Client, attestationconfig.AzureSEVSNPVersionList{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
var versions []string
|
||||
if err := json.Unmarshal(bt, &versions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return versions, nil
|
||||
return nil, fmt.Errorf("unsupported attestation type: %s", attestation)
|
||||
}
|
||||
|
||||
// DeleteList empties the list of versions for the given attestation type.
|
||||
func (a Client) DeleteList(ctx context.Context, attestation variant.Variant) error {
|
||||
versions := []string{}
|
||||
bt, err := json.Marshal(&versions)
|
||||
if attestation.Equal(variant.AzureSEVSNP{}) {
|
||||
return apiclient.Update(ctx, a.s3Client, attestationconfig.AzureSEVSNPVersionList{})
|
||||
}
|
||||
return fmt.Errorf("unsupported attestation type: %s", attestation)
|
||||
}
|
||||
|
||||
func (a Client) deleteAzureSEVSNPVersion(versions attestationconfig.AzureSEVSNPVersionList, versionStr string) (ops []crudOPNew, err error) {
|
||||
versionStr = versionStr + ".json"
|
||||
ops = append(ops, deleteCmd{
|
||||
apiObject: attestationconfig.AzureSEVSNPVersionAPI{
|
||||
Version: versionStr,
|
||||
},
|
||||
})
|
||||
|
||||
ops = append(ops, deleteCmd{
|
||||
apiObject: attestationconfig.AzureSEVSNPVersionSignature{
|
||||
Version: versionStr,
|
||||
},
|
||||
})
|
||||
|
||||
removedVersions, err := removeVersion(versions, versionStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops = append(ops, putCmd{
|
||||
apiObject: removedVersions,
|
||||
})
|
||||
return ops, nil
|
||||
}
|
||||
|
||||
// DeleteAzureSEVSNPVersion deletes the given version (without .json suffix) from the API.
|
||||
func (a Client) DeleteAzureSEVSNPVersion(ctx context.Context, versionStr string) error {
|
||||
versions, err := a.List(ctx, variant.AzureSEVSNP{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("fetch version list: %w", err)
|
||||
}
|
||||
ops, err := a.deleteAzureSEVSNPVersion(versions, versionStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return put(ctx, a.s3Client, a.bucketID, path.Join(constants.CDNAttestationConfigPrefixV1, attestation.String(), "list"), bt)
|
||||
for _, op := range ops {
|
||||
if err := op.Execute(ctx, a.s3Client); err != nil {
|
||||
return fmt.Errorf("execute operation %+v: %w", op, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a Client) addVersionToList(ctx context.Context, attestation variant.Variant, fname string) error {
|
||||
versions := []string{}
|
||||
key := path.Join(constants.CDNAttestationConfigPrefixV1, attestation.String(), "list")
|
||||
bt, err := get(ctx, a.s3Client, a.bucketID, key)
|
||||
if err == nil {
|
||||
if err := json.Unmarshal(bt, &versions); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !errors.Is(err, storage.ErrDEKUnset) {
|
||||
versions, err := a.List(ctx, attestation)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
versions = append(versions, fname)
|
||||
versions = variant.RemoveDuplicate(versions)
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(versions)))
|
||||
json, err := json.Marshal(versions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return put(ctx, a.s3Client, a.bucketID, key, json)
|
||||
return apiclient.Update(ctx, a.s3Client, attestationconfig.AzureSEVSNPVersionList(versions))
|
||||
}
|
||||
|
||||
// get is a convenience method.
|
||||
func get(ctx context.Context, client s3Client, bucket, path string) ([]byte, error) {
|
||||
getObjectInput := &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &path,
|
||||
func removeVersion(versions attestationconfig.AzureSEVSNPVersionList, versionStr string) (removedVersions attestationconfig.AzureSEVSNPVersionList, err error) {
|
||||
for i, v := range versions {
|
||||
if v == versionStr {
|
||||
if i == len(versions)-1 {
|
||||
removedVersions = versions[:i]
|
||||
} else {
|
||||
removedVersions = append(versions[:i], versions[i+1:]...)
|
||||
}
|
||||
return removedVersions, nil
|
||||
}
|
||||
}
|
||||
output, err := client.GetObject(ctx, getObjectInput)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting object: %w", err)
|
||||
}
|
||||
return io.ReadAll(output.Body)
|
||||
return nil, fmt.Errorf("version %s not found in list %v", versionStr, versions)
|
||||
}
|
||||
|
||||
// put is a convenience method.
|
||||
func put(ctx context.Context, client s3Client, bucket, path string, data []byte) error {
|
||||
putObjectInput := &s3.PutObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &path,
|
||||
Body: bytes.NewReader(data),
|
||||
}
|
||||
_, err := client.Upload(ctx, putObjectInput)
|
||||
return err
|
||||
type deleteCmd struct {
|
||||
apiObject apiclient.APIObject
|
||||
}
|
||||
|
||||
type s3Client interface {
|
||||
GetObject(
|
||||
ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options),
|
||||
) (*s3.GetObjectOutput, error)
|
||||
Upload(
|
||||
ctx context.Context, input *s3.PutObjectInput, opts ...func(*s3manager.Uploader),
|
||||
) (*s3manager.UploadOutput, error)
|
||||
func (d deleteCmd) Execute(ctx context.Context, c *apiclient.Client) error {
|
||||
return apiclient.Delete(ctx, c, d.apiObject)
|
||||
}
|
||||
|
||||
// CloseFunc is a function that closes the client.
|
||||
type CloseFunc func(ctx context.Context) error
|
||||
type putCmd struct {
|
||||
apiObject apiclient.APIObject
|
||||
}
|
||||
|
||||
func (p putCmd) Execute(ctx context.Context, c *apiclient.Client) error {
|
||||
return apiclient.Update(ctx, c, p.apiObject)
|
||||
}
|
||||
|
||||
type crudOPNew interface {
|
||||
Execute(ctx context.Context, c *apiclient.Client) error
|
||||
}
|
||||
|
||||
func addVersion(versions []string, newVersion string) []string {
|
||||
versions = append(versions, newVersion)
|
||||
versions = variant.RemoveDuplicate(versions)
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(versions)))
|
||||
return versions
|
||||
}
|
||||
|
@ -1,82 +1,74 @@
|
||||
//go:build e2e
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package client_test
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
awsBucket = "cdn-constellation-backend"
|
||||
awsRegion = "eu-central-1"
|
||||
envAwsKeyID = "AWS_ACCESS_KEY_ID"
|
||||
envAwsKey = "AWS_ACCESS_KEY"
|
||||
)
|
||||
|
||||
var cfg staticupload.Config
|
||||
|
||||
var (
|
||||
cosignPwd = flag.String("cosign-pwd", "", "Password to decrypt the cosign private key. Required for signing.")
|
||||
privateKeyPath = flag.String("private-key", "", "Path to the private key used for signing. Required for signing.")
|
||||
privateKey []byte
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
if *cosignPwd == "" || *privateKeyPath == "" {
|
||||
flag.Usage()
|
||||
fmt.Println("Required flags not set: --cosign-pwd, --private-key. Skipping tests.")
|
||||
os.Exit(1)
|
||||
func TestUploadAzureSEVSNP(t *testing.T) {
|
||||
sut := Client{
|
||||
bucketID: "bucket",
|
||||
signer: fakeSigner{},
|
||||
}
|
||||
if _, present := os.LookupEnv(envAwsKey); !present {
|
||||
fmt.Printf("%s not set. Skipping tests.\n", envAwsKey)
|
||||
os.Exit(1)
|
||||
}
|
||||
if _, present := os.LookupEnv(envAwsKeyID); !present {
|
||||
fmt.Printf("%s not set. Skipping tests.\n", envAwsKeyID)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg = staticupload.Config{
|
||||
Bucket: awsBucket,
|
||||
Region: awsRegion,
|
||||
}
|
||||
file, _ := os.Open(*privateKeyPath)
|
||||
var err error
|
||||
privateKey, err = io.ReadAll(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
version := attestationconfig.AzureSEVSNPVersion{}
|
||||
date := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC)
|
||||
ops, err := sut.uploadAzureSEVSNP(version, []string{"2021-01-01-01-01.json", "2019-01-01-01-01.json"}, date)
|
||||
assert := assert.New(t)
|
||||
assert.NoError(err)
|
||||
dateStr := "2023-01-01-01-01.json"
|
||||
assert.Contains(ops, putCmd{
|
||||
apiObject: attestationconfig.AzureSEVSNPVersionAPI{
|
||||
Version: dateStr,
|
||||
AzureSEVSNPVersion: version,
|
||||
},
|
||||
})
|
||||
assert.Contains(ops, putCmd{
|
||||
apiObject: attestationconfig.AzureSEVSNPVersionSignature{
|
||||
Version: dateStr,
|
||||
Signature: []byte("signature"),
|
||||
},
|
||||
})
|
||||
assert.Contains(ops, putCmd{
|
||||
apiObject: attestationconfig.AzureSEVSNPVersionList([]string{"2023-01-01-01-01.json", "2021-01-01-01-01.json", "2019-01-01-01-01.json"}),
|
||||
})
|
||||
}
|
||||
|
||||
var versionValues = attestationconfig.AzureSEVSNPVersion{
|
||||
Bootloader: 2,
|
||||
TEE: 0,
|
||||
SNP: 6,
|
||||
Microcode: 93,
|
||||
func TestDeleteAzureSEVSNPVersions(t *testing.T) {
|
||||
sut := Client{
|
||||
bucketID: "bucket",
|
||||
}
|
||||
versions := attestationconfig.AzureSEVSNPVersionList([]string{"2023-01-01.json", "2021-01-01.json", "2019-01-01.json"})
|
||||
|
||||
ops, err := sut.deleteAzureSEVSNPVersion(versions, "2021-01-01")
|
||||
|
||||
assert := assert.New(t)
|
||||
assert.NoError(err)
|
||||
assert.Contains(ops, deleteCmd{
|
||||
apiObject: attestationconfig.AzureSEVSNPVersionAPI{
|
||||
Version: "2021-01-01.json",
|
||||
},
|
||||
})
|
||||
assert.Contains(ops, deleteCmd{
|
||||
apiObject: attestationconfig.AzureSEVSNPVersionSignature{
|
||||
Version: "2021-01-01.json",
|
||||
},
|
||||
})
|
||||
|
||||
assert.Contains(ops, putCmd{
|
||||
apiObject: attestationconfig.AzureSEVSNPVersionList([]string{"2023-01-01.json", "2019-01-01.json"}),
|
||||
})
|
||||
}
|
||||
|
||||
func TestUploadAzureSEVSNPVersions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client, clientClose, err := client.New(ctx, cfg, []byte(*cosignPwd), privateKey)
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = clientClose(ctx) }()
|
||||
d := time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC)
|
||||
require.NoError(t, client.UploadAzureSEVSNP(ctx, versionValues, d))
|
||||
type fakeSigner struct{}
|
||||
|
||||
func (fakeSigner) Sign(_ []byte) ([]byte, error) {
|
||||
return []byte("signature"), nil
|
||||
}
|
||||
|
@ -0,0 +1,84 @@
|
||||
//go:build integration
|
||||
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
|
||||
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig/client"
|
||||
"github.com/edgelesssys/constellation/v2/internal/logger"
|
||||
"github.com/edgelesssys/constellation/v2/internal/staticupload"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
awsBucket = "cdn-constellation-backend"
|
||||
awsRegion = "eu-central-1"
|
||||
envAwsKeyID = "AWS_ACCESS_KEY_ID"
|
||||
envAwsKey = "AWS_ACCESS_KEY"
|
||||
)
|
||||
|
||||
var cfg staticupload.Config
|
||||
|
||||
var (
|
||||
cosignPwd = flag.String("cosign-pwd", "", "Password to decrypt the cosign private key. Required for signing.")
|
||||
privateKeyPath = flag.String("private-key", "", "Path to the private key used for signing. Required for signing.")
|
||||
privateKey []byte
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
if *cosignPwd == "" || *privateKeyPath == "" {
|
||||
flag.Usage()
|
||||
fmt.Println("Required flags not set: --cosign-pwd, --private-key. Skipping tests.")
|
||||
os.Exit(1)
|
||||
}
|
||||
if _, present := os.LookupEnv(envAwsKey); !present {
|
||||
fmt.Printf("%s not set. Skipping tests.\n", envAwsKey)
|
||||
os.Exit(1)
|
||||
}
|
||||
if _, present := os.LookupEnv(envAwsKeyID); !present {
|
||||
fmt.Printf("%s not set. Skipping tests.\n", envAwsKeyID)
|
||||
os.Exit(1)
|
||||
}
|
||||
cfg = staticupload.Config{
|
||||
Bucket: awsBucket,
|
||||
Region: awsRegion,
|
||||
}
|
||||
file, _ := os.Open(*privateKeyPath)
|
||||
var err error
|
||||
privateKey, err = io.ReadAll(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
var versionValues = attestationconfig.AzureSEVSNPVersion{
|
||||
Bootloader: 2,
|
||||
TEE: 0,
|
||||
SNP: 6,
|
||||
Microcode: 93,
|
||||
}
|
||||
|
||||
func TestUploadAzureSEVSNPVersions(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
client, clientClose, err := client.New(ctx, cfg, []byte(*cosignPwd), privateKey, false, logger.New(logger.PlainLog, zap.DebugLevel).Named("attestationconfig"))
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = clientClose(ctx) }()
|
||||
d := time.Date(2021, 1, 1, 1, 1, 1, 1, time.UTC)
|
||||
require.NoError(t, client.UploadAzureSEVSNP(ctx, versionValues, d))
|
||||
}
|
@ -24,9 +24,9 @@ const cosignPublicKey = constants.CosignPublicKeyReleases
|
||||
|
||||
// AttestationConfigAPIFetcher fetches config API resources without authentication.
|
||||
type AttestationConfigAPIFetcher interface {
|
||||
FetchAzureSEVSNPVersion(ctx context.Context, azureVersion attestationconfig.AzureSEVSNPVersionGet) (attestationconfig.AzureSEVSNPVersionGet, error)
|
||||
FetchAzureSEVSNPVersion(ctx context.Context, azureVersion attestationconfig.AzureSEVSNPVersionAPI) (attestationconfig.AzureSEVSNPVersionAPI, error)
|
||||
FetchAzureSEVSNPVersionList(ctx context.Context, attestation attestationconfig.AzureSEVSNPVersionList) (attestationconfig.AzureSEVSNPVersionList, error)
|
||||
FetchAzureSEVSNPVersionLatest(ctx context.Context) (attestationconfig.AzureSEVSNPVersionGet, error)
|
||||
FetchAzureSEVSNPVersionLatest(ctx context.Context) (attestationconfig.AzureSEVSNPVersionAPI, error)
|
||||
}
|
||||
|
||||
// Fetcher fetches AttestationCfg API resources without authentication.
|
||||
@ -50,7 +50,7 @@ func (f *Fetcher) FetchAzureSEVSNPVersionList(ctx context.Context, attestation a
|
||||
}
|
||||
|
||||
// FetchAzureSEVSNPVersion fetches the version information from the config API.
|
||||
func (f *Fetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion attestationconfig.AzureSEVSNPVersionGet) (attestationconfig.AzureSEVSNPVersionGet, error) {
|
||||
func (f *Fetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion attestationconfig.AzureSEVSNPVersionAPI) (attestationconfig.AzureSEVSNPVersionAPI, error) {
|
||||
urlString, err := azureVersion.URL()
|
||||
if err != nil {
|
||||
return azureVersion, err
|
||||
@ -77,13 +77,13 @@ func (f *Fetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion atte
|
||||
}
|
||||
|
||||
// FetchAzureSEVSNPVersionLatest returns the latest versions of the given type.
|
||||
func (f *Fetcher) FetchAzureSEVSNPVersionLatest(ctx context.Context) (res attestationconfig.AzureSEVSNPVersionGet, err error) {
|
||||
func (f *Fetcher) FetchAzureSEVSNPVersionLatest(ctx context.Context) (res attestationconfig.AzureSEVSNPVersionAPI, 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 := attestationconfig.AzureSEVSNPVersionAPI{Version: list[0]} // get latest version (as sorted reversely alphanumerically)
|
||||
get, err = f.FetchAzureSEVSNPVersion(ctx, get)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("failed fetching version: %w", err)
|
||||
|
@ -18,7 +18,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var testCfg = configapi.AzureSEVSNPVersionGet{
|
||||
var testCfg = configapi.AzureSEVSNPVersionAPI{
|
||||
AzureSEVSNPVersion: configapi.AzureSEVSNPVersion{
|
||||
Microcode: 93,
|
||||
TEE: 0,
|
||||
@ -31,7 +31,7 @@ func TestFetchLatestAzureSEVSNPVersion(t *testing.T) {
|
||||
testcases := map[string]struct {
|
||||
signature []byte
|
||||
wantErr bool
|
||||
want configapi.AzureSEVSNPVersionGet
|
||||
want configapi.AzureSEVSNPVersionAPI
|
||||
}{
|
||||
"get version with valid signature": {
|
||||
signature: []byte("MEQCIBPEbYg89MIQuaGStLhKGLGMKvKFoYCaAniDLwoIwulqAiB+rj7KMaMOMGxmUsjI7KheCXSNM8NzN+tuDw6AywI75A=="), // signed with release key
|
||||
|
@ -43,10 +43,9 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// Client is the client for the versions API.
|
||||
// Client is the a general client for all APIs.
|
||||
type Client struct {
|
||||
uploadClient uploadClient
|
||||
s3Client s3Client
|
||||
s3Client
|
||||
s3ClientClose func(ctx context.Context) error
|
||||
bucket string
|
||||
cacheInvalidationWaitTimeout time.Duration
|
||||
@ -101,7 +100,6 @@ func NewClient(ctx context.Context, region, bucket, distributionID string, dryRu
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
uploadClient: staticUploadClient,
|
||||
s3Client: staticUploadClient,
|
||||
s3ClientClose: staticUploadClientClose,
|
||||
bucket: bucket,
|
||||
@ -179,14 +177,15 @@ func ptr[T any](t T) *T {
|
||||
return &t
|
||||
}
|
||||
|
||||
type apiObject interface {
|
||||
// APIObject is an object that is used to perform CRUD operations on the API.
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
@ -218,8 +217,8 @@ func Fetch[T apiObject](ctx context.Context, c *Client, obj T) (T, error) {
|
||||
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 {
|
||||
// Update creates/updates the given apiObject in the public Constellation API.
|
||||
func Update(ctx context.Context, c *Client, obj APIObject) error {
|
||||
if err := obj.Validate(); err != nil {
|
||||
return fmt.Errorf("validating %T struct: %w", obj, err)
|
||||
}
|
||||
@ -243,13 +242,32 @@ func Update[T apiObject](ctx context.Context, c *Client, obj T) error {
|
||||
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 {
|
||||
if _, err := c.Upload(ctx, in); err != nil {
|
||||
return fmt.Errorf("uploading %T: %w", obj, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the given apiObject from the public Constellation API.
|
||||
func Delete(ctx context.Context, c *Client, obj APIObject) error {
|
||||
if err := obj.ValidateRequest(); err != nil {
|
||||
return fmt.Errorf("validating request for %T: %w", obj, err)
|
||||
}
|
||||
|
||||
in := &s3.DeleteObjectInput{
|
||||
Bucket: &c.bucket,
|
||||
Key: ptr(obj.JSONPath()),
|
||||
}
|
||||
|
||||
c.Log.Debugf("Deleting %T from s3: %s", obj, obj.JSONPath())
|
||||
if _, err := c.DeleteObject(ctx, in); err != nil {
|
||||
return fmt.Errorf("deleting s3 object at %s: %w", obj.JSONPath(), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotFoundError is an error that is returned when a resource is not found.
|
||||
type NotFoundError struct {
|
||||
err error
|
||||
@ -273,6 +291,10 @@ type s3Client interface {
|
||||
DeleteObjects(
|
||||
ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options),
|
||||
) (*s3.DeleteObjectsOutput, error)
|
||||
DeleteObject(ctx context.Context, params *s3.DeleteObjectInput,
|
||||
optFns ...func(*s3.Options),
|
||||
) (*s3.DeleteObjectOutput, error)
|
||||
uploadClient
|
||||
}
|
||||
|
||||
type uploadClient interface {
|
||||
|
@ -900,14 +900,14 @@ func (f fakeConfigFetcher) FetchAzureSEVSNPVersionList(_ context.Context, _ conf
|
||||
), nil
|
||||
}
|
||||
|
||||
func (f fakeConfigFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ configapi.AzureSEVSNPVersionGet) (configapi.AzureSEVSNPVersionGet, error) {
|
||||
return configapi.AzureSEVSNPVersionGet{
|
||||
func (f fakeConfigFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ configapi.AzureSEVSNPVersionAPI) (configapi.AzureSEVSNPVersionAPI, error) {
|
||||
return configapi.AzureSEVSNPVersionAPI{
|
||||
AzureSEVSNPVersion: testCfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f fakeConfigFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context) (configapi.AzureSEVSNPVersionGet, error) {
|
||||
return configapi.AzureSEVSNPVersionGet{
|
||||
func (f fakeConfigFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context) (configapi.AzureSEVSNPVersionAPI, error) {
|
||||
return configapi.AzureSEVSNPVersionAPI{
|
||||
AzureSEVSNPVersion: testCfg,
|
||||
}, nil
|
||||
}
|
||||
|
@ -26,6 +26,29 @@ const (
|
||||
sigstorePrivateKeyPemType = "ENCRYPTED SIGSTORE PRIVATE KEY"
|
||||
)
|
||||
|
||||
// Signer is used to sign the version file. Used for unit testing.
|
||||
type Signer interface {
|
||||
Sign(content []byte) (res []byte, err error)
|
||||
}
|
||||
|
||||
// NewSigner returns a new Signer.
|
||||
func NewSigner(cosignPwd, privKey []byte) Signer {
|
||||
return signer{cosignPwd: cosignPwd, privKey: privKey}
|
||||
}
|
||||
|
||||
type signer struct {
|
||||
cosignPwd []byte // used to decrypt the cosign private key
|
||||
privKey []byte // used to sign
|
||||
}
|
||||
|
||||
func (s signer) Sign(content []byte) (signature []byte, err error) {
|
||||
signature, err = SignContent(s.cosignPwd, s.privKey, content)
|
||||
if err != nil {
|
||||
return signature, fmt.Errorf("sign version file: %w", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SignContent signs the content with the cosign encrypted private key and corresponding cosign password.
|
||||
func SignContent(password, encryptedPrivateKey, content []byte) ([]byte, error) {
|
||||
sv, err := loadPrivateKey(encryptedPrivateKey, password)
|
||||
|
@ -93,7 +93,7 @@ func (e InvalidationError) Unwrap() error {
|
||||
return e.inner
|
||||
}
|
||||
|
||||
// New creates a new Client.
|
||||
// New creates a new Client. Call CloseFunc when done with operations.
|
||||
func New(ctx context.Context, config Config) (*Client, CloseFunc, error) {
|
||||
config.SetsDefault()
|
||||
cfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(config.Region))
|
||||
@ -114,12 +114,7 @@ func New(ctx context.Context, config Config) (*Client, CloseFunc, error) {
|
||||
cacheInvalidationWaitTimeout: config.CacheInvalidationWaitTimeout,
|
||||
bucketID: config.Bucket,
|
||||
}
|
||||
clientClose := func(ctx context.Context) error {
|
||||
// ensure that all keys are invalidated
|
||||
return client.Flush(ctx)
|
||||
}
|
||||
|
||||
return client, clientClose, nil
|
||||
return client, client.Flush, nil
|
||||
}
|
||||
|
||||
// Flush flushes the client by invalidating the CDN cache for modified keys.
|
||||
|
Loading…
x
Reference in New Issue
Block a user