-
Notifications
You must be signed in to change notification settings - Fork 149
Per role quote policies #6471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Per role quote policies #6471
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| go/registry/api: Allow at most one runtime SGX role | ||
|
|
||
| A stricter node registration rule is behind the future flag, | ||
| and therefore not breaking. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Add support for per-role quote policies | ||
|
|
||
| This enables a more relaxed general policy but a stricter requirements | ||
| for nodes that can access the key manager (e.g. compute/observer nodes). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -230,6 +230,18 @@ func (m RolesMask) IsSingleRole() bool { | |
| return m != 0 && m&(m-1) == 0 && m&RoleReserved == 0 | ||
| } | ||
|
|
||
| // AtMostOneRuntimeSGXRole returns true when RoleMask has at most one SGX runtime role. | ||
| func (m RolesMask) AtMostOneRuntimeSGXRole() bool { | ||
| sgxRoles := m & (RoleComputeWorker | RoleObserver | RoleKeyManager) | ||
| if sgxRoles.IsEmptyRole() { | ||
| return true | ||
| } | ||
| if sgxRoles.IsSingleRole() { | ||
| return true | ||
| } | ||
| return false | ||
| } | ||
|
|
||
| func (m RolesMask) String() string { | ||
| if m&RoleReserved != 0 { | ||
| return "[invalid roles]" | ||
|
|
@@ -332,7 +344,7 @@ func (n *Node) UnmarshalCBOR(data []byte) error { | |
| } | ||
|
|
||
| // ValidateBasic performs basic descriptor validity checks. | ||
| func (n *Node) ValidateBasic(strictVersion bool) error { | ||
| func (n *Node) ValidateBasic(strictVersion bool, isFeatureVersion242 bool) error { | ||
| v := n.Versioned.V | ||
| switch strictVersion { | ||
| case true: | ||
|
|
@@ -366,6 +378,13 @@ func (n *Node) ValidateBasic(strictVersion bool) error { | |
| return fmt.Errorf("invalid role specified") | ||
| } | ||
|
|
||
| // Make sure a node can have at most one runtime SGX role. | ||
| if isFeatureVersion242 { | ||
| if !n.Roles.AtMostOneRuntimeSGXRole() { | ||
| return fmt.Errorf("multiple runtime SGX roles (roles: %s)", n.Roles) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
|
|
@@ -574,7 +593,7 @@ func HashRAK(rak signature.PublicKey) hash.Hash { | |
| } | ||
|
|
||
| // Verify verifies the node's TEE capabilities, at the provided timestamp and height. | ||
| func (c *CapabilityTEE) Verify(teeCfg *TEEFeatures, ts time.Time, height uint64, constraints []byte, nodeID signature.PublicKey, isFeatureVersion242 bool) error { | ||
| func (c *CapabilityTEE) Verify(teeCfg *TEEFeatures, ts time.Time, height uint64, constraints []byte, nodeID signature.PublicKey, nodeRoles RolesMask, isFeatureVersion242 bool) error { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. NIT: All this functions should accept validation options structs? |
||
| switch c.Hardware { | ||
| case TEEHardwareIntelSGX: | ||
| // Parse SGX remote attestation. | ||
|
|
@@ -596,7 +615,7 @@ func (c *CapabilityTEE) Verify(teeCfg *TEEFeatures, ts time.Time, height uint64, | |
| } | ||
|
|
||
| // Verify SGX remote attestation. | ||
| return sa.Verify(teeCfg, ts, height, &sc, c.RAK, c.REK, nodeID) | ||
| return sa.Verify(teeCfg, ts, height, &sc, c.RAK, c.REK, nodeID, nodeRoles) | ||
| default: | ||
| return ErrInvalidTEEHardware | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,9 +32,17 @@ type SGXConstraints struct { | |
| // Enclaves is the allowed MRENCLAVE/MRSIGNER pairs. | ||
| Enclaves []sgx.EnclaveIdentity `json:"enclaves,omitempty"` | ||
|
|
||
| // Policy is the quote policy. | ||
| // Policy is the default quote policy. The default policy must be satisfied | ||
| // unless there exists a corresponding per-role policy. | ||
| Policy *quote.Policy `json:"policy,omitempty"` | ||
|
|
||
| // PerRolePolicy defines additional role specific quote policies, that overwrite | ||
| // Policy when node with these roles does an attestation. | ||
| // | ||
| // A valid entry is for either [RoleComputeWorker] or [RoleObserver]. Single entry | ||
| // should not encode multiple roles. | ||
| PerRolePolicy map[RolesMask]quote.Policy `json:"per_role_policy,omitempty"` | ||
|
Comment on lines
+39
to
+44
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that:
How about simplifying things further and instead have This should simplify assumptions, tests, comments, validations and avoid the need for new "at most one runtime sgx role" invariant. |
||
|
|
||
| // MaxAttestationAge is the maximum attestation age (in blocks). | ||
| MaxAttestationAge uint64 `json:"max_attestation_age,omitempty"` | ||
| } | ||
|
|
@@ -111,23 +119,63 @@ func (sc *SGXConstraints) ValidateBasic(cfg *TEEFeatures, isFeatureVersion242 bo | |
| return fmt.Errorf("unsupported SGX constraints version: %d", sc.V) | ||
| } | ||
|
|
||
| if sc.Policy == nil { | ||
| return nil | ||
| validatePolicy := func(policy *quote.Policy) error { | ||
| if policy == nil { | ||
| return nil | ||
| } | ||
|
|
||
| // Check for TDX enablement. | ||
| if !cfg.SGX.TDX && policy.PCS != nil && policy.PCS.TDX != nil { | ||
| return fmt.Errorf("TDX policy not supported") | ||
| } | ||
|
|
||
| // Check that policy is compliant with the current feature version. | ||
| return policy.Validate(isFeatureVersion242) | ||
| } | ||
|
|
||
| validatePerRolePolicyEntry := func(role RolesMask, policy quote.Policy) error { | ||
| if !role.IsSingleRole() { | ||
| return fmt.Errorf("quote policies should have a single role") | ||
| } | ||
| if role != RoleComputeWorker && role != RoleObserver { | ||
| return fmt.Errorf("invalid role: only compute or observer role allowed") | ||
| } | ||
| if policy.IAS != nil { | ||
| return fmt.Errorf("invalid policy: IAS not allowed") | ||
| } | ||
| return validatePolicy(&policy) | ||
| } | ||
|
|
||
| // Check for TDX enablement. | ||
| if !cfg.SGX.TDX && sc.Policy.PCS != nil && sc.Policy.PCS.TDX != nil { | ||
| return fmt.Errorf("TDX policy not supported") | ||
| if err := validatePolicy(sc.Policy); err != nil { | ||
| return fmt.Errorf("invalid default policy: %w", err) | ||
| } | ||
|
|
||
| // Check that policy is compliant with the current feature version. | ||
| if err := sc.Policy.Validate(isFeatureVersion242); err != nil { | ||
| return fmt.Errorf("invalid policy: %w", err) | ||
| if !isFeatureVersion242 && sc.PerRolePolicy != nil { | ||
| return fmt.Errorf("per role policy should be nil until feature version 24.2") | ||
| } | ||
|
|
||
| for role, policy := range sc.PerRolePolicy { | ||
| if err := validatePerRolePolicyEntry(role, policy); err != nil { | ||
| return fmt.Errorf("invalid per role policy entry (role: %s): %w", role, err) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // PolicyFor returns a matching per-role policy when present, or otherwise falls back to the default policy. | ||
| // | ||
| // This function expects role mask that has at most one runtime SGX role. | ||
| func (sc *SGXConstraints) PolicyFor(roles RolesMask) *quote.Policy { | ||
| for role, policy := range sc.PerRolePolicy { | ||
| if role&roles == 0 { | ||
| continue | ||
| } | ||
| return &policy | ||
| } | ||
| return sc.Policy | ||
| } | ||
|
|
||
| // ContainsEnclave returns true iff the allowed enclave list in SGX constraints contain the given | ||
| // enclave identity. | ||
| func (sc *SGXConstraints) ContainsEnclave(eid sgx.EnclaveIdentity) bool { | ||
|
|
@@ -223,16 +271,23 @@ func (sa *SGXAttestation) Verify( | |
| rak signature.PublicKey, | ||
| rek *x25519.PublicKey, | ||
| nodeID signature.PublicKey, | ||
| nodeRoles RolesMask, | ||
| ) error { | ||
| if cfg == nil { | ||
| cfg = &emptyFeatures | ||
| } | ||
|
|
||
| // Use defaults from consensus parameters. | ||
| // TODO: Handle default constraints overwrite consistently. | ||
| // See https://github.com/oasisprotocol/oasis-core/issues/6459. | ||
| cfg.SGX.ApplyDefaultConstraints(sc) | ||
|
|
||
| // Prior to 24.2 nodeRoles might have multiple roles, but as per-role policies are guaranteed | ||
| // to be empty this works fine. | ||
| policy := sc.PolicyFor(nodeRoles) | ||
|
|
||
| // Verify the quote. | ||
| verifiedQuote, err := sa.Quote.Verify(sc.Policy, ts) | ||
| verifiedQuote, err := sa.Quote.Verify(policy, ts) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: There is not such thing as
ValidateBasic. OnlyValidatewith clear invariants and tests for the invariants theValidatewill check. Moreover, the validation params should probably be passes as part of the validation options struct. Possibly this check could also be part of theVerifyRegisterNodeArgsdirectly which avoids some additional changes, but feels off there.