api: use new signature type for Azure SNP versions

* Remove signature checks from unittests. Would need to export
signature from client/fetcher (unwanted). Can't figure out a better way.
e2e test completes in ~4sec and runs automatically.
So seems like a acceptable tradeoff.
* list object is now signed, but not verified. If we start to verify the list
we will have to adapt the e2e test to restore the previous list.
Otherwise there could be conflicts between dev and release keys.
This commit is contained in:
Otto Bittner 2023-08-25 15:10:24 +02:00
parent 2b19632e09
commit fdaa5aab3c
6 changed files with 28 additions and 117 deletions

View File

@ -35,35 +35,6 @@ type AzureSEVSNPVersion struct {
Microcode uint8 `json:"microcode"` Microcode uint8 `json:"microcode"`
} }
// 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("%s version has no .json suffix", s.Version)
}
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. // AzureSEVSNPVersionAPI is the request to get the version information of the specific version in the config api.
type AzureSEVSNPVersionAPI struct { type AzureSEVSNPVersionAPI struct {
Version string `json:"-"` Version string `json:"-"`

View File

@ -7,7 +7,6 @@ package attestationconfigapi
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"time" "time"
@ -45,16 +44,14 @@ func NewClient(ctx context.Context, cfg staticupload.Config, cosignPwd, privateK
return repo, clientClose, nil return repo, clientClose, nil
} }
// UploadAzureSEVSNP 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. // UploadAzureSEVSNPVersion 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) UploadAzureSEVSNP(ctx context.Context, version AzureSEVSNPVersion, date time.Time) error { func (a Client) UploadAzureSEVSNPVersion(ctx context.Context, version AzureSEVSNPVersion, date time.Time) error {
versions, err := a.List(ctx, variant.AzureSEVSNP{}) versions, err := a.List(ctx, variant.AzureSEVSNP{})
if err != nil { if err != nil {
return fmt.Errorf("fetch version list: %w", err) return fmt.Errorf("fetch version list: %w", err)
} }
ops, err := a.uploadAzureSEVSNP(version, versions, date) ops := a.constructUploadCmd(version, versions, date)
if err != nil {
return err
}
return executeAllCmds(ctx, a.s3Client, ops) return executeAllCmds(ctx, a.s3Client, ops)
} }
@ -91,50 +88,33 @@ func (a Client) deleteAzureSEVSNPVersion(versions AzureSEVSNPVersionList, versio
}, },
}) })
ops = append(ops, deleteCmd{
apiObject: AzureSEVSNPVersionSignature{
Version: versionStr,
},
})
removedVersions, err := removeVersion(versions, versionStr) removedVersions, err := removeVersion(versions, versionStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ops = append(ops, putCmd{ ops = append(ops, putCmd{
apiObject: removedVersions, apiObject: removedVersions,
signer: a.signer,
}) })
return ops, nil return ops, nil
} }
func (a Client) uploadAzureSEVSNP(versions AzureSEVSNPVersion, versionNames []string, date time.Time) (res []crudCmd, err error) { func (a Client) constructUploadCmd(versions AzureSEVSNPVersion, versionNames []string, date time.Time) []crudCmd {
dateStr := date.Format(VersionFormat) + ".json" dateStr := date.Format(VersionFormat) + ".json"
var res []crudCmd
res = append(res, putCmd{AzureSEVSNPVersionAPI{Version: dateStr, AzureSEVSNPVersion: versions}}) res = append(res, putCmd{
apiObject: AzureSEVSNPVersionAPI{Version: dateStr, AzureSEVSNPVersion: versions},
signer: a.signer,
})
versionBytes, err := json.Marshal(versions)
if err != nil {
return res, err
}
signature, err := a.createSignature(versionBytes, dateStr)
if err != nil {
return res, err
}
res = append(res, putCmd{signature})
newVersions := addVersion(versionNames, dateStr) newVersions := addVersion(versionNames, dateStr)
res = append(res, putCmd{AzureSEVSNPVersionList(newVersions)}) res = append(res, putCmd{
return apiObject: AzureSEVSNPVersionList(newVersions),
} signer: a.signer,
})
func (a Client) createSignature(content []byte, dateStr string) (res AzureSEVSNPVersionSignature, err error) { return res
signature, err := a.signer.Sign(content)
if err != nil {
return res, fmt.Errorf("sign version file: %w", err)
}
return AzureSEVSNPVersionSignature{
Signature: signature,
Version: dateStr,
}, nil
} }
func removeVersion(versions AzureSEVSNPVersionList, versionStr string) (removedVersions AzureSEVSNPVersionList, err error) { func removeVersion(versions AzureSEVSNPVersionList, versionStr string) (removedVersions AzureSEVSNPVersionList, err error) {
@ -160,15 +140,16 @@ type deleteCmd struct {
} }
func (d deleteCmd) Execute(ctx context.Context, c *apiclient.Client) error { func (d deleteCmd) Execute(ctx context.Context, c *apiclient.Client) error {
return apiclient.Delete(ctx, c, d.apiObject) return apiclient.DeleteWithSignature(ctx, c, d.apiObject)
} }
type putCmd struct { type putCmd struct {
apiObject apiclient.APIObject apiObject apiclient.APIObject
signer sigstore.Signer
} }
func (p putCmd) Execute(ctx context.Context, c *apiclient.Client) error { func (p putCmd) Execute(ctx context.Context, c *apiclient.Client) error {
return apiclient.Update(ctx, c, p.apiObject) return apiclient.SignAndUpdate(ctx, c, p.apiObject, p.signer)
} }
func executeAllCmds(ctx context.Context, client *apiclient.Client, cmds []crudCmd) error { func executeAllCmds(ctx context.Context, client *apiclient.Client, cmds []crudCmd) error {

View File

@ -19,24 +19,19 @@ func TestUploadAzureSEVSNP(t *testing.T) {
} }
version := AzureSEVSNPVersion{} version := AzureSEVSNPVersion{}
date := time.Date(2023, 1, 1, 1, 1, 1, 1, time.UTC) 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) ops := sut.constructUploadCmd(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" dateStr := "2023-01-01-01-01.json"
assert := assert.New(t)
assert.Contains(ops, putCmd{ assert.Contains(ops, putCmd{
apiObject: AzureSEVSNPVersionAPI{ apiObject: AzureSEVSNPVersionAPI{
Version: dateStr, Version: dateStr,
AzureSEVSNPVersion: version, AzureSEVSNPVersion: version,
}, },
}) signer: fakeSigner{},
assert.Contains(ops, putCmd{
apiObject: AzureSEVSNPVersionSignature{
Version: dateStr,
Signature: []byte("signature"),
},
}) })
assert.Contains(ops, putCmd{ assert.Contains(ops, putCmd{
apiObject: AzureSEVSNPVersionList([]string{"2023-01-01-01-01.json", "2021-01-01-01-01.json", "2019-01-01-01-01.json"}), apiObject: AzureSEVSNPVersionList([]string{"2023-01-01-01-01.json", "2021-01-01-01-01.json", "2019-01-01-01-01.json"}),
signer: fakeSigner{},
}) })
} }
@ -55,11 +50,6 @@ func TestDeleteAzureSEVSNPVersions(t *testing.T) {
Version: "2021-01-01.json", Version: "2021-01-01.json",
}, },
}) })
assert.Contains(ops, deleteCmd{
apiObject: AzureSEVSNPVersionSignature{
Version: "2021-01-01.json",
},
})
assert.Contains(ops, putCmd{ assert.Contains(ops, putCmd{
apiObject: AzureSEVSNPVersionList([]string{"2023-01-01.json", "2019-01-01.json"}), apiObject: AzureSEVSNPVersionList([]string{"2023-01-01.json", "2019-01-01.json"}),

View File

@ -8,7 +8,6 @@ package attestationconfigapi
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -57,31 +56,17 @@ func newFetcherWithClientAndVerifier(client apifetcher.HTTPClient, cosignVerifie
// FetchAzureSEVSNPVersionList fetches the version list information from the config API. // FetchAzureSEVSNPVersionList fetches the version list information from the config API.
func (f *fetcher) FetchAzureSEVSNPVersionList(ctx context.Context, attestation AzureSEVSNPVersionList) (AzureSEVSNPVersionList, error) { func (f *fetcher) FetchAzureSEVSNPVersionList(ctx context.Context, attestation AzureSEVSNPVersionList) (AzureSEVSNPVersionList, error) {
// TODO (derpsteb): Replace with FetchAndVerify once we move to v2 of the config API.
return apifetcher.Fetch(ctx, f.HTTPClient, attestation) return apifetcher.Fetch(ctx, f.HTTPClient, attestation)
} }
// FetchAzureSEVSNPVersion fetches the version information from the config API. // FetchAzureSEVSNPVersion fetches the version information from the config API.
func (f *fetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion AzureSEVSNPVersionAPI) (AzureSEVSNPVersionAPI, error) { func (f *fetcher) FetchAzureSEVSNPVersion(ctx context.Context, azureVersion AzureSEVSNPVersionAPI) (AzureSEVSNPVersionAPI, error) {
fetchedVersion, err := apifetcher.Fetch(ctx, f.HTTPClient, azureVersion) fetchedVersion, err := apifetcher.FetchAndVerify(ctx, f.HTTPClient, azureVersion, f.verifier)
if err != nil { if err != nil {
return fetchedVersion, fmt.Errorf("fetch version %s: %w", fetchedVersion.Version, err) 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", azureVersion.Version, err)
}
signature, err := apifetcher.Fetch(ctx, f.HTTPClient, AzureSEVSNPVersionSignature{
Version: azureVersion.Version,
})
if err != nil {
return fetchedVersion, fmt.Errorf("fetch version %s signature: %w", azureVersion.Version, err)
}
err = f.verifier.VerifySignature(versionBytes, signature.Signature)
if err != nil {
return fetchedVersion, fmt.Errorf("verify version %s signature: %w", azureVersion.Version, err)
}
return fetchedVersion, nil return fetchedVersion, nil
} }

View File

@ -97,7 +97,6 @@ type fakeConfigAPIHandler struct {
// RoundTrip resolves the request and returns a dummy response. // RoundTrip resolves the request and returns a dummy response.
func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, error) { func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, error) {
signature := []byte("placeholderSignature")
if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/list" { if req.URL.Path == "/constellation/v1/attestation/azure-sev-snp/list" {
res := &http.Response{} res := &http.Response{}
bt, err := json.Marshal(f.versions) bt, err := json.Marshal(f.versions)
@ -128,30 +127,15 @@ func (f *fakeConfigAPIHandler) RoundTrip(req *http.Request) (*http.Response, err
res.Body = io.NopCloser(bytes.NewReader(bt)) res.Body = io.NopCloser(bytes.NewReader(bt))
res.StatusCode = http.StatusOK res.StatusCode = http.StatusOK
return res, nil return res, nil
} else if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/azure-sev-snp/%s.sig", f.latestVersion) { } else if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/azure-sev-snp/%s.sig", f.latestVersion) {
res := &http.Response{} res := &http.Response{}
obj := AzureSEVSNPVersionSignature{ res.Body = io.NopCloser(bytes.NewReader([]byte("null")))
Signature: signature,
}
bt, err := json.Marshal(obj)
if err != nil {
return nil, err
}
res.Body = io.NopCloser(bytes.NewReader(bt))
res.StatusCode = http.StatusOK res.StatusCode = http.StatusOK
return res, nil return res, nil
} else if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/azure-sev-snp/%s.sig", f.olderVersion) { } else if req.URL.Path == fmt.Sprintf("/constellation/v1/attestation/azure-sev-snp/%s.sig", f.olderVersion) {
res := &http.Response{} res := &http.Response{}
obj := AzureSEVSNPVersionSignature{ res.Body = io.NopCloser(bytes.NewReader([]byte("null")))
Signature: signature,
}
bt, err := json.Marshal(obj)
if err != nil {
return nil, err
}
res.Body = io.NopCloser(bytes.NewReader(bt))
res.StatusCode = http.StatusOK res.StatusCode = http.StatusOK
return res, nil return res, nil

View File

@ -374,5 +374,5 @@ func (s signature) ValidateRequest() error {
// Validate checks that the signature is base64 encoded. // Validate checks that the signature is base64 encoded.
func (s signature) Validate() error { func (s signature) Validate() error {
return sigstore.IsBase64([]byte(s.Signature)) return sigstore.IsBase64(s.Signature)
} }