mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-24 23:19:39 -05:00
AB#2644 Fetch measurements from CDN (#653)
* Fetch measurements from CDN * Perform metadata validation on fetched measurements * Remove deprecated public bucket Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
c978329839
commit
d52f3db2a3
@ -11,13 +11,15 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/file"
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/image"
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
"github.com/edgelesssys/constellation/v2/internal/sigstore"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -49,10 +51,13 @@ func runConfigFetchMeasurements(cmd *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("constructing Rekor client: %w", err)
|
return fmt.Errorf("constructing Rekor client: %w", err)
|
||||||
}
|
}
|
||||||
return configFetchMeasurements(cmd, rekor, fileHandler, http.DefaultClient, image.New())
|
return configFetchMeasurements(cmd, rekor, []byte(constants.CosignPublicKey), fileHandler, http.DefaultClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configFetchMeasurements(cmd *cobra.Command, verifier rekorVerifier, fileHandler file.Handler, client *http.Client, img imageFetcher) error {
|
func configFetchMeasurements(
|
||||||
|
cmd *cobra.Command, verifier rekorVerifier, cosignPublicKey []byte,
|
||||||
|
fileHandler file.Handler, client *http.Client,
|
||||||
|
) error {
|
||||||
flags, err := parseFetchMeasurementsFlags(cmd)
|
flags, err := parseFetchMeasurementsFlags(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -70,12 +75,21 @@ func configFetchMeasurements(cmd *cobra.Command, verifier rekorVerifier, fileHan
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := flags.updateURLs(ctx, conf, img); err != nil {
|
if err := flags.updateURLs(conf); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var fetchedMeasurements measurements.M
|
var fetchedMeasurements measurements.M
|
||||||
hash, err := fetchedMeasurements.FetchAndVerify(ctx, client, flags.measurementsURL, flags.signatureURL, []byte(constants.CosignPublicKey))
|
hash, err := fetchedMeasurements.FetchAndVerify(
|
||||||
|
ctx, client,
|
||||||
|
flags.measurementsURL,
|
||||||
|
flags.signatureURL,
|
||||||
|
cosignPublicKey,
|
||||||
|
measurements.WithMetadata{
|
||||||
|
CSP: conf.GetProvider(),
|
||||||
|
Image: conf.Image,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -129,31 +143,31 @@ func parseFetchMeasurementsFlags(cmd *cobra.Command) (*fetchMeasurementsFlags, e
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fetchMeasurementsFlags) updateURLs(ctx context.Context, conf *config.Config, img imageFetcher) error {
|
func (f *fetchMeasurementsFlags) updateURLs(conf *config.Config) error {
|
||||||
imageRef, err := img.FetchReference(ctx, conf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.measurementsURL == nil {
|
if f.measurementsURL == nil {
|
||||||
// TODO(AB#2644): resolve image version to reference
|
url, err := measurementURL(conf.GetProvider(), conf.Image, "measurements.json")
|
||||||
parsedURL, err := url.Parse(constants.S3PublicBucket + imageRef + "/measurements.json")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.measurementsURL = parsedURL
|
f.measurementsURL = url
|
||||||
}
|
}
|
||||||
|
|
||||||
if f.signatureURL == nil {
|
if f.signatureURL == nil {
|
||||||
parsedURL, err := url.Parse(constants.S3PublicBucket + imageRef + "/measurements.json.sig")
|
url, err := measurementURL(conf.GetProvider(), conf.Image, "measurements.json.sig")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.signatureURL = parsedURL
|
f.signatureURL = url
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type imageFetcher interface {
|
func measurementURL(provider cloudprovider.Provider, image, file string) (*url.URL, error) {
|
||||||
FetchReference(ctx context.Context, config *config.Config) (string, error)
|
url, err := url.Parse(constants.CDNRepositoryURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing image version repository URL: %w", err)
|
||||||
|
}
|
||||||
|
url.Path = path.Join(constants.CDNMeasurementsPath, image, strings.ToLower(provider.String()), file)
|
||||||
|
|
||||||
|
return url, nil
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -109,8 +109,8 @@ func TestUpdateURLs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
flags: &fetchMeasurementsFlags{},
|
flags: &fetchMeasurementsFlags{},
|
||||||
wantMeasurementsURL: constants.S3PublicBucket + "some/image/path/image-123456/measurements.json",
|
wantMeasurementsURL: constants.CDNRepositoryURL + "/" + constants.CDNMeasurementsPath + "/someImageVersion/gcp/measurements.json",
|
||||||
wantMeasurementsSigURL: constants.S3PublicBucket + "some/image/path/image-123456/measurements.json.sig",
|
wantMeasurementsSigURL: constants.CDNRepositoryURL + "/" + constants.CDNMeasurementsPath + "/someImageVersion/gcp/measurements.json.sig",
|
||||||
},
|
},
|
||||||
"both set by user": {
|
"both set by user": {
|
||||||
conf: &config.Config{},
|
conf: &config.Config{},
|
||||||
@ -127,9 +127,7 @@ func TestUpdateURLs(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
err := tc.flags.updateURLs(context.Background(), tc.conf, &stubImageFetcher{
|
err := tc.flags.updateURLs(tc.conf)
|
||||||
reference: "some/image/path/image-123456",
|
|
||||||
})
|
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(tc.wantMeasurementsURL, tc.flags.measurementsURL.String())
|
assert.Equal(tc.wantMeasurementsURL, tc.flags.measurementsURL.String())
|
||||||
})
|
})
|
||||||
@ -152,32 +150,57 @@ func newTestClient(fn roundTripFunc) *http.Client {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigFetchMeasurements(t *testing.T) {
|
func TestConfigFetchMeasurements(t *testing.T) {
|
||||||
measurements := `1: fPRxd3lV3uybnSVhcBmM6XLzcvMitXW78G0RRuQxYGc=
|
// Cosign private key used to sign the measurements.
|
||||||
2: PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=
|
// Generated with: cosign generate-key-pair
|
||||||
3: PUWM/lXMA+ofRD8VYr7sjfUcdeFKn8+acjShPxmOeWk=
|
// Password left empty.
|
||||||
4: HaV5ivUAGzMxmKkfKjcG3wmW08MRUWr+vsfIMVQpOH0=
|
//
|
||||||
5: PemdXV59WnLLzPz0F4GGCTKm8KbHskPRvon1dtNw7oY=
|
// -----BEGIN ENCRYPTED COSIGN PRIVATE KEY-----
|
||||||
7: 8dI/6SUmQ5sd8+bulPDpJ8ghs0UX0+fgLlW8kutAYKw=
|
// eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6
|
||||||
8: XJ5IBWy6b6vqojkTsk/GLOWyfNUB2qaf58+JjMYiAB4=
|
// OCwicCI6MX0sInNhbHQiOiJlRHVYMWRQMGtIWVRnK0xkbjcxM0tjbFVJaU92eFVX
|
||||||
9: Gw5gq8D1WXfz46sF/OKiWbkBssyt4ayGybzNyV9cUCQ=
|
// VXgvNi9BbitFVk5BPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
|
||||||
|
// Iiwibm9uY2UiOiJwaWhLL2txNmFXa2hqSVVHR3RVUzhTVkdHTDNIWWp4TCJ9LCJj
|
||||||
|
// aXBoZXJ0ZXh0Ijoidm81SHVWRVFWcUZ2WFlQTTVPaTVaWHM5a255bndZU2dvcyth
|
||||||
|
// VklIeHcrOGFPamNZNEtvVjVmL3lHRHR0K3BHV2toanJPR1FLOWdBbmtsazFpQ0c5
|
||||||
|
// a2czUXpPQTZsU2JRaHgvZlowRVRZQ0hLeElncEdPRVRyTDlDenZDemhPZXVSOXJ6
|
||||||
|
// TDcvRjBBVy9vUDVqZXR3dmJMNmQxOEhjck9kWE8yVmYxY2w0YzNLZjVRcnFSZzlN
|
||||||
|
// dlRxQWFsNXJCNHNpY1JaMVhpUUJjb0YwNHc9PSJ9
|
||||||
|
// -----END ENCRYPTED COSIGN PRIVATE KEY-----
|
||||||
|
|
||||||
|
cosignPublicKey := []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----")
|
||||||
|
|
||||||
|
measurements := `{
|
||||||
|
"csp": "gcp",
|
||||||
|
"image": "v999.999.999",
|
||||||
|
"measurements": {
|
||||||
|
"0": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
|
"1": "1111111111111111111111111111111111111111111111111111111111111111",
|
||||||
|
"2": "2222222222222222222222222222222222222222222222222222222222222222",
|
||||||
|
"3": "3333333333333333333333333333333333333333333333333333333333333333",
|
||||||
|
"4": "4444444444444444444444444444444444444444444444444444444444444444",
|
||||||
|
"5": "5555555555555555555555555555555555555555555555555555555555555555",
|
||||||
|
"6": "6666666666666666666666666666666666666666666666666666666666666666"
|
||||||
|
}
|
||||||
|
}
|
||||||
`
|
`
|
||||||
signature := "MEUCIFdJ5dH6HDywxQWTUh9Bw77wMrq0mNCUjMQGYP+6QsVmAiEAmazj/L7rFGA4/Gz8y+kI5h5E5cDgc3brihvXBKF6qZA="
|
signature := "MEYCIQDRAQNK2NjHJBGrnw3HQAyBsXMCmVCptBdgA6VZ3IlyiAIhAPG42waF1aFZq7dnjP3b2jsMNUtaKYDQQSazW1AX8jgF"
|
||||||
|
|
||||||
client := newTestClient(func(req *http.Request) *http.Response {
|
client := newTestClient(func(req *http.Request) *http.Response {
|
||||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/someImage/measurements.json" {
|
if req.URL.String() == "https://cdn.confidential.cloud/constellation/v1/measurements/v999.999.999/gcp/measurements.json" {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewBufferString(measurements)),
|
Body: io.NopCloser(bytes.NewBufferString(measurements)),
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/someImage/measurements.json.sig" {
|
if req.URL.String() == "https://cdn.confidential.cloud/constellation/v1/measurements/v999.999.999/gcp/measurements.json.sig" {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewBufferString(signature)),
|
Body: io.NopCloser(bytes.NewBufferString(signature)),
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("unexpected request", req.URL.String())
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusNotFound,
|
StatusCode: http.StatusNotFound,
|
||||||
Body: io.NopCloser(bytes.NewBufferString("Not found.")),
|
Body: io.NopCloser(bytes.NewBufferString("Not found.")),
|
||||||
@ -215,23 +238,12 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
|||||||
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
fileHandler := file.NewHandler(afero.NewMemMapFs())
|
||||||
|
|
||||||
gcpConfig := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.GCP)
|
gcpConfig := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.GCP)
|
||||||
gcpConfig.Image = "someImage"
|
gcpConfig.Image = "v999.999.999"
|
||||||
|
|
||||||
err := fileHandler.WriteYAML(constants.ConfigFilename, gcpConfig, file.OptMkdirAll)
|
err := fileHandler.WriteYAML(constants.ConfigFilename, gcpConfig, file.OptMkdirAll)
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
assert.NoError(configFetchMeasurements(cmd, tc.verifier, fileHandler, client, &stubImageFetcher{
|
assert.NoError(configFetchMeasurements(cmd, tc.verifier, cosignPublicKey, fileHandler, client))
|
||||||
reference: "someImage",
|
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type stubImageFetcher struct {
|
|
||||||
reference string
|
|
||||||
fetchReferenceErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *stubImageFetcher) FetchReference(_ context.Context, _ *config.Config) (string, error) {
|
|
||||||
return f.reference, f.fetchReferenceErr
|
|
||||||
}
|
|
||||||
|
@ -13,10 +13,12 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
@ -163,14 +165,20 @@ func getCompatibleImages(csp cloudprovider.Provider, currentVersion string, imag
|
|||||||
case cloudprovider.Azure:
|
case cloudprovider.Azure:
|
||||||
for imgVersion, image := range images {
|
for imgVersion, image := range images {
|
||||||
if semver.Compare(currentVersion, imgVersion) < 0 {
|
if semver.Compare(currentVersion, imgVersion) < 0 {
|
||||||
compatibleImages[imgVersion] = config.UpgradeConfig{Image: image.AzureImage}
|
compatibleImages[imgVersion] = config.UpgradeConfig{
|
||||||
|
Image: image.AzureImage,
|
||||||
|
CSP: cloudprovider.Azure,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case cloudprovider.GCP:
|
case cloudprovider.GCP:
|
||||||
for imgVersion, image := range images {
|
for imgVersion, image := range images {
|
||||||
if semver.Compare(currentVersion, imgVersion) < 0 {
|
if semver.Compare(currentVersion, imgVersion) < 0 {
|
||||||
compatibleImages[imgVersion] = config.UpgradeConfig{Image: image.GCPImage}
|
compatibleImages[imgVersion] = config.UpgradeConfig{
|
||||||
|
Image: image.GCPImage,
|
||||||
|
CSP: cloudprovider.GCP,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,17 +189,26 @@ func getCompatibleImages(csp cloudprovider.Provider, currentVersion string, imag
|
|||||||
// getCompatibleImageMeasurements retrieves the expected measurements for each image.
|
// getCompatibleImageMeasurements retrieves the expected measurements for each image.
|
||||||
func getCompatibleImageMeasurements(ctx context.Context, cmd *cobra.Command, client *http.Client, rekor rekorVerifier, pubK []byte, images map[string]config.UpgradeConfig) error {
|
func getCompatibleImageMeasurements(ctx context.Context, cmd *cobra.Command, client *http.Client, rekor rekorVerifier, pubK []byte, images map[string]config.UpgradeConfig) error {
|
||||||
for idx, img := range images {
|
for idx, img := range images {
|
||||||
measurementsURL, err := url.Parse(constants.S3PublicBucket + strings.ToLower(img.Image) + "/measurements.json")
|
measurementsURL, err := url.Parse(constants.CDNRepositoryURL + "/" + path.Join(img.Image, strings.ToLower(img.CSP.String()), "measurements.json"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
signatureURL, err := url.Parse(constants.S3PublicBucket + strings.ToLower(img.Image) + "/measurements.json.sig")
|
signatureURL, err := url.Parse(constants.CDNRepositoryURL + "/" + path.Join(img.Image, strings.ToLower(img.CSP.String()), "measurements.json.sig"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := img.Measurements.FetchAndVerify(ctx, client, measurementsURL, signatureURL, pubK)
|
hash, err := img.Measurements.FetchAndVerify(
|
||||||
|
ctx, client,
|
||||||
|
measurementsURL,
|
||||||
|
signatureURL,
|
||||||
|
pubK,
|
||||||
|
measurements.WithMetadata{
|
||||||
|
Image: img.Image,
|
||||||
|
CSP: img.CSP,
|
||||||
|
},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -192,12 +192,15 @@ func TestGetCompatibleImages(t *testing.T) {
|
|||||||
wantImages: map[string]config.UpgradeConfig{
|
wantImages: map[string]config.UpgradeConfig{
|
||||||
"v1.0.1": {
|
"v1.0.1": {
|
||||||
Image: "azure-v1.0.1",
|
Image: "azure-v1.0.1",
|
||||||
|
CSP: cloudprovider.Azure,
|
||||||
},
|
},
|
||||||
"v1.0.2": {
|
"v1.0.2": {
|
||||||
Image: "azure-v1.0.2",
|
Image: "azure-v1.0.2",
|
||||||
|
CSP: cloudprovider.Azure,
|
||||||
},
|
},
|
||||||
"v1.1.0": {
|
"v1.1.0": {
|
||||||
Image: "azure-v1.1.0",
|
Image: "azure-v1.1.0",
|
||||||
|
CSP: cloudprovider.Azure,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -208,12 +211,15 @@ func TestGetCompatibleImages(t *testing.T) {
|
|||||||
wantImages: map[string]config.UpgradeConfig{
|
wantImages: map[string]config.UpgradeConfig{
|
||||||
"v1.0.1": {
|
"v1.0.1": {
|
||||||
Image: "gcp-v1.0.1",
|
Image: "gcp-v1.0.1",
|
||||||
|
CSP: cloudprovider.GCP,
|
||||||
},
|
},
|
||||||
"v1.0.2": {
|
"v1.0.2": {
|
||||||
Image: "gcp-v1.0.2",
|
Image: "gcp-v1.0.2",
|
||||||
|
CSP: cloudprovider.GCP,
|
||||||
},
|
},
|
||||||
"v1.1.0": {
|
"v1.1.0": {
|
||||||
Image: "gcp-v1.1.0",
|
Image: "gcp-v1.1.0",
|
||||||
|
CSP: cloudprovider.GCP,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -240,25 +246,42 @@ func TestGetCompatibleImageMeasurements(t *testing.T) {
|
|||||||
|
|
||||||
testImages := map[string]config.UpgradeConfig{
|
testImages := map[string]config.UpgradeConfig{
|
||||||
"v0.0.0": {
|
"v0.0.0": {
|
||||||
Image: "azure-v0.0.0",
|
Image: "v0.0.0",
|
||||||
|
CSP: cloudprovider.Azure,
|
||||||
},
|
},
|
||||||
"v1.0.0": {
|
"v1.0.0": {
|
||||||
Image: "azure-v1.0.0",
|
Image: "v1.0.0",
|
||||||
|
CSP: cloudprovider.Azure,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
client := newTestClient(func(req *http.Request) *http.Response {
|
client := newTestClient(func(req *http.Request) *http.Response {
|
||||||
if strings.HasSuffix(req.URL.String(), "/measurements.json") {
|
if strings.HasSuffix(req.URL.String(), "v0.0.0/azure/measurements.json") {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(strings.NewReader("0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n")),
|
Body: io.NopCloser(strings.NewReader(`{"csp":"azure","image":"v0.0.0","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`)),
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(req.URL.String(), "/measurements.json.sig") {
|
if strings.HasSuffix(req.URL.String(), "v0.0.0/azure/measurements.json.sig") {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(strings.NewReader("MEUCIBs1g2/n0FsgPfJ+0uLD5TaunGhxwDcQcUGBroejKvg3AiEAzZtcLU9O6IiVhxB8tBS+ty6MXoPNwL8WRWMzyr35eKI=")),
|
Body: io.NopCloser(strings.NewReader("MEQCIGRR7RaSMs892Ta06/Tz7LqPUxI05X4wQcP+nFFmZtmaAiBNl9X8mUKmUBfxg13LQBfmmpw6JwYQor5hOwM3NFVPAg==")),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(req.URL.String(), "v1.0.0/azure/measurements.json") {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(strings.NewReader(`{"csp":"azure","image":"v1.0.0","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`)),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(req.URL.String(), "v1.0.0/azure/measurements.json.sig") {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: http.StatusOK,
|
||||||
|
Body: io.NopCloser(strings.NewReader("MEQCIFh8CVELp/Da2U2Jt404OXsUeDfqtrf3pqGRuvxnxhI8AiBTHF9tHEPwFedYG3Jgn2ELOxss+Ybc6135vEtClBrbpg==")),
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -270,7 +293,7 @@ func TestGetCompatibleImageMeasurements(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
pubK := []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----")
|
pubK := []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----")
|
||||||
|
|
||||||
err := getCompatibleImageMeasurements(context.Background(), &cobra.Command{}, client, singleUUIDVerifier(), pubK, testImages)
|
err := getCompatibleImageMeasurements(context.Background(), &cobra.Command{}, client, singleUUIDVerifier(), pubK, testImages)
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
@ -283,15 +306,28 @@ func TestGetCompatibleImageMeasurements(t *testing.T) {
|
|||||||
func TestUpgradePlan(t *testing.T) {
|
func TestUpgradePlan(t *testing.T) {
|
||||||
testImages := map[string]imageManifest{
|
testImages := map[string]imageManifest{
|
||||||
"v1.0.0": {
|
"v1.0.0": {
|
||||||
AzureImage: "azure-v1.0.0",
|
AzureImage: "v1.0.0",
|
||||||
GCPImage: "gcp-v1.0.0",
|
GCPImage: "v1.0.0",
|
||||||
},
|
|
||||||
"v2.0.0": {
|
|
||||||
AzureImage: "azure-v2.0.0",
|
|
||||||
GCPImage: "gcp-v2.0.0",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cosign private key used to sign the measurements.
|
||||||
|
// Generated with: cosign generate-key-pair
|
||||||
|
// Password left empty.
|
||||||
|
//
|
||||||
|
// -----BEGIN ENCRYPTED COSIGN PRIVATE KEY-----
|
||||||
|
// eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjozMjc2OCwiciI6
|
||||||
|
// OCwicCI6MX0sInNhbHQiOiJlRHVYMWRQMGtIWVRnK0xkbjcxM0tjbFVJaU92eFVX
|
||||||
|
// VXgvNi9BbitFVk5BPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94
|
||||||
|
// Iiwibm9uY2UiOiJwaWhLL2txNmFXa2hqSVVHR3RVUzhTVkdHTDNIWWp4TCJ9LCJj
|
||||||
|
// aXBoZXJ0ZXh0Ijoidm81SHVWRVFWcUZ2WFlQTTVPaTVaWHM5a255bndZU2dvcyth
|
||||||
|
// VklIeHcrOGFPamNZNEtvVjVmL3lHRHR0K3BHV2toanJPR1FLOWdBbmtsazFpQ0c5
|
||||||
|
// a2czUXpPQTZsU2JRaHgvZlowRVRZQ0hLeElncEdPRVRyTDlDenZDemhPZXVSOXJ6
|
||||||
|
// TDcvRjBBVy9vUDVqZXR3dmJMNmQxOEhjck9kWE8yVmYxY2w0YzNLZjVRcnFSZzlN
|
||||||
|
// dlRxQWFsNXJCNHNpY1JaMVhpUUJjb0YwNHc9PSJ9
|
||||||
|
// -----END ENCRYPTED COSIGN PRIVATE KEY-----
|
||||||
|
pubK := "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----"
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
planner stubUpgradePlanner
|
planner stubUpgradePlanner
|
||||||
flags upgradePlanFlags
|
flags upgradePlanFlags
|
||||||
@ -311,7 +347,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "upgrade-plan.yaml",
|
filePath: "upgrade-plan.yaml",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
verifier: singleUUIDVerifier(),
|
verifier: singleUUIDVerifier(),
|
||||||
@ -319,14 +355,14 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"upgrades gcp": {
|
"upgrades gcp": {
|
||||||
planner: stubUpgradePlanner{
|
planner: stubUpgradePlanner{
|
||||||
image: "projects/constellation-images/global/images/constellation-v1-0-0",
|
image: "projects/constellation-images/global/images/constellation-v0-0-0",
|
||||||
},
|
},
|
||||||
imageFetchStatus: http.StatusOK,
|
imageFetchStatus: http.StatusOK,
|
||||||
measurementsFetchStatus: http.StatusOK,
|
measurementsFetchStatus: http.StatusOK,
|
||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "upgrade-plan.yaml",
|
filePath: "upgrade-plan.yaml",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
verifier: singleUUIDVerifier(),
|
verifier: singleUUIDVerifier(),
|
||||||
@ -341,7 +377,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "upgrade-plan.yaml",
|
filePath: "upgrade-plan.yaml",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.Azure,
|
csp: cloudprovider.Azure,
|
||||||
verifier: singleUUIDVerifier(),
|
verifier: singleUUIDVerifier(),
|
||||||
@ -349,14 +385,14 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"upgrade to stdout": {
|
"upgrade to stdout": {
|
||||||
planner: stubUpgradePlanner{
|
planner: stubUpgradePlanner{
|
||||||
image: "projects/constellation-images/global/images/constellation-v1-0-0",
|
image: "projects/constellation-images/global/images/constellation-v0-0-0",
|
||||||
},
|
},
|
||||||
imageFetchStatus: http.StatusOK,
|
imageFetchStatus: http.StatusOK,
|
||||||
measurementsFetchStatus: http.StatusOK,
|
measurementsFetchStatus: http.StatusOK,
|
||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "-",
|
filePath: "-",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
verifier: singleUUIDVerifier(),
|
verifier: singleUUIDVerifier(),
|
||||||
@ -371,7 +407,7 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "upgrade-plan.yaml",
|
filePath: "upgrade-plan.yaml",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
verifier: singleUUIDVerifier(),
|
verifier: singleUUIDVerifier(),
|
||||||
@ -379,14 +415,14 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"image fetch error": {
|
"image fetch error": {
|
||||||
planner: stubUpgradePlanner{
|
planner: stubUpgradePlanner{
|
||||||
image: "projects/constellation-images/global/images/constellation-v1-0-0",
|
image: "projects/constellation-images/global/images/constellation-v0-0-0",
|
||||||
},
|
},
|
||||||
imageFetchStatus: http.StatusInternalServerError,
|
imageFetchStatus: http.StatusInternalServerError,
|
||||||
measurementsFetchStatus: http.StatusOK,
|
measurementsFetchStatus: http.StatusOK,
|
||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "upgrade-plan.yaml",
|
filePath: "upgrade-plan.yaml",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
verifier: singleUUIDVerifier(),
|
verifier: singleUUIDVerifier(),
|
||||||
@ -394,14 +430,14 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"measurements fetch error": {
|
"measurements fetch error": {
|
||||||
planner: stubUpgradePlanner{
|
planner: stubUpgradePlanner{
|
||||||
image: "projects/constellation-images/global/images/constellation-v1-0-0",
|
image: "projects/constellation-images/global/images/constellation-v0-0-0",
|
||||||
},
|
},
|
||||||
imageFetchStatus: http.StatusOK,
|
imageFetchStatus: http.StatusOK,
|
||||||
measurementsFetchStatus: http.StatusInternalServerError,
|
measurementsFetchStatus: http.StatusInternalServerError,
|
||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "upgrade-plan.yaml",
|
filePath: "upgrade-plan.yaml",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
verifier: singleUUIDVerifier(),
|
verifier: singleUUIDVerifier(),
|
||||||
@ -409,14 +445,14 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"failing search should not result in error": {
|
"failing search should not result in error": {
|
||||||
planner: stubUpgradePlanner{
|
planner: stubUpgradePlanner{
|
||||||
image: "projects/constellation-images/global/images/constellation-v1-0-0",
|
image: "projects/constellation-images/global/images/constellation-v0-0-0",
|
||||||
},
|
},
|
||||||
imageFetchStatus: http.StatusOK,
|
imageFetchStatus: http.StatusOK,
|
||||||
measurementsFetchStatus: http.StatusOK,
|
measurementsFetchStatus: http.StatusOK,
|
||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "upgrade-plan.yaml",
|
filePath: "upgrade-plan.yaml",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
verifier: &stubRekorVerifier{
|
verifier: &stubRekorVerifier{
|
||||||
@ -427,14 +463,14 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"failing verify should not result in error": {
|
"failing verify should not result in error": {
|
||||||
planner: stubUpgradePlanner{
|
planner: stubUpgradePlanner{
|
||||||
image: "projects/constellation-images/global/images/constellation-v1-0-0",
|
image: "projects/constellation-images/global/images/constellation-v0-0-0",
|
||||||
},
|
},
|
||||||
imageFetchStatus: http.StatusOK,
|
imageFetchStatus: http.StatusOK,
|
||||||
measurementsFetchStatus: http.StatusOK,
|
measurementsFetchStatus: http.StatusOK,
|
||||||
flags: upgradePlanFlags{
|
flags: upgradePlanFlags{
|
||||||
configPath: constants.ConfigFilename,
|
configPath: constants.ConfigFilename,
|
||||||
filePath: "upgrade-plan.yaml",
|
filePath: "upgrade-plan.yaml",
|
||||||
cosignPubKey: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUs5fDUIz9aiwrfr8BK4VjN7jE6sl\ngz7UuXsOin8+dB0SGrbNHy7TJToa2fAiIKPVLTOfvY75DqRAtffhO1fpBA==\n-----END PUBLIC KEY-----",
|
cosignPubKey: pubK,
|
||||||
},
|
},
|
||||||
csp: cloudprovider.GCP,
|
csp: cloudprovider.GCP,
|
||||||
verifier: &stubRekorVerifier{
|
verifier: &stubRekorVerifier{
|
||||||
@ -470,17 +506,32 @@ func TestUpgradePlan(t *testing.T) {
|
|||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(req.URL.String(), "/measurements.json") {
|
if strings.HasSuffix(req.URL.String(), "azure/measurements.json") {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: tc.measurementsFetchStatus,
|
StatusCode: tc.measurementsFetchStatus,
|
||||||
Body: io.NopCloser(strings.NewReader("0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n")),
|
Body: io.NopCloser(strings.NewReader(`{"csp":"azure","image":"v1.0.0","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`)),
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(req.URL.String(), "/measurements.json.sig") {
|
if strings.HasSuffix(req.URL.String(), "azure/measurements.json.sig") {
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: tc.measurementsFetchStatus,
|
StatusCode: tc.measurementsFetchStatus,
|
||||||
Body: io.NopCloser(strings.NewReader("MEUCIBs1g2/n0FsgPfJ+0uLD5TaunGhxwDcQcUGBroejKvg3AiEAzZtcLU9O6IiVhxB8tBS+ty6MXoPNwL8WRWMzyr35eKI=")),
|
Body: io.NopCloser(strings.NewReader("MEQCIFh8CVELp/Da2U2Jt404OXsUeDfqtrf3pqGRuvxnxhI8AiBTHF9tHEPwFedYG3Jgn2ELOxss+Ybc6135vEtClBrbpg==")),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasSuffix(req.URL.String(), "gcp/measurements.json") {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: tc.measurementsFetchStatus,
|
||||||
|
Body: io.NopCloser(strings.NewReader(`{"csp":"gcp","image":"v1.0.0","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`)),
|
||||||
|
Header: make(http.Header),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(req.URL.String(), "gcp/measurements.json.sig") {
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: tc.measurementsFetchStatus,
|
||||||
|
Body: io.NopCloser(strings.NewReader("MEYCIQCr/gDGjj11mR5OeImwOLjxnBqMbBmqoK7yXqy0cXR3HQIhALpVDdYwR9VNJnWwtl8bTfrezyJbc7UNZJO4PJe+stFP")),
|
||||||
Header: make(http.Header),
|
Header: make(http.Header),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
"github.com/edgelesssys/constellation/v2/internal/crypto"
|
||||||
"github.com/edgelesssys/constellation/v2/verify/verifyproto"
|
"github.com/edgelesssys/constellation/v2/verify/verifyproto"
|
||||||
@ -73,7 +74,7 @@ func main() {
|
|||||||
|
|
||||||
if *metadata {
|
if *metadata {
|
||||||
outputWithMetadata := measurements.WithMetadata{
|
outputWithMetadata := measurements.WithMetadata{
|
||||||
CSP: strings.ToLower(*csp),
|
CSP: cloudprovider.FromString(*csp),
|
||||||
Image: strings.ToLower(*image),
|
Image: strings.ToLower(*image),
|
||||||
Measurements: pcrs,
|
Measurements: pcrs,
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/google/go-tpm-tools/proto/attest"
|
"github.com/google/go-tpm-tools/proto/attest"
|
||||||
"github.com/google/go-tpm-tools/proto/tpm"
|
"github.com/google/go-tpm-tools/proto/tpm"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -152,22 +153,22 @@ func TestPrintPCRs(t *testing.T) {
|
|||||||
func TestPrintPCRsWithMetadata(t *testing.T) {
|
func TestPrintPCRsWithMetadata(t *testing.T) {
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
format string
|
format string
|
||||||
csp string
|
csp cloudprovider.Provider
|
||||||
image string
|
image string
|
||||||
}{
|
}{
|
||||||
"json": {
|
"json": {
|
||||||
format: "json",
|
format: "json",
|
||||||
csp: "azure",
|
csp: cloudprovider.Azure,
|
||||||
image: "v2.0.0",
|
image: "v2.0.0",
|
||||||
},
|
},
|
||||||
"yaml": {
|
"yaml": {
|
||||||
csp: "gcp",
|
csp: cloudprovider.GCP,
|
||||||
image: "v2.0.0-testimage",
|
image: "v2.0.0-testimage",
|
||||||
format: "yaml",
|
format: "yaml",
|
||||||
},
|
},
|
||||||
"empty format": {
|
"empty format": {
|
||||||
format: "",
|
format: "",
|
||||||
csp: "qemu",
|
csp: cloudprovider.QEMU,
|
||||||
image: "v2.0.0-testimage",
|
image: "v2.0.0-testimage",
|
||||||
},
|
},
|
||||||
"empty": {},
|
"empty": {},
|
||||||
|
@ -40,15 +40,18 @@ type M map[uint32]Measurement
|
|||||||
|
|
||||||
// WithMetadata is a struct supposed to provide CSP & image metadata next to measurements.
|
// WithMetadata is a struct supposed to provide CSP & image metadata next to measurements.
|
||||||
type WithMetadata struct {
|
type WithMetadata struct {
|
||||||
CSP string `json:"csp" yaml:"csp"`
|
CSP cloudprovider.Provider `json:"csp" yaml:"csp"`
|
||||||
Image string `json:"image" yaml:"image"`
|
Image string `json:"image" yaml:"image"`
|
||||||
Measurements M `json:"measurements" yaml:"measurements"`
|
Measurements M `json:"measurements" yaml:"measurements"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchAndVerify fetches measurement and signature files via provided URLs,
|
// FetchAndVerify fetches measurement and signature files via provided URLs,
|
||||||
// using client for download. The publicKey is used to verify the measurements.
|
// using client for download. The publicKey is used to verify the measurements.
|
||||||
// The hash of the fetched measurements is returned.
|
// The hash of the fetched measurements is returned.
|
||||||
func (m *M) FetchAndVerify(ctx context.Context, client *http.Client, measurementsURL *url.URL, signatureURL *url.URL, publicKey []byte) (string, error) {
|
func (m *M) FetchAndVerify(
|
||||||
|
ctx context.Context, client *http.Client, measurementsURL, signatureURL *url.URL,
|
||||||
|
publicKey []byte, metadata WithMetadata,
|
||||||
|
) (string, error) {
|
||||||
measurements, err := getFromURL(ctx, client, measurementsURL)
|
measurements, err := getFromURL(ctx, client, measurementsURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to fetch measurements: %w", err)
|
return "", fmt.Errorf("failed to fetch measurements: %w", err)
|
||||||
@ -61,8 +64,9 @@ func (m *M) FetchAndVerify(ctx context.Context, client *http.Client, measurement
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := json.Unmarshal(measurements, m); err != nil {
|
var mWithMetadata WithMetadata
|
||||||
if yamlErr := yaml.Unmarshal(measurements, m); yamlErr != nil {
|
if err := json.Unmarshal(measurements, &mWithMetadata); err != nil {
|
||||||
|
if yamlErr := yaml.Unmarshal(measurements, &mWithMetadata); yamlErr != nil {
|
||||||
return "", multierr.Append(
|
return "", multierr.Append(
|
||||||
err,
|
err,
|
||||||
fmt.Errorf("trying yaml format: %w", yamlErr),
|
fmt.Errorf("trying yaml format: %w", yamlErr),
|
||||||
@ -70,6 +74,15 @@ func (m *M) FetchAndVerify(ctx context.Context, client *http.Client, measurement
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if mWithMetadata.CSP != metadata.CSP {
|
||||||
|
return "", fmt.Errorf("invalid measurement metadata: CSP mismatch: expected %s, got %s", metadata.CSP, mWithMetadata.CSP)
|
||||||
|
}
|
||||||
|
if mWithMetadata.Image != metadata.Image {
|
||||||
|
return "", fmt.Errorf("invalid measurement metadata: image mismatch: expected %s, got %s", metadata.Image, mWithMetadata.Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
*m = mWithMetadata.Measurements
|
||||||
|
|
||||||
shaHash := sha256.Sum256(measurements)
|
shaHash := sha256.Sum256(measurements)
|
||||||
|
|
||||||
return hex.EncodeToString(shaHash[:]), nil
|
return hex.EncodeToString(shaHash[:]), nil
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
@ -278,80 +279,86 @@ func TestMeasurementsFetchAndVerify(t *testing.T) {
|
|||||||
// TDcvRjBBVy9vUDVqZXR3dmJMNmQxOEhjck9kWE8yVmYxY2w0YzNLZjVRcnFSZzlN
|
// TDcvRjBBVy9vUDVqZXR3dmJMNmQxOEhjck9kWE8yVmYxY2w0YzNLZjVRcnFSZzlN
|
||||||
// dlRxQWFsNXJCNHNpY1JaMVhpUUJjb0YwNHc9PSJ9
|
// dlRxQWFsNXJCNHNpY1JaMVhpUUJjb0YwNHc9PSJ9
|
||||||
// -----END ENCRYPTED COSIGN PRIVATE KEY-----
|
// -----END ENCRYPTED COSIGN PRIVATE KEY-----
|
||||||
|
cosignPublicKey := []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----")
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
measurements string
|
measurements string
|
||||||
|
metadata WithMetadata
|
||||||
measurementsStatus int
|
measurementsStatus int
|
||||||
signature string
|
signature string
|
||||||
signatureStatus int
|
signatureStatus int
|
||||||
publicKey []byte
|
|
||||||
wantMeasurements M
|
wantMeasurements M
|
||||||
wantSHA string
|
wantSHA string
|
||||||
wantError bool
|
wantError bool
|
||||||
}{
|
}{
|
||||||
"simple": {
|
|
||||||
measurements: "0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n",
|
|
||||||
measurementsStatus: http.StatusOK,
|
|
||||||
signature: "MEUCIQDcHS2bLls7OrLHpQKuiFGXhPrTcehPDwgVyERHl4V02wIgeIxK4J9oJpXWRBjokbog2lgifRXuJK8ljlAID26MbHk=",
|
|
||||||
signatureStatus: http.StatusOK,
|
|
||||||
publicKey: []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----"),
|
|
||||||
wantMeasurements: M{
|
|
||||||
0: WithAllBytes(0x00, false),
|
|
||||||
},
|
|
||||||
wantSHA: "4cd9d6ed8d9322150dff7738994c5e2fabff35f3bae6f5c993412d13249a5e87",
|
|
||||||
},
|
|
||||||
"json measurements": {
|
"json measurements": {
|
||||||
measurements: `{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}`,
|
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||||
|
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||||
measurementsStatus: http.StatusOK,
|
measurementsStatus: http.StatusOK,
|
||||||
signature: "MEUCIQDh3nCgrdTiYWiV4NkiaZ6vxovj79Pk8V90mdWAnmCEOwIgMAVWAx5dW0saut+8X15SgtBEiKqEixYiSICSqqhxUMg=",
|
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||||
signatureStatus: http.StatusOK,
|
signatureStatus: http.StatusOK,
|
||||||
publicKey: []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----"),
|
|
||||||
wantMeasurements: M{
|
wantMeasurements: M{
|
||||||
0: WithAllBytes(0x00, false),
|
0: WithAllBytes(0x00, false),
|
||||||
},
|
},
|
||||||
wantSHA: "1da09758c89537946496358f80b892e508563fcbbc695c90b6c16bf158e69c11",
|
wantSHA: "c04e13c1312b6f5659303871d14bf49b05c99a6515548763b6322f60bbb61a24",
|
||||||
},
|
},
|
||||||
"yaml measurements": {
|
"yaml measurements": {
|
||||||
measurements: "0:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n",
|
measurements: "csp: test\nimage: test\nmeasurements:\n 0:\n expected: \"0000000000000000000000000000000000000000000000000000000000000000\"\n warnOnly: false\n",
|
||||||
|
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||||
measurementsStatus: http.StatusOK,
|
measurementsStatus: http.StatusOK,
|
||||||
signature: "MEUCIFzQdwBS92aJjY0bcIag1uQRl42lUSBmmjEvO0tM/N0ZAiEAvuWaP744qYMw5uEmc7BY4mm4Ij3TEqAWFgxNhFkckp4=",
|
signature: "MEUCIQC9WI2ijlQjBktYFctKpbnqkUTey3U9W99Jp1NTLi5AbQIgNZxxOtiawgTkWPXLoH9D2CxpEjxQrqLn/zWF6NoKxWQ=",
|
||||||
signatureStatus: http.StatusOK,
|
signatureStatus: http.StatusOK,
|
||||||
publicKey: []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----"),
|
|
||||||
wantMeasurements: M{
|
wantMeasurements: M{
|
||||||
0: WithAllBytes(0x00, false),
|
0: WithAllBytes(0x00, false),
|
||||||
},
|
},
|
||||||
wantSHA: "c651cd419fd536c63cfc5349ad44da140a09987465e31192660059d383413807",
|
wantSHA: "648fcfd5d22e623a948ab2dd4eb334be2701d8f158231726084323003daab8d4",
|
||||||
},
|
},
|
||||||
"404 measurements": {
|
"404 measurements": {
|
||||||
measurements: `{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}`,
|
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||||
|
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||||
measurementsStatus: http.StatusNotFound,
|
measurementsStatus: http.StatusNotFound,
|
||||||
signature: "MEUCIQDh3nCgrdTiYWiV4NkiaZ6vxovj79Pk8V90mdWAnmCEOwIgMAVWAx5dW0saut+8X15SgtBEiKqEixYiSICSqqhxUMg=",
|
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||||
signatureStatus: http.StatusOK,
|
signatureStatus: http.StatusOK,
|
||||||
publicKey: []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----"),
|
|
||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
"404 signature": {
|
"404 signature": {
|
||||||
measurements: `{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}`,
|
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||||
|
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||||
measurementsStatus: http.StatusOK,
|
measurementsStatus: http.StatusOK,
|
||||||
signature: "MEUCIQDh3nCgrdTiYWiV4NkiaZ6vxovj79Pk8V90mdWAnmCEOwIgMAVWAx5dW0saut+8X15SgtBEiKqEixYiSICSqqhxUMg=",
|
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||||
signatureStatus: http.StatusNotFound,
|
signatureStatus: http.StatusNotFound,
|
||||||
publicKey: []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----"),
|
|
||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
"broken signature": {
|
"broken signature": {
|
||||||
measurements: `{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}`,
|
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||||
|
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||||
measurementsStatus: http.StatusOK,
|
measurementsStatus: http.StatusOK,
|
||||||
signature: "AAAAAAAA3nCgrdTiYWiV4NkiaZ6vxovj79Pk8V90mdWAnmCEOwIgMAVWAx5dW0saut+8X15SgtBEiKqEixYiSICSqqhxUMg=",
|
signature: "AAAAAAA1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||||
|
signatureStatus: http.StatusOK,
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
"metadata CSP mismatch": {
|
||||||
|
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||||
|
metadata: WithMetadata{CSP: cloudprovider.GCP, Image: "test"},
|
||||||
|
measurementsStatus: http.StatusOK,
|
||||||
|
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||||
|
signatureStatus: http.StatusOK,
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
"metadata image mismatch": {
|
||||||
|
measurements: `{"csp":"test","image":"test","measurements":{"0":{"expected":"0000000000000000000000000000000000000000000000000000000000000000","warnOnly":false}}}`,
|
||||||
|
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "another-image"},
|
||||||
|
measurementsStatus: http.StatusOK,
|
||||||
|
signature: "MEYCIQD1RR91pWPw1BMWXTSmTBHg/JtfKerbZNQ9PJTWDdW0sgIhANQbETJGb67qzQmMVmcq007VUFbHRMtYWKZeeyRf0gVa",
|
||||||
signatureStatus: http.StatusOK,
|
signatureStatus: http.StatusOK,
|
||||||
publicKey: []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----"),
|
|
||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
"not yaml or json": {
|
"not yaml or json": {
|
||||||
measurements: "This is some content to be signed!\n",
|
measurements: "This is some content to be signed!\n",
|
||||||
|
metadata: WithMetadata{CSP: cloudprovider.Unknown, Image: "test"},
|
||||||
measurementsStatus: http.StatusOK,
|
measurementsStatus: http.StatusOK,
|
||||||
signature: "MEUCIQCGA/lSu5qCJgNNvgMaTKJ9rj6vQMecUDaQo3ukaiAfUgIgWoxXRoDKLY9naN7YgxokM7r2fwnyYk3M2WKJJO1g6yo=",
|
signature: "MEUCIQCGA/lSu5qCJgNNvgMaTKJ9rj6vQMecUDaQo3ukaiAfUgIgWoxXRoDKLY9naN7YgxokM7r2fwnyYk3M2WKJJO1g6yo=",
|
||||||
signatureStatus: http.StatusOK,
|
signatureStatus: http.StatusOK,
|
||||||
publicKey: []byte("-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEu78QgxOOcao6U91CSzEXxrKhvFTt\nJHNy+eX6EMePtDm8CnDF9HSwnTlD0itGJ/XHPQA5YX10fJAqI1y+ehlFMw==\n-----END PUBLIC KEY-----"),
|
|
||||||
wantError: true,
|
wantError: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -386,7 +393,13 @@ func TestMeasurementsFetchAndVerify(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
m := M{}
|
m := M{}
|
||||||
hash, err := m.FetchAndVerify(context.Background(), client, measurementsURL, signatureURL, tc.publicKey)
|
|
||||||
|
hash, err := m.FetchAndVerify(
|
||||||
|
context.Background(), client,
|
||||||
|
measurementsURL, signatureURL,
|
||||||
|
cosignPublicKey,
|
||||||
|
tc.metadata,
|
||||||
|
)
|
||||||
|
|
||||||
if tc.wantError {
|
if tc.wantError {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
|
@ -44,6 +44,21 @@ func (p *Provider) UnmarshalJSON(b []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarshalYAML marshals the Provider to YAML string.
|
||||||
|
func (p Provider) MarshalYAML() (interface{}, error) {
|
||||||
|
return p.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML unmarshals the Provider from YAML string.
|
||||||
|
func (p *Provider) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var s string
|
||||||
|
if err := unmarshal(&s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*p = FromString(s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// FromString returns cloud provider from string.
|
// FromString returns cloud provider from string.
|
||||||
func FromString(s string) Provider {
|
func FromString(s string) Provider {
|
||||||
s = strings.ToLower(s)
|
s = strings.ToLower(s)
|
||||||
|
@ -7,9 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package cloudprovider
|
package cloudprovider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarshalJSON(t *testing.T) {
|
func TestMarshalJSON(t *testing.T) {
|
||||||
@ -43,7 +45,7 @@ func TestMarshalJSON(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
b, err := tc.input.MarshalJSON()
|
b, err := json.Marshal(tc.input)
|
||||||
|
|
||||||
assert.NoError(err)
|
assert.NoError(err)
|
||||||
assert.Equal(tc.want, b)
|
assert.Equal(tc.want, b)
|
||||||
@ -88,7 +90,95 @@ func TestUnmarshalJSON(t *testing.T) {
|
|||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
|
||||||
var p Provider
|
var p Provider
|
||||||
err := p.UnmarshalJSON(tc.input)
|
err := json.Unmarshal(tc.input, &p)
|
||||||
|
|
||||||
|
if tc.wantErr {
|
||||||
|
assert.Error(err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.want, p)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalYAML(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
input Provider
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
"unknown": {
|
||||||
|
input: Unknown,
|
||||||
|
want: []byte("Unknown\n"),
|
||||||
|
},
|
||||||
|
"aws": {
|
||||||
|
input: AWS,
|
||||||
|
want: []byte("AWS\n"),
|
||||||
|
},
|
||||||
|
"azure": {
|
||||||
|
input: Azure,
|
||||||
|
want: []byte("Azure\n"),
|
||||||
|
},
|
||||||
|
"gcp": {
|
||||||
|
input: GCP,
|
||||||
|
want: []byte("GCP\n"),
|
||||||
|
},
|
||||||
|
"qemu": {
|
||||||
|
input: QEMU,
|
||||||
|
want: []byte("QEMU\n"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
b, err := yaml.Marshal(tc.input)
|
||||||
|
|
||||||
|
assert.NoError(err)
|
||||||
|
assert.Equal(tc.want, b)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalYAML(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
input []byte
|
||||||
|
want Provider
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
input: []byte("foo: bar\n"),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
"unknown": {
|
||||||
|
input: []byte("unknown\n"),
|
||||||
|
want: Unknown,
|
||||||
|
},
|
||||||
|
"aws": {
|
||||||
|
input: []byte("aws\n"),
|
||||||
|
want: AWS,
|
||||||
|
},
|
||||||
|
"azure": {
|
||||||
|
input: []byte("azure\n"),
|
||||||
|
want: Azure,
|
||||||
|
},
|
||||||
|
"gcp": {
|
||||||
|
input: []byte("gcp\n"),
|
||||||
|
want: GCP,
|
||||||
|
},
|
||||||
|
"qemu": {
|
||||||
|
input: []byte("qemu\n"),
|
||||||
|
want: QEMU,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
var p Provider
|
||||||
|
err := yaml.Unmarshal(tc.input, &p)
|
||||||
|
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
assert.Error(err)
|
assert.Error(err)
|
||||||
|
@ -73,6 +73,10 @@ type UpgradeConfig struct {
|
|||||||
// description: |
|
// description: |
|
||||||
// Measurements of the updated image.
|
// Measurements of the updated image.
|
||||||
Measurements Measurements `yaml:"measurements"`
|
Measurements Measurements `yaml:"measurements"`
|
||||||
|
// description: |
|
||||||
|
// temporary field for upgrade migration
|
||||||
|
// TODO(AB#2654): Remove with refactoring upgrade plan command
|
||||||
|
CSP cloudprovider.Provider `yaml:"csp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProviderConfig are cloud-provider specific configuration values used by the CLI.
|
// ProviderConfig are cloud-provider specific configuration values used by the CLI.
|
||||||
|
@ -74,7 +74,7 @@ func init() {
|
|||||||
FieldName: "upgrade",
|
FieldName: "upgrade",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
UpgradeConfigDoc.Fields = make([]encoder.Doc, 2)
|
UpgradeConfigDoc.Fields = make([]encoder.Doc, 3)
|
||||||
UpgradeConfigDoc.Fields[0].Name = "image"
|
UpgradeConfigDoc.Fields[0].Name = "image"
|
||||||
UpgradeConfigDoc.Fields[0].Type = "string"
|
UpgradeConfigDoc.Fields[0].Type = "string"
|
||||||
UpgradeConfigDoc.Fields[0].Note = ""
|
UpgradeConfigDoc.Fields[0].Note = ""
|
||||||
@ -85,6 +85,11 @@ func init() {
|
|||||||
UpgradeConfigDoc.Fields[1].Note = ""
|
UpgradeConfigDoc.Fields[1].Note = ""
|
||||||
UpgradeConfigDoc.Fields[1].Description = "Measurements of the updated image."
|
UpgradeConfigDoc.Fields[1].Description = "Measurements of the updated image."
|
||||||
UpgradeConfigDoc.Fields[1].Comments[encoder.LineComment] = "Measurements of the updated image."
|
UpgradeConfigDoc.Fields[1].Comments[encoder.LineComment] = "Measurements of the updated image."
|
||||||
|
UpgradeConfigDoc.Fields[2].Name = "csp"
|
||||||
|
UpgradeConfigDoc.Fields[2].Type = "Provider"
|
||||||
|
UpgradeConfigDoc.Fields[2].Note = ""
|
||||||
|
UpgradeConfigDoc.Fields[2].Description = "temporary field for upgrade migration\nTODO(AB#2654): Remove with refactoring upgrade plan command"
|
||||||
|
UpgradeConfigDoc.Fields[2].Comments[encoder.LineComment] = "temporary field for upgrade migration"
|
||||||
|
|
||||||
ProviderConfigDoc.Type = "ProviderConfig"
|
ProviderConfigDoc.Type = "ProviderConfig"
|
||||||
ProviderConfigDoc.Comments[encoder.LineComment] = "ProviderConfig are cloud-provider specific configuration values used by the CLI."
|
ProviderConfigDoc.Comments[encoder.LineComment] = "ProviderConfig are cloud-provider specific configuration values used by the CLI."
|
||||||
|
@ -148,18 +148,12 @@ const (
|
|||||||
// Releases.
|
// Releases.
|
||||||
//
|
//
|
||||||
|
|
||||||
// S3PublicBucket contains measurements & releases.
|
// CDNRepositoryURL is the base URL of the Constellation CDN artifact repository.
|
||||||
S3PublicBucket = "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/"
|
CDNRepositoryURL = "https://cdn.confidential.cloud"
|
||||||
// CosignPublicKey signs all our releases.
|
// CDNImagePath is the default path to image references in the CDN repository.
|
||||||
CosignPublicKey = `-----BEGIN PUBLIC KEY-----
|
CDNImagePath = "constellation/v1/images"
|
||||||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEf8F1hpmwE+YCFXzjGtaQcrL6XZVT
|
// CDNMeasurementsPath is the default path to image measurements in the CDN repository.
|
||||||
JmEe5iSLvG1SyQSAew7WdMKF6o9t8e2TFuCkzlOhhlws2OHWbiFZnFWCFw==
|
CDNMeasurementsPath = "constellation/v1/measurements"
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
`
|
|
||||||
|
|
||||||
// ImageVersionRepositoryURL is the base URL of the repository containing
|
|
||||||
// image version information.
|
|
||||||
ImageVersionRepositoryURL = "https://cdn.confidential.cloud"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// VersionInfo is the version of a binary. Left as a separate variable to allow override during build.
|
// VersionInfo is the version of a binary. Left as a separate variable to allow override during build.
|
||||||
|
16
internal/constants/keys_enterprise.go
Normal file
16
internal/constants/keys_enterprise.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//go:build enterprise
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package constants
|
||||||
|
|
||||||
|
// CosignPublicKey signs all our releases.
|
||||||
|
const CosignPublicKey = `-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEf8F1hpmwE+YCFXzjGtaQcrL6XZVT
|
||||||
|
JmEe5iSLvG1SyQSAew7WdMKF6o9t8e2TFuCkzlOhhlws2OHWbiFZnFWCFw==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`
|
16
internal/constants/keys_oss.go
Normal file
16
internal/constants/keys_oss.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
//go:build !enterprise
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright (c) Edgeless Systems GmbH
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package constants
|
||||||
|
|
||||||
|
// CosignPublicKey signs all our development builds.
|
||||||
|
const CosignPublicKey = `-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELcPl4Ik+qZuH4K049wksoXK/Os3Z
|
||||||
|
b92PDCpM7FZAINQF88s1TZS/HmRXYk62UJ4eqPduvUnJmXhNikhLbMi6fw==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
`
|
@ -125,12 +125,12 @@ func getFromFile(fs *afero.Afero, version string) ([]byte, error) {
|
|||||||
|
|
||||||
// getFromURL fetches the image lookup table from a URL.
|
// getFromURL fetches the image lookup table from a URL.
|
||||||
func getFromURL(ctx context.Context, client httpc, version string) ([]byte, error) {
|
func getFromURL(ctx context.Context, client httpc, version string) ([]byte, error) {
|
||||||
url, err := url.Parse(constants.ImageVersionRepositoryURL)
|
url, err := url.Parse(constants.CDNRepositoryURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("parsing image version repository URL: %w", err)
|
return nil, fmt.Errorf("parsing image version repository URL: %w", err)
|
||||||
}
|
}
|
||||||
versionFilename := path.Base(version) + ".json"
|
versionFilename := path.Base(version) + ".json"
|
||||||
url.Path = path.Join("constellation/v1/images", versionFilename)
|
url.Path = path.Join(constants.CDNImagePath, versionFilename)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), http.NoBody)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), http.NoBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
Loading…
Reference in New Issue
Block a user