Skip to content

Commit acaa9fd

Browse files
committed
coordinator: support insecure manifests behind opt-in
1 parent a8f6eb6 commit acaa9fd

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-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
@@ -151,8 +151,11 @@ func (s *Server) SetManifest(ctx context.Context, req *userapi.SetManifestReques
151151
state, err := s.guard.UpdateState(ctx, oldState, se, req.GetManifest(), req.GetPolicies())
152152
if err != nil {
153153
code := codes.Internal
154-
if errors.Is(err, stateguard.ErrConcurrentUpdate) {
154+
switch {
155+
case errors.Is(err, stateguard.ErrConcurrentUpdate):
155156
code = codes.FailedPrecondition
157+
case errors.Is(err, stateguard.ErrInsecureNotAllowed):
158+
code = codes.InvalidArgument
156159
}
157160
return nil, status.Errorf(code, "updating Coordinator state: %v", err)
158161
}

coordinator/internal/userapi/userapi_test.go

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

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

@@ -404,7 +432,7 @@ func TestRecovery(t *testing.T) {
404432
fs := afero.NewMemMapFs()
405433
store := aferostore.New(&afero.Afero{Fs: fs})
406434
hist := history.NewWithStore(slog.Default(), store)
407-
auth := stateguard.New(hist, prometheus.NewRegistry(), logger)
435+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, false)
408436
discovery := &stubDiscovery{
409437
peers: tc.peers,
410438
err: tc.peersErr,
@@ -438,7 +466,7 @@ func TestRecovery(t *testing.T) {
438466
}
439467

440468
// Simulate a restarted Coordinator.
441-
a.guard = stateguard.New(hist, prometheus.NewRegistry(), slog.Default())
469+
a.guard = stateguard.New(hist, prometheus.NewRegistry(), slog.Default(), false)
442470
_, err = a.GetManifests(t.Context(), nil)
443471
require.ErrorContains(err, ErrNeedsRecovery.Error())
444472
_, err = a.Recover(rpcContext(t.Context(), seedShareOwnerKey), recoverReq)
@@ -460,7 +488,7 @@ func TestRecoveryFlow(t *testing.T) {
460488
fs := afero.NewMemMapFs()
461489
store := aferostore.New(&afero.Afero{Fs: fs})
462490
hist := history.NewWithStore(slog.Default(), store)
463-
auth := stateguard.New(hist, prometheus.NewRegistry(), logger)
491+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, false)
464492
a := New(logger, auth, &stubDiscovery{})
465493

466494
// 2. A manifest is set and the returned seed is recorded.
@@ -496,7 +524,7 @@ func TestRecoveryFlow(t *testing.T) {
496524
// 3. A new Coordinator is created with the existing history.
497525
// GetManifests and SetManifest are expected to fail.
498526

499-
a.guard = stateguard.New(hist, prometheus.NewRegistry(), slog.Default())
527+
a.guard = stateguard.New(hist, prometheus.NewRegistry(), slog.Default(), false)
500528
_, err = a.SetManifest(t.Context(), req)
501529
require.ErrorContains(err, ErrNeedsRecovery.Error())
502530

@@ -539,7 +567,7 @@ func TestUserAPIConcurrent(t *testing.T) {
539567
fs := afero.NewBasePathFs(afero.NewOsFs(), t.TempDir())
540568
store := aferostore.New(&afero.Afero{Fs: fs})
541569
hist := history.NewWithStore(slog.Default(), store)
542-
auth := stateguard.New(hist, prometheus.NewRegistry(), logger)
570+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, false)
543571
coordinator := New(logger, auth, &stubDiscovery{})
544572

545573
setReq := &userapi.SetManifestRequest{
@@ -853,14 +881,32 @@ func newCoordinatorWithRegistry(reg *prometheus.Registry) *Server {
853881
fs := afero.NewMemMapFs()
854882
store := aferostore.New(&afero.Afero{Fs: fs})
855883
hist := history.NewWithStore(slog.Default(), store)
856-
auth := stateguard.New(hist, reg, logger)
884+
auth := stateguard.New(hist, reg, logger, false)
857885
return New(logger, auth, &stubDiscovery{})
858886
}
859887

888+
func newCoordinatorAllowInsecure() *Server {
889+
logger := slog.Default()
890+
fs := afero.NewMemMapFs()
891+
store := aferostore.New(&afero.Afero{Fs: fs})
892+
hist := history.NewWithStore(slog.Default(), store)
893+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, true)
894+
return New(logger, auth, &stubDiscovery{})
895+
}
896+
897+
func newInsecureManifest(t *testing.T) *manifest.Manifest {
898+
t.Helper()
899+
mnfst := &manifest.Manifest{}
900+
mnfst.ReferenceValues.SNP = []manifest.SNPReferenceValues{
901+
{Platform: "Metal-QEMU-Insecure"},
902+
}
903+
return mnfst
904+
}
905+
860906
func newCoordinatorWithWatcher(t *testing.T, hist *history.History) *Server {
861907
t.Helper()
862908
logger := slog.Default()
863-
auth := stateguard.New(hist, prometheus.NewRegistry(), logger)
909+
auth := stateguard.New(hist, prometheus.NewRegistry(), logger, false)
864910
coordinator := New(logger, auth, &stubDiscovery{})
865911

866912
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)