mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-07-24 07:50:40 -04: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
22 changed files with 549 additions and 228 deletions
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue