mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-08-14 01:35:34 -04:00
Refactor enforced/expected PCRs (#553)
* Merge enforced and expected measurements * Update measurement generation to new format * Write expected measurements hex encoded by default * Allow hex or base64 encoded expected measurements * Allow hex or base64 encoded clusterID * Allow security upgrades to warnOnly flag * Upload signed measurements in JSON format * Fetch measurements either from JSON or YAML * Use yaml.v3 instead of yaml.v2 * Error on invalid enforced selection * Add placeholder measurements to config * Update e2e test to new measurement format Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
8ce954e012
commit
f8001efbc0
46 changed files with 1180 additions and 801 deletions
|
@ -137,7 +137,7 @@ func (f *fetchMeasurementsFlags) updateURLs(ctx context.Context, conf *config.Co
|
|||
|
||||
if f.measurementsURL == nil {
|
||||
// TODO(AB#2644): resolve image version to reference
|
||||
parsedURL, err := url.Parse(constants.S3PublicBucket + imageRef + "/measurements.yaml")
|
||||
parsedURL, err := url.Parse(constants.S3PublicBucket + imageRef + "/measurements.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ func (f *fetchMeasurementsFlags) updateURLs(ctx context.Context, conf *config.Co
|
|||
}
|
||||
|
||||
if f.signatureURL == nil {
|
||||
parsedURL, err := url.Parse(constants.S3PublicBucket + imageRef + "/measurements.yaml.sig")
|
||||
parsedURL, err := url.Parse(constants.S3PublicBucket + imageRef + "/measurements.json.sig")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -109,17 +109,17 @@ func TestUpdateURLs(t *testing.T) {
|
|||
},
|
||||
},
|
||||
flags: &fetchMeasurementsFlags{},
|
||||
wantMeasurementsURL: constants.S3PublicBucket + "some/image/path/image-123456/measurements.yaml",
|
||||
wantMeasurementsSigURL: constants.S3PublicBucket + "some/image/path/image-123456/measurements.yaml.sig",
|
||||
wantMeasurementsURL: constants.S3PublicBucket + "some/image/path/image-123456/measurements.json",
|
||||
wantMeasurementsSigURL: constants.S3PublicBucket + "some/image/path/image-123456/measurements.json.sig",
|
||||
},
|
||||
"both set by user": {
|
||||
conf: &config.Config{},
|
||||
flags: &fetchMeasurementsFlags{
|
||||
measurementsURL: urlMustParse("get.my/measurements.yaml"),
|
||||
signatureURL: urlMustParse("get.my/measurements.yaml.sig"),
|
||||
measurementsURL: urlMustParse("get.my/measurements.json"),
|
||||
signatureURL: urlMustParse("get.my/measurements.json.sig"),
|
||||
},
|
||||
wantMeasurementsURL: "get.my/measurements.yaml",
|
||||
wantMeasurementsSigURL: "get.my/measurements.yaml.sig",
|
||||
wantMeasurementsURL: "get.my/measurements.json",
|
||||
wantMeasurementsSigURL: "get.my/measurements.json.sig",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -164,14 +164,14 @@ func TestConfigFetchMeasurements(t *testing.T) {
|
|||
signature := "MEUCIFdJ5dH6HDywxQWTUh9Bw77wMrq0mNCUjMQGYP+6QsVmAiEAmazj/L7rFGA4/Gz8y+kI5h5E5cDgc3brihvXBKF6qZA="
|
||||
|
||||
client := newTestClient(func(req *http.Request) *http.Response {
|
||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/someImage/measurements.yaml" {
|
||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/someImage/measurements.json" {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(measurements)),
|
||||
Header: make(http.Header),
|
||||
}
|
||||
}
|
||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/someImage/measurements.yaml.sig" {
|
||||
if req.URL.String() == "https://public-edgeless-constellation.s3.us-east-2.amazonaws.com/someImage/measurements.json.sig" {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(bytes.NewBufferString(signature)),
|
||||
|
|
|
@ -8,7 +8,7 @@ package cmd
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
@ -134,7 +134,7 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator
|
|||
KubernetesVersion: conf.KubernetesVersion,
|
||||
KubernetesComponents: versions.VersionConfigs[k8sVersion].KubernetesComponents.ToProto(),
|
||||
HelmDeployments: helmDeployments,
|
||||
EnforcedPcrs: conf.GetEnforcedPCRs(),
|
||||
EnforcedPcrs: conf.EnforcedPCRs(),
|
||||
EnforceIdkeydigest: conf.EnforcesIDKeyDigest(),
|
||||
ConformanceMode: flags.conformance,
|
||||
}
|
||||
|
@ -190,8 +190,8 @@ func (d *initDoer) Do(ctx context.Context) error {
|
|||
func writeOutput(idFile clusterid.File, resp *initproto.InitResponse, wr io.Writer, fileHandler file.Handler) error {
|
||||
fmt.Fprint(wr, "Your Constellation cluster was successfully initialized.\n\n")
|
||||
|
||||
ownerID := base64.StdEncoding.EncodeToString(resp.OwnerId)
|
||||
clusterID := base64.StdEncoding.EncodeToString(resp.ClusterId)
|
||||
ownerID := hex.EncodeToString(resp.OwnerId)
|
||||
clusterID := hex.EncodeToString(resp.ClusterId)
|
||||
|
||||
tw := tabwriter.NewWriter(wr, 0, 0, 2, ' ', 0)
|
||||
// writeRow(tw, "Constellation cluster's owner identifier", ownerID)
|
||||
|
|
|
@ -9,7 +9,7 @@ package cmd
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
|
@ -101,16 +101,6 @@ func TestInitialize(t *testing.T) {
|
|||
initServerAPI: &stubInitServer{initErr: someErr},
|
||||
wantErr: true,
|
||||
},
|
||||
"fail missing enforced PCR": {
|
||||
provider: cloudprovider.GCP,
|
||||
idFile: &clusterid.File{IP: "192.0.2.1"},
|
||||
configMutator: func(c *config.Config) {
|
||||
c.Provider.GCP.EnforcedMeasurements = append(c.Provider.GCP.EnforcedMeasurements, 10)
|
||||
},
|
||||
serviceAccKey: gcpServiceAccKey,
|
||||
initServerAPI: &stubInitServer{initResp: testInitResp},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
|
@ -174,7 +164,7 @@ func TestInitialize(t *testing.T) {
|
|||
}
|
||||
require.NoError(err)
|
||||
// assert.Contains(out.String(), base64.StdEncoding.EncodeToString([]byte("ownerID")))
|
||||
assert.Contains(out.String(), base64.StdEncoding.EncodeToString([]byte("clusterID")))
|
||||
assert.Contains(out.String(), hex.EncodeToString([]byte("clusterID")))
|
||||
var secret masterSecret
|
||||
assert.NoError(fileHandler.ReadJSON(constants.MasterSecretFilename, &secret))
|
||||
assert.NotEmpty(secret.Key)
|
||||
|
@ -192,8 +182,8 @@ func TestWriteOutput(t *testing.T) {
|
|||
Kubeconfig: []byte("kubeconfig"),
|
||||
}
|
||||
|
||||
ownerID := base64.StdEncoding.EncodeToString(resp.OwnerId)
|
||||
clusterID := base64.StdEncoding.EncodeToString(resp.ClusterId)
|
||||
ownerID := hex.EncodeToString(resp.OwnerId)
|
||||
clusterID := hex.EncodeToString(resp.ClusterId)
|
||||
|
||||
expectedIDFile := clusterid.File{
|
||||
ClusterID: clusterID,
|
||||
|
@ -361,11 +351,11 @@ func TestAttestation(t *testing.T) {
|
|||
|
||||
issuer := &testIssuer{
|
||||
Getter: oid.QEMU{},
|
||||
pcrs: measurements.M{
|
||||
0: measurements.PCRWithAllBytes(0xFF),
|
||||
1: measurements.PCRWithAllBytes(0xFF),
|
||||
2: measurements.PCRWithAllBytes(0xFF),
|
||||
3: measurements.PCRWithAllBytes(0xFF),
|
||||
pcrs: map[uint32][]byte{
|
||||
0: bytes.Repeat([]byte{0xFF}, 32),
|
||||
1: bytes.Repeat([]byte{0xFF}, 32),
|
||||
2: bytes.Repeat([]byte{0xFF}, 32),
|
||||
3: bytes.Repeat([]byte{0xFF}, 32),
|
||||
},
|
||||
}
|
||||
serverCreds := atlscredentials.New(issuer, nil)
|
||||
|
@ -390,13 +380,13 @@ func TestAttestation(t *testing.T) {
|
|||
cfg := config.Default()
|
||||
cfg.Image = "image"
|
||||
cfg.RemoveProviderExcept(cloudprovider.QEMU)
|
||||
cfg.Provider.QEMU.Measurements[0] = measurements.PCRWithAllBytes(0x00)
|
||||
cfg.Provider.QEMU.Measurements[1] = measurements.PCRWithAllBytes(0x11)
|
||||
cfg.Provider.QEMU.Measurements[2] = measurements.PCRWithAllBytes(0x22)
|
||||
cfg.Provider.QEMU.Measurements[3] = measurements.PCRWithAllBytes(0x33)
|
||||
cfg.Provider.QEMU.Measurements[4] = measurements.PCRWithAllBytes(0x44)
|
||||
cfg.Provider.QEMU.Measurements[9] = measurements.PCRWithAllBytes(0x99)
|
||||
cfg.Provider.QEMU.Measurements[12] = measurements.PCRWithAllBytes(0xcc)
|
||||
cfg.Provider.QEMU.Measurements[0] = measurements.WithAllBytes(0x00, false)
|
||||
cfg.Provider.QEMU.Measurements[1] = measurements.WithAllBytes(0x11, false)
|
||||
cfg.Provider.QEMU.Measurements[2] = measurements.WithAllBytes(0x22, false)
|
||||
cfg.Provider.QEMU.Measurements[3] = measurements.WithAllBytes(0x33, false)
|
||||
cfg.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, false)
|
||||
cfg.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x99, false)
|
||||
cfg.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, false)
|
||||
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg, file.OptNone))
|
||||
|
||||
ctx := context.Background()
|
||||
|
@ -418,14 +408,14 @@ type testValidator struct {
|
|||
func (v *testValidator) Validate(attDoc []byte, nonce []byte) ([]byte, error) {
|
||||
var attestation struct {
|
||||
UserData []byte
|
||||
PCRs measurements.M
|
||||
PCRs map[uint32][]byte
|
||||
}
|
||||
if err := json.Unmarshal(attDoc, &attestation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, pcr := range v.pcrs {
|
||||
if !bytes.Equal(attestation.PCRs[k], pcr) {
|
||||
if !bytes.Equal(attestation.PCRs[k], pcr.Expected[:]) {
|
||||
return nil, errors.New("invalid PCR value")
|
||||
}
|
||||
}
|
||||
|
@ -434,14 +424,14 @@ func (v *testValidator) Validate(attDoc []byte, nonce []byte) ([]byte, error) {
|
|||
|
||||
type testIssuer struct {
|
||||
oid.Getter
|
||||
pcrs measurements.M
|
||||
pcrs map[uint32][]byte
|
||||
}
|
||||
|
||||
func (i *testIssuer) Issue(userData []byte, nonce []byte) ([]byte, error) {
|
||||
return json.Marshal(
|
||||
struct {
|
||||
UserData []byte
|
||||
PCRs measurements.M
|
||||
PCRs map[uint32][]byte
|
||||
}{
|
||||
UserData: userData,
|
||||
PCRs: i.pcrs,
|
||||
|
@ -474,21 +464,21 @@ func defaultConfigWithExpectedMeasurements(t *testing.T, conf *config.Config, cs
|
|||
conf.Provider.Azure.ResourceGroup = "test-resource-group"
|
||||
conf.Provider.Azure.AppClientID = "01234567-0123-0123-0123-0123456789ab"
|
||||
conf.Provider.Azure.ClientSecretValue = "test-client-secret"
|
||||
conf.Provider.Azure.Measurements[4] = measurements.PCRWithAllBytes(0x44)
|
||||
conf.Provider.Azure.Measurements[9] = measurements.PCRWithAllBytes(0x11)
|
||||
conf.Provider.Azure.Measurements[12] = measurements.PCRWithAllBytes(0xcc)
|
||||
conf.Provider.Azure.Measurements[4] = measurements.WithAllBytes(0x44, false)
|
||||
conf.Provider.Azure.Measurements[9] = measurements.WithAllBytes(0x11, false)
|
||||
conf.Provider.Azure.Measurements[12] = measurements.WithAllBytes(0xcc, false)
|
||||
case cloudprovider.GCP:
|
||||
conf.Provider.GCP.Region = "test-region"
|
||||
conf.Provider.GCP.Project = "test-project"
|
||||
conf.Provider.GCP.Zone = "test-zone"
|
||||
conf.Provider.GCP.ServiceAccountKeyPath = "test-key-path"
|
||||
conf.Provider.GCP.Measurements[4] = measurements.PCRWithAllBytes(0x44)
|
||||
conf.Provider.GCP.Measurements[9] = measurements.PCRWithAllBytes(0x11)
|
||||
conf.Provider.GCP.Measurements[12] = measurements.PCRWithAllBytes(0xcc)
|
||||
conf.Provider.GCP.Measurements[4] = measurements.WithAllBytes(0x44, false)
|
||||
conf.Provider.GCP.Measurements[9] = measurements.WithAllBytes(0x11, false)
|
||||
conf.Provider.GCP.Measurements[12] = measurements.WithAllBytes(0xcc, false)
|
||||
case cloudprovider.QEMU:
|
||||
conf.Provider.QEMU.Measurements[4] = measurements.PCRWithAllBytes(0x44)
|
||||
conf.Provider.QEMU.Measurements[9] = measurements.PCRWithAllBytes(0x11)
|
||||
conf.Provider.QEMU.Measurements[12] = measurements.PCRWithAllBytes(0xcc)
|
||||
conf.Provider.QEMU.Measurements[4] = measurements.WithAllBytes(0x44, false)
|
||||
conf.Provider.QEMU.Measurements[9] = measurements.WithAllBytes(0x11, false)
|
||||
conf.Provider.QEMU.Measurements[12] = measurements.WithAllBytes(0xcc, false)
|
||||
}
|
||||
|
||||
conf.RemoveProviderExcept(csp)
|
||||
|
|
|
@ -181,12 +181,12 @@ func getCompatibleImages(csp cloudprovider.Provider, currentVersion string, imag
|
|||
// 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 {
|
||||
for idx, img := range images {
|
||||
measurementsURL, err := url.Parse(constants.S3PublicBucket + strings.ToLower(img.Image) + "/measurements.yaml")
|
||||
measurementsURL, err := url.Parse(constants.S3PublicBucket + strings.ToLower(img.Image) + "/measurements.json")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
signatureURL, err := url.Parse(constants.S3PublicBucket + strings.ToLower(img.Image) + "/measurements.yaml.sig")
|
||||
signatureURL, err := url.Parse(constants.S3PublicBucket + strings.ToLower(img.Image) + "/measurements.json.sig")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/mod/semver"
|
||||
"gopkg.in/yaml.v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
|
@ -248,14 +248,14 @@ func TestGetCompatibleImageMeasurements(t *testing.T) {
|
|||
}
|
||||
|
||||
client := newTestClient(func(req *http.Request) *http.Response {
|
||||
if strings.HasSuffix(req.URL.String(), "/measurements.yaml") {
|
||||
if strings.HasSuffix(req.URL.String(), "/measurements.json") {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(strings.NewReader("0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n")),
|
||||
Header: make(http.Header),
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(req.URL.String(), "/measurements.yaml.sig") {
|
||||
if strings.HasSuffix(req.URL.String(), "/measurements.json.sig") {
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(strings.NewReader("MEUCIBs1g2/n0FsgPfJ+0uLD5TaunGhxwDcQcUGBroejKvg3AiEAzZtcLU9O6IiVhxB8tBS+ty6MXoPNwL8WRWMzyr35eKI=")),
|
||||
|
@ -470,14 +470,14 @@ func TestUpgradePlan(t *testing.T) {
|
|||
Header: make(http.Header),
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(req.URL.String(), "/measurements.yaml") {
|
||||
if strings.HasSuffix(req.URL.String(), "/measurements.json") {
|
||||
return &http.Response{
|
||||
StatusCode: tc.measurementsFetchStatus,
|
||||
Body: io.NopCloser(strings.NewReader("0: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n")),
|
||||
Header: make(http.Header),
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(req.URL.String(), "/measurements.yaml.sig") {
|
||||
if strings.HasSuffix(req.URL.String(), "/measurements.json.sig") {
|
||||
return &http.Response{
|
||||
StatusCode: tc.measurementsFetchStatus,
|
||||
Body: io.NopCloser(strings.NewReader("MEUCIBs1g2/n0FsgPfJ+0uLD5TaunGhxwDcQcUGBroejKvg3AiEAzZtcLU9O6IiVhxB8tBS+ty6MXoPNwL8WRWMzyr35eKI=")),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue