Skip to content

Commit 27802b6

Browse files
committed
coordinator: support insecure manifests behind opt-in
1 parent 657abac commit 27802b6

8 files changed

Lines changed: 173 additions & 17 deletions

File tree

coordinator/internal/stateguard/credentials.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/edgelesssys/contrast/internal/atls"
1616
"github.com/edgelesssys/contrast/internal/attestation"
1717
"github.com/edgelesssys/contrast/internal/attestation/certcache"
18+
"github.com/edgelesssys/contrast/internal/attestation/insecure"
1819
"github.com/edgelesssys/contrast/internal/attestation/snp"
1920
"github.com/edgelesssys/contrast/internal/attestation/tdx"
2021
"github.com/edgelesssys/contrast/internal/constants"
@@ -96,6 +97,12 @@ func (c *Credentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.A
9697
logger.NewWithAttrs(logger.NewNamed(c.logger, "validator"), map[string]string{"reference-values": name}), &authInfo, name))
9798
}
9899

100+
if state.Manifest().AllowInsecure() {
101+
validators = append(validators, insecure.NewValidatorWithReportSetter(
102+
logger.NewWithAttrs(logger.NewNamed(c.logger, "validator"), map[string]string{"reference-values": "insecure"}),
103+
&authInfo, "insecure"))
104+
}
105+
99106
serverCfg, err := atls.CreateAttestationServerTLSConfig(c.issuer, validators, c.attestationFailuresCounter)
100107
if err != nil {
101108
log.Error("Could not create TLS config", "error", err)

coordinator/internal/stateguard/stateguard.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ var (
5252
// ErrConcurrentUpdate is returned by state-modifying operations if the input oldState is not
5353
// the current state. This usually happens when a concurrent operation succeeded.
5454
ErrConcurrentUpdate = errors.New("coordinator state was updated concurrently")
55+
56+
// ErrInsecureNotAllowed is returned when a manifest contains insecure platforms but the
57+
// coordinator was not started with the allow-insecure flag.
58+
ErrInsecureNotAllowed = errors.New("manifest contains insecure platforms, but the coordinator is not configured to allow them")
5559
)
5660

5761
// Guard manages the manifest state of Contrast.
@@ -65,6 +69,9 @@ type Guard struct {
6569
logger *slog.Logger
6670
metrics metrics
6771

72+
// allowInsecure controls whether manifests with insecure platforms are accepted.
73+
allowInsecure bool
74+
6875
clock clock.Clock
6976
}
7077

@@ -73,7 +80,10 @@ type metrics struct {
7380
}
7481

7582
// New creates a new state Guard instance.
76-
func New(hist *history.History, reg *prometheus.Registry, log *slog.Logger) *Guard {
83+
//
84+
// If allowInsecure is true, the Guard will accept manifests that contain insecure platforms.
85+
// Otherwise, setting such a manifest will be rejected with ErrInsecureNotAllowed.
86+
func New(hist *history.History, reg *prometheus.Registry, log *slog.Logger, allowInsecure bool) *Guard {
7787
manifestGeneration := promauto.With(reg).NewGauge(prometheus.GaugeOpts{
7888
Subsystem: "contrast_coordinator",
7989
Name: "manifest_generation",
@@ -82,8 +92,9 @@ func New(hist *history.History, reg *prometheus.Registry, log *slog.Logger) *Gua
8292
manifestGeneration.Set(0)
8393

8494
return &Guard{
85-
hist: hist,
86-
logger: log.WithGroup("stateguard"),
95+
hist: hist,
96+
logger: log.WithGroup("stateguard"),
97+
allowInsecure: allowInsecure,
8798
metrics: metrics{
8899
manifestGeneration: manifestGeneration,
89100
},
@@ -271,6 +282,9 @@ func (g *Guard) UpdateState(_ context.Context, oldState *State, se *seedengine.S
271282
if err := json.Unmarshal(manifestBytes, &mnfst); err != nil {
272283
return nil, fmt.Errorf("unmarshaling manifest: %w", err)
273284
}
285+
if !g.allowInsecure && mnfst.AllowInsecure() {
286+
return nil, ErrInsecureNotAllowed
287+
}
274288
policyMap := make(map[[history.HashSize]byte][]byte)
275289
for _, policy := range policies {
276290
policyHash, err := g.hist.SetPolicy(policy)

coordinator/internal/stateguard/stateguard_test.go

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,37 @@ func TestResetState(t *testing.T) {
192192
require.ErrorIs(err, assert.AnError)
193193
}
194194

195+
func TestUpdateStateInsecure(t *testing.T) {
196+
ctx := t.Context()
197+
198+
_, insecureManifestBytes, policies := newInsecureManifest(t)
199+
se := newSeedEngine(t)
200+
201+
t.Run("rejected when allowInsecure is false", func(t *testing.T) {
202+
require := require.New(t)
203+
204+
store := aferostore.New(&afero.Afero{Fs: afero.NewMemMapFs()})
205+
hist := history.NewWithStore(slog.Default(), store)
206+
g := New(hist, prometheus.NewRegistry(), slog.Default(), false)
207+
208+
state, err := g.UpdateState(ctx, nil, se, insecureManifestBytes, policies)
209+
require.ErrorIs(err, ErrInsecureNotAllowed)
210+
require.Nil(state)
211+
})
212+
213+
t.Run("accepted when allowInsecure is true", func(t *testing.T) {
214+
require := require.New(t)
215+
216+
store := aferostore.New(&afero.Afero{Fs: afero.NewMemMapFs()})
217+
hist := history.NewWithStore(slog.Default(), store)
218+
g := New(hist, prometheus.NewRegistry(), slog.Default(), true)
219+
220+
state, err := g.UpdateState(ctx, nil, se, insecureManifestBytes, policies)
221+
require.NoError(err)
222+
require.NotNil(state)
223+
})
224+
}
225+
195226
func TestConcurrentUpdateState(t *testing.T) {
196227
ctx := t.Context()
197228
assert := assert.New(t)
@@ -200,7 +231,7 @@ func TestConcurrentUpdateState(t *testing.T) {
200231
Store: aferostore.New(&afero.Afero{Fs: afero.NewMemMapFs()}),
201232
}
202233
hist := history.NewWithStore(slog.Default(), store)
203-
guard := New(hist, prometheus.NewRegistry(), slog.Default())
234+
guard := New(hist, prometheus.NewRegistry(), slog.Default(), false)
204235

205236
numWorkers := 20
206237

@@ -303,7 +334,7 @@ func TestWatchHistory(t *testing.T) {
303334
notifications: make(chan []byte),
304335
}
305336
hist := history.NewWithStore(slog.Default(), store)
306-
g := New(hist, prometheus.NewRegistry(), slog.Default())
337+
g := New(hist, prometheus.NewRegistry(), slog.Default(), false)
307338

308339
_, manifestBytes, policies := newManifest(t)
309340

@@ -352,7 +383,7 @@ func TestWatchHistoryLateNotifications(t *testing.T) {
352383
notifications: make(chan []byte),
353384
}
354385
hist := history.NewWithStore(slog.Default(), store)
355-
g := New(hist, prometheus.NewRegistry(), slog.Default())
386+
g := New(hist, prometheus.NewRegistry(), slog.Default(), false)
356387

357388
_, manifestBytes, policies := newManifest(t)
358389

@@ -409,7 +440,7 @@ func TestBadStoreWatcherIsRestarted(t *testing.T) {
409440
store.storeUpdates.Store(&ch)
410441
hist := history.NewWithStore(slog.Default(), store)
411442
reg := prometheus.NewRegistry()
412-
a := New(hist, reg, slog.Default())
443+
a := New(hist, reg, slog.Default(), false)
413444
clock := &waitingClock{
414445
FakeClock: testingclock.NewFakeClock(time.Now()),
415446
afterCalls: make(chan struct{}, 1),
@@ -502,7 +533,7 @@ func newTestGuard(t *testing.T) (*Guard, *prometheus.Registry) {
502533
store := aferostore.New(&afero.Afero{Fs: afero.NewMemMapFs()})
503534
hist := history.NewWithStore(slog.Default(), store)
504535
reg := prometheus.NewRegistry()
505-
return New(hist, reg, slog.Default()), reg
536+
return New(hist, reg, slog.Default(), false), reg
506537
}
507538

508539
func newManifest(t *testing.T) (*manifest.Manifest, []byte, [][]byte) {
@@ -542,6 +573,28 @@ func newManifest(t *testing.T) (*manifest.Manifest, []byte, [][]byte) {
542573
return mnfst, mnfstBytes, [][]byte{policy}
543574
}
544575

576+
func newInsecureManifest(t *testing.T) (*manifest.Manifest, []byte, [][]byte) {
577+
t.Helper()
578+
policy := []byte("=== SOME REGO HERE ===")
579+
policyHash := sha256.Sum256(policy)
580+
policyHashHex := manifest.NewHexString(policyHash[:])
581+
582+
mnfst := &manifest.Manifest{}
583+
mnfst.Policies = map[manifest.HexString]manifest.PolicyEntry{
584+
policyHashHex: {
585+
SANs: []string{"test"},
586+
WorkloadSecretID: "test2",
587+
Role: manifest.RoleCoordinator,
588+
},
589+
}
590+
mnfst.ReferenceValues.SNP = []manifest.SNPReferenceValues{
591+
{Platform: "Metal-QEMU-SNP-Insecure"},
592+
}
593+
mnfstBytes, err := json.Marshal(mnfst)
594+
require.NoError(t, err)
595+
return mnfst, mnfstBytes, [][]byte{policy}
596+
}
597+
545598
func newSeedEngine(t *testing.T) *seedengine.SeedEngine {
546599
t.Helper()
547600
data := make([]byte, 32)

coordinator/internal/userapi/userapi.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,11 @@ func (s *Server) SetManifest(ctx context.Context, req *userapi.SetManifestReques
139139
state, err := s.guard.UpdateState(ctx, oldState, se, req.GetManifest(), req.GetPolicies())
140140
if err != nil {
141141
code := codes.Internal
142-
if errors.Is(err, stateguard.ErrConcurrentUpdate) {
142+
switch {
143+
case errors.Is(err, stateguard.ErrConcurrentUpdate):
143144
code = codes.FailedPrecondition
145+
case errors.Is(err, stateguard.ErrInsecureNotAllowed):
146+
code = codes.InvalidArgument
144147
}
145148
return nil, status.Errorf(code, "updating Coordinator state: %v", err)
146149
}

coordinator/internal/userapi/userapi_test.go

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,34 @@ func TestSetManifest(t *testing.T) {
230230
require.Equal(codes.InvalidArgument, status.Code(err))
231231
})
232232

233+
t.Run("insecure manifest rejected", func(t *testing.T) {
234+
require := require.New(t)
235+
236+
// Default coordinator does not allow insecure manifests.
237+
coordinator := newCoordinator()
238+
m := newInsecureManifest(t)
239+
manifestBytes, err := json.Marshal(m)
240+
require.NoError(err)
241+
req := &userapi.SetManifestRequest{Manifest: manifestBytes}
242+
_, err = coordinator.SetManifest(t.Context(), req)
243+
require.Error(err)
244+
require.Equal(codes.InvalidArgument, status.Code(err))
245+
require.ErrorContains(err, "insecure")
246+
})
247+
248+
t.Run("insecure manifest accepted when allowed", func(t *testing.T) {
249+
require := require.New(t)
250+
251+
coordinator := newCoordinatorAllowInsecure()
252+
m := newInsecureManifest(t)
253+
manifestBytes, err := json.Marshal(m)
254+
require.NoError(err)
255+
req := &userapi.SetManifestRequest{Manifest: manifestBytes}
256+
resp, err := coordinator.SetManifest(t.Context(), req)
257+
require.NoError(err)
258+
require.NotNil(resp)
259+
})
260+
233261
t.Run("atomic manifest update", func(t *testing.T) {
234262
require := require.New(t)
235263

@@ -366,7 +394,7 @@ func TestRecovery(t *testing.T) {
366394
fs := afero.NewMemMapFs()
367395
store := aferostore.New(&afero.Afero{Fs: fs})
368396
hist := history.NewWithStore(slog.Default(), store)
369-
auth := stateguard.New(hist, prometheus.NewRegistry(), logger)
397+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, false)
370398
discovery := &stubDiscovery{
371399
peers: tc.peers,
372400
err: tc.peersErr,
@@ -400,7 +428,7 @@ func TestRecovery(t *testing.T) {
400428
}
401429

402430
// Simulate a restarted Coordinator.
403-
a.guard = stateguard.New(hist, prometheus.NewRegistry(), slog.Default())
431+
a.guard = stateguard.New(hist, prometheus.NewRegistry(), slog.Default(), false)
404432
_, err = a.GetManifests(t.Context(), nil)
405433
require.ErrorContains(err, ErrNeedsRecovery.Error())
406434
_, err = a.Recover(rpcContext(t.Context(), seedShareOwnerKey), recoverReq)
@@ -422,7 +450,7 @@ func TestRecoveryFlow(t *testing.T) {
422450
fs := afero.NewMemMapFs()
423451
store := aferostore.New(&afero.Afero{Fs: fs})
424452
hist := history.NewWithStore(slog.Default(), store)
425-
auth := stateguard.New(hist, prometheus.NewRegistry(), logger)
453+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, false)
426454
a := New(logger, auth, &stubDiscovery{})
427455

428456
// 2. A manifest is set and the returned seed is recorded.
@@ -458,7 +486,7 @@ func TestRecoveryFlow(t *testing.T) {
458486
// 3. A new Coordinator is created with the existing history.
459487
// GetManifests and SetManifest are expected to fail.
460488

461-
a.guard = stateguard.New(hist, prometheus.NewRegistry(), slog.Default())
489+
a.guard = stateguard.New(hist, prometheus.NewRegistry(), slog.Default(), false)
462490
_, err = a.SetManifest(t.Context(), req)
463491
require.ErrorContains(err, ErrNeedsRecovery.Error())
464492

@@ -501,7 +529,7 @@ func TestUserAPIConcurrent(t *testing.T) {
501529
fs := afero.NewBasePathFs(afero.NewOsFs(), t.TempDir())
502530
store := aferostore.New(&afero.Afero{Fs: fs})
503531
hist := history.NewWithStore(slog.Default(), store)
504-
auth := stateguard.New(hist, prometheus.NewRegistry(), logger)
532+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, false)
505533
coordinator := New(logger, auth, &stubDiscovery{})
506534

507535
setReq := &userapi.SetManifestRequest{
@@ -815,14 +843,32 @@ func newCoordinatorWithRegistry(reg *prometheus.Registry) *Server {
815843
fs := afero.NewMemMapFs()
816844
store := aferostore.New(&afero.Afero{Fs: fs})
817845
hist := history.NewWithStore(slog.Default(), store)
818-
auth := stateguard.New(hist, reg, logger)
846+
auth := stateguard.New(hist, reg, logger, false)
819847
return New(logger, auth, &stubDiscovery{})
820848
}
821849

850+
func newCoordinatorAllowInsecure() *Server {
851+
logger := slog.Default()
852+
fs := afero.NewMemMapFs()
853+
store := aferostore.New(&afero.Afero{Fs: fs})
854+
hist := history.NewWithStore(slog.Default(), store)
855+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, true)
856+
return New(logger, auth, &stubDiscovery{})
857+
}
858+
859+
func newInsecureManifest(t *testing.T) *manifest.Manifest {
860+
t.Helper()
861+
mnfst := &manifest.Manifest{}
862+
mnfst.ReferenceValues.SNP = []manifest.SNPReferenceValues{
863+
{Platform: "Metal-QEMU-SNP-Insecure"},
864+
}
865+
return mnfst
866+
}
867+
822868
func newCoordinatorWithWatcher(t *testing.T, hist *history.History) *Server {
823869
t.Helper()
824870
logger := slog.Default()
825-
auth := stateguard.New(hist, prometheus.NewRegistry(), logger)
871+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, false)
826872
coordinator := New(logger, auth, &stubDiscovery{})
827873

828874
ctx, cancel := context.WithCancel(t.Context())

coordinator/main.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import (
5252

5353
const (
5454
metricsEnvVar = "CONTRAST_METRICS"
55+
allowInsecureEnvVar = "CONTRAST_ALLOW_INSECURE"
5556
probeAndMetricsPort = 9102
5657
// transitEngineAPIPort specifies the default port to expose the transit engine API.
5758
transitEngineAPIPort = "8200"
@@ -115,7 +116,12 @@ func run() (retErr error) {
115116

116117
hist := history.NewWithStore(logger.WithGroup("history"), store)
117118

118-
meshAuth := stateguard.New(hist, promRegistry, logger)
119+
_, allowInsecure := os.LookupEnv(allowInsecureEnvVar)
120+
if allowInsecure {
121+
logger.Warn("Coordinator is configured to allow insecure manifests")
122+
}
123+
124+
meshAuth := stateguard.New(hist, promRegistry, logger, allowInsecure)
119125

120126
issuer, err := issuer.New(logger)
121127
if err != nil {

internal/manifest/manifest.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,21 @@ func (m *Manifest) CoordinatorPolicyHash() (HexString, error) {
119119
return "", errors.New("no coordinator found in manifest")
120120
}
121121

122+
// AllowInsecure returns true if the manifest contains reference values for insecure platforms.
123+
func (m *Manifest) AllowInsecure() bool {
124+
for _, v := range m.ReferenceValues.SNP {
125+
if p, err := platforms.FromString(v.Platform); err == nil && platforms.IsInsecure(p) {
126+
return true
127+
}
128+
}
129+
for _, v := range m.ReferenceValues.TDX {
130+
if p, err := platforms.FromString(v.Platform); err == nil && platforms.IsInsecure(p) {
131+
return true
132+
}
133+
}
134+
return false
135+
}
136+
122137
// SNPValidateOpts returns validate options generators populated with the manifest's
123138
// SNP reference values and trusted measurement for the given runtime.
124139
func (m *Manifest) SNPValidateOpts(kdsGetter *certcache.CachedHTTPSGetter) ([]SNPValidatorOptions, error) {
@@ -128,6 +143,9 @@ func (m *Manifest) SNPValidateOpts(kdsGetter *certcache.CachedHTTPSGetter) ([]SN
128143

129144
var out []SNPValidatorOptions
130145
for _, refVal := range m.ReferenceValues.SNP {
146+
if p, err := platforms.FromString(refVal.Platform); err == nil && platforms.IsInsecure(p) {
147+
continue
148+
}
131149
if len(refVal.TrustedMeasurement) == 0 {
132150
return nil, errors.New("trusted measurement cannot be empty")
133151
}
@@ -213,6 +231,9 @@ func (m *Manifest) TDXValidateOpts(kdsGetter *certcache.CachedHTTPSGetter) ([]TD
213231

214232
var out []TDXValidatorOptions
215233
for _, refVal := range m.ReferenceValues.TDX {
234+
if p, err := platforms.FromString(refVal.Platform); err == nil && platforms.IsInsecure(p) {
235+
continue
236+
}
216237
verifyOpts := tdxverify.DefaultOptions()
217238

218239
var err error

internal/manifest/referencevalues.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ type SNPReferenceValues struct {
208208

209209
// Validate checks the validity of all fields in the AKS reference values.
210210
func (r SNPReferenceValues) Validate() error {
211+
if p, err := platforms.FromString(r.Platform); err == nil && platforms.IsInsecure(p) {
212+
return nil
213+
}
211214
var minTCBErrs []error
212215
if r.MinimumTCB.BootloaderVersion == nil {
213216
minTCBErrs = append(minTCBErrs, newValidationError("BootloaderVersion", ExpectedMissingReferenceValueError{Err: errors.New("field cannot be empty")}))
@@ -312,6 +315,9 @@ type TDXReferenceValues struct {
312315

313316
// Validate checks the validity of all fields in the bare metal TDX reference values.
314317
func (r TDXReferenceValues) Validate() error {
318+
if p, err := platforms.FromString(r.Platform); err == nil && platforms.IsInsecure(p) {
319+
return nil
320+
}
315321
var errs []error
316322
if err := validateHexString(r.MrTd, 48); err != nil {
317323
errs = append(errs, newValidationError("MrTd", err))

0 commit comments

Comments
 (0)