-
Notifications
You must be signed in to change notification settings - Fork 47
/
client.go
182 lines (151 loc) · 5.48 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package attestationconfigapi
import (
"context"
"errors"
"fmt"
"time"
apiclient "github.com/edgelesssys/constellation/v2/internal/api/client"
"github.com/edgelesssys/constellation/v2/internal/attestation/variant"
"github.com/edgelesssys/constellation/v2/internal/logger"
"github.com/edgelesssys/constellation/v2/internal/sigstore"
"github.com/edgelesssys/constellation/v2/internal/staticupload"
)
// VersionFormat is the format of the version name in the S3 bucket.
const VersionFormat = "2006-01-02-15-04"
// Client manages (modifies) the version information for the attestation variants.
type Client struct {
s3Client *apiclient.Client
s3ClientClose func(ctx context.Context) error
bucketID string
signer sigstore.Signer
cacheWindowSize int
}
// NewClient returns a new Client.
func NewClient(ctx context.Context, cfg staticupload.Config, cosignPwd, privateKey []byte, dryRun bool, versionWindowSize int, 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: s3Client,
s3ClientClose: clientClose,
signer: sigstore.NewSigner(cosignPwd, privateKey),
bucketID: cfg.Bucket,
cacheWindowSize: versionWindowSize,
}
return repo, clientClose, nil
}
// uploadSEVSNPVersion uploads the latest version numbers of the Azure SEVSNP. Then version name is the UTC timestamp of the date. The /list entry stores the version name + .json suffix.
func (a Client) uploadSEVSNPVersion(ctx context.Context, attestation variant.Variant, version SEVSNPVersion, date time.Time) error {
versions, err := a.List(ctx, attestation)
if err != nil {
return fmt.Errorf("fetch version list: %w", err)
}
ops := a.constructUploadCmd(attestation, version, versions, date)
return executeAllCmds(ctx, a.s3Client, ops)
}
// DeleteSEVSNPVersion deletes the given version (without .json suffix) from the API.
func (a Client) DeleteSEVSNPVersion(ctx context.Context, attestation variant.Variant, versionStr string) error {
versions, err := a.List(ctx, attestation)
if err != nil {
return fmt.Errorf("fetch version list: %w", err)
}
ops, err := a.deleteSEVSNPVersion(versions, versionStr)
if err != nil {
return err
}
return executeAllCmds(ctx, a.s3Client, ops)
}
// List returns the list of versions for the given attestation variant.
func (a Client) List(ctx context.Context, attestation variant.Variant) (SEVSNPVersionList, error) {
if !attestation.Equal(variant.AzureSEVSNP{}) && !attestation.Equal(variant.AWSSEVSNP{}) {
return SEVSNPVersionList{}, fmt.Errorf("unsupported attestation variant: %s", attestation)
}
versions, err := apiclient.Fetch(ctx, a.s3Client, SEVSNPVersionList{variant: attestation})
if err != nil {
var notFoundErr *apiclient.NotFoundError
if errors.As(err, ¬FoundErr) {
return SEVSNPVersionList{variant: attestation}, nil
}
return SEVSNPVersionList{}, err
}
versions.variant = attestation
return versions, nil
}
func (a Client) deleteSEVSNPVersion(versions SEVSNPVersionList, versionStr string) (ops []crudCmd, err error) {
versionStr = versionStr + ".json"
ops = append(ops, deleteCmd{
apiObject: SEVSNPVersionAPI{
Variant: versions.variant,
Version: versionStr,
},
})
removedVersions, err := removeVersion(versions, versionStr)
if err != nil {
return nil, err
}
ops = append(ops, putCmd{
apiObject: removedVersions,
signer: a.signer,
})
return ops, nil
}
func (a Client) constructUploadCmd(attestation variant.Variant, version SEVSNPVersion, versionNames SEVSNPVersionList, date time.Time) []crudCmd {
if !attestation.Equal(versionNames.variant) {
return nil
}
dateStr := date.Format(VersionFormat) + ".json"
var res []crudCmd
res = append(res, putCmd{
apiObject: SEVSNPVersionAPI{Version: dateStr, Variant: attestation, SEVSNPVersion: version},
signer: a.signer,
})
versionNames.addVersion(dateStr)
res = append(res, putCmd{
apiObject: versionNames,
signer: a.signer,
})
return res
}
func removeVersion(list SEVSNPVersionList, versionStr string) (removedVersions SEVSNPVersionList, err error) {
versions := list.List()
for i, v := range versions {
if v == versionStr {
if i == len(versions)-1 {
removedVersions = SEVSNPVersionList{list: versions[:i], variant: list.variant}
} else {
removedVersions = SEVSNPVersionList{list: append(versions[:i], versions[i+1:]...), variant: list.variant}
}
return removedVersions, nil
}
}
return SEVSNPVersionList{}, fmt.Errorf("version %s not found in list %v", versionStr, versions)
}
type crudCmd interface {
Execute(ctx context.Context, c *apiclient.Client) error
}
type deleteCmd struct {
apiObject apiclient.APIObject
}
func (d deleteCmd) Execute(ctx context.Context, c *apiclient.Client) error {
return apiclient.DeleteWithSignature(ctx, c, d.apiObject)
}
type putCmd struct {
apiObject apiclient.APIObject
signer sigstore.Signer
}
func (p putCmd) Execute(ctx context.Context, c *apiclient.Client) error {
return apiclient.SignAndUpdate(ctx, c, p.apiObject, p.signer)
}
func executeAllCmds(ctx context.Context, client *apiclient.Client, cmds []crudCmd) error {
for _, cmd := range cmds {
if err := cmd.Execute(ctx, client); err != nil {
return fmt.Errorf("execute operation %+v: %w", cmd, err)
}
}
return nil
}