Add --insecure to config fetch-measurement (#1879)

* cli: add --insecure to fetch-measurements

* cli: rename fake to stub

* ci: upload measurements for debug images

* fix cli docs
This commit is contained in:
3u13r 2023-06-06 10:32:22 +02:00 committed by GitHub
parent f7f11c32f8
commit 7c07e3be18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 85 additions and 77 deletions

View File

@ -32,51 +32,15 @@ runs:
with:
shortname: ${{ inputs.osImage }}
- name: Get attestation variant
id: get-variant
- name: Constellation fetch measurements
shell: bash
run: |
# TODO(AB#3144): Refactor when API is update for attestation variants
case ${{ inputs.cloudProvider }} in
aws)
echo ATTESTATION_VARIANT=awsNitroTPM >> $GITHUB_OUTPUT
;;
azure)
echo ATTESTATION_VARIANT=azureSEVSNP >> $GITHUB_OUTPUT
;;
gcp)
echo ATTESTATION_VARIANT=gcpSEVES >> $GITHUB_OUTPUT
;;
qemu)
echo ATTESTATION_VARIANT=qemuVTPM >> $GITHUB_OUTPUT
;;
esac
- name: Fetch & write measurements
shell: bash
run: |
ref=${{ steps.expand-version.outputs.ref }}
stream=${{ steps.expand-version.outputs.stream }}
version=${{ steps.expand-version.outputs.version }}
verPath="ref/${ref}/stream/${stream}/${version}"
MEASUREMENTS=$(curl -fsSL https://cdn.confidential.cloud/constellation/v1/${verPath}/image/csp/${{ inputs.cloudProvider }}/measurements.json | jq '.measurements' -r)
for key in $(echo $MEASUREMENTS | jq 'keys[]' -r); do
echo Updating $key to $(echo $MEASUREMENTS | jq ".\"$key\"" -r)
if [[ $(yq '.version' constellation-conf.yaml) == "v2" ]]
then
yq -i ".provider.${{ inputs.cloudProvider }}.measurements.[$key] = $(echo $MEASUREMENTS | jq ".\"$key\"")" constellation-conf.yaml
else
yq -i ".attestation.${{ steps.get-variant.outputs.ATTESTATION_VARIANT }}.measurements.[$key] = $(echo $MEASUREMENTS | jq ".\"$key\"")" constellation-conf.yaml
fi
done
if [[ $(yq '.version' constellation-conf.yaml) == "v2" ]]
if [[ ${{ steps.expand-version.outputs.stream }} == "debug" ]]
then
yq -i '.provider.${{ inputs.cloudProvider }}.measurements |= array_to_map' constellation-conf.yaml
constellation config fetch-measurements --insecure
else
yq -i '.attestation.${{ steps.get-variant.outputs.ATTESTATION_VARIANT }}.measurements |= array_to_map' constellation-conf.yaml
constellation config fetch-measurements
fi
cat constellation-conf.yaml
- name: Constellation verify
shell: bash

View File

@ -717,7 +717,6 @@ jobs:
upload-pcrs:
name: "Sign & upload PCRs"
needs: [build-settings, calculate-pcrs]
if: inputs.stream != 'debug'
permissions:
id-token: write
contents: read
@ -759,6 +758,7 @@ jobs:
echo "::endgroup::"
- name: Sign measurements
if: inputs.stream != 'debug'
shell: bash
env:
COSIGN_PUBLIC_KEY: ${{ inputs.isRelease && secrets.COSIGN_PUBLIC_KEY || secrets.COSIGN_DEV_PUBLIC_KEY }}
@ -779,6 +779,12 @@ jobs:
sig=$(rekor-cli get --uuid="${uuid}" --format=json | jq -r .Body.HashedRekordObj.signature.content)
cosign verify-blob --key cosign.pub --signature <(echo "${sig}") "${{ github.workspace }}/measurements.json"
- name: Create stub signature file
if: inputs.stream == 'debug'
shell: bash
run: |
echo "THOSE MEASUREMENTS BELONG TO A DEBUG IMAGE. THOSE ARE NOT SINGED BY ANY KEY." > "${{ github.workspace }}/measurements.json.sig"
- name: Upload measurements
shell: bash
run: |

