AB#2512 Config secrets via env var & config refactoring (#544)

* refactor measurements to use consistent types and less byte pushing
* refactor: only rely on a single multierr dependency
* extend config creation with envar support
* document changes
Signed-off-by: Fabian Kammel <fk@edgeless.systems>
This commit is contained in:
Fabian Kammel 2022-11-15 15:40:49 +01:00 committed by GitHub
parent 80a801629e
commit bb76a4e4c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 932 additions and 791 deletions

View file

@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only
package cloudcmd
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/constants"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -58,7 +58,7 @@ func NewUpgrader(outWriter io.Writer) (*Upgrader, error) {
}
// Upgrade upgrades the cluster to the given measurements and image.
func (u *Upgrader) Upgrade(ctx context.Context, image string, measurements map[uint32][]byte) error {
func (u *Upgrader) Upgrade(ctx context.Context, image string, measurements measurements.M) error {
if err := u.updateMeasurements(ctx, measurements); err != nil {
return fmt.Errorf("updating measurements: %w", err)
}
@ -97,36 +97,25 @@ func (u *Upgrader) GetCurrentImage(ctx context.Context) (*unstructured.Unstructu
return imageStruct, imageDefinition, nil
}
func (u *Upgrader) updateMeasurements(ctx context.Context, measurements map[uint32][]byte) error {
func (u *Upgrader) updateMeasurements(ctx context.Context, newMeasurements measurements.M) error {
existingConf, err := u.measurementsUpdater.getCurrent(ctx, constants.JoinConfigMap)
if err != nil {
return fmt.Errorf("retrieving current measurements: %w", err)
}
var currentMeasurements map[uint32][]byte
var currentMeasurements measurements.M
if err := json.Unmarshal([]byte(existingConf.Data[constants.MeasurementsFilename]), &currentMeasurements); err != nil {
return fmt.Errorf("retrieving current measurements: %w", err)
}
if len(currentMeasurements) == len(measurements) {
changed := false
for k, v := range currentMeasurements {
if !bytes.Equal(v, measurements[k]) {
// measurements have changed
changed = true
break
}
}
if !changed {
// measurements are the same, nothing to be done
fmt.Fprintln(u.outWriter, "Cluster is already using the chosen measurements, skipping measurements upgrade")
return nil
}
if currentMeasurements.EqualTo(newMeasurements) {
fmt.Fprintln(u.outWriter, "Cluster is already using the chosen measurements, skipping measurements upgrade")
return nil
}
// backup of previous measurements
existingConf.Data["oldMeasurements"] = existingConf.Data[constants.MeasurementsFilename]
measurementsJSON, err := json.Marshal(measurements)
measurementsJSON, err := json.Marshal(newMeasurements)
if err != nil {
return fmt.Errorf("marshaling measurements: %w", err)
}

View file

@ -13,6 +13,7 @@ import (
"errors"
"testing"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -24,7 +25,7 @@ func TestUpdateMeasurements(t *testing.T) {
someErr := errors.New("error")
testCases := map[string]struct {
updater *stubMeasurementsUpdater
newMeasurements map[uint32][]byte
newMeasurements measurements.M
wantUpdate bool
wantErr bool
}{
@ -36,7 +37,7 @@ func TestUpdateMeasurements(t *testing.T) {
},
},
},
newMeasurements: map[uint32][]byte{
newMeasurements: measurements.M{
0: []byte("1"),
},
wantUpdate: true,
@ -49,7 +50,7 @@ func TestUpdateMeasurements(t *testing.T) {
},
},
},
newMeasurements: map[uint32][]byte{
newMeasurements: measurements.M{
0: []byte("1"),
},
},

View file

@ -18,6 +18,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp"
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch"
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -28,7 +29,7 @@ import (
// Validator validates Platform Configuration Registers (PCRs).
type Validator struct {
provider cloudprovider.Provider
pcrs map[uint32][]byte
pcrs measurements.M
enforcedPCRs []uint32
idkeydigest []byte
enforceIDKeyDigest bool
@ -147,7 +148,7 @@ func (v *Validator) V(cmd *cobra.Command) atls.Validator {
}
// PCRS returns the validator's PCR map.
func (v *Validator) PCRS() map[uint32][]byte {
func (v *Validator) PCRS() measurements.M {
return v.pcrs
}
@ -169,7 +170,7 @@ func (v *Validator) updateValidator(cmd *cobra.Command) {
}
}
func (v *Validator) checkPCRs(pcrs map[uint32][]byte, enforcedPCRs []uint32) error {
func (v *Validator) checkPCRs(pcrs measurements.M, enforcedPCRs []uint32) error {
if len(pcrs) == 0 {
return errors.New("no PCR values provided")
}

View file

@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
package cloudcmd
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"testing"
@ -15,6 +16,7 @@ import (
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/snp"
"github.com/edgelesssys/constellation/v2/internal/attestation/azure/trustedlaunch"
"github.com/edgelesssys/constellation/v2/internal/attestation/gcp"
"github.com/edgelesssys/constellation/v2/internal/attestation/measurements"
"github.com/edgelesssys/constellation/v2/internal/attestation/qemu"
"github.com/edgelesssys/constellation/v2/internal/attestation/vtpm"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -24,21 +26,19 @@ import (
)
func TestNewValidator(t *testing.T) {
zero := []byte("00000000000000000000000000000000")
one := []byte("11111111111111111111111111111111")
testPCRs := map[uint32][]byte{
0: zero,
1: one,
2: zero,
3: one,
4: zero,
5: zero,
testPCRs := measurements.M{
0: measurements.PCRWithAllBytes(0x00),
1: measurements.PCRWithAllBytes(0xFF),
2: measurements.PCRWithAllBytes(0x00),
3: measurements.PCRWithAllBytes(0xFF),
4: measurements.PCRWithAllBytes(0x00),
5: measurements.PCRWithAllBytes(0x00),
}
testCases := map[string]struct {
provider cloudprovider.Provider
config *config.Config
pcrs map[uint32][]byte
pcrs measurements.M
enforceIDKeyDigest bool
idKeyDigest string
azureCVM bool
@ -64,13 +64,15 @@ func TestNewValidator(t *testing.T) {
},
"no pcrs provided": {
provider: cloudprovider.Azure,
pcrs: map[uint32][]byte{},
pcrs: measurements.M{},
wantErr: true,
},
"invalid pcr length": {
provider: cloudprovider.GCP,
pcrs: map[uint32][]byte{0: []byte("0000000000000000000000000000000")},
wantErr: true,
pcrs: measurements.M{
0: bytes.Repeat([]byte{0x00}, 31),
},
wantErr: true,
},
"unknown provider": {
provider: cloudprovider.Unknown,
@ -99,16 +101,13 @@ func TestNewValidator(t *testing.T) {
conf := &config.Config{Provider: config.ProviderConfig{}}
if tc.provider == cloudprovider.GCP {
measurements := config.Measurements(tc.pcrs)
conf.Provider.GCP = &config.GCPConfig{Measurements: measurements}
conf.Provider.GCP = &config.GCPConfig{Measurements: tc.pcrs}
}
if tc.provider == cloudprovider.Azure {
measurements := config.Measurements(tc.pcrs)
conf.Provider.Azure = &config.AzureConfig{Measurements: measurements, EnforceIDKeyDigest: &tc.enforceIDKeyDigest, IDKeyDigest: tc.idKeyDigest, ConfidentialVM: &tc.azureCVM}
conf.Provider.Azure = &config.AzureConfig{Measurements: tc.pcrs, EnforceIDKeyDigest: &tc.enforceIDKeyDigest, IDKeyDigest: tc.idKeyDigest, ConfidentialVM: &tc.azureCVM}
}
if tc.provider == cloudprovider.QEMU {
measurements := config.Measurements(tc.pcrs)
conf.Provider.QEMU = &config.QEMUConfig{Measurements: measurements}
conf.Provider.QEMU = &config.QEMUConfig{Measurements: tc.pcrs}
}
validators, err := NewValidator(tc.provider, conf)
@ -125,29 +124,27 @@ func TestNewValidator(t *testing.T) {
}
func TestValidatorV(t *testing.T) {
zero := []byte("00000000000000000000000000000000")
newTestPCRs := func() map[uint32][]byte {
return map[uint32][]byte{
0: zero,
1: zero,
2: zero,
3: zero,
4: zero,
5: zero,
6: zero,
7: zero,
8: zero,
9: zero,
10: zero,
11: zero,
12: zero,
newTestPCRs := func() measurements.M {
return measurements.M{
0: measurements.PCRWithAllBytes(0x00),
1: measurements.PCRWithAllBytes(0x00),
2: measurements.PCRWithAllBytes(0x00),
3: measurements.PCRWithAllBytes(0x00),
4: measurements.PCRWithAllBytes(0x00),
5: measurements.PCRWithAllBytes(0x00),
6: measurements.PCRWithAllBytes(0x00),
7: measurements.PCRWithAllBytes(0x00),
8: measurements.PCRWithAllBytes(0x00),
9: measurements.PCRWithAllBytes(0x00),
10: measurements.PCRWithAllBytes(0x00),
11: measurements.PCRWithAllBytes(0x00),
12: measurements.PCRWithAllBytes(0x00),
}
}
testCases := map[string]struct {
provider cloudprovider.Provider
pcrs map[uint32][]byte
pcrs measurements.M
wantVs atls.Validator
azureCVM bool
}{
@ -224,7 +221,7 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
testCases := map[string]struct {
provider cloudprovider.Provider
pcrs map[uint32][]byte
pcrs measurements.M
ownerID string
clusterID string
wantErr bool
@ -321,14 +318,14 @@ func TestValidatorUpdateInitPCRs(t *testing.T) {
}
func TestUpdatePCR(t *testing.T) {
emptyMap := map[uint32][]byte{}
defaultMap := map[uint32][]byte{
0: []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),
1: []byte("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"),
emptyMap := measurements.M{}
defaultMap := measurements.M{
0: measurements.PCRWithAllBytes(0xAA),
1: measurements.PCRWithAllBytes(0xBB),
}
testCases := map[string]struct {
pcrMap map[uint32][]byte
pcrMap measurements.M
pcrIndex uint32
encoded string
wantEntries int
@ -389,7 +386,7 @@ func TestUpdatePCR(t *testing.T) {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
pcrs := make(map[uint32][]byte)
pcrs := make(measurements.M)
for k, v := range tc.pcrMap {
pcrs[k] = v
}