View File

@ -36,6 +36,7 @@ func newConfigFetchMeasurementsCmd() *cobra.Command {
}
cmd.Flags().StringP("url", "u", "", "alternative URL to fetch measurements from")
cmd.Flags().StringP("signature-url", "s", "", "alternative URL to fetch measurements' signature from")
cmd.Flags().Bool("insecure", false, "skip the measurement signature verification")
return cmd
}
@ -43,6 +44,7 @@ func newConfigFetchMeasurementsCmd() *cobra.Command {
type fetchMeasurementsFlags struct {
measurementsURL *url.URL
signatureURL *url.URL
insecure bool
configPath string
force bool
}
@ -115,25 +117,44 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements(
}
var fetchedMeasurements measurements.M
hash, err := fetchedMeasurements.FetchAndVerify(
ctx, client, cosign,
flags.measurementsURL,
flags.signatureURL,
imageVersion,
conf.GetProvider(),
conf.GetAttestationConfig().GetVariant(),
)
if err != nil {
return err
var hash string
if flags.insecure {
if err := fetchedMeasurements.FetchNoVerify(
ctx,
client,
flags.measurementsURL,
imageVersion,
conf.GetProvider(),
conf.GetAttestationConfig().GetVariant(),
); err != nil {
return fmt.Errorf("fetching measurements without verification: %w", err)
}
cfm.log.Debugf("Fetched measurements without verification")
} else {
hash, err = fetchedMeasurements.FetchAndVerify(
ctx,
client,
cosign,
flags.measurementsURL,
flags.signatureURL,
imageVersion,
conf.GetProvider(),
conf.GetAttestationConfig().GetVariant(),
)
if err != nil {
return fmt.Errorf("fetching and verifying measurements: %w", err)
}
cfm.log.Debugf("Fetched and verified measurements, hash is %s", hash)
if err := sigstore.VerifyWithRekor(cmd.Context(), imageVersion, rekor, hash); err != nil {
cmd.PrintErrf("Ignoring Rekor related error: %v\n", err)
cmd.PrintErrln("Make sure the downloaded measurements are trustworthy!")
}
cfm.log.Debugf("Verified measurements with Rekor")
}
cfm.log.Debugf("Fetched and verified measurements, hash is %s", hash)
if err := sigstore.VerifyWithRekor(cmd.Context(), imageVersion, rekor, hash); err != nil {
cmd.PrintErrf("Ignoring Rekor related error: %v\n", err)
cmd.PrintErrln("Make sure the downloaded measurements are trustworthy!")
}
cfm.log.Debugf("Verified measurements with Rekor, updating measurements in configuration")
cfm.log.Debugf("Updating measurements in configuration")
conf.UpdateMeasurements(fetchedMeasurements)
if err := fileHandler.WriteYAML(flags.configPath, conf, file.OptOverwrite); err != nil {
return err
@ -170,6 +191,12 @@ func (cfm *configFetchMeasurementsCmd) parseFetchMeasurementsFlags(cmd *cobra.Co
}
cfm.log.Debugf("Parsed measurements signature URL as %v", measurementsSignatureURL)
insecure, err := cmd.Flags().GetBool("insecure")
if err != nil {
return &fetchMeasurementsFlags{}, fmt.Errorf("parsing insecure argument: %w", err)
}
cfm.log.Debugf("Insecure flag is %v", insecure)
config, err := cmd.Flags().GetString("config")
if err != nil {
return &fetchMeasurementsFlags{}, fmt.Errorf("parsing config path argument: %w", err)
@ -184,6 +211,7 @@ func (cfm *configFetchMeasurementsCmd) parseFetchMeasurementsFlags(cmd *cobra.Co
return &fetchMeasurementsFlags{
measurementsURL: measurementsURL,
signatureURL: measurementsSignatureURL,
insecure: insecure,
configPath: config,
force: force,
}, nil

View File

@ -9,11 +9,11 @@ package cmd
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"testing"
"github.com/edgelesssys/constellation/v2/internal/api/attestationconfig"
@ -233,31 +233,39 @@ func TestConfigFetchMeasurements(t *testing.T) {
})
testCases := map[string]struct {
cosign cosignVerifier
rekor rekorVerifier
wantErr bool
cosign cosignVerifier
rekor rekorVerifier
insecureFlag bool
wantErr bool
}{
"success": {
cosign: &stubCosignVerifier{},
rekor: singleUUIDVerifier(),
},
"success without cosign": {
insecureFlag: true,
cosign: &stubCosignVerifier{
verifyError: assert.AnError,
},
rekor: singleUUIDVerifier(),
},
"failing search should not result in error": {
cosign: &stubCosignVerifier{},
rekor: &stubRekorVerifier{
SearchByHashUUIDs: []string{},
SearchByHashError: errors.New("some error"),
SearchByHashError: assert.AnError,
},
},
"failing verify should not result in error": {
cosign: &stubCosignVerifier{},
rekor: &stubRekorVerifier{
SearchByHashUUIDs: []string{"11111111111111111111111111111111111111111111111111111111111111111111111111111111"},
VerifyEntryError: errors.New("some error"),
VerifyEntryError: assert.AnError,
},
},
"signature verification failure": {
cosign: &stubCosignVerifier{
verifyError: errors.New("some error"),
verifyError: assert.AnError,
},
rekor: singleUUIDVerifier(),
wantErr: true,
@ -272,6 +280,7 @@ func TestConfigFetchMeasurements(t *testing.T) {
cmd := newConfigFetchMeasurementsCmd()
cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
cmd.Flags().Bool("force", true, "") // register persistent flag manually
require.NoError(cmd.Flags().Set("insecure", strconv.FormatBool(tc.insecureFlag)))
fileHandler := file.NewHandler(afero.NewMemMapFs())
gcpConfig := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.GCP)
@ -281,7 +290,7 @@ func TestConfigFetchMeasurements(t *testing.T) {
require.NoError(err)
cfm := &configFetchMeasurementsCmd{canFetchMeasurements: true, log: logger.NewTest(t)}
err = cfm.configFetchMeasurements(cmd, tc.cosign, tc.rekor, fileHandler, fakeAttestationFetcher{}, client)
err = cfm.configFetchMeasurements(cmd, tc.cosign, tc.rekor, fileHandler, stubAttestationFetcher{}, client)
if tc.wantErr {
assert.Error(err)
return
@ -291,21 +300,21 @@ func TestConfigFetchMeasurements(t *testing.T) {
}
}
type fakeAttestationFetcher struct{}
type stubAttestationFetcher struct{}
func (f fakeAttestationFetcher) FetchAzureSEVSNPVersionList(_ context.Context, _ attestationconfig.AzureSEVSNPVersionList) (attestationconfig.AzureSEVSNPVersionList, error) {
func (f stubAttestationFetcher) FetchAzureSEVSNPVersionList(_ context.Context, _ attestationconfig.AzureSEVSNPVersionList) (attestationconfig.AzureSEVSNPVersionList, error) {
return attestationconfig.AzureSEVSNPVersionList(
[]string{},
), nil
}
func (f fakeAttestationFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ attestationconfig.AzureSEVSNPVersionAPI) (attestationconfig.AzureSEVSNPVersionAPI, error) {
func (f stubAttestationFetcher) FetchAzureSEVSNPVersion(_ context.Context, _ attestationconfig.AzureSEVSNPVersionAPI) (attestationconfig.AzureSEVSNPVersionAPI, error) {
return attestationconfig.AzureSEVSNPVersionAPI{
AzureSEVSNPVersion: testCfg,
}, nil
}
func (f fakeAttestationFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context) (attestationconfig.AzureSEVSNPVersionAPI, error) {
func (f stubAttestationFetcher) FetchAzureSEVSNPVersionLatest(_ context.Context) (attestationconfig.AzureSEVSNPVersionAPI, error) {
return attestationconfig.AzureSEVSNPVersionAPI{
AzureSEVSNPVersion: testCfg,
}, nil

View File

@ -202,7 +202,7 @@ func TestCreate(t *testing.T) {
fileHandler := file.NewHandler(tc.setupFs(require, tc.provider))
c := &createCmd{log: logger.NewTest(t)}
err := c.create(cmd, tc.creator, fileHandler, &nopSpinner{}, fakeAttestationFetcher{})
err := c.create(cmd, tc.creator, fileHandler, &nopSpinner{}, stubAttestationFetcher{})
if tc.wantErr {
assert.Error(err)

View File

@ -175,7 +175,7 @@ func TestInitialize(t *testing.T) {
defer cancel()
cmd.SetContext(ctx)
i := &initCmd{log: logger.NewTest(t), spinner: &nopSpinner{}}
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, fakeAttestationFetcher{})
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, stubAttestationFetcher{})
if tc.wantErr {
assert.Error(err)
@ -519,7 +519,7 @@ func TestAttestation(t *testing.T) {
cmd.SetContext(ctx)
i := &initCmd{log: logger.NewTest(t), spinner: &nopSpinner{}}
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, fakeAttestationFetcher{})
err := i.initialize(cmd, newDialer, fileHandler, &stubLicenseClient{}, stubAttestationFetcher{})
assert.Error(err)
// make sure the error is actually a TLS handshake error
assert.Contains(err.Error(), "transport: authentication handshake failed")

View File

@ -163,7 +163,7 @@ func TestRecover(t *testing.T) {
))
newDialer := func(atls.Validator) *dialer.Dialer { return nil }
r := &recoverCmd{log: logger.NewTest(t), configFetcher: fakeAttestationFetcher{}}
r := &recoverCmd{log: logger.NewTest(t), configFetcher: stubAttestationFetcher{}}
err := r.recover(cmd, fileHandler, time.Millisecond, tc.doer, newDialer)
if tc.wantErr {
assert.Error(err)

View File

@ -142,7 +142,7 @@ func TestUpgradeApply(t *testing.T) {
require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg))
require.NoError(handler.WriteJSON(constants.ClusterIDsFileName, clusterid.File{}))
upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t), imageFetcher: tc.fetcher, configFetcher: fakeAttestationFetcher{}}
upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t), imageFetcher: tc.fetcher, configFetcher: stubAttestationFetcher{}}
err := upgrader.upgradeApply(cmd, handler)
if tc.wantErr {
assert.Error(err)

View File

@ -271,7 +271,7 @@ func TestUpgradeCheck(t *testing.T) {
cmd := newUpgradeCheckCmd()
err := checkCmd.upgradeCheck(cmd, fileHandler, fakeAttestationFetcher{}, tc.flags)
err := checkCmd.upgradeCheck(cmd, fileHandler, stubAttestationFetcher{}, tc.flags)
if tc.wantError {
assert.Error(err)
return

View File

@ -190,7 +190,7 @@ func TestVerify(t *testing.T) {
}
v := &verifyCmd{log: logger.NewTest(t)}
err := v.verify(cmd, fileHandler, tc.protoClient, tc.formatter, fakeAttestationFetcher{})
err := v.verify(cmd, fileHandler, tc.protoClient, tc.formatter, stubAttestationFetcher{})
if tc.wantErr {
assert.Error(err)

View File

@ -108,6 +108,7 @@ constellation config fetch-measurements [flags]
```
-h, --help help for fetch-measurements
--insecure skip the measurement signature verification
-s, --signature-url string alternative URL to fetch measurements' signature from
-u, --url string alternative URL to fetch measurements from
```